diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b82a747 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Required: GitHub Token (Needs "repo" permissions) +# GITHUB_TOKEN=your_personal_access_token_or_org_token + +# Required: HackMD Configuration +# HACKMD_API_TOKEN=your_hackmd_api_token +# HACKMD_TEAM_NAME=your_hackmd_team_name_optional # Defaults to your own personal space + +# Google Calendar API Authentication +# You can get this from the Google Cloud Console +# Create an API Key and restrict it to the Google Calendar API +# GOOGLE_API_KEY=your_google_calendar_api_key_here diff --git a/.github/workflows/create-meeting-artifacts.yml b/.github/workflows/create-meeting-artifacts.yml new file mode 100644 index 0000000..d65cecb --- /dev/null +++ b/.github/workflows/create-meeting-artifacts.yml @@ -0,0 +1,71 @@ +name: Create Meeting Artifacts + +on: + # Run every Monday at 10 AM UTC (adjust as needed) + schedule: + - cron: '0 10 * * 1' + + # Allow manual triggering + workflow_dispatch: + inputs: + meeting_group: + description: 'Meeting group to create artifacts for' + required: true + type: choice + options: + - uvwasi + - tsc + - build + - diag + - diag_deepdive + - typescript + - Release + - cross_project_council + - modules + - tooling + - security-wg + - next-10 + - package-maintenance + - package_metadata_interop + - ecosystem_report + - sustainability_collab + - standards + - security_collab + - loaders + - web-server-frameworks + +jobs: + create-artifacts: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Node.js + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Create meeting artifacts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HACKMD_API_TOKEN: ${{ secrets.HACKMD_API_TOKEN }} + HACKMD_TEAM_NAME: ${{ secrets.HACKMD_TEAM_NAME }} + GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} + run: node create-node-meeting-artifacts.mjs ${{ github.event.inputs.meeting_group }} + + - name: Upload artifacts + if: always() + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + with: + name: meeting-artifacts-${{ github.event.inputs.meeting_group || 'tsc' }} + path: | + ~/.make-node-meeting/ + *.md + retention-days: 7 + if-no-files-found: ignore diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..df86373 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,39 @@ +name: Test Suite + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [22.x, 'latest'] + + steps: + - name: Checkout repository + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linting + run: npm run lint + + - name: Run tests + run: npm test + + - name: Check format + run: npm run format:check diff --git a/.gitignore b/.gitignore index 5afa0b2..7f00be4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ +# Node.js Packages node_modules/ -client_secret.json -package-lock.json + +# Environment File +.env + +# Test artifacts and temporary files +test/fixtures/temp/ +test/snapshots/*.snap +*.log +coverage/ + +# Files created during unit test runs +*.sh +meeting-test/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..0350652 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +templates/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..f49129f --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,11 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "avoid" +} diff --git a/AUTHORS b/AUTHORS index 38f115d..2886933 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ Michael Dawson +Claudio Wunder diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dee67d4..f87e3b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,7 @@ Writing good commit logs is important. A commit log should describe what changed and why. Follow these guidelines when writing one: 1. The first line should be a short description of the change - (e.g. "get-metadata: check if the committer matches the author"). + (e.g. "get-metadata: check if the committer matches the author"). 2. Keep the second line blank. 3. Wrap all lines at 72 columns. @@ -90,11 +90,11 @@ in this project. By making a contribution to this project, I certify that: -* (a) The contribution was created in whole or in part by me and I +- (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or -* (b) The contribution is based upon previous work that, to the best +- (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part @@ -102,11 +102,11 @@ By making a contribution to this project, I certify that: permitted to submit under a different license), as indicated in the file; or -* (c) The contribution was provided directly to me by some other +- (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. -* (d) I understand and agree that this project and the contribution +- (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with diff --git a/LICENSE.md b/LICENSE.md index 26a999d..dc92bbb 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,10 +1,8 @@ -The MIT License (MIT) -===================== +# The MIT License (MIT) -Copyright (c) 2018 create-node-meeting-artifacts authors -------------------------------------------------------------- +## Copyright (c) 2018 create-node-meeting-artifacts authors -*contributors listed at * +_contributors listed at _ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 6b037e5..6f26df1 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,231 @@ -# create-node-meeting-artifacts -Tool to create artifacts for node.js team meetings +# Node.js Meeting Artifacts Creator -Uses the Foundation calendar to find the next instance of the meeting, -and then creates an issue and matching google doc for minutes. The key -thing is that it automatically creates the issue in github and the doc -in google docs as opposed to just creating the content. +A modern Node.js application that creates GitHub issues and HackMD documents for Node.js team meetings. This tool automates the process of reading meeting configuration, fetching calendar events, creating meeting minutes documents, and posting GitHub issues. -Re-uses make-node-meeting for much of the content generation. +## 📋 Requirements -The hardest part to get going is doing the google auth setup -as described in: https://github.com/mhdawson/google-auth-wrapper. +- Node.js 22+ (LTS) +- GitHub Personal Access Token +- Google Cloud Project with Calendar API enabled (for meeting scheduling) +- Google API Key for Calendar access +- HackMD API Token (for meeting minutes) -Basic documentation for each of the templates lives in [TEMPLATES_DOCUMENTATION.md](./TEMPLATES_DOCUMENTATION.md) +## 🔑 Authentication Setup -Currently I'm testing out for generation of the Node.js TSC meetings. +### GitHub Authentication +1. Create a [GitHub Personal Access Token](https://github.com/settings/tokens) +2. Grant the following permissions: + - `repo` (Full control of private repositories) + - `user` (Read user information) -NOTE: The following must be commented out of make-node-meeting +### HackMD Authentication +1. Go to [HackMD](https://hackmd.io/) and sign in to your account +2. Navigate to Account Settings > API Tokens +3. Create a new API token for the meeting artifacts tool +4. Optionally, create or join a team workspace for better organization +### Google Authentication (Calendar Only) + +#### API Key Authentication (Recommended) + +1. Go to [Google Cloud Console](https://console.cloud.google.com/) +2. Create a new project or select an existing one +3. Enable the Google Calendar API +4. Go to **Credentials** → **Create Credentials** → **API Key** +5. Restrict the API key to the Google Calendar API for security +6. Add the API key to your environment variables as `GOOGLE_API_KEY` + +**Note:** API Keys provide simplified authentication and are sufficient for read-only calendar access. They don't require complex OAuth flows or service account setup. + +## 🎯 Available Meeting Commands + +| Meeting Group | Production Command | Development Command | +| ------------------------ | ------------------------------------------ | ---------------------------------------------- | +| UVWASI | `npm run uvwasi-meeting` | `npm run uvwasi-meeting:dev` | +| TSC | `npm run tsc-meeting` | `npm run tsc-meeting:dev` | +| Build | `npm run build-meeting` | `npm run build-meeting:dev` | +| Diagnostics | `npm run diag-meeting` | `npm run diag-meeting:dev` | +| Diagnostics Deep Dive | `npm run diag-deepdive-meeting` | `npm run diag-deepdive-meeting:dev` | +| TypeScript | `npm run typescript-meeting` | `npm run typescript-meeting:dev` | +| Release | `npm run release-meeting` | `npm run release-meeting:dev` | +| Cross Project Council | `npm run cross-project-council-meeting` | `npm run cross-project-council-meeting:dev` | +| Modules | `npm run modules-meeting` | `npm run modules-meeting:dev` | +| Tooling | `npm run tooling-meeting` | `npm run tooling-meeting:dev` | +| Security WG | `npm run security-wg-meeting` | `npm run security-wg-meeting:dev` | +| Next-10 | `npm run next-10-meeting` | `npm run next-10-meeting:dev` | +| Package Maintenance | `npm run package-maintenance-meeting` | `npm run package-maintenance-meeting:dev` | +| Package Metadata Interop | `npm run package-metadata-interop-meeting` | `npm run package-metadata-interop-meeting:dev` | +| Ecosystem Report | `npm run ecosystem-report-meeting` | `npm run ecosystem-report-meeting:dev` | +| Sustainability Collab | `npm run sustainability-collab-meeting` | `npm run sustainability-collab-meeting:dev` | +| Standards | `npm run standards-meeting` | `npm run standards-meeting:dev` | +| Security Collab | `npm run security-collab-meeting` | `npm run security-collab-meeting:dev` | +| Loaders | `npm run loaders-meeting` | `npm run loaders-meeting:dev` | +| Web Server Frameworks | `npm run web-server-frameworks-meeting` | `npm run web-server-frameworks-meeting:dev` | + +## 📁 Project Structure + +``` +create-node-meeting-artifacts/ +├── src/ +│ ├── config.mjs # Configuration management +│ ├── constants.mjs # Application constants +│ ├── github.mjs # GitHub API integration +│ ├── google.mjs # Google APIs integration +│ ├── meeting.mjs # Meeting operations +│ └── utils.mjs # Utility functions +├── templates/ # Meeting templates +├── .nvmrc # Node.js version +├── .env.example # Environment variables example +├── create-node-meeting-artifacts.mjs # Main application +├── TEMPLATES_DOCUMENTATION.md # Template creation guide +└── README.md # This file +``` + +## 📝 Meeting Templates + +Meeting configurations are stored in the `templates/` directory. Each meeting group requires four template files: + +- `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_ ``` -echo -n "Previous Meeting Minutes Google Docs URL: " -read prev_doc_url -echo -n "This Meeting Minutes Google Docs URL: " -read curr_doc_url + +See [TEMPLATES_DOCUMENTATION.md](./TEMPLATES_DOCUMENTATION.md) for detailed template examples and variable explanations. + +### 2. Update GitHub Actions Workflow + +Add your meeting group to `.github/workflows/create-meeting-artifacts.yml`: + +```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 +``` + +### 3. Update Package.json Scripts + +Add npm scripts to `package.json` following this pattern: + +```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" + } +} +``` + +**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 + +## 🏗️ Development + +### Environment Setup + +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` + +### Code Quality + +```bash +npm run lint # Run ESLint +npm run lint:fix # Fix ESLint issues automatically +npm run format # Format code with Prettier +npm run format:check # Check code formatting +npm run check # Run both linting and formatting checks +``` + +## 🚀 Usage + +### Local Development + +```bash +# Using npm scripts (recommended) +npm run tsc-meeting:dev + +# Direct execution +node --env-file=.env create-node-meeting-artifacts.mjs tsc +``` + +## 📂 Output + +The application creates: + +1. **GitHub Issue**: Posted to the configured repository with meeting details and agenda +2. **HackMD Document**: Meeting minutes document in Markdown format with collaborative editing +3. **Console Output**: Links to both the created issue and HackMD document + +## 🔧 Configuration + +### Environment Variables + +#### Required + +- `GITHUB_TOKEN`: GitHub Personal Access Token +- `HACKMD_API_TOKEN`: HackMD API token for creating and managing documents +- `GOOGLE_API_KEY`: Google Calendar API Key for read-only calendar access + +#### Optional + +- `HACKMD_TEAM_NAME`: HackMD team name/path for team workspaces + +### Meeting Base Configuration + +Each `meeting_base_` file contains: + +```bash +CALENDAR_FILTER="Meeting Name in Calendar" +CALENDAR_ID="nodejs.org_nr77ama8p7d7f9ajrpnu506c98@group.calendar.google.com" +USER="nodejs" +REPO="repository-name" +GROUP_NAME="Full Group Name" +AGENDA_TAG="agenda-label" +ISSUE_LABEL="optional-issue-label" +JOINING_INSTRUCTIONS="Meeting join instructions" ``` diff --git a/TEMPLATES_DOCUMENTATION.md b/TEMPLATES_DOCUMENTATION.md index 1bda9e6..de16d8e 100644 --- a/TEMPLATES_DOCUMENTATION.md +++ b/TEMPLATES_DOCUMENTATION.md @@ -1,6 +1,8 @@ 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. -There are only four variables in the documents that _need_ to be changed: +## 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. @@ -11,24 +13,46 @@ There are only four variables in the documents that _need_ to be changed: - **``:** 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 +- **`CALENDAR_ID`:** The Google Calendar ID for the Node.js calendar (typically `nodejs.org_nr77ama8p7d7f9ajrpnu506c98@group.calendar.google.com`) +- **`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="" CALENDAR_ID="nodejs.org_nr77ama8p7d7f9ajrpnu506c98@group.calendar.google.com" @@ -43,7 +67,9 @@ JOINING_INSTRUCTIONS=" ``` ## Meeting Base Example + **File:** `meeting_base_commcomm` + ``` CALENDAR_FILTER="Node.js Community Committee" CALENDAR_ID="nodejs.org_nr77ama8p7d7f9ajrpnu506c98@group.calendar.google.com" @@ -58,9 +84,11 @@ JOINING_INSTRUCTIONS=" ``` # 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 @@ -92,7 +120,9 @@ Click `+GoogleCalendar` at the bottom right to add to your own Google calendar. ``` ## Minutes Base Example + **File:** `minutes_base_commcomm` + ``` @@ -125,8 +155,10 @@ Click `+GoogleCalendar` at the bottom right to add to your own Google calendar. ``` # Observers + List meeting observers who will consistently attend meetings _as observers_. **File:** `observers_` + ``` * * @@ -135,7 +167,9 @@ List meeting observers who will consistently attend meetings _as observers_. ``` ## Observers Example + **File:** `observers_commcomm` + ``` * @therebelrobot (Oz Haven - observer) * @ParidelPooya (Pooya Paridel - observer) diff --git a/bin/create-meeting b/bin/create-meeting index 5d9c984..e2cea8b 100755 --- a/bin/create-meeting +++ b/bin/create-meeting @@ -1,14 +1,43 @@ #!/usr/bin/env node -'use strict'; -const path = require('path'); -const { spawn } = require('child_process'); +/** + * CLI wrapper for create-node-meeting-artifacts + * + * This script provides a convenient way to run the meeting artifacts creator + * with the specified group name. + * + * Usage: + * create-meeting + * create-meeting tsc + * create-meeting benchmarking + */ -let group = process.argv[2]; -if(group === undefined) { - console.error('please pass in group as a argument'); +import { spawn } from 'node:child_process'; + +const group = process.argv[2]; + +if (group === undefined) { + console.error('Please pass in group as an argument'); + console.error('Usage: create-meeting '); + console.error('Example: create-meeting tsc'); process.exit(1); } -group = group.toLowerCase() + '-meeting'; -spawn('npm', [ 'run', group ], { stdio: 'inherit' }); +const scriptName = group.toLowerCase() + '-meeting'; + +console.log(`Running meeting artifacts creation for group: ${group}`); + +// Spawn the npm script with proper stdio inheritance +const child = spawn('npm', ['run', scriptName], { + stdio: 'inherit', + shell: true, +}); + +child.on('close', code => { + process.exit(code); +}); + +child.on('error', error => { + console.error(`Error running script: ${error.message}`); + process.exit(1); +}); diff --git a/create-node-meeting-artifacts.js b/create-node-meeting-artifacts.js deleted file mode 100644 index 47ec647..0000000 --- a/create-node-meeting-artifacts.js +++ /dev/null @@ -1,158 +0,0 @@ -'use strict' -const fs = require('fs'); -const path = require('path'); -const process = require('process'); -const child_process = require('child_process'); -const GitHubApi = require("github"); -const ghauth = require('ghauth'); -const parser = require('properties-parser'); -const gcal = require('google-calendar'); -const googleAuth = require('google-auth-wrapper'); -const gdriveWrapper = require('google-drive-wrapper'); -const meetingGroup = process.argv[2] || 'tsc'; - -const authOptions = { configName: 'iojs-tools', scopes: [ 'user', 'repo' ] }; -const repos = []; - -let githubOrg = 'nodejs'; - -const github = new GitHubApi({ -}); - -ghauth(authOptions, (err, authData) => { - if (err) { - throw err; - } - - // first authenticate to google and github - googleAuth.execute('./', 'client_secret', (googleAuthToken, google) => { - github.authenticate({ - type: "token", - token: authData.token - }); - - // ok all authenticated - - // read in the configuration for the meeting - const invited = fs.readFileSync(path.join('templates', - 'invited_' + - meetingGroup)).toString(); - const observers = fs.readFileSync(path.join('templates', - 'observers_' + - meetingGroup)).toString(); - const baseMeetingInfo = fs.readFileSync(path.join('templates', - 'meeting_base_' + - meetingGroup)); - const meetingProperties = parser.parse(baseMeetingInfo); - - var meetingGroupForTag = meetingGroup; - if (meetingProperties.AGENDA_TAG) { - meetingGroupForTag = meetingProperties.AGENDA_TAG.replace('-agenda', ''); - } - - if (meetingProperties.GITHUB_ORG) { - githubOrg = meetingProperties.GITHUB_ORG.replace(/"/g, ''); - } - - // find the next meeting instance in the google calendar. We assume 1 meeting - // in the next week - const calendar = google.calendar('v3'); - calendar.events.list({ - auth: googleAuthToken, - calendarId: meetingProperties.CALENDAR_ID.replace(/"/g, ''), - timeMin: (new Date()).toISOString(), - timeMax: (new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)).toISOString(), - singleEvents: true, - q: meetingProperties.CALENDAR_FILTER.replace(/"/g, '').replace(/ /g, '.'), - }, (err, response) => { - if (err) { - throw err; - } - - const events = response.items; - if (events.length == 0) { - console.log('Could not find calendar event'); - } else { - // extract the time and complete the meeting info for make-node-meeting - const meetingTime = (new Date(events[0].start.dateTime)).toISOString(); - const meetingInfo = baseMeetingInfo.toString() + - 'MEETING_TIME="' + meetingTime + '"\n' + - 'INVITEES="' + - invited + - '\n' + - '### Observers/Guests\n' + - observers + - '"'; - - fs.writeFileSync( - path.join(process.env.HOME, - '.make-node-meeting/' + meetingGroupForTag + '.sh'), - meetingInfo); - - // generate the meeting issue content with make-node-meeting - var newIssue = child_process.spawnSync( - path.join(__dirname, 'node_modules/make-node-meeting/make-node-meeting.sh'), - [ meetingGroupForTag ]).stdout.toString(); - - // parse out the title - const issueLines = newIssue.split('\n'); - const title = issueLines[1]; - newIssue = issueLines.splice(4).join('\n'); - - - // create the minutes document - const agendaInfo = child_process.spawnSync( - 'node', - ['node_modules/node-meeting-agenda/node-meeting-agenda.js', - meetingGroupForTag + '-agenda', githubOrg]).stdout.toString(); - let minutesDoc = fs.readFileSync(path.join('templates', - 'minutes_base_' + - meetingGroup)).toString(); - - minutesDoc = minutesDoc.replace('$TITLE$', title); - minutesDoc = minutesDoc.replace('$AGENDA_CONTENT$', agendaInfo); - minutesDoc = minutesDoc.replace('$INVITED$', invited); - minutesDoc = minutesDoc.replace('$OBSERVERS$', observers); - const minutesDocName = path.join(__dirname, 'minutes_temp.txt'); - fs.writeFileSync( minutesDocName, minutesDoc); - - // upload the minutes doc - const wrapper = new gdriveWrapper(googleAuthToken, google, 'dummy' ); - wrapper.getMetaForFilename('/nodejs-meetings', function(err, parentMeta) { - if (err !== null) { - console.log('Directory called "nodejs-meetings" does not exist, exiting'); - process.exit(-1); - } - - wrapper.uploadFile(title, minutesDocName, - { parent: parentMeta.id, - compress: false, - encrypt: false, - convert: true, - mimeType: 'application/vnd.google-apps.document' }, - function(err, meta) { - if (err !== null) { - console.log('Failed to upload minutes file'); - console.log(err); - } else { - // create the issue in github - newIssue = newIssue.toString().replace( - /\* \*\*Minutes Google Doc\*\*: <>/, - '* Minutes Google Doc: '); - newIssue = newIssue.replace(/\* _Previous Minutes Google Doc: <>_/,''); - let issueLabel = (meetingProperties.ISSUE_LABEL || '').replace(/"/g, ''); - github.issues.create({ - owner: meetingProperties.USER.replace(/"/g, ''), - repo: meetingProperties.REPO.replace(/"/g, ''), - title: title, - body: newIssue, - assignee: "mhdawson", - labels: issueLabel ? [issueLabel] : undefined - }); - } - }); - }); - } - }); - }); -}) diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs new file mode 100644 index 0000000..6466fd0 --- /dev/null +++ b/create-node-meeting-artifacts.mjs @@ -0,0 +1,106 @@ +#!/usr/bin/env node + +/** + * Node.js Meeting Artifacts Creator + * + * Usage: + * node create-node-meeting-artifacts.mjs [meetingGroup] + * npm run tsc-meeting + * npm run dev -- tsc + */ + +import { getConfig } from './src/config.mjs'; +import * as github from './src/github.mjs'; +import * as google from './src/google.mjs'; +import * as hackmd from './src/hackmd.mjs'; +import * as meetings from './src/meeting.mjs'; + +// Step 1: Application configuration +const config = getConfig(); + +// Step 2: Initialize Google Calendar client with API Key +const calendarClient = google.createCalendarClient(config.google); + +// Step 3: Initialize GitHub client +const githubClient = github.createGitHubClient(config); + +// Step 4: Initialize HackMD client +const hackmdClient = hackmd.createHackMDClient(config); + +// Step 5: Read meeting configuration from templates +const meetingConfig = await meetings.readMeetingConfig(config); + +// Step 6: Find next meeting event in calendar +const event = await google.findNextMeetingEvent(calendarClient, meetingConfig); + +// Step 7: Extract meeting date from event +const meetingDate = new Date(event.start.dateTime); + +// Step 8: Get Meeting Title +const meetingTitle = meetings.generateMeetingTitle( + config, + meetingConfig, + meetingDate +); + +// Step 9: Get agenda information using native implementation +const gitHubAgendaIssues = await github.getAgendaIssues( + githubClient, + config, + meetingConfig +); + +// Step 10: Parse meeting agenda from GitHub issues +const meetingAgenda = meetings.generateMeetingAgenda( + gitHubAgendaIssues, + meetingConfig +); + +// Step 11: Create HackMD document with meeting notes +const hackmdNote = await hackmd.createMeetingNotesDocument( + hackmdClient, + meetingTitle, + '' +); + +// 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 +); + +// Step 14: Create GitHub issue with HackMD link +const githubIssue = await github.createGitHubIssue( + githubClient, + meetingConfig, + meetingTitle, + issueContent +); + +// Step 15: Update the minutes content with the HackMD link +const minutesContent = await meetings.generateMeetingMinutes( + config, + meetingConfig, + meetingTitle, + meetingAgenda, + minutesDocLink, + githubIssue.html_url +); + +// Step 16: Update the HackMD document with the self-referencing link +await hackmd.updateMeetingNotesDocument( + hackmdClient, + hackmdNote.id, + minutesContent +); + +// Output success information with links +console.log(`Created GitHub issue: ${githubIssue.html_url}`); +console.log(`Created HackMD document: ${minutesDocLink}`); diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..979c62d --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,67 @@ +import pluginJs from '@eslint/js'; +import importX from 'eslint-plugin-import-x'; +import jsdoc from 'eslint-plugin-jsdoc'; +import globals from 'globals'; + +export default [ + pluginJs.configs.recommended, + importX.flatConfigs.recommended, + { + ignores: ['node_modules', '.env*', 'minutes_temp.txt'], + }, + { + files: ['**/*.mjs'], + rules: { + 'object-shorthand': 'error', + 'import-x/namespace': 'off', + 'import-x/no-named-as-default': 'off', + 'import-x/no-named-as-default-member': 'off', + 'import-x/no-unresolved': 'off', + 'import-x/order': [ + 'error', + { + groups: [ + 'builtin', + 'external', + 'internal', + ['sibling', 'parent'], + 'index', + 'unknown', + ], + 'newlines-between': 'always', + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + }, + ], + }, + }, + { + files: ['src/**/*.mjs', 'bin/**/*', '*.mjs'], + plugins: { + jsdoc, + }, + languageOptions: { + ecmaVersion: 'latest', + globals: { ...globals.nodeBuiltin }, + }, + rules: { + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-indentation': 'error', + 'jsdoc/require-jsdoc': [ + 'error', + { + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: true, + FunctionExpression: true, + }, + }, + ], + 'jsdoc/require-param': 'error', + }, + }, +]; diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 0000000..29df2df --- /dev/null +++ b/global.d.ts @@ -0,0 +1,21 @@ +/** + * Global TypeScript definitions for JSDoc usage + * This file makes types from third-party libraries available globally without imports in JSDoc comments + */ + +import type { calendar_v3 } from '@googleapis/calendar'; +import type { RestEndpointMethodTypes } from '@octokit/rest'; +import type { HackMDAPI } from '@hackmd/api'; +import type { SingleNote } from '@hackmd/api/dist/type.d.ts'; + +declare global { + // Google API type aliases + type CalendarEvent = calendar_v3.Schema$Event; + type CalendarClient = calendar_v3.Calendar; + type HackMDClient = HackMDAPI; + type HackMDNote = SingleNote; + + // GitHub API type aliases + type GitHubIssue = + RestEndpointMethodTypes['issues']['create']['response']['data']; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3fc3ab9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2765 @@ +{ + "name": "create-node-meeting-artifacts", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "create-node-meeting-artifacts", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@googleapis/calendar": "^11.0.1", + "@hackmd/api": "^2.5.0", + "@octokit/rest": "^22.0.0", + "dedent": "^1.6.0" + }, + "bin": { + "create-meeting": "bin/create-meeting" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/properties-parser": "^0.3.3", + "eslint": "^9.33.0", + "eslint-plugin-import-x": "^4.16.1", + "eslint-plugin-jsdoc": "^54.0.0", + "globals": "^16.3.0", + "prettier": "^3.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.52.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.52.0.tgz", + "integrity": "sha512-BXuN7BII+8AyNtn57euU2Yxo9yA/KUDNzrpXyi3pfqKmBhhysR6ZWOebFh3vyPoqA3/j1SOvGgucElMGwlXing==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.34.1", + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" + }, + "engines": { + "node": ">=20.11.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@googleapis/calendar": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@googleapis/calendar/-/calendar-11.0.1.tgz", + "integrity": "sha512-XR6/V8SjuOyGMMx5shcKB2Ouyqkw2OG5a1srCb6ifPkIJG5qu/aocQ4VVizPc89izgdCGaKqcSpRFNJUsUuizg==", + "license": "Apache-2.0", + "dependencies": { + "googleapis-common": "^8.0.2-rc.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@hackmd/api": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@hackmd/api/-/api-2.5.0.tgz", + "integrity": "sha512-eG4COWt2odRwiUHgJc3vP73iUS2vuAN1ECpPbKF0kRNJt6YerlsAsf3TNBs/CfVuemsC3g7JeAZpIrT7gmsTRQ==", + "license": "MIT", + "dependencies": { + "axios": "^1.8.4", + "tslib": "^1.14.1" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.3.tgz", + "integrity": "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.1", + "@octokit/request": "^10.0.2", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.1.tgz", + "integrity": "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz", + "integrity": "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.1.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.0.0.tgz", + "integrity": "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.1.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.0.tgz", + "integrity": "sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.2", + "@octokit/plugin-paginate-rest": "^13.0.1", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tybys/wasm-util/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "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==", + "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": "*" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", + "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "license": "Apache-2.0" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import-x": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", + "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.35.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.0.1", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-import-x" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "eslint-import-resolver-node": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "eslint-import-resolver-node": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import-x/node_modules/minimatch": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "54.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-54.0.0.tgz", + "integrity": "sha512-8w5c8OmmD5WD5MNQy1AhmYbiyV4IlSscXUyg5MwvN3BI/bLUmRpeEXc+Mj37y2UZsLhzvHyCscQenUzvbLxQ7Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.52.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.4.1", + "escape-string-regexp": "^4.0.0", + "espree": "^10.4.0", + "esquery": "^1.6.0", + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.2", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": ">=20.11.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.1.tgz", + "integrity": "sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz", + "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.2.1.tgz", + "integrity": "sha512-HMxFl2NfeHYnaL1HoRIN1XgorKS+6CDaM+z9LSSN+i/nKDDL4KFFEWogMXu7jV4HZQy2MsxpY+wA5XIf3w410A==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^7.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.1.tgz", + "integrity": "sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common": { + "version": "8.0.2-rc.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.2-rc.0.tgz", + "integrity": "sha512-JTcxRvmFa9Ec1uyfMEimEMeeKq1sHNZX3vn2qmoUMtnvixXXvcqTcbDZvEZXkEWpGlPlOf4joyep6/qs0BrLyg==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^7.0.0-rc.4", + "google-auth-library": "^10.0.0-rc.1", + "qs": "^6.7.0", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "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", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 4a54335..2e244ae 100644 --- a/package.json +++ b/package.json @@ -2,28 +2,79 @@ "name": "create-node-meeting-artifacts", "version": "0.0.1", "description": "Creates issue and minutes doc for node meeting", - "main": "index.js", + "type": "module", + "main": "create-node-meeting-artifacts.mjs", "bin": { "create-meeting": "./bin/create-meeting" }, + "engines": { + "node": ">=22.0.0" + }, "dependencies": { - "ghauth": "^3.2.1", - "github": "^11.0.0", - "google-auth-wrapper": "^0.5.0", - "google-calendar": "^1.3.2", - "google-drive-wrapper": "^0.6.0", - "make-node-meeting": "^1.0.11", - "node-meeting-agenda": "^1.0.4", - "properties-parser": "^0.3.1" + "@googleapis/calendar": "^11.0.1", + "@hackmd/api": "^2.5.0", + "@octokit/rest": "^22.0.0", + "dedent": "^1.6.0" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/properties-parser": "^0.3.3", + "eslint": "^9.33.0", + "eslint-plugin-import-x": "^4.16.1", + "eslint-plugin-jsdoc": "^54.0.0", + "globals": "^16.3.0", + "prettier": "^3.6.2" }, - "devDependencies": {}, "scripts": { - "tsc-meeting": "node create-node-meeting-artifacts.js tsc", - "benchmarking-meeting": "node create-node-meeting-artifacts.js benchmarking", - "release-meeting": "node create-node-meeting-artifacts.js Release", - "cc-meeting": "node create-node-meeting-artifacts.js commcomm", - "diag": "node create-node-meeting-artifacts.js diag", - "all": "npm run tsc-meeting && npm run benchmarking-meeting && npm run release-meeting && npm run cc-meeting && npm run diag" + "dev": "node --env-file=.env create-node-meeting-artifacts.mjs", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "check": "npm run lint && npm run format:check", + "test": "node --test test/**/*.test.mjs", + "test:watch": "node --test --watch test/**/*.test.mjs", + "test:coverage": "node --test --experimental-test-coverage test/**/*.test.mjs", + "uvwasi-meeting": "node create-node-meeting-artifacts.mjs uvwasi", + "uvwasi-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs uvwasi", + "tsc-meeting": "node create-node-meeting-artifacts.mjs tsc", + "tsc-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs tsc", + "build-meeting": "node create-node-meeting-artifacts.mjs build", + "build-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs build", + "diag-meeting": "node create-node-meeting-artifacts.mjs diag", + "diag-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs diag", + "diag-deepdive-meeting": "node create-node-meeting-artifacts.mjs diag_deepdive", + "diag-deepdive-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs diag_deepdive", + "typescript-meeting": "node create-node-meeting-artifacts.mjs typescript", + "typescript-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs typescript", + "release-meeting": "node create-node-meeting-artifacts.mjs Release", + "release-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs Release", + "cross-project-council-meeting": "node create-node-meeting-artifacts.mjs cross_project_council", + "cross-project-council-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs cross_project_council", + "modules-meeting": "node create-node-meeting-artifacts.mjs modules", + "modules-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs modules", + "tooling-meeting": "node create-node-meeting-artifacts.mjs tooling", + "tooling-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs tooling", + "security-wg-meeting": "node create-node-meeting-artifacts.mjs security-wg", + "security-wg-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs security-wg", + "next-10-meeting": "node create-node-meeting-artifacts.mjs next-10", + "next-10-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs next-10", + "package-maintenance-meeting": "node create-node-meeting-artifacts.mjs package-maintenance", + "package-maintenance-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs package-maintenance", + "package-metadata-interop-meeting": "node create-node-meeting-artifacts.mjs package_metadata_interop", + "package-metadata-interop-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs package_metadata_interop", + "ecosystem-report-meeting": "node create-node-meeting-artifacts.mjs ecosystem_report", + "ecosystem-report-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs ecosystem_report", + "sustainability-collab-meeting": "node create-node-meeting-artifacts.mjs sustainability_collab", + "sustainability-collab-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs sustainability_collab", + "standards-meeting": "node create-node-meeting-artifacts.mjs standards", + "standards-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs standards", + "security-collab-meeting": "node create-node-meeting-artifacts.mjs security_collab", + "security-collab-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs security_collab", + "loaders-meeting": "node create-node-meeting-artifacts.mjs loaders", + "loaders-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs loaders", + "web-server-frameworks-meeting": "node create-node-meeting-artifacts.mjs web-server-frameworks", + "web-server-frameworks-meeting:dev": "node --env-file=.env create-node-meeting-artifacts.mjs web-server-frameworks" }, "author": "", "license": "MIT" diff --git a/src/config.mjs b/src/config.mjs new file mode 100644 index 0000000..891ff4f --- /dev/null +++ b/src/config.mjs @@ -0,0 +1,37 @@ +import { homedir } from 'node:os'; +import { join, dirname } from 'node:path'; + +const defaultMeetingsDirectory = join(homedir(), '.make-node-meeting'); + +/** + * Gets the application configuration from environment variables and arguments + * @returns {import('./types.d.ts').AppConfig} Application configuration object + */ +export const getConfig = () => ({ + // Meeting group from command line argument, defaults to 'tsc' + meetingGroup: process.argv[2], + + // GitHub personal access token from environment + githubToken: process.env.GITHUB_TOKEN, + + // Google authentication configuration - now uses API Keys for simplicity + google: { + // Google API Key for Calendar access (preferred method) + apiKey: process.env.GOOGLE_API_KEY, + }, + + // HackMD configuration for meeting notes + hackmd: { + // HackMD API token for authentication + apiToken: process.env.HACKMD_API_TOKEN, + // HackMD team name + teamName: process.env.HACKMD_TEAM_NAME, + }, + + // 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 new file mode 100644 index 0000000..897a892 --- /dev/null +++ b/src/constants.mjs @@ -0,0 +1,36 @@ +// Default configuration values +export const DEFAULT_CONFIG = { + // Default GitHub organization name + githubOrg: 'nodejs', + // Default Host of the Meeting + host: 'Node.js', +}; + +// Time constants for date calculations +export const TIME_CONSTANTS = { + // Week in milliseconds for calendar search + WEEK_IN_MS: 7 * 24 * 60 * 60 * 1000, +}; + +// Relevant Timezones for Date manipulation +export const RELEVANT_TIMEZONES = [ + { label: 'US / Pacific', tz: 'America/Los_Angeles' }, + { label: 'US / Mountain', tz: 'America/Denver' }, + { label: 'US / Central', tz: 'America/Chicago' }, + { label: 'US / Eastern', tz: 'America/New_York' }, + { label: 'EU / Western', tz: 'Europe/London' }, + { label: 'EU / Central', tz: 'Europe/Amsterdam' }, + { label: 'EU / Eastern', tz: 'Europe/Helsinki' }, + { label: 'Moscow', tz: 'Europe/Moscow' }, + { label: 'Chennai', tz: 'Asia/Kolkata' }, + { label: 'Hangzhou', tz: 'Asia/Shanghai' }, + { label: 'Tokyo', tz: 'Asia/Tokyo' }, + { label: 'Sydney', tz: 'Australia/Sydney' }, +]; + +// Creates the default permissions for our generated docs +export const HACKMD_DEFAULT_PERMISSIONS = { + readPermission: 'guest', + writePermission: 'signed_in', + commentPermission: 'signed_in_users', +}; diff --git a/src/github.mjs b/src/github.mjs new file mode 100644 index 0000000..e1bb747 --- /dev/null +++ b/src/github.mjs @@ -0,0 +1,83 @@ +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 + * @returns {import('@octokit/rest').Octokit} Configured GitHub API client + */ +export const createGitHubClient = ({ githubToken: auth }) => + new Octokit({ auth }); + +/** + * Creates GitHub issue with meeting information and Google Doc link + * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client + * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration object + * @param {string} title - Issue title + * @param {string} content - Issue content + * @returns {Promise} Created issue data + */ +export const createGitHubIssue = async ( + { rest }, + { properties }, + title, + content +) => { + const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; + + const issueLabel = properties.ISSUE_LABEL + ? [properties.ISSUE_LABEL] + : undefined; + + // Create the GitHub issue with meeting information + const response = await rest.issues.create({ + owner: githubOrg, + repo: properties.REPO, + title, + body: content, + labels: issueLabel, + }); + + return response.data; +}; + +/** + * Fetches GitHub issues from all repositories in an organization with a specific 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<{ repoName: string, issues: Array }> } Formatted markdown string of issues + */ +export const getAgendaIssues = async ( + { paginate, rest }, + { meetingGroup }, + { properties } +) => { + const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; + const agendaTag = properties.AGENDA_TAG ?? `${meetingGroup}-agenda`; + + // Get all public repositories in the organization + const repos = await paginate(rest.repos.listForOrg, { + org: properties.USER, + type: 'public', + per_page: 100, + }); + + // Fetch issues from all repositories concurrently + const issuePromises = repos.map(async repo => { + const issues = await paginate(rest.issues.listForRepo, { + owner: githubOrg, + repo: repo.name, + labels: agendaTag, + state: 'open', + per_page: 100, + }); + + const filteredIssues = issues.filter(({ pull_request }) => !pull_request); // Exclude PRs + + return { repoName: repo.name, issues: filteredIssues }; + }); + + return Promise.all(issuePromises); +}; diff --git a/src/google.mjs b/src/google.mjs new file mode 100644 index 0000000..52af36e --- /dev/null +++ b/src/google.mjs @@ -0,0 +1,44 @@ +import { calendar } from '@googleapis/calendar'; + +import { TIME_CONSTANTS } from './constants.mjs'; + +/** + * Creates an authenticated Google Calendar client using API Key + * @param {import('./types.d.ts').GoogleConfig} gConfig - Google configuration object + * @returns {CalendarClient} Authenticated Google Calendar client + */ +export const createCalendarClient = ({ apiKey: auth }) => + calendar({ version: 'v3', auth }); + +/** + * Finds the next meeting event in Google Calendar within the next week + * @param {import('@googleapis/calendar').calendar_v3.Calendar} calendarClient - Google Calendar client + * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration object + * @returns {Promise} Calendar event object + */ +export const findNextMeetingEvent = async (calendarClient, meetingConfig) => { + const now = new Date(); + + const nextWeek = new Date(now.getTime() + TIME_CONSTANTS.WEEK_IN_MS); + + // Search for events in the specified calendar using the filter text + const response = await calendarClient.events.list({ + calendarId: meetingConfig.properties.CALENDAR_ID?.replace(/"/g, ''), + timeMin: now.toISOString(), + timeMax: nextWeek.toISOString(), + singleEvents: true, + // Replace spaces with dots for Google Calendar search compatibility + q: meetingConfig.properties.CALENDAR_FILTER?.replace(/"/g, '').replace( + / /g, + '.' + ), + }); + + // Ensure we found at least one event + if (!response.data.items || response.data.items.length === 0) { + throw new Error('Could not find calendar event for the next week'); + } + + // Return the first (next) event found + return response.data.items[0]; +}; diff --git a/src/hackmd.mjs b/src/hackmd.mjs new file mode 100644 index 0000000..90b16aa --- /dev/null +++ b/src/hackmd.mjs @@ -0,0 +1,37 @@ +import HackMDAPI from '@hackmd/api'; + +import { HACKMD_DEFAULT_PERMISSIONS } from './constants.mjs'; + +/** + * Creates a HackMD API client + * @param {import('./types.d.ts').AppConfig} config - Application configuration + * @returns {HackMDClient} Configured HackMD API client + */ +export const createHackMDClient = ({ hackmd: { apiToken, teamName } }) => { + // Use team-specific API endpoint if teamPath is provided + const baseURL = teamName + ? `https://api.hackmd.io/v1/teams/${teamName}` + : 'https://api.hackmd.io/v1'; + + return new HackMDAPI(apiToken, baseURL); +}; + +/** + * Creates a new meeting notes document in HackMD + * @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 + */ +export const createMeetingNotesDocument = (hackmdClient, title, content) => + hackmdClient.createNote({ title, content, ...HACKMD_DEFAULT_PERMISSIONS }); + +/** + * Updates an existing meeting notes document in HackMD + * @param {HackMDClient} hackmdClient - HackMD API client + * @param {string} noteId - HackMD note ID + * @param {string} content - Updated document content in Markdown + * @returns {Promise} Updated note data + */ +export const updateMeetingNotesDocument = (hackmdClient, noteId, content) => + hackmdClient.updateNote(noteId, { content }); diff --git a/src/meeting.mjs b/src/meeting.mjs new file mode 100644 index 0000000..60a401f --- /dev/null +++ b/src/meeting.mjs @@ -0,0 +1,182 @@ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +import { DEFAULT_CONFIG } from './constants.mjs'; +import * as dates from './utils/dates.mjs'; +import * as templates from './utils/templates.mjs'; +import * as urls from './utils/urls.mjs'; + +/** + * Reads and parses meeting configuration from template files + * @param {import('./types.d.ts').AppConfig} config - Application configuration + * @returns {Promise} Meeting configuration object + */ +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' + ); + + return { + invited, + observers, + baseMeetingInfo, + properties: templates.parseMeetingProperties(baseMeetingInfo), + }; +}; + +/** + * 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 {Date} meetingDate - Date of the meeting + * @returns {Promise} 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; + + const utcShort = meetingDate.toISOString().split('T')[0]; + + return `${host} ${groupName} Meeting ${utcShort}`; +}; + +/** + * Generates the meeting agenda from the list of agenda issues + * @param {Array<{ repoName: string, issues: Array }>} agendaIssues - List of agenda issues + * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration + * @returns {Promise} Formatted meeting agenda + */ +export const generateMeetingAgenda = (agendaIssues, meetingConfig) => { + const props = meetingConfig.properties; + + const githubOrg = props.USER ?? DEFAULT_CONFIG.githubOrg; + + // Format issues as markdown + let agendaMarkdown = ''; + + agendaIssues.forEach(({ repoName, issues }) => { + if (issues.length > 0) { + agendaMarkdown += `### ${githubOrg}/${repoName}\n\n`; + + issues.forEach(issue => { + // Escape markdown characters in title + const cleanTitle = issue.title.replace(/([[\]])/g, '\\$1'); + + agendaMarkdown += `* ${cleanTitle} [#${issue.number}](${issue.html_url})\n`; + }); + + agendaMarkdown += '\n'; + } + }); + + return agendaMarkdown.trim(); +}; + +/** + * 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) + * @param {Date} meetingDate - Date of the meeting + */ +export const generateMeetingIssue = async ( + config, + meetingConfig, + meetingDate, + meetingAgenda, + minutesDocLink +) => { + 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 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 + */ +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, + }; + + return templates.parseVariables(template, templateVariables); +}; diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..3efcc46 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,79 @@ +/** + * Application configuration object + */ +export interface AppConfig { + /** Meeting group from command line argument */ + meetingGroup: string; + /** GitHub personal access token */ + githubToken: string; + /** Google API configuration (Calendar only) */ + google: GoogleConfig; + /** HackMD API configuration */ + hackmd: HackMDConfig; + /** Directory paths configuration */ + directories: DirectoryConfig; +} + +/** + * Google authentication configuration (Calendar only) + */ +export interface GoogleConfig { + /** Google API Key for Calendar access */ + apiKey?: string; +} + +/** + * HackMD API configuration + */ +export interface HackMDConfig { + /** HackMD API token */ + apiToken: string; + /** HackMD team name/path */ + teamName?: string; +} + +/** + * Directory paths configuration + */ +export interface DirectoryConfig { + /** Templates directory path */ + templates: string; +} + +/** + * Meeting configuration object parsed from templates + */ +export interface MeetingConfig { + /** Invited attendees list */ + invited: string; + /** Observers list */ + observers: string; + /** Base meeting information */ + baseMeetingInfo: string; + /** Parsed meeting properties */ + properties: MeetingProperties; +} + +/** + * Meeting properties parsed from template file + */ +export interface MeetingProperties { + /** Calendar ID to search for events */ + CALENDAR_ID?: 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; + /** Meeting joining instructions */ + JOINING_INSTRUCTIONS?: string; +} diff --git a/src/utils/dates.mjs b/src/utils/dates.mjs new file mode 100644 index 0000000..1ee01bb --- /dev/null +++ b/src/utils/dates.mjs @@ -0,0 +1,44 @@ +import { RELEVANT_TIMEZONES } from '../constants.mjs'; + +/** + * Generic datetime formatter that accepts DateTimeFormat options + * @param {Date} date - The date to format + * @param {Intl.DateTimeFormatOptions} options - DateTimeFormat options + * @returns {string} Formatted date/time string + */ +export const formatDateTime = (date, options = {}) => { + const formatter = new Intl.DateTimeFormat('en-US', options); + + return formatter.format(date); +}; + +/** + * Formats a date to different timezones for the meeting schedule + * @param {Date} meetingDate - The meeting date + * @returns {Object} Object with formatted times for different timezones + */ +export const formatTimezones = meetingDate => ({ + utc: formatDateTime(meetingDate, { + weekday: 'short', + day: '2-digit', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZone: 'UTC', + }), + timezones: RELEVANT_TIMEZONES.map(({ label, tz }) => ({ + label, + time: formatDateTime(meetingDate, { + weekday: 'short', + day: '2-digit', + month: 'short', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + hour12: true, + timeZone: tz, + }), + })), +}); diff --git a/src/utils/templates.mjs b/src/utils/templates.mjs new file mode 100644 index 0000000..6ca65d1 --- /dev/null +++ b/src/utils/templates.mjs @@ -0,0 +1,46 @@ +/** + * 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; +}; + +/** + * Simple parser for template properties (KEY="value" format) + * @param {string} content - Template content + * @returns {Record} Parsed properties + */ +export const parseMeetingProperties = content => { + const properties = {}; + + // Handle multiline properties first with a generic regex + // Matches: KEY="multiline content" where content can span multiple lines + const multilineMatches = content.matchAll( + /^([A-Z_][A-Z0-9_]*)="([\s\S]*?)"$/gm + ); + + for (const match of multilineMatches) { + const [, key, value] = match; + + properties[key] = value; + } + + return properties; +}; diff --git a/src/utils/urls.mjs b/src/utils/urls.mjs new file mode 100644 index 0000000..0bbbd19 --- /dev/null +++ b/src/utils/urls.mjs @@ -0,0 +1,43 @@ +import { formatDateTime } from './dates.mjs'; + +/** + * Generates TimeAndDate.com world clock link + * @param {Date} meetingDate - The meeting date + * @param {string} groupName - The meeting group name + * @returns {string} TimeAndDate.com URL + */ +export const generateTimeAndDateLink = (meetingDate, groupName) => { + const encodedGroupName = encodeURIComponent(groupName); + + const utcShort = meetingDate.toISOString().split('T')[0]; + + const isoDateTime = meetingDate + .toISOString() + .replace(/[-:]/g, '') + .split('.')[0]; + + return `https://www.timeanddate.com/worldclock/fixedtime.html?msg=Node.js+Foundation+${encodedGroupName}+Meeting+${utcShort}&iso=${isoDateTime}`; +}; + +/** + * Generates WolframAlpha timezone conversion link + * @param {Date} meetingDate - The meeting date + * @returns {string} WolframAlpha URL + */ +export const generateWolframAlphaLink = meetingDate => { + const utcTime = formatDateTime(meetingDate, { + timeZone: 'UTC', + hour: '2-digit', + minute: '2-digit', + hour12: true, + }); + + const utcDate = formatDateTime(meetingDate, { + timeZone: 'UTC', + month: 'short', + day: 'numeric', + year: 'numeric', + }); + + return `https://www.wolframalpha.com/input/?i=${encodeURIComponent(utcTime)}+UTC%2C+${encodeURIComponent(utcDate)}+in+local+time`; +}; diff --git a/templates/meeting_base_cross_project_council b/templates/meeting_base_cross_project_council index 87df473..257349e 100644 --- a/templates/meeting_base_cross_project_council +++ b/templates/meeting_base_cross_project_council @@ -2,7 +2,6 @@ CALENDAR_FILTER="Cross Project Council Meeting" CALENDAR_ID="c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8@group.calendar.google.com" CALENDAR_ID="linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com" USER="openjs-foundation" -GITHUB_ORG="openjs-foundation" HOST="OpenJS Foundation" REPO="cross-project-council" GROUP_NAME="Cross Project Council" diff --git a/templates/meeting_base_ecosystem_report b/templates/meeting_base_ecosystem_report index 9f6075e..45c23fb 100644 --- a/templates/meeting_base_ecosystem_report +++ b/templates/meeting_base_ecosystem_report @@ -1,7 +1,6 @@ CALENDAR_FILTER="Ecosystem Report Collab Space" CALENDAR_ID="linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com" USER="openjs-foundation" -GITHUB_ORG="openjs-foundation" HOST="OpenJS Foundation" REPO="ecosystem-report-collab-space" GROUP_NAME="Ecosystem Report Collab Space" diff --git a/templates/meeting_base_nodejsafrica b/templates/meeting_base_nodejsafrica index b22bfdd..4f9ced5 100644 --- a/templates/meeting_base_nodejsafrica +++ b/templates/meeting_base_nodejsafrica @@ -1,7 +1,6 @@ CALENDAR_FILTER="Node.js Africa Membership/Leadership" CALENDAR_ID="node.js.africa@gmail.com" USER="nodejsafrica" -GITHUB_ORG="nodejsafrica" HOST="Node.js Africa Team" REPO="admin" GROUP_NAME="leadership" diff --git a/templates/meeting_base_package_metadata_interop b/templates/meeting_base_package_metadata_interop index 12c13d3..8c933cc 100644 --- a/templates/meeting_base_package_metadata_interop +++ b/templates/meeting_base_package_metadata_interop @@ -1,7 +1,6 @@ CALENDAR_FILTER="Package Metadata Interoperability" CALENDAR_ID="linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com" USER="openjs-foundation" -GITHUB_ORG="openjs-foundation" HOST="OpenJS Foundation" REPO="package-metadata-interoperability-collab-space" GROUP_NAME="Package Metadata Interoperability Collab Space" diff --git a/templates/meeting_base_security_collab b/templates/meeting_base_security_collab index b6ea01f..9176730 100644 --- a/templates/meeting_base_security_collab +++ b/templates/meeting_base_security_collab @@ -1,7 +1,6 @@ CALENDAR_FILTER="Security Collab Space meeting" CALENDAR_ID="linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com" USER="openjs-foundation" -GITHUB_ORG="openjs-foundation" HOST="OpenJS Foundation" REPO="security-collab-space" GROUP_NAME="Security Collab Space" diff --git a/templates/meeting_base_standards b/templates/meeting_base_standards index 05b157e..183908a 100644 --- a/templates/meeting_base_standards +++ b/templates/meeting_base_standards @@ -1,7 +1,6 @@ CALENDAR_FILTER="Standards Working Group Meeting" CALENDAR_ID="linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com" USER="openjs-foundation" -GITHUB_ORG="openjs-foundation" HOST="OpenJS Foundation" REPO="standards" GROUP_NAME="Standards Working Group" diff --git a/templates/meeting_base_sustainability_collab b/templates/meeting_base_sustainability_collab index 9c05896..8483139 100644 --- a/templates/meeting_base_sustainability_collab +++ b/templates/meeting_base_sustainability_collab @@ -1,7 +1,6 @@ CALENDAR_FILTER="Sustainability Collaboration Space" CALENDAR_ID="linuxfoundation.org_fuop4ufv766f9avc517ujs4i0g@group.calendar.google.com" USER="openjs-foundation" -GITHUB_ORG="openjs-foundation" HOST="OpenJS Foundation" REPO="sustainability-collab-space" GROUP_NAME="Sustainability Collaboration Space" diff --git a/templates/meeting_base_tsc b/templates/meeting_base_tsc index 5cd6b31..33c6511 100644 --- a/templates/meeting_base_tsc +++ b/templates/meeting_base_tsc @@ -1,6 +1,6 @@ CALENDAR_FILTER="Node.js TSC Meeting" CALENDAR_ID="c_16f0ae5d3a22625175d199dbdb1cac84c2d09eab7f173e94f558417cb5cdbfd8@group.calendar.google.com" -USER="nodejs" +USER="nodejs-test-tsc" REPO="TSC" HOST="Node.js" GROUP_NAME="Technical Steering Committee (TSC)" diff --git a/templates/meeting_issue.md b/templates/meeting_issue.md new file mode 100644 index 0000000..f4d39b6 --- /dev/null +++ b/templates/meeting_issue.md @@ -0,0 +1,38 @@ +## Time + +**UTC $UTC_TIME$**: + +| Timezone | Date/Time | +| -------- | --------- | +$TIMEZONE_TABLE$ + +Or in your local time: + +* $TIME_AND_DATE_LINK$ +* or $WOLFRAM_ALPHA_LINK$ + +## Links + +* Minutes Google Doc: <$MINUTES_DOC$> + +## Agenda + +Extracted from **$AGENDA_LABEL$** labelled issues and pull requests from the **$GITHUB_ORG$ org** prior to the meeting. + +$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/test/config.test.mjs b/test/config.test.mjs new file mode 100644 index 0000000..c8d97a1 --- /dev/null +++ b/test/config.test.mjs @@ -0,0 +1,105 @@ +import assert from 'node:assert'; +import process from 'node:process'; +import { describe, it, beforeEach, afterEach } from 'node:test'; + +import { getConfig } from '../src/config.mjs'; + +describe('Config', () => { + let originalEnv; + let originalArgv; + + beforeEach(() => { + // Save original environment and argv + originalEnv = { ...process.env }; + originalArgv = [...process.argv]; + }); + + afterEach(() => { + // Restore original environment and argv + process.env = originalEnv; + process.argv = originalArgv; + }); + + describe('getConfig', () => { + it('should return empty meeting group when no argument provided', () => { + process.argv = ['node', 'script.mjs']; + + const config = getConfig(); + + assert.strictEqual(config.meetingGroup, undefined); + }); + + it('should use command line argument for meeting group', () => { + process.argv = ['node', 'script.mjs', 'build']; + + const config = getConfig(); + + assert.strictEqual(config.meetingGroup, 'build'); + }); + + it('should read GitHub token from environment', () => { + process.env.GITHUB_TOKEN = 'test_token'; + + const config = getConfig(); + + assert.strictEqual(config.githubToken, 'test_token'); + }); + + it('should read Google API Key config from environment', () => { + process.env.GOOGLE_API_KEY = 'test_google_api_key_123'; + + const config = getConfig(); + + assert.strictEqual(config.google.apiKey, 'test_google_api_key_123'); + }); + + it('should handle missing Google API Key gracefully', () => { + delete process.env.GOOGLE_API_KEY; + + const config = getConfig(); + + assert.strictEqual(config.google.apiKey, undefined); + }); + + it('should read HackMD config from environment', () => { + process.env.HACKMD_API_TOKEN = 'hackmd_token'; + process.env.HACKMD_TEAM_NAME = 'nodejs'; + + const config = getConfig(); + + assert.strictEqual(config.hackmd.apiToken, 'hackmd_token'); + assert.strictEqual(config.hackmd.teamName, 'nodejs'); + }); + + it('should handle undefined environment variables gracefully', () => { + // Clear all relevant env vars + delete process.env.GITHUB_TOKEN; + delete process.env.HACKMD_API_TOKEN; + delete process.env.GOOGLE_CLIENT_ID; + + const config = getConfig(); + + assert.strictEqual(config.githubToken, undefined); + assert.strictEqual(config.hackmd.apiToken, undefined); + assert.strictEqual(config.google.clientId, undefined); + }); + + it('should contain all required config sections', () => { + const config = getConfig(); + + assert.ok('meetingGroup' in config); + assert.ok('githubToken' in config); + assert.ok('google' in config); + assert.ok('hackmd' in config); + assert.ok('directories' in config); + }); + + it('should contain all required directory paths', () => { + const config = getConfig(); + + assert.ok('config' in config.directories); + assert.ok('output' in config.directories); + assert.ok('templates' in config.directories); + }); + }); +}); diff --git a/test/meeting.test.mjs b/test/meeting.test.mjs new file mode 100644 index 0000000..9163cae --- /dev/null +++ b/test/meeting.test.mjs @@ -0,0 +1,164 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; + +import { generateMeetingTitle } from '../src/meeting.mjs'; + +describe('Meeting', () => { + describe('generateMeetingTitle', () => { + it('should generate meeting title with default values', () => { + const config = { meetingGroup: 'tsc' }; + const meetingConfig = { properties: {} }; + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual(result, 'Node.js tsc Meeting 2023-10-15'); + }); + + it('should use HOST from properties', () => { + const config = { meetingGroup: 'tsc' }; + + const meetingConfig = { + properties: { + HOST: 'OpenJS Foundation', + }, + }; + + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual(result, 'OpenJS Foundation tsc Meeting 2023-10-15'); + }); + + it('should use GROUP_NAME from properties', () => { + const config = { meetingGroup: 'tsc' }; + + const meetingConfig = { + properties: { + GROUP_NAME: 'Technical Steering Committee', + }, + }; + + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual( + result, + 'Node.js Technical Steering Committee Meeting 2023-10-15' + ); + }); + + it('should use both custom HOST and GROUP_NAME', () => { + const config = { meetingGroup: 'tsc' }; + + const meetingConfig = { + properties: { + HOST: 'OpenJS Foundation', + GROUP_NAME: 'Technical Steering Committee', + }, + }; + + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual( + result, + 'OpenJS Foundation Technical Steering Committee Meeting 2023-10-15' + ); + }); + + it('should handle different date formats correctly', () => { + const config = { meetingGroup: 'build' }; + const meetingConfig = { properties: {} }; + const meetingDate = new Date('2023-12-31T23:59:59Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual(result, 'Node.js build Meeting 2023-12-31'); + }); + + it('should handle different meeting groups', () => { + const config = { meetingGroup: 'security-wg' }; + + const meetingConfig = { + properties: { + GROUP_NAME: 'Security Working Group', + }, + }; + + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual( + result, + 'Node.js Security Working Group Meeting 2023-10-15' + ); + }); + + it('should handle edge case dates', () => { + const config = { meetingGroup: 'tsc' }; + const meetingConfig = { properties: {} }; + const meetingDate = new Date('2024-02-29T12:00:00Z'); // Leap year + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual(result, 'Node.js tsc Meeting 2024-02-29'); + }); + + it('should handle null/undefined properties gracefully', () => { + const config = { meetingGroup: 'tsc' }; + + const meetingConfig = { + properties: { + HOST: null, + GROUP_NAME: undefined, + }, + }; + + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + // Should fall back to defaults + assert.strictEqual(result, 'Node.js tsc Meeting 2023-10-15'); + }); + + it('should handle empty string properties', () => { + const config = { meetingGroup: 'tsc' }; + + const meetingConfig = { + properties: { + HOST: '', + GROUP_NAME: '', + }, + }; + + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + // Empty strings are used as-is (nullish coalescing doesn't catch empty strings) + assert.strictEqual(result, ' Meeting 2023-10-15'); + }); + + it('should handle very long meeting group names', () => { + const config = { + meetingGroup: 'very-long-working-group-name-for-testing-purposes', + }; + + const meetingConfig = { properties: {} }; + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateMeetingTitle(config, meetingConfig, meetingDate); + + assert.strictEqual( + result, + 'Node.js very-long-working-group-name-for-testing-purposes Meeting 2023-10-15' + ); + }); + }); +}); diff --git a/test/snapshots/README.md b/test/snapshots/README.md new file mode 100644 index 0000000..e69de29 diff --git a/test/utils/dates.test.mjs b/test/utils/dates.test.mjs new file mode 100644 index 0000000..f504ed1 --- /dev/null +++ b/test/utils/dates.test.mjs @@ -0,0 +1,148 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; + +import { formatDateTime, formatTimezones } from '../../src/utils/dates.mjs'; + +describe('Utils - Dates', () => { + describe('formatDateTime', () => { + it('should format date with default options', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const result = formatDateTime(date); + + assert.strictEqual(typeof result, 'string'); + assert.ok(result.length > 0); + }); + + it('should format date with custom options', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const options = { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }; + + const result = formatDateTime(date, options); + + assert.strictEqual(typeof result, 'string'); + assert.ok(result.includes('2023')); + assert.ok(result.includes('October')); + }); + + it('should format date with timezone option', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const options = { + timeZone: 'America/New_York', + hour: '2-digit', + minute: '2-digit', + hour12: true, + }; + + const result = formatDateTime(date, options); + + assert.strictEqual(typeof result, 'string'); + assert.ok(result.includes('AM') || result.includes('PM')); + }); + + it('should handle edge cases with invalid dates gracefully', () => { + const invalidDate = new Date('invalid'); + + assert.throws(() => { + formatDateTime(invalidDate); + }); + }); + }); + + describe('formatTimezones', () => { + it('should return object with utc and timezones properties', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const result = formatTimezones(date); + + assert.strictEqual(typeof result, 'object'); + assert.ok(Object.hasOwn(result, 'utc')); + assert.ok(Object.hasOwn(result, 'timezones')); + }); + + it('should format UTC time correctly', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const result = formatTimezones(date); + + assert.strictEqual(typeof result.utc, 'string'); + assert.ok(result.utc.includes('2023')); + assert.ok(result.utc.includes('Oct')); + assert.ok(result.utc.includes('2:30')); + }); + + it('should return array of timezone objects', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const result = formatTimezones(date); + + assert.ok(Array.isArray(result.timezones)); + assert.ok(result.timezones.length > 0); + + // Check first timezone object structure + const firstTz = result.timezones[0]; + + assert.ok(Object.hasOwn(firstTz, 'label')); + assert.ok(Object.hasOwn(firstTz, 'time')); + assert.strictEqual(typeof firstTz.label, 'string'); + assert.strictEqual(typeof firstTz.time, 'string'); + }); + + it('should include all expected timezones', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const result = formatTimezones(date); + + const expectedLabels = [ + 'US / Pacific', + 'US / Mountain', + 'US / Central', + 'US / Eastern', + 'EU / Western', + 'EU / Central', + 'EU / Eastern', + 'Moscow', + 'Chennai', + 'Hangzhou', + 'Tokyo', + 'Sydney', + ]; + + const actualLabels = result.timezones.map(tz => tz.label); + + expectedLabels.forEach(label => { + assert.ok(actualLabels.includes(label), `Missing timezone: ${label}`); + }); + }); + + it('should format times for different timezones', () => { + const date = new Date('2023-10-15T14:30:00Z'); + const result = formatTimezones(date); + + // Check that different timezones have different times + const times = result.timezones.map(tz => tz.time); + const uniqueTimes = new Set(times); + + // Should have multiple unique times since timezones differ + assert.ok(uniqueTimes.size > 1); + }); + + it('should handle midnight edge case', () => { + const date = new Date('2023-10-15T00:00:00Z'); + const result = formatTimezones(date); + + assert.strictEqual(typeof result.utc, 'string'); + assert.ok(result.utc.includes('12:00')); + assert.ok(result.timezones.length > 0); + }); + + it('should handle noon edge case', () => { + const date = new Date('2023-10-15T12:00:00Z'); + const result = formatTimezones(date); + + assert.strictEqual(typeof result.utc, 'string'); + assert.ok(result.utc.includes('12:00')); + assert.ok(result.timezones.length > 0); + }); + }); +}); diff --git a/test/utils/templates.test.mjs b/test/utils/templates.test.mjs new file mode 100644 index 0000000..6346576 --- /dev/null +++ b/test/utils/templates.test.mjs @@ -0,0 +1,266 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; + +import { + parseVariables, + parseMeetingProperties, +} from '../../src/utils/templates.mjs'; + +describe('Utils - Templates', () => { + describe('parseVariables', () => { + it('should replace single variable in template', () => { + const template = 'Hello $NAME$, welcome!'; + const variables = { NAME: 'John' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'Hello John, welcome!'); + }); + + it('should replace multiple variables in template', () => { + const template = 'Meeting: $TITLE$ on $DATE$ at $TIME$'; + const variables = { + TITLE: 'TSC Meeting', + DATE: '2023-10-15', + TIME: '14:30', + }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'Meeting: TSC Meeting on 2023-10-15 at 14:30'); + }); + + it('should replace same variable multiple times', () => { + const template = "$NAME$ loves $NAME$'s work"; + const variables = { NAME: 'Alice' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, "Alice loves Alice's work"); + }); + + it('should handle empty string values', () => { + const template = 'Value: $EMPTY$'; + const variables = { EMPTY: '' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'Value: '); + }); + + it('should handle null/undefined values', () => { + const template = 'Value: $NULL$ and $UNDEFINED$'; + const variables = { NULL: null, UNDEFINED: undefined }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'Value: and '); + }); + + it('should remove unmatched placeholders', () => { + const template = 'Hello $NAME$, your $UNMATCHED$ is ready'; + const variables = { NAME: 'Bob' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'Hello Bob, your is ready'); + }); + + it('should handle template with no placeholders', () => { + const template = 'No placeholders here'; + const variables = { NAME: 'Alice' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'No placeholders here'); + }); + + it('should handle empty template', () => { + const template = ''; + const variables = { NAME: 'Alice' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, ''); + }); + + it('should handle special characters in values', () => { + const template = 'Pattern: $PATTERN$'; + const variables = { PATTERN: '$.*+?^{}()|[]\\' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'Pattern: $.*+?^{}()|[]\\'); + }); + + it('should handle variables with underscores and numbers', () => { + const template = '$VAR_1$ and $VAR_2_TEST$'; + const variables = { VAR_1: 'first', VAR_2_TEST: 'second' }; + + const result = parseVariables(template, variables); + + assert.strictEqual(result, 'first and second'); + }); + + it('should handle multiline templates', () => { + const template = `Line 1: $VAR1$ +Line 2: $VAR2$ +Line 3: $VAR1$ again`; + const variables = { VAR1: 'Hello', VAR2: 'World' }; + + const result = parseVariables(template, variables); + + assert.strictEqual( + result, + `Line 1: Hello +Line 2: World +Line 3: Hello again` + ); + }); + }); + + describe('parseMeetingProperties', () => { + it('should parse simple property', () => { + const content = 'NAME="John Doe"'; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { NAME: 'John Doe' }); + }); + + it('should parse multiple properties', () => { + const content = `NAME="John Doe" +EMAIL="john@example.com" +ROLE="Developer"`; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + NAME: 'John Doe', + EMAIL: 'john@example.com', + ROLE: 'Developer', + }); + }); + + it('should parse multiline property values', () => { + const content = `DESCRIPTION="This is a +multiline description +with several lines"`; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + DESCRIPTION: 'This is a\nmultiline description\nwith several lines', + }); + }); + + it('should parse properties with underscores and numbers', () => { + const content = `VAR_1="value1" +VAR_2_TEST="value2" +VAR3="value3"`; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + VAR_1: 'value1', + VAR_2_TEST: 'value2', + VAR3: 'value3', + }); + }); + + it('should handle empty property values', () => { + const content = 'EMPTY=""'; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { EMPTY: '' }); + }); + + it('should handle properties with special characters in values', () => { + const content = 'SPECIAL="Value with $pecial ch@rs & symbols!"'; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + SPECIAL: 'Value with $pecial ch@rs & symbols!', + }); + }); + + it('should handle properties with quotes in values', () => { + const content = `QUOTED="He said \\"Hello\\" to me"`; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + QUOTED: 'He said \\"Hello\\" to me', + }); + }); + + it('should handle content with no properties', () => { + const content = 'Just some text without properties'; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, {}); + }); + + it('should handle empty content', () => { + const content = ''; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, {}); + }); + + it('should handle mixed content with properties and other text', () => { + const content = `Some random text +NAME="John Doe" +More text here +EMAIL="john@example.com" +Final text`; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + NAME: 'John Doe', + EMAIL: 'john@example.com', + }); + }); + + it('should handle properties with markdown-like content', () => { + const content = `DESCRIPTION="# Meeting Notes + +## Agenda +- Item 1 +- Item 2 + +**Important**: Don't forget!"`; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + DESCRIPTION: `# Meeting Notes + +## Agenda +- Item 1 +- Item 2 + +**Important**: Don't forget!`, + }); + }); + + it('should handle properties with URLs and special formatting', () => { + const content = `LINK="https://example.com/path?param=value&other=123" +INSTRUCTIONS="Join at: https://zoom.us/j/123456789 +Passcode: 123456"`; + + const result = parseMeetingProperties(content); + + assert.deepStrictEqual(result, { + LINK: 'https://example.com/path?param=value&other=123', + INSTRUCTIONS: `Join at: https://zoom.us/j/123456789 +Passcode: 123456`, + }); + }); + }); +}); diff --git a/test/utils/urls.test.mjs b/test/utils/urls.test.mjs new file mode 100644 index 0000000..2e09ee8 --- /dev/null +++ b/test/utils/urls.test.mjs @@ -0,0 +1,211 @@ +import assert from 'node:assert'; +import { describe, it } from 'node:test'; +import { URL } from 'node:url'; + +import { + generateTimeAndDateLink, + generateWolframAlphaLink, +} from '../../src/utils/urls.mjs'; + +describe('Utils - URLs', () => { + describe('generateTimeAndDateLink', () => { + it('should generate valid TimeAndDate.com link', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + const groupName = 'TSC'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.strictEqual(typeof result, 'string'); + assert.ok( + result.startsWith( + 'https://www.timeanddate.com/worldclock/fixedtime.html' + ) + ); + }); + + it('should include encoded group name in URL', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + const groupName = 'Security Working Group'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.ok(result.includes(encodeURIComponent('Security Working Group'))); + assert.ok(result.includes('Security%20Working%20Group')); + }); + + it('should include formatted date in URL', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + const groupName = 'TSC'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.ok(result.includes('2023-10-15')); + }); + + it('should include ISO datetime in URL', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + const groupName = 'TSC'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.ok(result.includes('iso=20231015T1430')); + }); + + it('should handle group names with special characters', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + const groupName = 'Build & Release'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.ok(result.includes(encodeURIComponent('Build & Release'))); + assert.strictEqual(typeof result, 'string'); + }); + + it('should handle midnight meeting times', () => { + const meetingDate = new Date('2023-10-15T00:00:00Z'); + const groupName = 'TSC'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.ok(result.includes('iso=20231015T0000')); + assert.ok(result.includes('2023-10-15')); + }); + + it('should handle end of year dates', () => { + const meetingDate = new Date('2023-12-31T23:59:00Z'); + const groupName = 'TSC'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.ok(result.includes('2023-12-31')); + assert.ok(result.includes('iso=20231231T2359')); + }); + + it('should handle single character group names', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + const groupName = 'X'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + assert.ok(result.includes('X')); + assert.strictEqual(typeof result, 'string'); + }); + + it('should properly encode spaces and special characters', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + const groupName = 'Node.js Foundation Group'; + + const result = generateTimeAndDateLink(meetingDate, groupName); + + // Should not contain unencoded spaces + assert.ok(!result.includes('Node.js Foundation Group')); + // Should contain encoded version + assert.ok( + result.includes(encodeURIComponent('Node.js Foundation Group')) + ); + }); + }); + + describe('generateWolframAlphaLink', () => { + it('should generate valid WolframAlpha link', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.strictEqual(typeof result, 'string'); + assert.ok(result.startsWith('https://www.wolframalpha.com/input/?i=')); + }); + + it('should include UTC time in URL', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + // Should include 2:30 PM format + assert.ok(result.includes('2%3A30') || result.includes('2:30')); + assert.ok(result.includes('PM')); + }); + + it('should include UTC date in URL', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.ok(result.includes('Oct')); + assert.ok(result.includes('15')); + assert.ok(result.includes('2023')); + }); + + it('should include "local time" query in URL', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.ok( + result.includes('local%20time') || result.includes('local+time') + ); + }); + + it('should handle midnight times', () => { + const meetingDate = new Date('2023-10-15T00:00:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.ok(result.includes('12%3A00') || result.includes('12:00')); + assert.ok(result.includes('AM')); + }); + + it('should handle noon times', () => { + const meetingDate = new Date('2023-10-15T12:00:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.ok(result.includes('12%3A00') || result.includes('12:00')); + assert.ok(result.includes('PM')); + }); + + it('should handle single digit minutes', () => { + const meetingDate = new Date('2023-10-15T14:05:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.ok(result.includes('2%3A05') || result.includes('2:05')); + }); + + it('should handle end of year dates', () => { + const meetingDate = new Date('2023-12-31T23:59:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.ok(result.includes('Dec')); + assert.ok(result.includes('31')); + assert.ok(result.includes('2023')); + assert.ok(result.includes('11%3A59') || result.includes('11:59')); + assert.ok(result.includes('PM')); + }); + + it('should properly encode the URL', () => { + const meetingDate = new Date('2023-10-15T14:30:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + // Should be a valid URL format + assert.doesNotThrow(() => new URL(result)); + + // Should contain proper encoding + assert.ok(result.includes('%2C') || result.includes(',')); + }); + + it('should handle beginning of year dates', () => { + const meetingDate = new Date('2023-01-01T00:00:00Z'); + + const result = generateWolframAlphaLink(meetingDate); + + assert.ok(result.includes('Jan')); + assert.ok(result.includes('1')); + assert.ok(result.includes('2023')); + assert.ok(result.includes('12%3A00') || result.includes('12:00')); + assert.ok(result.includes('AM')); + }); + }); +});