diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..7231a22
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,31 @@
+var ERROR = 2;
+
+module.exports = {
+ env: {
+ es6: true,
+ browser: true,
+ node: true,
+ },
+ parserOptions: {
+ sourceType: 'module',
+ ecmaVersion: 2020,
+ ecmaFeatures: {
+ impliedStrict: true,
+ },
+ },
+ extends: ['eslint:recommended'],
+ rules: {
+ 'comma-style': ERROR,
+ 'no-console': ERROR,
+ 'max-len': [
+ ERROR,
+ 100,
+ {
+ ignoreStrings: true,
+ ignoreTemplateLiterals: true,
+ ignoreUrls: true,
+ ignoreTrailingComments: true,
+ },
+ ],
+ },
+};
diff --git a/.github/ISSUE_TEMPLATE/epic_issue.md b/.github/ISSUE_TEMPLATE/epic_issue.md
index 922c8c2..3f3386b 100644
--- a/.github/ISSUE_TEMPLATE/epic_issue.md
+++ b/.github/ISSUE_TEMPLATE/epic_issue.md
@@ -9,7 +9,7 @@ assignees: ''
_[General guidance: When drafting a feature project, the goal is to create a reference for project scope and context. Write for our core product team. The issue should not be overly technical, and should be be comprehensible and a useful reference to devs, designers, and QA team, to build a shared source of understanding. Anyone at LE should be able to read this and more or less understand the project. If there is relevant information or context that is for the internal team only, please add that in a notion page and link it, rather than adding directly to these issues.]_
-## Overview
+## Overview
_[First, the project and purpose in one sentence, i.e.: Allow coaches and admins to customize quiz questions in Koilbri]_
@@ -18,11 +18,11 @@ _[Then, more detail]_
- Why are we doing this now?
- What feedback, requests, insights and/or concerns are shaping this feature? Include specific comments from partners, clearinghouse, or teammates as relevant.
-### Contributors
-_[Fill this out as people are assigned]_
+### Contributors
+_[Fill this out as people are assigned]_
- Technical lead: [github handle]
-- Individual contributors: [list all github handles]
-- Designer: [if a specific designer is leading]
+- Individual contributors: [list all github handles]
+- Designer: [if a specific designer is leading]
- Other relevant LE team members: [optional, i.e. imps point person]
### Goals
@@ -30,11 +30,11 @@ _[Fill this out as people are assigned]_
- How do we know this feature is successful?
#### User Stories or Requirements
-_[Break down the main workflows thematically, but still at a high level. How are the goals of the project realized in the UI? If there isn't a UI portion of this project, what are the key outcomes or improvements? There should probably only be a few of these per project]_
+_[Break down the main workflows thematically, but still at a high level. How are the goals of the project realized in the UI? If there isn't a UI portion of this project, what are the key outcomes or improvements? There should probably only be a few of these per project]_
### Target Quarter and Due date
- What is the timeline of this project?
- If there is a specific due date, especially for a contractual deliverable, include it. Otherwise, include an estimated delivery date, and what quarter(s) this will be worked on
-### Product Issues
+### Product Issues
_[Issues should be linked here as they are created. There will probably be 1-2 per User Story]_
diff --git a/.github/workflows/call-contributor-issue-comment.yml b/.github/workflows/call-contributor-issue-comment.yml
index 4e8b7db..a9512b4 100644
--- a/.github/workflows/call-contributor-issue-comment.yml
+++ b/.github/workflows/call-contributor-issue-comment.yml
@@ -1,9 +1,7 @@
name: Handle contributor comment on GitHub issue
-
on:
issue_comment:
types: [created]
-
jobs:
call-workflow:
uses: learningequality/.github/.github/workflows/contributor-issue-comment.yml@main
diff --git a/.github/workflows/call-contributor-pr-reply.yml b/.github/workflows/call-contributor-pr-reply.yml
new file mode 100644
index 0000000..e8316e0
--- /dev/null
+++ b/.github/workflows/call-contributor-pr-reply.yml
@@ -0,0 +1,12 @@
+name: Send reply on a new contributor pull request
+on:
+ pull_request_target:
+ types: [opened]
+jobs:
+ call-workflow:
+ name: Call shared workflow
+ uses: learningequality/.github/.github/workflows/contributor-pr-reply.yml@main
+ secrets:
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
diff --git a/.github/workflows/call-holiday-message.yml b/.github/workflows/call-holiday-message.yml
new file mode 100644
index 0000000..6ed898b
--- /dev/null
+++ b/.github/workflows/call-holiday-message.yml
@@ -0,0 +1,14 @@
+name: Post holiday message on pull request or issue comment
+on:
+ pull_request_target:
+ types: [opened]
+ issue_comment:
+ types: [created]
+jobs:
+ call-workflow:
+ name: Call shared workflow
+ uses: learningequality/.github/.github/workflows/holiday-message.yml@main
+ secrets:
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
diff --git a/.github/workflows/call-manage-issue-header.yml b/.github/workflows/call-manage-issue-header.yml
index 4af6730..845a679 100644
--- a/.github/workflows/call-manage-issue-header.yml
+++ b/.github/workflows/call-manage-issue-header.yml
@@ -1,13 +1,11 @@
name: Manage issue header
-
on:
issues:
types: [opened, reopened, labeled, unlabeled]
-
jobs:
call-workflow:
name: Call shared workflow
uses: learningequality/.github/.github/workflows/manage-issue-header.yml@main
secrets:
- LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
- LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
diff --git a/.github/workflows/call-update-pr-spreadsheet.yml b/.github/workflows/call-update-pr-spreadsheet.yml
new file mode 100644
index 0000000..837a34e
--- /dev/null
+++ b/.github/workflows/call-update-pr-spreadsheet.yml
@@ -0,0 +1,14 @@
+name: Update community pull requests spreadsheet
+on:
+ pull_request_target:
+ types: [assigned, unassigned, opened, closed, reopened, edited, review_requested, review_request_removed]
+jobs:
+ call-workflow:
+ name: Call shared workflow
+ uses: learningequality/.github/.github/workflows/update-pr-spreadsheet.yml@main
+ secrets:
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ CONTRIBUTIONS_SPREADSHEET_ID: ${{ secrets.CONTRIBUTIONS_SPREADSHEET_ID }}
+ CONTRIBUTIONS_SHEET_NAME: ${{ secrets.CONTRIBUTIONS_SHEET_NAME }}
+ GH_UPLOADER_GCP_SA_CREDENTIALS: ${{ secrets.GH_UPLOADER_GCP_SA_CREDENTIALS }}
diff --git a/.github/workflows/community-contribution-label.yml b/.github/workflows/community-contribution-label.yml
index 2e75764..f410caa 100644
--- a/.github/workflows/community-contribution-label.yml
+++ b/.github/workflows/community-contribution-label.yml
@@ -1,49 +1,43 @@
name: Community Contribution Label
-
on:
- workflow_call:
- secrets:
- LE_BOT_APP_ID:
- description: "GitHub App ID for authentication"
- required: true
- LE_BOT_PRIVATE_KEY:
- description: "GitHub App Private Key for authentication"
- required: true
+ workflow_call:
+ secrets:
+ LE_BOT_APP_ID:
+ description: 'GitHub App ID for authentication'
+ required: true
+ LE_BOT_PRIVATE_KEY:
+ description: 'GitHub App Private Key for authentication'
+ required: true
jobs:
reusable-job:
runs-on: ubuntu-latest
-
steps:
- name: Check event context
run: |
- if [[ "${{ github.event_name }}" != "issues" ]] || [[ "${{ github.event.action }}" != "assigned" && "${{ github.event.action }}" != "unassigned" ]]; then
- echo "This workflow should only run on issue assigned/unassigned events"
- exit 1
- fi
+ if [[ "${{ github.event_name }}" != "issues" ]] || [[ "${{ github.event.action }}" != "assigned" && "${{ github.event.action }}" != "unassigned" ]]; then
+ echo "This workflow should only run on issue assigned/unassigned events"
+ exit 1
+ fi
- name: Checkout repository
uses: actions/checkout@v4
with:
- repository: learningequality/.github
- path: .github-repo
-
+ repository: learningequality/.github
+ path: .github-repo
- name: Generate App Token
id: generate-token
uses: tibdex/github-app-token@v2
with:
- app_id: ${{ secrets.LE_BOT_APP_ID }}
- private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
-
+ app_id: ${{ secrets.LE_BOT_APP_ID }}
+ private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
-
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
-
- name: Add/Remove Community Contribution Label
run: python .github-repo/scripts/community-contribution-label.py
env:
- token: ${{ steps.generate-token.outputs.token }}
\ No newline at end of file
+ token: ${{ steps.generate-token.outputs.token }}
diff --git a/.github/workflows/contributor-issue-comment.yml b/.github/workflows/contributor-issue-comment.yml
index cbd4c2a..4e182c5 100644
--- a/.github/workflows/contributor-issue-comment.yml
+++ b/.github/workflows/contributor-issue-comment.yml
@@ -1,45 +1,33 @@
name: Handle contributor comment on GitHub issue
-
on:
workflow_call:
secrets:
LE_BOT_APP_ID:
- description: "GitHub App ID for authentication"
- required: true
+ description: 'GitHub App ID for authentication'
+ required: true
LE_BOT_PRIVATE_KEY:
- description: "GitHub App Private Key for authentication"
- required: true
+ description: 'GitHub App Private Key for authentication'
+ required: true
SLACK_WEBHOOK_URL:
required: true
- description: "Webhook URL for Slack #support-dev channel"
+ description: 'Webhook URL for Slack #support-dev channel'
SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL:
required: true
- description: "Webhook URL for Slack #support-dev-notifications channel"
-
-
+ description: 'Webhook URL for Slack #support-dev-notifications channel'
jobs:
- check-if-close-contributor:
- name: Check if user is a close contributor
- uses: learningequality/.github/.github/workflows/is-close-contributor.yml@main
+ check-if-contributor:
+ name: Check if author is contributor
+ uses: learningequality/.github/.github/workflows/is-contributor.yml@main
secrets:
- LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
- LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
with:
username: ${{ github.event.comment.user.login }}
-
-
+ author_association: ${{ github.event.comment.author_association }}
process-issue-comment:
name: Process issue comment
- needs: [check-if-close-contributor]
- if: >-
- ${{
- !github.event.issue.pull_request &&
- github.event.comment.author_association != 'OWNER' &&
- github.event.comment.user.login != 'sentry-io[bot]' &&
- github.event.comment.user.login != 'learning-equality-bot[bot]' &&
- (github.event.comment.author_association != 'MEMBER' || needs.check-if-close-contributor.outputs.is_close_contributor == 'true')
- }}
-
+ needs: [check-if-contributor]
+ if: ${{ !github.event.issue.pull_request && github.event.issue.state == 'open' && needs.check-if-contributor.outputs.is_contributor == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Generate App Token
@@ -48,63 +36,53 @@ jobs:
with:
app_id: ${{ secrets.LE_BOT_APP_ID }}
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
-
- name: Checkout .github repository
uses: actions/checkout@v4
with:
repository: learningequality/.github
ref: main
token: ${{ steps.generate-token.outputs.token }}
-
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
-
- name: Install dependencies
run: yarn install --frozen-lockfile
-
- name: Run script
id: script
- uses: actions/github-script@v7
+ uses: actions/github-script@v7
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
- const script = require('./scripts/contributor-issue-comment.js');
- return await script({github, context, core});
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
- IS_CLOSE_CONTRIBUTOR: ${{ needs.check-if-close-contributor.outputs.is_close_contributor }}
-
- - name: Send Slack notification about GitHub comment
+ const script = require('./scripts/contributor-issue-comment.js');
+ return await script({ github, context, core });
+ - name: Send Slack notification to support-dev channel
+ if: ${{ steps.script.outputs.support_dev_message }}
uses: slackapi/slack-github-action@v2.1.0
with:
webhook-type: incoming-webhook
- webhook: ${{ steps.script.outputs.webhook_url }}
- payload: >
+ webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
+ payload: |
{
- "text": "${{ steps.script.outputs.slack_notification_comment }}"
+ "text": "${{ steps.script.outputs.support_dev_message }}"
}
-
- - name: Send Slack notification about GitHub bot reply
- if: ${{ steps.script.outputs.bot_replied }}
+ - name: Send Slack notification to support-dev-notifications channel about comment
+ if: ${{ steps.script.outputs.support_dev_notifications_message }}
uses: slackapi/slack-github-action@v2.1.0
with:
webhook-type: incoming-webhook
- webhook: ${{ steps.script.outputs.webhook_url }}
- payload: >
+ webhook: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
+ payload: |
{
- "text": "${{ steps.script.outputs.slack_notification_bot_comment }}"
+ "text": "${{ steps.script.outputs.support_dev_notifications_message }}"
}
-
- - name: Send Slack notification about skipped GitHub bot reply
- if: ${{ steps.script.outputs.bot_reply_skipped }}
+ - name: Send Slack notification to support-dev-notifications channel about bot action
+ if: ${{ steps.script.outputs.support_dev_notifications_bot }}
uses: slackapi/slack-github-action@v2.1.0
with:
webhook-type: incoming-webhook
- webhook: ${{ steps.script.outputs.webhook_url }}
- payload: >
+ webhook: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
+ payload: |
{
- "text": "${{ steps.script.outputs.slack_notification_bot_skipped }}"
+ "text": "${{ steps.script.outputs.support_dev_notifications_bot }}"
}
diff --git a/.github/workflows/contributor-pr-reply.yml b/.github/workflows/contributor-pr-reply.yml
new file mode 100644
index 0000000..48df6a1
--- /dev/null
+++ b/.github/workflows/contributor-pr-reply.yml
@@ -0,0 +1,65 @@
+name: Send reply on a new contributor pull request
+on:
+ workflow_call:
+ secrets:
+ LE_BOT_APP_ID:
+ description: 'GitHub App ID for authentication'
+ required: true
+ LE_BOT_PRIVATE_KEY:
+ description: 'GitHub App Private Key for authentication'
+ required: true
+ SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL:
+ required: true
+ description: 'Webhook URL for Slack #support-dev-notifications channel'
+jobs:
+ check-if-contributor:
+ name: Check if author is contributor
+ uses: learningequality/.github/.github/workflows/is-contributor.yml@main
+ secrets:
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ with:
+ username: ${{ github.event.pull_request.user.login }}
+ author_association: ${{ github.event.pull_request.author_association }}
+ send-reply:
+ name: Send reply
+ needs: [check-if-contributor]
+ if: ${{ needs.check-if-contributor.outputs.is_contributor == 'true' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Generate App Token
+ id: generate-token
+ uses: tibdex/github-app-token@v2
+ with:
+ app_id: ${{ secrets.LE_BOT_APP_ID }}
+ private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ - name: Checkout .github repository
+ uses: actions/checkout@v4
+ with:
+ repository: learningequality/.github
+ ref: main
+ token: ${{ steps.generate-token.outputs.token }}
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+ - name: Run script
+ id: script
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ steps.generate-token.outputs.token }}
+ script: |
+ const script = require('./scripts/contributor-pr-reply.js');
+ return await script({ github, context, core });
+ - name: Send Slack notification
+ if: ${{ steps.script.outputs.slack_notification != ''}}
+ uses: slackapi/slack-github-action@v2.1.0
+ with:
+ webhook-type: incoming-webhook
+ webhook: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
+ payload: |
+ {
+ "text": "${{ steps.script.outputs.slack_notification }}"
+ }
diff --git a/.github/workflows/holiday-message.yml b/.github/workflows/holiday-message.yml
new file mode 100644
index 0000000..8a67a2f
--- /dev/null
+++ b/.github/workflows/holiday-message.yml
@@ -0,0 +1,65 @@
+name: Post holiday message on pull request or issue comment
+on:
+ workflow_call:
+ secrets:
+ LE_BOT_APP_ID:
+ description: 'GitHub App ID for authentication'
+ required: true
+ LE_BOT_PRIVATE_KEY:
+ description: 'GitHub App Private Key for authentication'
+ required: true
+ SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL:
+ required: true
+ description: 'Webhook URL for Slack #support-dev-notifications channel'
+jobs:
+ check-if-contributor:
+ name: Check if author is contributor
+ uses: learningequality/.github/.github/workflows/is-contributor.yml@main
+ secrets:
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ with:
+ username: ${{ github.event.pull_request.user.login || github.event.comment.user.login }}
+ author_association: ${{ github.event.pull_request.author_association || github.event.comment.author_association }}
+ post-holiday-message:
+ name: Post holiday message
+ needs: [check-if-contributor]
+ if: ${{ needs.check-if-contributor.outputs.is_contributor == 'true' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Generate App Token
+ id: generate-token
+ uses: tibdex/github-app-token@v2
+ with:
+ app_id: ${{ secrets.LE_BOT_APP_ID }}
+ private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ - name: Checkout .github repository
+ uses: actions/checkout@v4
+ with:
+ repository: learningequality/.github
+ ref: main
+ token: ${{ steps.generate-token.outputs.token }}
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+ - name: Run script
+ id: script
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ steps.generate-token.outputs.token }}
+ script: |
+ const script = require('./scripts/holiday-message.js');
+ return await script({ github, context, core });
+ - name: Send Slack notification
+ if: ${{ steps.script.outputs.slack_notification }}
+ uses: slackapi/slack-github-action@v2.1.0
+ with:
+ webhook-type: incoming-webhook
+ webhook: ${{ secrets.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL }}
+ payload: |
+ {
+ "text": "${{ steps.script.outputs.slack_notification }}"
+ }
diff --git a/.github/workflows/is-close-contributor.yml b/.github/workflows/is-contributor.yml
similarity index 62%
rename from .github/workflows/is-close-contributor.yml
rename to .github/workflows/is-contributor.yml
index 91f502a..4621daa 100644
--- a/.github/workflows/is-close-contributor.yml
+++ b/.github/workflows/is-contributor.yml
@@ -1,5 +1,4 @@
-name: Check if user is a close contributor
-
+name: Check if user is contributor
on:
workflow_call:
inputs:
@@ -7,22 +6,25 @@ on:
description: 'Github username'
required: true
type: string
+ author_association:
+ description: 'Author association from GitHub event'
+ required: true
+ type: string
secrets:
LE_BOT_APP_ID:
required: true
LE_BOT_PRIVATE_KEY:
required: true
outputs:
- is_close_contributor:
- description: "True if the user is a close contributor, otherwise false"
- value: ${{ jobs.check-contributor.outputs.is_close_contributor }}
-
+ is_contributor:
+ description: 'True if the user is a contributor (= not a core team member)'
+ value: ${{ jobs.check-contributor.outputs.is_contributor }}
jobs:
check-contributor:
- name: Run check
+ name: Check if user is contributor
runs-on: ubuntu-latest
outputs:
- is_close_contributor: ${{ steps.check-script.outputs.is_close_contributor }}
+ is_contributor: ${{ steps.run-script.outputs.is_contributor }}
steps:
- name: Generate App Token
id: generate-token
@@ -30,28 +32,25 @@ jobs:
with:
app_id: ${{ secrets.LE_BOT_APP_ID }}
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
-
- name: Checkout .github repository
uses: actions/checkout@v4
with:
repository: learningequality/.github
ref: main
-
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
-
- name: Install dependencies
run: yarn install --frozen-lockfile
-
- name: Run script
- id: check-script
+ id: run-script
uses: actions/github-script@v7
env:
USERNAME: ${{ inputs.username }}
+ AUTHOR_ASSOCIATION: ${{ inputs.author_association }}
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
- const script = require('./scripts/is-close-contributor.js');
- return await script({core, github, context });
+ const script = require('./scripts/is-contributor.js');
+ return await script({ core, github, context });
diff --git a/.github/workflows/manage-issue-header.yml b/.github/workflows/manage-issue-header.yml
index 531cfb2..7411b2a 100644
--- a/.github/workflows/manage-issue-header.yml
+++ b/.github/workflows/manage-issue-header.yml
@@ -1,15 +1,13 @@
name: Manage issue header
-
on:
workflow_call:
secrets:
LE_BOT_APP_ID:
- description: "GitHub App ID for authentication"
- required: true
+ description: 'GitHub App ID for authentication'
+ required: true
LE_BOT_PRIVATE_KEY:
- description: "GitHub App Private Key for authentication"
- required: true
-
+ description: 'GitHub App Private Key for authentication'
+ required: true
jobs:
manage-issue-header:
runs-on: ubuntu-latest
@@ -25,23 +23,19 @@ jobs:
with:
app_id: ${{ secrets.LE_BOT_APP_ID }}
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
-
- name: Checkout .github repository
uses: actions/checkout@v4
with:
repository: learningequality/.github
ref: main
token: ${{ steps.generate-token.outputs.token }}
-
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
-
- name: Install dependencies
run: yarn install --frozen-lockfile
-
- name: Run script
uses: actions/github-script@v7
with:
diff --git a/.github/workflows/unassign-inactive-issues.yaml b/.github/workflows/unassign-inactive-issues.yaml
index d6b1942..0093279 100644
--- a/.github/workflows/unassign-inactive-issues.yaml
+++ b/.github/workflows/unassign-inactive-issues.yaml
@@ -1,19 +1,17 @@
name: Unassign Inactive Issues
run-name: Unassigns users from issues if they are inactive based on comments or PR activity.
-
on:
workflow_call:
secrets:
LE_BOT_APP_ID:
- description: "GitHub App ID for authentication"
+ description: 'GitHub App ID for authentication'
required: true
LE_BOT_PRIVATE_KEY:
- description: "GitHub App Private Key for authentication"
+ description: 'GitHub App Private Key for authentication'
required: true
SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL:
- description: "Slack webhook URL for notifications"
+ description: 'Slack webhook URL for notifications'
required: true
-
jobs:
unassign_inactive_issue:
runs-on: ubuntu-latest
@@ -24,19 +22,16 @@ jobs:
with:
app_id: ${{ secrets.LE_BOT_APP_ID }}
private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
-
- - name: Checkout called repository
- uses: actions/checkout@v3
+ - name: Checkout .github repository
+ uses: actions/checkout@v4
with:
repository: learningequality/.github
ref: main
- token: ${{ steps.generate-token.outputs.token }}
-
+ token: ${{ steps.generate-token.outputs.token }}
- name: Setup Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: '16'
-
+ node-version: '20'
- name: Debug directory
run: |
echo "Current directory:"
@@ -45,19 +40,16 @@ jobs:
ls -la
echo "Scripts directory contents:"
ls -la scripts
-
- name: Install dependencies
- run: npm install
-
+ run: npm install
- name: Run unassign script
id: unassign
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const script = require('./scripts/unassign-inactive-issues.js');
return await script({github, context, core});
-
- name: Send Slack notifications
if: ${{ steps.unassign.outputs.unassignments != '' }}
uses: slackapi/slack-github-action@v2.0.0
@@ -67,4 +59,4 @@ jobs:
payload: |
{
"text": "\nThe following users have been unassigned due to inactivity:\n${{ steps.unassign.outputs.unassignments }}"
- }
\ No newline at end of file
+ }
diff --git a/.github/workflows/update-pr-spreadsheet.yml b/.github/workflows/update-pr-spreadsheet.yml
index 0ec2f07..f03ecc5 100644
--- a/.github/workflows/update-pr-spreadsheet.yml
+++ b/.github/workflows/update-pr-spreadsheet.yml
@@ -1,9 +1,13 @@
name: Update community pull requests spreadsheet
on:
- pull_request_target:
- types: [assigned,unassigned,opened,closed,reopened]
workflow_call:
secrets:
+ LE_BOT_APP_ID:
+ description: 'GitHub App ID for authentication'
+ required: true
+ LE_BOT_PRIVATE_KEY:
+ description: 'GitHub App Private Key for authentication'
+ required: true
CONTRIBUTIONS_SPREADSHEET_ID:
required: true
CONTRIBUTIONS_SHEET_NAME:
@@ -11,37 +15,47 @@ on:
GH_UPLOADER_GCP_SA_CREDENTIALS:
required: true
jobs:
+ check-if-contributor:
+ name: Check if author is contributor
+ uses: learningequality/.github/.github/workflows/is-contributor.yml@main
+ secrets:
+ LE_BOT_APP_ID: ${{ secrets.LE_BOT_APP_ID }}
+ LE_BOT_PRIVATE_KEY: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ with:
+ username: ${{ github.event.pull_request.user.login }}
+ author_association: ${{ github.event.pull_request.author_association }}
update-spreadsheet:
+ needs: [check-if-contributor]
runs-on: ubuntu-latest
- if: ${{ github.event.pull_request }}
+ if: ${{ needs.check-if-contributor.outputs.is_contributor == 'true' }}
steps:
- # Checkout the code for the test-actions repository
- - name: Checkout code
- uses: actions/checkout@v4
-
- # Checkout the .github repository
- - name: Checkout .github repository
- uses: actions/checkout@v4
- with:
- repository: learningequality/.github
- path: .github-repo
-
- # Setup Node.js
- - name: Setup Node.js
- uses: actions/setup-node@v2
- with:
- node-version: '20'
-
- # Install dependencies from the .github repository
- - name: Install dependencies
- run: npm install
- working-directory: .github-repo
-
- # Run the update-pr-spreadsheet script
- - name: Run script
- run: node .github-repo/scripts/update-pr-spreadsheet.js
- env:
- SPREADSHEET_ID: ${{ secrets.CONTRIBUTIONS_SPREADSHEET_ID }}
- SHEET_NAME: ${{ secrets.CONTRIBUTIONS_SHEET_NAME }}
- GOOGLE_CREDENTIALS: ${{ secrets.GH_UPLOADER_GCP_SA_CREDENTIALS }}
- GITHUB_EVENT: ${{ toJson(github.event) }}
+ - name: Generate App Token
+ id: generate-token
+ uses: tibdex/github-app-token@v2
+ with:
+ app_id: ${{ secrets.LE_BOT_APP_ID }}
+ private_key: ${{ secrets.LE_BOT_PRIVATE_KEY }}
+ - name: Checkout .github repository
+ uses: actions/checkout@v4
+ with:
+ repository: learningequality/.github
+ ref: main
+ token: ${{ steps.generate-token.outputs.token }}
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+ - name: Run script
+ id: script
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ steps.generate-token.outputs.token }}
+ script: |
+ const script = require('./scripts/update-pr-spreadsheet.js');
+ return await script({ github, context, core });
+ env:
+ CONTRIBUTIONS_SPREADSHEET_ID: ${{ secrets.CONTRIBUTIONS_SPREADSHEET_ID }}
+ CONTRIBUTIONS_SHEET_NAME: ${{ secrets.CONTRIBUTIONS_SHEET_NAME }}
+ GOOGLE_CREDENTIALS: ${{ secrets.GH_UPLOADER_GCP_SA_CREDENTIALS }}
diff --git a/.gitignore b/.gitignore
index c74ff1b..f3ba074 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.env
+.venv
/node_modules
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..09b0ab0
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,22 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.1.0
+ hooks:
+ - id: trailing-whitespace
+ - id: check-yaml
+ - id: check-added-large-files
+ exclude: '^tests/cassettes'
+ - id: debug-statements
+ - id: end-of-file-fixer
+ exclude: '^.+?\.json$'
+ - repo: https://github.com/google/yamlfmt
+ rev: v0.14.0
+ hooks:
+ - id: yamlfmt
+ exclude: '^tests/cassettes'
+ - repo: https://github.com/rhysd/actionlint
+ rev: v1.7.7
+ hooks:
+ - id: actionlint
+ # Expects shellcheck to be installed on the system
+ # https://github.com/koalaman/shellcheck#installing
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..51ddf53
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+node_modules
+package-lock.json
+*.md
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..7aee780
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,11 @@
+/*
+ * Configuration object for prettier options, should be used wherever prettier is invoked.
+ */
+
+module.exports = {
+ printWidth: 100,
+ singleQuote: true,
+ arrowParens: 'avoid',
+ vueIndentScriptAndStyle: true,
+ singleAttributePerLine: true,
+};
diff --git a/docs/community-automations.md b/docs/community-automations.md
new file mode 100644
index 0000000..2a51041
--- /dev/null
+++ b/docs/community-automations.md
@@ -0,0 +1,38 @@
+# `contributor-issue-comment`
+
+Manages GitHub issue comments. Sends Slack notifications and GitHub bot replies.
+
+| Contributor type | Issue type | Comment type | #support-dev | #support-dev-notifications | GitHub bot | GitHub bot message |
+|------------------|------------|--------------|--------------|---------------------------|------------------|-------------|
+| **Core team** | Any | Any | No | No | No | - |
+| **Close contributor** | Any | Any | **Yes** | No | No | - |
+| **Issue creator** | `help-wanted` | Any | **Yes** | No | No | - |
+| **Issue creator** | Private | Any | No | Yes | No | - |
+| **Other** | Private | Regular | No | Yes | No | - |
+| **Other** | Private | Assignment request | No | Yes | Yes`*` | `BOT_MESSAGE_ISSUE_NOT_OPEN` |
+| **Other** | Unassigned `help-wanted` | Any | **Yes** | No | No | - |
+| **Other** | `help-wanted` assigned to the comment author | Any | **Yes** | No | No | - |
+| **Other** | `help-wanted` assigned to someone else | Regular | No | Yes | No | - |
+| **Other** | `help-wanted` assigned to someone else | Assignment request | No | Yes | Yes`*` | `BOT_MESSAGE_ALREADY_ASSIGNED` |
+
+`*` There is an additional optimization that prevents more than one bot message per hour to not overwhelm issue comment section
+
+In `scripts/contants.js` set:
+- `BOT_MESSAGE_ISSUE_NOT_OPEN`: _Issue not open for contribution_ message text
+- `BOT_MESSAGE_ALREADY_ASSIGNED`: _Issue already assigned_ message text
+
+# `contributor-pr-reply`
+
+Sends reply to a community pull requests.
+
+In `scripts/contants.js` set:
+- `BOT_MESSAGE_PULL_REQUEST`: Message text
+
+# `holiday-message`
+
+Sends a holiday message to community pull requests and issue comments.
+
+In `scripts/contants.js` set:
+- `HOLIDAY_MESSAGE`: Message text
+
+Before/after holidays, enable/disable all related workflows in all repositories that use it (search for `call-holiday-message`).
diff --git a/package.json b/package.json
index 6fb5939..b049a7c 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,18 @@
{
+ "scripts": {
+ "lint": "eslint .",
+ "format": "prettier --write ."
+ },
"dependencies": {
"axios": "^1.7.5",
"dotenv": "^16.4.5",
"googleapis": "^142.0.0",
"node-fetch-native": "^1.6.4",
"path": "^0.12.7"
+ },
+ "devDependencies": {
+ "eslint": "^8.57.0",
+ "eslint-config-prettier": "^10.1.8",
+ "prettier": "^3.6.2"
}
}
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..416634f
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1 @@
+pre-commit
diff --git a/scripts/constants.js b/scripts/constants.js
index 6945880..5e6e6d0 100644
--- a/scripts/constants.js
+++ b/scripts/constants.js
@@ -1,26 +1,66 @@
+// See docs/community-automations.md
+
const LE_BOT_USERNAME = 'learning-equality-bot[bot]';
+const SENTRY_BOT_USERNAME = 'sentry-io[bot]';
// close contributors are treated a bit special in some workflows,
// for example, we receive a high priority notification about their
// comments on all issues rather than just on 'help wanted' issues
-const CLOSE_CONTRIBUTORS = ['AadarshM07', 'Abhishek-Punhani', 'BabyElias', 'Dimi20cen', 'EshaanAgg', 'GarvitSinghal47', 'habibayman', 'iamshobhraj', 'indirectlylit', 'Jakoma02', 'KshitijThareja', 'muditchoudhary', 'nathanaelg16', 'nikkuAg', 'Sahil-Sinha-11', 'shivam-daksh', 'shruti862', 'thesujai', 'WinnyChang', 'yeshwanth235'];
+const CLOSE_CONTRIBUTORS = [
+ 'AadarshM07',
+ 'Abhishek-Punhani',
+ 'BabyElias',
+ 'Dimi20cen',
+ 'EshaanAgg',
+ 'GarvitSinghal47',
+ 'habibayman',
+ 'iamshobhraj',
+ 'indirectlylit',
+ 'Jakoma02',
+ 'KshitijThareja',
+ 'muditchoudhary',
+ 'nathanaelg16',
+ 'nikkuAg',
+ 'Sahil-Sinha-11',
+ 'shivam-daksh',
+ 'shruti862',
+ 'thesujai',
+ 'WinnyChang',
+ 'yeshwanth235',
+];
const TEAMS_WITH_CLOSE_CONTRIBUTORS = ['gsoc-contributors', 'learning-equality-community-guide'];
const KEYWORDS_DETECT_ASSIGNMENT_REQUEST = [
- 'assign', 'assigned',
- 'work', 'working',
- 'contribute', 'contributing',
- 'request', 'requested',
- 'pick', 'picked', 'picking',
- 'address', 'addressing',
- 'handle', 'handling',
- 'solve', 'solving', 'resolve', 'resolving',
- 'try', 'trying',
- 'grab', 'grabbing',
- 'claim', 'claimed',
- 'interest', 'interested',
- 'do', 'doing',
+ 'assign',
+ 'assigned',
+ 'work',
+ 'working',
+ 'contribute',
+ 'contributing',
+ 'request',
+ 'requested',
+ 'pick',
+ 'picked',
+ 'picking',
+ 'address',
+ 'addressing',
+ 'handle',
+ 'handling',
+ 'solve',
+ 'solving',
+ 'resolve',
+ 'resolving',
+ 'try',
+ 'trying',
+ 'grab',
+ 'grabbing',
+ 'claim',
+ 'claimed',
+ 'interest',
+ 'interested',
+ 'do',
+ 'doing',
'help',
'take',
'want',
@@ -28,18 +68,32 @@ const KEYWORDS_DETECT_ASSIGNMENT_REQUEST = [
'own',
'on it',
'available',
- 'got this'
+ 'got this',
];
const ISSUE_LABEL_HELP_WANTED = 'help wanted';
-const BOT_MESSAGE_ISSUE_NOT_OPEN = `Hi! 👋 \n\n Thanks so much for your interest! **This issue is not open for contribution. Visit [Contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base) to learn about the contributing process and how to find suitable issues.** \n\n We really appreciate your willingness to help—you're welcome to find a more suitable issue, and let us know if you have any questions. 😊`;
+// Will be attached to bot messages when not empty
+// const GSOC_NOTE = '';
+const GSOC_NOTE = `\n\n**Are you preparing for Google Summer of Code? See our [GSoC guidelines.](https://learningequality.org/contributing-to-our-open-code-base/#google-summer-of-code)**`;
+
+const BOT_MESSAGE_ISSUE_NOT_OPEN = `Hi! 👋 \n\n Thanks so much for your interest! **This issue is not open for contribution. Visit [Contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base) to learn about the contributing process and how to find suitable issues.** \n\n We really appreciate your willingness to help—you're welcome to find a more suitable issue, and let us know if you have any questions. 😊${GSOC_NOTE}`;
+
+const BOT_MESSAGE_ALREADY_ASSIGNED = `Hi! 👋 \n\n Thanks so much for your interest! **This issue is already assigned. Visit [Contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base) to learn about the contributing process and how to find suitable issues.** \n\n We really appreciate your willingness to help—you're welcome to find a more suitable issue, and let us know if you have any questions. 😊${GSOC_NOTE}`;
+
+const BOT_MESSAGE_PULL_REQUEST = `👋 Thanks for contributing! \n\n We will assign a reviewer within the next two weeks. In the meantime, please ensure that:\n\n- [ ] **You ran \`pre-commit\` locally**\n- [ ] **All issue requirements are satisfied**\n- [ ] **The contribution is aligned with our [Contributing guidelines](https://learningequality.org/contributing-to-our-open-code-base). Pay extra attention to [Using generative AI](https://learningequality.org/contributing-to-our-open-code-base/#using-generative-ai). Pull requests that don't follow the guidelines will be closed.**\n\nWe'll be in touch! 😊`;
+
+const HOLIDAY_MESSAGE = `Season’s greetings! 👋 \n\n We’d like to thank everyone for another year of fruitful collaborations, engaging discussions, and for the continued support of our work. **Learning Equality will be on holidays from December 22 to January 5.** We look forward to much more in the new year and wish you a very happy holiday season!${GSOC_NOTE}`;
module.exports = {
LE_BOT_USERNAME,
+ SENTRY_BOT_USERNAME,
CLOSE_CONTRIBUTORS,
KEYWORDS_DETECT_ASSIGNMENT_REQUEST,
ISSUE_LABEL_HELP_WANTED,
BOT_MESSAGE_ISSUE_NOT_OPEN,
+ BOT_MESSAGE_ALREADY_ASSIGNED,
+ BOT_MESSAGE_PULL_REQUEST,
TEAMS_WITH_CLOSE_CONTRIBUTORS,
+ HOLIDAY_MESSAGE,
};
diff --git a/scripts/contributor-issue-comment.js b/scripts/contributor-issue-comment.js
index 7b81c02..399ac7c 100644
--- a/scripts/contributor-issue-comment.js
+++ b/scripts/contributor-issue-comment.js
@@ -1,111 +1,215 @@
+// See docs/community-automations.md
+
const {
LE_BOT_USERNAME,
KEYWORDS_DETECT_ASSIGNMENT_REQUEST,
ISSUE_LABEL_HELP_WANTED,
- BOT_MESSAGE_ISSUE_NOT_OPEN
+ BOT_MESSAGE_ISSUE_NOT_OPEN,
+ BOT_MESSAGE_ALREADY_ASSIGNED,
} = require('./constants');
+const {
+ isCloseContributor,
+ sendBotMessage,
+ escapeIssueTitleForSlackMessage,
+ hasRecentBotComment,
+} = require('./utils');
+
+async function hasLabel(name, owner, repo, issueNumber, github, core) {
+ let labels = [];
+ try {
+ const response = await github.rest.issues.listLabelsOnIssue({
+ owner,
+ repo,
+ issue_number: issueNumber,
+ });
+ labels = response.data.map(label => label.name);
+ } catch (error) {
+ core.warning(`Failed to fetch labels on issue #${issueNumber}: ${error.message}`);
+ labels = [];
+ }
+ return labels.some(label => label.toLowerCase() === name.toLowerCase());
+}
+
+async function getIssues(assignee, state, owner, repo, github, core) {
+ try {
+ const response = await github.rest.issues.listForRepo({
+ owner,
+ repo,
+ assignee,
+ state,
+ });
+ return response.data.filter(issue => !issue.pull_request);
+ } catch (error) {
+ core.warning(`Failed to fetch issues: ${error.message}`);
+ return [];
+ }
+}
+
+async function getPullRequests(assignee, state, owner, repo, github, core) {
+ try {
+ const response = await github.rest.pulls.list({
+ owner,
+ repo,
+ state,
+ });
+ return response.data.filter(pr => pr.user.login === assignee);
+ } catch (error) {
+ core.warning(`Failed to fetch pull requests: ${error.message}`);
+ return [];
+ }
+}
+
+// Format information about author's assigned open issues
+// as '(Issues #1 #2 | PRs #3)' and PRs for Slack message
+function formatAuthorActivity(issues, pullRequests) {
+ const parts = [];
+
+ if (issues.length > 0) {
+ const issueLinks = issues.map(issue => `<${issue.html_url}|#${issue.number}>`).join(' ');
+ parts.push(`Issues ${issueLinks}`);
+ } else {
+ parts.push(`Issues none`);
+ }
+
+ if (pullRequests.length > 0) {
+ const prLinks = pullRequests.map(pr => `<${pr.html_url}|#${pr.number}>`).join(' ');
+ parts.push(`PRs ${prLinks}`);
+ } else {
+ parts.push(`PRs none`);
+ }
+
+ return `(${parts.join(' | ')})`;
+}
+
+function shouldSendBotReply(
+ issueCreator,
+ commentAuthor,
+ commentAuthorIsCloseContributor,
+ isHelpWanted,
+ isAssignmentRequest,
+ isIssueAssignedToSomeoneElse,
+) {
+ if (commentAuthorIsCloseContributor) {
+ return [false, null];
+ }
+
+ if (issueCreator === commentAuthor) {
+ return [false, null];
+ }
+
+ if (isHelpWanted && isIssueAssignedToSomeoneElse && isAssignmentRequest) {
+ return [true, BOT_MESSAGE_ALREADY_ASSIGNED];
+ }
+
+ if (!isHelpWanted && isAssignmentRequest) {
+ return [true, BOT_MESSAGE_ISSUE_NOT_OPEN];
+ }
+
+ return [false, null];
+}
+
+function shouldContactSupport(
+ commentAuthorIsCloseContributor,
+ isHelpWanted,
+ isIssueAssignedToSomeoneElse,
+) {
+ if (commentAuthorIsCloseContributor) {
+ return true;
+ }
+
+ if (!isHelpWanted) {
+ return false;
+ }
+
+ if (isHelpWanted && isIssueAssignedToSomeoneElse) {
+ return false;
+ }
+
+ return true;
+}
module.exports = async ({ github, context, core }) => {
try {
const issueNumber = context.payload.issue.number;
const issueUrl = context.payload.issue.html_url;
- const issueTitle = context.payload.issue.title;
- const escapedTitle = issueTitle.replace(/"/g, '\\"');
+ const issueTitle = escapeIssueTitleForSlackMessage(context.payload.issue.title);
+ const issueCreator = context.payload.issue.user.login;
+ const issueAssignees = context.payload.issue.assignees?.map(assignee => assignee.login) || [];
const commentId = context.payload.comment.id;
- const commentTime = new Date(context.payload.comment.created_at);
- const oneHourBefore = new Date(commentTime - 3600000);
const commentAuthor = context.payload.comment.user.login;
const commentBody = context.payload.comment.body;
const repo = context.repo.repo;
const owner = context.repo.owner;
- const supportDevSlackWebhookUrl = process.env.SLACK_WEBHOOK_URL;
- const supportDevNotificationsSlackWebhookUrl = process.env.SLACK_COMMUNITY_NOTIFICATIONS_WEBHOOK_URL;
- const isCloseContributor = process.env.IS_CLOSE_CONTRIBUTOR === 'true';
- const keywordRegexes = KEYWORDS_DETECT_ASSIGNMENT_REQUEST
- .map(k => k.trim().toLowerCase())
+ const keywordRegexes = KEYWORDS_DETECT_ASSIGNMENT_REQUEST.map(k => k.trim().toLowerCase())
.filter(Boolean)
.map(keyword => new RegExp(`\\b${keyword}\\b`, 'i'));
+ const isAssignmentRequest = keywordRegexes.find(regex => regex.test(commentBody));
+ const isIssueAssignedToSomeoneElse =
+ issueAssignees && issueAssignees.length > 0 && !issueAssignees.includes(commentAuthor);
+ const isHelpWanted = await hasLabel(
+ ISSUE_LABEL_HELP_WANTED,
+ owner,
+ repo,
+ issueNumber,
+ github,
+ core,
+ );
+ const commentAuthorIsCloseContributor = await isCloseContributor(commentAuthor, {
+ github,
+ context,
+ core,
+ });
-
-
- async function hasLabel(name) {
- let labels = [];
- try {
- const response = await github.rest.issues.listLabelsOnIssue({
- owner,
- repo,
- issue_number: issueNumber
- });
- labels = response.data.map(label => label.name);
- } catch (error) {
- core.warning(`⚠️ Failed to fetch labels on issue #${issueNumber}: ${error.message}`);
- labels = [];
+ const [shouldPostBot, botMessage] = shouldSendBotReply(
+ issueCreator,
+ commentAuthor,
+ commentAuthorIsCloseContributor,
+ isHelpWanted,
+ isAssignmentRequest,
+ isIssueAssignedToSomeoneElse,
+ );
+ if (shouldPostBot) {
+ // post bot reply only when there are no same bot comments
+ // in the past hour to prevent overwhelming issue comment section
+ const skipBot = await hasRecentBotComment(issueNumber, LE_BOT_USERNAME, botMessage, 3600000, {
+ github,
+ context,
+ core,
+ });
+ if (skipBot) {
+ const slackMessage = `*[${repo}] Bot response skipped on issue: <${issueUrl}|${issueTitle}> (less than 1 hour since last bot message)*`;
+ core.setOutput('support_dev_notifications_bot', slackMessage);
+ } else {
+ const botMessageUrl = await sendBotMessage(issueNumber, botMessage, {
+ github,
+ context,
+ core,
+ });
+ if (botMessageUrl) {
+ const slackMessage = `*[${repo}] <${botMessageUrl}|Bot response sent> on issue: <${issueUrl}|${issueTitle}>*`;
+ core.setOutput('support_dev_notifications_bot', slackMessage);
}
- return labels.some(label => label.toLowerCase() === name.toLowerCase());
- }
-
- async function findRecentCommentsByUser(username) {
- try{
- let response = await github.rest.issues.listComments({
- owner,
- repo,
- issue_number: issueNumber,
- since: oneHourBefore.toISOString()
- });
- return response.data.filter(comment => comment.user.login === username);
- } catch (error) {
- core.warning(`⚠️ Failed to fetch comments on issue #${issueNumber}: ${error.message}`);
- return [];
}
}
- async function botReply(){
- let response = null;
- try {
- response = await github.rest.issues.createComment({
- owner,
- repo,
- issue_number: issueNumber,
- body: BOT_MESSAGE_ISSUE_NOT_OPEN
- });
- if (response?.data?.html_url) {
- core.setOutput('bot_replied', true);
- const slackMessage = `*[${repo}] <${response.data.html_url}|Bot response sent> on issue: <${issueUrl}|${escapedTitle}>*`;
- core.setOutput('slack_notification_bot_comment', slackMessage);
- }
- } catch (error) {
- core.warning(`Failed to post bot comment: ${error.message}`);
- core.setOutput('bot_replied', false);
- }
- return response;
- }
-
+ const contactSupport = shouldContactSupport(
+ commentAuthorIsCloseContributor,
+ isHelpWanted,
+ isIssueAssignedToSomeoneElse,
+ );
+ let slackMessage = `*[${repo}] <${issueUrl}#issuecomment-${commentId}|New comment> on issue: <${issueUrl}|${issueTitle}> by _${commentAuthor}_*`;
- if ( isCloseContributor || await hasLabel(ISSUE_LABEL_HELP_WANTED) ) {
- core.setOutput('webhook_url', supportDevSlackWebhookUrl);
+ if (contactSupport) {
+ const [assignedOpenIssues, openPRs] = await Promise.all([
+ getIssues(commentAuthor, 'open', owner, repo, github, core),
+ getPullRequests(commentAuthor, 'open', owner, repo, github, core),
+ ]);
+ const authorActivity = formatAuthorActivity(assignedOpenIssues, openPRs);
+ slackMessage += ` _${authorActivity}_`;
+ core.setOutput('support_dev_message', slackMessage);
} else {
- core.setOutput('webhook_url', supportDevNotificationsSlackWebhookUrl);
- const matchedKeyword = keywordRegexes.find(regex => regex.test(commentBody));
- // post a bot reply if there is matched keyword and no previous bot comment in past hour
- if(matchedKeyword){
- let lastBotComment;
- let PastBotComments = await findRecentCommentsByUser(LE_BOT_USERNAME);
- if(PastBotComments.length > 0){
- lastBotComment = PastBotComments.at(-1);
- core.setOutput('bot_replied', false);
- core.setOutput('bot_reply_skipped', true);
- const slackMessage = `*[${repo}] Bot response skipped on issue: <${issueUrl}|${escapedTitle}> (less than 1 hour since last bot reply)*`;
- core.setOutput('slack_notification_bot_skipped', slackMessage);
- } else if(PastBotComments.length === 0){
- console.log("bot is replying");
- lastBotComment = await botReply();
- }
- }
+ core.setOutput('support_dev_notifications_message', slackMessage);
}
-
- const message = `*[${repo}] <${issueUrl}#issuecomment-${commentId}|New comment> on issue: <${issueUrl}|${escapedTitle}> by ${commentAuthor}*`;
- core.setOutput('slack_notification_comment', message);
-
} catch (error) {
core.setFailed(`Action failed with error: ${error.message}`);
}
diff --git a/scripts/contributor-pr-reply.js b/scripts/contributor-pr-reply.js
new file mode 100644
index 0000000..14f3ec8
--- /dev/null
+++ b/scripts/contributor-pr-reply.js
@@ -0,0 +1,27 @@
+const { BOT_MESSAGE_PULL_REQUEST } = require('./constants');
+const { sendBotMessage } = require('./utils');
+
+module.exports = async ({ github, context, core }) => {
+ try {
+ const repo = context.repo.repo;
+ const number = context.payload.pull_request.number;
+ const url = context.payload.pull_request.html_url;
+ const title = context.payload.pull_request.title;
+
+ const botMessageUrl = await sendBotMessage(number, BOT_MESSAGE_PULL_REQUEST, {
+ github,
+ context,
+ core,
+ });
+
+ if (botMessageUrl) {
+ const slackMessage = `*[${repo}] <${botMessageUrl}|Reply sent> on pull request: <${url}|${title}>*`;
+ core.setOutput('slack_notification', slackMessage);
+ } else {
+ core.setOutput('slack_notification', '');
+ }
+ } catch (error) {
+ core.setOutput('slack_notification', '');
+ core.setFailed(`Action failed with error: ${error.message}`);
+ }
+};
diff --git a/scripts/get_community_assigned_issues.py b/scripts/get_community_assigned_issues.py
index 4e59bf5..9e51dbd 100644
--- a/scripts/get_community_assigned_issues.py
+++ b/scripts/get_community_assigned_issues.py
@@ -2,7 +2,7 @@
# to external contributors.
# This is not used by any workflows, and meant to be used locally
# whenever need arises. The script requires `GITHUB_TOKEN`
-# environment variable with 'repo' and 'read:org' permissions.
+# environment variable with 'repo' and 'read:org' permissions.
import os
import requests
@@ -20,14 +20,14 @@
"Accept": "application/vnd.github+json"
}
-def fetch_paginated_results(url):
- results = []
- while url:
- response = requests.get(url, headers=HEADERS)
- response.raise_for_status()
- results.extend(response.json())
- url = response.links.get("next", {}).get("url")
- return results
+def fetch_paginated_results(url):
+ results = []
+ while url:
+ response = requests.get(url, headers=HEADERS)
+ response.raise_for_status()
+ results.extend(response.json())
+ url = response.links.get("next", {}).get("url")
+ return results
def get_team_members(org):
"""Fetch all team members for the organization."""
@@ -61,7 +61,7 @@ def main():
issue["html_url"]
for issue in filtered_issues
)
-
+
for url in all_filtered_issues:
print(url)
diff --git a/scripts/holiday-message.js b/scripts/holiday-message.js
new file mode 100644
index 0000000..c9e4abf
--- /dev/null
+++ b/scripts/holiday-message.js
@@ -0,0 +1,45 @@
+// See docs/community-automations.md
+
+const { HOLIDAY_MESSAGE, LE_BOT_USERNAME } = require('./constants');
+const { sendBotMessage, escapeIssueTitleForSlackMessage, hasRecentBotComment } = require('./utils');
+
+module.exports = async ({ github, context, core }) => {
+ try {
+ const repo = context.repo.repo;
+
+ const isPullRequest = !!context.payload.pull_request;
+ const number = isPullRequest
+ ? context.payload.pull_request.number
+ : context.payload.issue.number;
+ const url = isPullRequest
+ ? context.payload.pull_request.html_url
+ : context.payload.issue.html_url;
+ const title = escapeIssueTitleForSlackMessage(
+ isPullRequest ? context.payload.pull_request.title : context.payload.issue.title,
+ );
+
+ // post bot reply only when there are no same bot comments
+ // in the past hour to prevent overwhelming issue comment section
+ const skipBot = await hasRecentBotComment(number, LE_BOT_USERNAME, HOLIDAY_MESSAGE, 3600000, {
+ github,
+ context,
+ core,
+ });
+ if (skipBot) {
+ const itemType = isPullRequest ? 'pull request' : 'issue';
+ const slackBotSkippedMessage = `*[${repo}] Holiday message skipped on ${itemType}: <${url}|${title}> (less than 1 hour since last holiday message)*`;
+ core.setOutput('slack_notification', slackBotSkippedMessage);
+ return;
+ }
+
+ const botMessageUrl = await sendBotMessage(number, HOLIDAY_MESSAGE, { github, context, core });
+
+ if (botMessageUrl) {
+ const itemType = isPullRequest ? 'pull request' : 'issue';
+ const slackMessage = `*[${repo}] <${botMessageUrl}|Holiday message sent> on ${itemType}: <${url}|${title}>*`;
+ core.setOutput('slack_notification', slackMessage);
+ }
+ } catch (error) {
+ core.setFailed(`Action failed with error: ${error.message}`);
+ }
+};
diff --git a/scripts/is-close-contributor.js b/scripts/is-close-contributor.js
deleted file mode 100644
index 18c0257..0000000
--- a/scripts/is-close-contributor.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const { CLOSE_CONTRIBUTORS, TEAMS_WITH_CLOSE_CONTRIBUTORS } = require('./constants');
-
-module.exports = async ({ core, github, context }) => {
- const username = process.env.USERNAME;
- if (!username) {
- core.setFailed('Missing username input.');
- return;
- }
-
- if (CLOSE_CONTRIBUTORS.map(c => c.toLowerCase().trim()).includes(username.toLowerCase().trim())) {
- core.info(`User '${username}' found in the CLOSE CONTRIBUTORS list.`);
- core.setOutput('is_close_contributor', true);
- return;
- }
-
- const org = context.repo.owner;
-
- // Even though we check on team members here, it's best
- // to add everyone to CLOSE_CONTRIBUTORS constant anyway
- // for reliable results (e.g. check below won't work
- // for private members)
- const promises = TEAMS_WITH_CLOSE_CONTRIBUTORS.map(team_slug =>
- github.rest.teams.getMembershipForUserInOrg({
- org,
- team_slug,
- username,
- })
- );
-
- try {
- const results = await Promise.allSettled(promises);
- let isTeamMember = false;
-
- for (const result of results) {
- if (result.status === 'fulfilled' && result.value.data.state === 'active') {
- isTeamMember = true;
- break;
- }
-
- if (result.status === 'rejected' && result.reason.status !== 404) {
- throw new Error(`API Error: ${result.reason.message}`);
- }
- }
-
- if (isTeamMember) {
- core.info(`User '${username}' was found to be a member of a monitored team.`);
- } else {
- core.info(`User '${username}' was not found to be a member of a monitored team.`);
- }
-
- core.setOutput('is_close_contributor', isTeamMember);
-
- } catch (error) {
- core.setFailed(error.message);
- }
-};
\ No newline at end of file
diff --git a/scripts/is-contributor.js b/scripts/is-contributor.js
new file mode 100644
index 0000000..dc693a4
--- /dev/null
+++ b/scripts/is-contributor.js
@@ -0,0 +1,18 @@
+const { isContributor } = require('./utils');
+
+/**
+ * Checks if a user is a contributor based on their
+ * username and author association. Sets `is_contributor` output.
+ */
+module.exports = async ({ core, github, context }) => {
+ const username = process.env.USERNAME;
+ const authorAssociation = process.env.AUTHOR_ASSOCIATION;
+
+ const isUserContributor = await isContributor(username, authorAssociation, {
+ github,
+ context,
+ core,
+ });
+
+ core.setOutput('is_contributor', isUserContributor);
+};
diff --git a/scripts/manage-issue-header.js b/scripts/manage-issue-header.js
index d606d57..36dd58d 100644
--- a/scripts/manage-issue-header.js
+++ b/scripts/manage-issue-header.js
@@ -7,19 +7,24 @@ const HELP_WANTED_LABEL = 'help wanted';
const HEADER_START_MARKER = '';
const HEADER_END_MARKER = '';
-const HELP_WANTED_HEADER = '\n\n
\n\n🙂 Looking for an issue? Welcome! This issue is open for contribution. If this is the first time you’re requesting an issue, please:\n\n- **Read Contributing guidelines** carefully. **Pay extra attention to [Using generative AI](https://learningequality.org/contributing-to-our-open-code-base/#using-generative-ai)**. **Pull requests and comments that don’t follow the guidelines won’t be answered.**\n- **Confirm that you’ve read the guidelines** in your comment.\n\n
\n\n\n\n';
+const HELP_WANTED_HEADER =
+ '\n\n
\n\n🙂 Looking for an issue? Welcome! This issue is open for contribution. If this is the first time you’re requesting an issue, please:\n\n- **Read Contributing guidelines** carefully. **Pay extra attention to [Using generative AI](https://learningequality.org/contributing-to-our-open-code-base/#using-generative-ai)**. **Pull requests and comments that don’t follow the guidelines won’t be answered.**\n- **Confirm that you’ve read the guidelines** in your comment.\n\n
\n\n\n\n';
-const NON_HELP_WANTED_HEADER = '\n\n
\n\n❌ **This issue is not open for contribution. Visit Contributing guidelines** to learn about the contributing process and how to find suitable issues.\n\n
\n\n\n\n';
+const NON_HELP_WANTED_HEADER =
+ '\n\n
\n\n❌ **This issue is not open for contribution. Visit Contributing guidelines** to learn about the contributing process and how to find suitable issues.\n\n
\n\n\n\n';
-function clearHeader(issueBody) {
+function clearHeader(issueBody) {
const startIndex = issueBody.indexOf(HEADER_START_MARKER);
const endIndex = issueBody.indexOf(HEADER_END_MARKER);
-
+
if (startIndex === -1 || endIndex === -1) {
return issueBody;
}
- return issueBody.substring(0, startIndex) + issueBody.substring(endIndex + HEADER_END_MARKER.length).trimStart();
+ return (
+ issueBody.substring(0, startIndex) +
+ issueBody.substring(endIndex + HEADER_END_MARKER.length).trimStart()
+ );
}
function isIssueHelpWanted(issue) {
@@ -38,16 +43,17 @@ module.exports = async ({ github, context, core }) => {
const labelName = context.payload.label?.name;
let issue = context.payload.issue;
let header = '';
-
+
switch (actionType) {
case 'opened':
- // also handle pre-existing 'help wanted' label on transferred issues (processed via 'opened' event in a receiving repository)
+ // also handle pre-existing 'help wanted' label on transferred issues
+ // (processed via 'opened' event in a receiving repository)
header = isIssueHelpWanted(issue) ? HELP_WANTED_HEADER : NON_HELP_WANTED_HEADER;
- break;
+ break;
case 'reopened':
// check for pre-existing 'help wanted' label
header = isIssueHelpWanted(issue) ? HELP_WANTED_HEADER : NON_HELP_WANTED_HEADER;
- break;
+ break;
case 'labeled':
if (labelName === HELP_WANTED_LABEL) {
header = HELP_WANTED_HEADER;
@@ -66,10 +72,10 @@ module.exports = async ({ github, context, core }) => {
issue = await github.rest.issues.get({
owner: repoOwner,
repo: repoName,
- issue_number: issueNumber
+ issue_number: issueNumber,
});
-
- const currentBody = issue.data.body || "";
+
+ const currentBody = issue.data.body || '';
let newBody = clearHeader(currentBody);
newBody = header + newBody;
@@ -78,9 +84,8 @@ module.exports = async ({ github, context, core }) => {
owner: repoOwner,
repo: repoName,
issue_number: issueNumber,
- body: newBody
- });
-
+ body: newBody,
+ });
} catch (error) {
core.setFailed(`Error: ${error.message}`);
}
diff --git a/scripts/unassign-inactive-issues.js b/scripts/unassign-inactive-issues.js
index 91f9081..741d5f6 100644
--- a/scripts/unassign-inactive-issues.js
+++ b/scripts/unassign-inactive-issues.js
@@ -1,8 +1,6 @@
-const fetch = require('node-fetch-native');
-
-const formatUnassignments = (unassignments) => {
+const formatUnassignments = unassignments => {
if (unassignments.length === 0) return '';
-
+
// Group unassignments by issue
const groupedByIssue = unassignments.reduce((acc, curr) => {
const key = `${curr.repo}#${curr.issueNumber}`;
@@ -12,7 +10,7 @@ const formatUnassignments = (unassignments) => {
owner: curr.owner,
issueNumber: curr.issueNumber,
issueUrl: curr.issueUrl,
- users: []
+ users: [],
};
}
acc[key].users.push(curr.user);
@@ -21,8 +19,9 @@ const formatUnassignments = (unassignments) => {
// Format the grouped unassignments
return Object.values(groupedByIssue)
- .map(({ users, repo, issueNumber, issueUrl }) =>
- `${users.map(u => `@${u}`).join(', ')} from <${issueUrl}|${repo}#${issueNumber}>`
+ .map(
+ ({ users, repo, issueNumber, issueUrl }) =>
+ `${users.map(u => `@${u}`).join(', ')} from <${issueUrl}|${repo}#${issueNumber}>`,
)
.join(', ');
};
@@ -31,10 +30,11 @@ async function getAllIssues(github, owner, repo) {
const allIssues = [];
let page = 1;
const perPage = 100;
-
+
+ // eslint-disable-next-line no-constant-condition
while (true) {
//console.log(`Fetching page ${page} of issues...`);
-
+
try {
const response = await github.rest.issues.listForRepo({
owner,
@@ -43,29 +43,30 @@ async function getAllIssues(github, owner, repo) {
per_page: perPage,
page: page,
filter: 'all',
- pulls: false
+ pulls: false,
});
-
+
const issues = response.data.filter(issue => !issue.pull_request);
-
+
if (issues.length === 0) {
break; // No more issues to fetch
}
-
+
allIssues.push(...issues);
//console.log(`Fetched ${issues.length} issues (excluding PRs) from page ${page}`);
-
+
if (issues.length < perPage) {
break; // Last page has fewer items than perPage
}
-
+
page++;
} catch (error) {
+ // eslint-disable-next-line no-console
console.error(`Error fetching issues page ${page}:`, error);
break;
}
}
-
+
//console.log(`Total issues fetched (excluding PRs): ${allIssues.length}`);
return allIssues;
}
@@ -73,6 +74,7 @@ async function getAllIssues(github, owner, repo) {
const checkLinkedPRs = async (issue, github, owner, repo) => {
try {
if (!issue || !issue.number) {
+ // eslint-disable-next-line no-console
console.error('Invalid issue object received:', issue);
return new Set(); // Return empty Set instead of false
}
@@ -80,38 +82,39 @@ const checkLinkedPRs = async (issue, github, owner, repo) => {
let linkedPRs = new Set();
// Method 1: Check timeline with enhanced connected event handling
- try {
+ try {
const { data: timelineEvents } = await github.rest.issues.listEventsForTimeline({
owner,
repo,
issue_number: issue.number,
- per_page: 100
+ per_page: 100,
});
-
for (const event of timelineEvents) {
if (
- // Check if the event type is 'connected' or 'cross-referenced'.
- // These events indicate that GitHub automatically linked the issue to a PR based on commit messages or references.
- (event.event === 'connected' || event.event === 'cross-referenced') ||
-
+ // Check if the event type is 'connected' or 'cross-referenced'.
+ // These events indicate that GitHub automatically linked the issue
+ // to a PR based on commit messages or references.
+ event.event === 'connected' ||
+ event.event === 'cross-referenced' ||
// Look for a 'referenced' event that includes a commit ID and a linked PR.
// This implies that the commit in a PR mentions the issue, suggesting a connection.
- (event.event === 'referenced' && event?.commit_id && event?.source?.issue?.pull_request) ||
-
+ (event.event === 'referenced' &&
+ event?.commit_id &&
+ event?.source?.issue?.pull_request) ||
// Check for a 'closed' event that has an associated commit and a linked PR.
// This typically means the issue was closed by a commit referenced in the PR.
(event.event === 'closed' && event?.commit_id && event?.source?.issue?.pull_request) ||
-
// Confirm a 'connected' event where the linked PR is not merged yet.
// This ensures that we consider PRs that are still open and haven't been merged.
(event.event === 'connected' && event?.source?.issue?.pull_request?.merged === false)
) {
try {
//issue.number is used because every pull request is also an issue.
- //Some GitHub events only provide the issue object, so using issue.number ensures consistent access to the PR number across events.
+ //Some GitHub events only provide the issue object, so using issue.number
+ // ensures consistent access to the PR number across events.
let prNumber = event?.source?.issue?.number;
-
+
if (!prNumber && event?.source?.pull_request?.number) {
prNumber = event.source.pull_request.number;
}
@@ -121,15 +124,16 @@ const checkLinkedPRs = async (issue, github, owner, repo) => {
const { data: pr } = await github.rest.pulls.get({
owner,
repo,
- pull_number: prNumber
+ pull_number: prNumber,
});
-
+
if (pr && pr.state === 'open') {
//console.log(`Found valid linked PR #${prNumber} (${pr.state})`);
- linkedPRs.add(prNumber);
+ linkedPRs.add(prNumber);
}
- }else{
- // Fallback for PRs linked via GitHub UI where PR number cannot be retrieved from the payload
+ } else {
+ // Fallback for PRs linked via GitHub UI where PR number
+ // cannot be retrieved from the payload
//console.log('found pr linked in the issue');
linkedPRs.add(1); // Adds a placeholder to indicate a linked PR was found
}
@@ -139,27 +143,28 @@ const checkLinkedPRs = async (issue, github, owner, repo) => {
}
}
} catch (timelineError) {
+ // eslint-disable-next-line no-console
console.error(`Error fetching timeline for issue #${issue.number}:`, timelineError.message);
}
// Method 2: Search for PRs that mention this issue using the updated endpoint
try {
const searchQuery = `repo:${owner}/${repo} type:pr is:open ${issue.number} in:body,title`;
-
+
const searchResult = await github.request('GET /search/issues', {
q: searchQuery,
advanced_search: true, // Enable advanced search
headers: {
- 'Accept': 'application/vnd.github+json',
- }
+ Accept: 'application/vnd.github+json',
+ },
});
-
+
// Local regex for "closes/fixes/resolves #123"
const closingRegex = new RegExp(
`(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\\s*:?\\s*#${issue.number}`,
- "i"
+ 'i',
);
-
+
for (const prItem of searchResult.data.items || []) {
if (prItem.pull_request && prItem.number) {
const prDetails = await github.rest.pulls.get({
@@ -167,10 +172,10 @@ const checkLinkedPRs = async (issue, github, owner, repo) => {
repo,
pull_number: prItem.number,
});
-
- if (prDetails.data.state === "open") {
- const prBody = prDetails.data.body || "";
- const prTitle = prDetails.data.title || "";
+
+ if (prDetails.data.state === 'open') {
+ const prBody = prDetails.data.body || '';
+ const prTitle = prDetails.data.title || '';
if (closingRegex.test(prBody) || closingRegex.test(prTitle)) {
linkedPRs.add(prItem.number);
}
@@ -179,10 +184,11 @@ const checkLinkedPRs = async (issue, github, owner, repo) => {
}
} catch (searchError) {
//console.log('Search API error:', searchError.message);
- }
+ }
// Return the Set of linked PR numbers (always return a Set)
return linkedPRs;
} catch (error) {
+ // eslint-disable-next-line no-console
console.error(`Error in checkLinkedPRs for issue #${issue.number}:`, error);
return new Set(); // Return empty Set instead of false
}
@@ -194,7 +200,7 @@ const checkUserMembership = async (owner, repo, username, github) => {
// Check if the user is an owner of the repository
const repoDetails = await github.rest.repos.get({
owner,
- repo
+ repo,
});
// Check if the repository owner matches the username
@@ -207,7 +213,7 @@ const checkUserMembership = async (owner, repo, username, github) => {
try {
await github.rest.orgs.getMembershipForUser({
org: owner,
- username: username
+ username: username,
});
//console.log(`${username} is an organization member`);
return true;
@@ -216,6 +222,7 @@ const checkUserMembership = async (owner, repo, username, github) => {
return false;
}
} catch (error) {
+ // eslint-disable-next-line no-console
console.error(`Error checking user membership for ${username}:`, error);
return false;
}
@@ -223,36 +230,38 @@ const checkUserMembership = async (owner, repo, username, github) => {
module.exports = async ({ github, context, core }) => {
try {
-
const unassignments = [];
const inactivityThresholdMs = 30 * 24 * 60 * 60 * 1000; // for 1 month
const [owner, repo] = context.payload.repository.full_name.split('/');
//console.log(`Processing repository: ${owner}/${repo}`);
-
+
try {
// Test API access by getting repository details
- const { data: repository } = await github.rest.repos.get({
+ await github.rest.repos.get({
owner,
- repo
+ repo,
});
// console.log('Successfully authenticated with GitHub App and verified repository access');
// console.log(`Repository: ${repository.full_name}`);
} catch (authError) {
+ // eslint-disable-next-line no-console
console.error('Authentication error details:', {
message: authError.message,
status: authError.status,
- documentation_url: authError.documentation_url
+ documentation_url: authError.documentation_url,
});
- throw new Error(`Repository access failed. Please check your GitHub App permissions for repository access. Error: ${authError.message}`);
+ throw new Error(
+ `Repository access failed. Please check your GitHub App permissions for repository access. Error: ${authError.message}`,
+ );
}
-
const issues = await getAllIssues(github, owner, repo);
//console.log(`Processing ${issues.length} open issues`);
for (const issue of issues) {
if (!issue || !issue.number) {
+ // eslint-disable-next-line no-console
console.error('Skipping invalid issue:', issue);
continue;
}
@@ -274,7 +283,7 @@ module.exports = async ({ github, context, core }) => {
// Check if issue is inactive
const lastActivity = new Date(issue.updated_at);
const now = new Date();
-
+
if (now - lastActivity <= inactivityThresholdMs) {
//console.log(`Issue #${issue.number} is still active, skipping`);
continue;
@@ -282,7 +291,7 @@ module.exports = async ({ github, context, core }) => {
//console.log(`Checking for linked PRs for issue #${issue.number}`);
const hasOpenPRs = await checkLinkedPRs(issue, github, owner, repo);
-
+
if (hasOpenPRs.size > 0) {
//console.log(`Issue #${issue.number} has open PRs, skipping unassignment`);
continue;
@@ -299,7 +308,10 @@ module.exports = async ({ github, context, core }) => {
continue;
}
- if (assignee.site_admin || await checkUserMembership(owner, repo, assignee.login, github)) {
+ if (
+ assignee.site_admin ||
+ (await checkUserMembership(owner, repo, assignee.login, github))
+ ) {
activeAssignees.push(assignee.login);
//console.log(`${assignee.login} is an active member, keeping assignment`);
} else {
@@ -340,36 +352,40 @@ module.exports = async ({ github, context, core }) => {
repo,
owner,
issueNumber: issue.number,
- issueUrl: `https://github.com/${owner}/${repo}/issues/${issue.number}`
+ issueUrl: `https://github.com/${owner}/${repo}/issues/${issue.number}`,
});
});
-
} catch (issueError) {
+ // eslint-disable-next-line no-console
console.error(`Error processing issue #${issue.number}:`, {
message: issueError.message,
- status: issueError.status
+ status: issueError.status,
});
if (issueError.status === 403) {
- throw new Error('Token lacks necessary permissions. Ensure it has "issues" and "write" access.');
+ throw new Error(
+ 'Token lacks necessary permissions. Ensure it has "issues" and "write" access.',
+ );
}
}
}
const formattedUnassignments = formatUnassignments(unassignments);
//console.log('Unassignments completed:', unassignments.length);
-
+
try {
core.setOutput('unassignments', formattedUnassignments);
return formattedUnassignments;
} catch (error) {
+ // eslint-disable-next-line no-console
console.error('Error setting output:', error);
core.setFailed(error.message);
}
-
} catch (error) {
+ // eslint-disable-next-line no-console
console.error('Action failed:', error);
+ // eslint-disable-next-line no-console
console.error('Full error details:', error);
core.setFailed(error.message);
return '';
}
-};
\ No newline at end of file
+};
diff --git a/scripts/update-pr-spreadsheet.js b/scripts/update-pr-spreadsheet.js
index 95fe226..f21add2 100644
--- a/scripts/update-pr-spreadsheet.js
+++ b/scripts/update-pr-spreadsheet.js
@@ -1,7 +1,7 @@
const { google } = require('googleapis');
// Extract relevant data from the event payload
-const extractPRData = (payload) => {
+const extractPRData = payload => {
const pr = payload.pull_request;
return {
merged_at: pr.merged_at,
@@ -10,71 +10,53 @@ const extractPRData = (payload) => {
title: pr.title,
repo_name: pr.base.repo.name,
created_at: pr.created_at,
- requested_reviewers: pr.requested_reviewers.map(r => r.login).join(','),
- assignees: pr.assignees.map(a => a.login).join(','),
+ requested_reviewers: pr.requested_reviewers?.map(r => r.login).join(',') || '',
+ assignees: pr.assignees?.map(a => a.login).join(',') || '',
user_site_admin: pr.user.site_admin,
user_type: pr.user.type,
author_association: pr.author_association,
- state: pr.state
+ state: pr.state,
};
};
-const SPREADSHEET_ID = process.env.SPREADSHEET_ID;
-const SHEET_NAME = process.env.SHEET_NAME;
-const GOOGLE_CREDENTIALS = process.env.GOOGLE_CREDENTIALS;
-
-// Validate environment variables
-function validateEnvVariables() {
- const missingVars = [];
- if (!SPREADSHEET_ID) missingVars.push("SPREADSHEET_ID");
- if (!SHEET_NAME) missingVars.push("SHEET_NAME");
- if (!GOOGLE_CREDENTIALS) missingVars.push("GOOGLE_CREDENTIALS");
-
- if (missingVars.length > 0) {
- throw new Error(
- `Missing required environment variables: ${missingVars.join(", ")}`
- );
- }
-}
-
// Set up GoogleAuth for Google Sheets API
-async function authorize() {
+async function authorize(googleCredentials) {
try {
- const credentials = JSON.parse(GOOGLE_CREDENTIALS);
+ const credentials = JSON.parse(googleCredentials);
const auth = new google.auth.GoogleAuth({
credentials,
- scopes: ["https://www.googleapis.com/auth/spreadsheets"],
+ scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
const authClient = await auth.getClient();
- return google.sheets({ version: "v4", auth: authClient });
+ return google.sheets({ version: 'v4', auth: authClient });
} catch (error) {
- throw new Error(
- `Failed to authorize: ${error.message}. Please check your GOOGLE_CREDENTIALS.`
- );
+ throw new Error(`Failed to authorize: ${error.message}.`);
}
}
// Update Google Sheets with pull request data
-async function updateSpreadsheet(pullRequest) {
- const sheets = await authorize();
+async function updateSpreadsheet(pullRequest, sheetId, sheetName, googleCredentials) {
+ const sheets = await authorize(googleCredentials);
const prData = [
- pullRequest.merged_at ? pullRequest.merged_at.split("T")[0].replace("'", "") : pullRequest.state === "closed" ? "closed" : pullRequest.state,
- pullRequest.html_url || "",
- pullRequest.user_login || "",
- pullRequest.title || "",
- pullRequest.repo_name || "",
- pullRequest.created_at
- ? pullRequest.created_at.split("T")[0].replace("'", "")
- : "",
- pullRequest.requested_reviewers || "",
- pullRequest.assignees || "",
+ pullRequest.merged_at
+ ? pullRequest.merged_at.split('T')[0].replace("'", '')
+ : pullRequest.state === 'closed'
+ ? 'closed'
+ : pullRequest.state,
+ pullRequest.html_url || '',
+ pullRequest.user_login || '',
+ pullRequest.title || '',
+ pullRequest.repo_name || '',
+ pullRequest.created_at ? pullRequest.created_at.split('T')[0].replace("'", '') : '',
+ pullRequest.requested_reviewers || '',
+ pullRequest.assignees || '',
];
try {
// Fetch existing rows from the sheet
const { data } = await sheets.spreadsheets.values.get({
- spreadsheetId: SPREADSHEET_ID,
- range: SHEET_NAME,
+ spreadsheetId: sheetId,
+ range: sheetName,
});
const existingRows = data.values || [];
@@ -95,14 +77,14 @@ async function updateSpreadsheet(pullRequest) {
const existingDataString = JSON.stringify(existingData);
if (prDataString !== existingDataString) {
- console.log(`Detected changes for row ${rowToUpdate}.`);
+ // console.log(`Detected changes for row ${rowToUpdate}.`);
const updates = [];
- const columns = ["A", "B", "C", "D", "E", "F", "G", "H"];
+ const columns = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
for (let col = 0; col < prData.length; col++) {
if (existingData[col] !== prData[col]) {
updates.push({
- range: `${SHEET_NAME}!${columns[col]}${rowToUpdate}`,
+ range: `${sheetName}!${columns[col]}${rowToUpdate}`,
values: [[prData[col]]],
});
}
@@ -111,64 +93,44 @@ async function updateSpreadsheet(pullRequest) {
if (updates.length > 0) {
// Batch update changed columns
await sheets.spreadsheets.values.batchUpdate({
- spreadsheetId: SPREADSHEET_ID,
+ spreadsheetId: sheetId,
resource: {
data: updates,
- valueInputOption: "RAW",
+ valueInputOption: 'RAW',
},
});
- console.log(`Updated row ${rowToUpdate} in Google Sheets.`);
+ // console.log(`Updated row ${rowToUpdate} in Google Sheets.`);
} else {
- console.log(`No changes detected for row ${rowToUpdate}.`);
+ // console.log(`No changes detected for row ${rowToUpdate}.`);
}
} else {
- console.log(`No changes detected for row ${rowToUpdate}.`);
+ // console.log(`No changes detected for row ${rowToUpdate}.`);
}
} else {
// Append new row starting from column B
await sheets.spreadsheets.values.append({
- spreadsheetId: SPREADSHEET_ID,
- range: `${SHEET_NAME}!A:H`,
- valueInputOption: "RAW",
+ spreadsheetId: sheetId,
+ range: `${sheetName}!A:H`,
+ valueInputOption: 'RAW',
resource: { values: [prData] },
});
- console.log(`Added new row to Google Sheets.`);
+ // console.log(`Added new row to Google Sheets.`);
}
} catch (error) {
throw new Error(`Failed to update spreadsheet: ${error.message}`);
}
}
-// Main function to handle pull request changes
-async function handlePullRequestChange(pullRequest) {
- // Filter out PRs from members of the organization
- if (
- !pullRequest.user_site_admin &&
- pullRequest.user_type === "User" &&
- !pullRequest.author_association.includes("MEMBER")
- ) {
- await updateSpreadsheet(pullRequest);
- } else {
- console.log(
- "PR skipped: Author is a member of the organization or a site admin."
- );
- }
-}
-
-// Validate environment variables
-try {
- validateEnvVariables();
-} catch (error) {
- console.error(error.message);
- process.exit(1);
-}
-
-// Get the GitHub event data
-const githubEvent = JSON.parse(process.env.GITHUB_EVENT);
+module.exports = async ({ context, core }) => {
+ const sheetId = process.env.CONTRIBUTIONS_SPREADSHEET_ID;
+ const sheetName = process.env.CONTRIBUTIONS_SHEET_NAME;
+ const googleCredentials = process.env.GOOGLE_CREDENTIALS;
-// Extract PR data and run the script
-const prData = extractPRData(githubEvent);
-handlePullRequestChange(prData).catch((error) => {
- console.error("An error occurred:", error.message);
- process.exit(1);
-});
+ try {
+ const prData = extractPRData(context.payload);
+ await updateSpreadsheet(prData, sheetId, sheetName, googleCredentials);
+ } catch (error) {
+ core.setFailed(`An error occurred: ${error.message}`);
+ throw error;
+ }
+};
diff --git a/scripts/utils.js b/scripts/utils.js
new file mode 100644
index 0000000..821bbcc
--- /dev/null
+++ b/scripts/utils.js
@@ -0,0 +1,180 @@
+const { LE_BOT_USERNAME, SENTRY_BOT_USERNAME } = require('./constants');
+// const { CLOSE_CONTRIBUTORS, TEAMS_WITH_CLOSE_CONTRIBUTORS } = require('./constants');
+const { CLOSE_CONTRIBUTORS } = require('./constants');
+
+/**
+ * Checks if username belongs to one of our bots.
+ */
+async function isBot(username, { core }) {
+ if (!username) {
+ core.setFailed('Missing username');
+ return false;
+ }
+ return [LE_BOT_USERNAME, SENTRY_BOT_USERNAME].includes(username);
+}
+
+/**
+ * Checks if a user is a contributor (= not a core team member).
+ */
+async function isContributor(username, authorAssociation, { github, context, core }) {
+ if (!username) {
+ core.setFailed('Missing username');
+ return false;
+ }
+
+ if (!authorAssociation) {
+ core.setFailed('Missing authorAssociation');
+ return false;
+ }
+
+ if (authorAssociation === 'OWNER') {
+ return false;
+ }
+
+ if (await isBot(username, { core })) {
+ return false;
+ }
+
+ const isClose = await isCloseContributor(username, { github, context, core });
+ // Some close contributors may be 'MEMBER's due to GSoC or other GitHub team
+ // memberships, so here we need to exclude only team members who are not
+ // close contributors
+ if (authorAssociation === 'MEMBER' && !isClose) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks if a user is a close contributor by checking
+ * both the constants list and team membership in monitored teams.
+ */
+async function isCloseContributor(username, { core }) {
+ if (!username) {
+ core.setFailed('Missing username');
+ return false;
+ }
+
+ if (CLOSE_CONTRIBUTORS.map(c => c.toLowerCase().trim()).includes(username.toLowerCase().trim())) {
+ return true;
+ } else {
+ return false;
+ }
+
+ // Detection on GitHub teams below is disabled until we re-think
+ // how close contributors are managed (see Notion tracker):
+ // - it was only fallback as explained lower
+ // - it causes the problem when we receive undesired notification
+ // to #support-dev when Richard posts issue comment since he is a member
+ // of GSoC and other GitHub teams with close contributors (they require moderator).
+
+ /* const org = context.repo.owner;
+
+ // Even though we check on team members here, it's best
+ // to add everyone to CLOSE_CONTRIBUTORS constant anyway
+ // for reliable results (e.g. check below won't work
+ // for people who have their membership set to private,
+ // and we don't have control over that)
+ const promises = TEAMS_WITH_CLOSE_CONTRIBUTORS.map(team_slug =>
+ github.rest.teams.getMembershipForUserInOrg({
+ org,
+ team_slug,
+ username,
+ })
+ );
+
+ try {
+ const results = await Promise.allSettled(promises);
+ let isMember = false;
+
+ for (const result of results) {
+ if (result.status === 'fulfilled' && result.value.data.state === 'active') {
+ isMember = true;
+ break;
+ }
+
+ if (result.status === 'rejected' && result.reason.status !== 404) {
+ throw new Error(`API Error: ${result.reason.message}`);
+ }
+ }
+
+ return isMember;
+ } catch (error) {
+ core.setFailed(error.message);
+ return false;
+ } */
+}
+
+/**
+ * Sends a bot message as a comment on an issue. Returns message URL if successful.
+ */
+async function sendBotMessage(issueNumber, message, { github, context }) {
+ try {
+ if (!issueNumber) {
+ throw new Error('Issue number is required');
+ }
+ if (!message) {
+ throw new Error('Message content is required');
+ }
+
+ const response = await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: issueNumber,
+ body: message,
+ });
+
+ if (!response?.data?.html_url) {
+ throw new Error('Comment created but no URL returned');
+ }
+
+ return response.data.html_url;
+ } catch (error) {
+ throw new Error(error.message);
+ }
+}
+
+function escapeIssueTitleForSlackMessage(issueTitle) {
+ return issueTitle.replace(/"/g, '\\"').replace(//g, '>');
+}
+
+/**
+ * Checks if a bot sent a message with a given text on an issue
+ * in the past specified milliseconds.
+ */
+async function hasRecentBotComment(
+ issueNumber,
+ botUsername,
+ commentText,
+ msAgo,
+ { github, context, core },
+) {
+ const oneHourAgo = new Date(Date.now() - msAgo);
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+
+ try {
+ const response = await github.rest.issues.listComments({
+ owner,
+ repo,
+ issue_number: issueNumber,
+ since: oneHourAgo.toISOString(),
+ });
+ return (response.data || []).some(
+ comment =>
+ comment.user && comment.user.login === botUsername && comment.body.includes(commentText),
+ );
+ } catch (error) {
+ core.warning(`Failed to fetch comments on issue #${issueNumber}: ${error.message}`);
+ }
+}
+
+module.exports = {
+ isContributor,
+ isCloseContributor,
+ isBot,
+ sendBotMessage,
+ escapeIssueTitleForSlackMessage,
+ hasRecentBotComment,
+};
diff --git a/yarn.lock b/yarn.lock
index 74d2e50..f20ead1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,43 +2,170 @@
# yarn lockfile v1
+"@eslint-community/eslint-utils@^4.2.0":
+ version "4.9.0"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3"
+ integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==
+ dependencies:
+ eslint-visitor-keys "^3.4.3"
+
+"@eslint-community/regexpp@^4.6.1":
+ version "4.12.2"
+ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
+ integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==
+
+"@eslint/eslintrc@^2.1.4":
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
+ integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^9.6.0"
+ globals "^13.19.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"
+
+"@eslint/js@8.57.1":
+ version "8.57.1"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
+ integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
+
+"@humanwhocodes/config-array@^0.13.0":
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
+ integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
+ dependencies:
+ "@humanwhocodes/object-schema" "^2.0.3"
+ debug "^4.3.1"
+ minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
+ integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+"@ungap/structured-clone@^1.2.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
+ integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
+
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn@^8.9.0:
+ version "8.15.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
+ integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
+
agent-base@^7.1.2:
version "7.1.4"
- resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
+ resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz"
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ 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"
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
asynckit@^0.4.0:
version "0.4.0"
- resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
axios@^1.7.5:
version "1.10.0"
- resolved "https://registry.yarnpkg.com/axios/-/axios-1.10.0.tgz#af320aee8632eaf2a400b6a1979fa75856f38d54"
+ resolved "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz"
integrity sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
base64-js@^1.3.0:
version "1.5.1"
- resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+ resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bignumber.js@^9.0.0:
version "9.3.1"
- resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7"
+ resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz"
integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==
+brace-expansion@^1.1.7:
+ version "1.1.12"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz"
+ integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
buffer-equal-constant-time@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+ resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
- resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
+ resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
dependencies:
es-errors "^1.3.0"
@@ -46,39 +173,90 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
call-bound@^1.0.2:
version "1.0.4"
- resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
+ resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz"
integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
dependencies:
call-bind-apply-helpers "^1.0.2"
get-intrinsic "^1.3.0"
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+chalk@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
combined-stream@^1.0.8:
version "1.0.8"
- resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
-debug@4:
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+cross-spawn@^7.0.2:
+ version "7.0.6"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+debug@4, debug@^4.3.1, debug@^4.3.2:
version "4.4.1"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
dependencies:
ms "^2.1.3"
+deep-is@^0.1.3:
+ 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==
+
delayed-stream@~1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+doctrine@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+ integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+ dependencies:
+ esutils "^2.0.2"
+
dotenv@^16.4.5:
version "16.6.1"
- resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020"
+ resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz"
integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==
dunder-proto@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
+ resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz"
integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
dependencies:
call-bind-apply-helpers "^1.0.1"
@@ -87,31 +265,31 @@ dunder-proto@^1.0.1:
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
- resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+ resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
es-define-property@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa"
+ resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz"
integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
es-errors@^1.3.0:
version "1.3.0"
- resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.1"
- resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1"
+ resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
dependencies:
es-errors "^1.3.0"
es-set-tostringtag@^2.1.0:
version "2.1.0"
- resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d"
+ resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz"
integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
dependencies:
es-errors "^1.3.0"
@@ -119,19 +297,170 @@ es-set-tostringtag@^2.1.0:
has-tostringtag "^1.0.2"
hasown "^2.0.2"
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+eslint-config-prettier@^10.1.8:
+ version "10.1.8"
+ resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz"
+ integrity sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==
+
+eslint-scope@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+ integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+ 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==
+
+eslint@^8.57.0:
+ version "8.57.1"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9"
+ integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.6.1"
+ "@eslint/eslintrc" "^2.1.4"
+ "@eslint/js" "8.57.1"
+ "@humanwhocodes/config-array" "^0.13.0"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@nodelib/fs.walk" "^1.2.8"
+ "@ungap/structured-clone" "^1.2.0"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
+ doctrine "^3.0.0"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^7.2.2"
+ eslint-visitor-keys "^3.4.3"
+ espree "^9.6.1"
+ esquery "^1.4.2"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ globals "^13.19.0"
+ graphemer "^1.4.0"
+ ignore "^5.2.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ is-path-inside "^3.0.3"
+ js-yaml "^4.1.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+espree@^9.6.0, espree@^9.6.1:
+ version "9.6.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+ integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
+ dependencies:
+ acorn "^8.9.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.4.1"
+
+esquery@^1.4.2:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
extend@^3.0.2:
version "3.0.2"
- resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ 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==
+
+fast-json-stable-stringify@^2.0.0:
+ 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==
+
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fastq@^1.6.0:
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5"
+ integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
+ dependencies:
+ reusify "^1.0.4"
+
+file-entry-cache@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
+ integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+ dependencies:
+ flat-cache "^3.0.4"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+flat-cache@^3.0.4:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
+ integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.3"
+ rimraf "^3.0.2"
+
+flatted@^3.2.9:
+ version "3.3.3"
+ resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz"
+ integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
+
follow-redirects@^1.15.6:
version "1.15.9"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
+ resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
form-data@^4.0.0:
version "4.0.3"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae"
+ resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz"
integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==
dependencies:
asynckit "^0.4.0"
@@ -140,14 +469,19 @@ form-data@^4.0.0:
hasown "^2.0.2"
mime-types "^2.1.12"
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
function-bind@^1.1.2:
version "1.1.2"
- resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
gaxios@^6.0.0, gaxios@^6.0.3, gaxios@^6.1.1:
version "6.7.1"
- resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.1.tgz#ebd9f7093ede3ba502685e73390248bb5b7f71fb"
+ resolved "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz"
integrity sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==
dependencies:
extend "^3.0.2"
@@ -158,7 +492,7 @@ gaxios@^6.0.0, gaxios@^6.0.3, gaxios@^6.1.1:
gcp-metadata@^6.1.0:
version "6.1.1"
- resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.1.tgz#f65aa69f546bc56e116061d137d3f5f90bdec494"
+ resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz"
integrity sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==
dependencies:
gaxios "^6.1.1"
@@ -167,7 +501,7 @@ gcp-metadata@^6.1.0:
get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
version "1.3.0"
- resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
+ resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
dependencies:
call-bind-apply-helpers "^1.0.2"
@@ -183,15 +517,41 @@ get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
get-proto@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
+ resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz"
integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
dependencies:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob@^7.1.3:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^13.19.0:
+ version "13.24.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
+ integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
+ dependencies:
+ type-fest "^0.20.2"
+
google-auth-library@^9.0.0, google-auth-library@^9.7.0:
version "9.15.1"
- resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.15.1.tgz#0c5d84ed1890b2375f1cd74f03ac7b806b392928"
+ resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz"
integrity sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==
dependencies:
base64-js "^1.3.0"
@@ -203,12 +563,12 @@ google-auth-library@^9.0.0, google-auth-library@^9.7.0:
google-logging-utils@^0.0.2:
version "0.0.2"
- resolved "https://registry.yarnpkg.com/google-logging-utils/-/google-logging-utils-0.0.2.tgz#5fd837e06fa334da450433b9e3e1870c1594466a"
+ resolved "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz"
integrity sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==
googleapis-common@^7.0.0:
version "7.2.0"
- resolved "https://registry.yarnpkg.com/googleapis-common/-/googleapis-common-7.2.0.tgz#5c19102c9af1e5d27560be5e69ee2ccf68755d42"
+ resolved "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz"
integrity sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==
dependencies:
extend "^3.0.2"
@@ -220,7 +580,7 @@ googleapis-common@^7.0.0:
googleapis@^142.0.0:
version "142.0.0"
- resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-142.0.0.tgz#fa7a0414ab6ec935554fe52eeba067602676a9c7"
+ resolved "https://registry.npmjs.org/googleapis/-/googleapis-142.0.0.tgz"
integrity sha512-LsU1ynez4/KNPwnFMSDI93pBEsETNdQPCrT3kz2qgiNg5H2pW4dKW+1VmENMkZ4u9lMxA89nnXD3nqWBJ0rruQ==
dependencies:
google-auth-library "^9.0.0"
@@ -228,64 +588,149 @@ googleapis@^142.0.0:
gopd@^1.2.0:
version "1.2.0"
- resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
+ resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
gtoken@^7.0.0:
version "7.1.0"
- resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26"
+ resolved "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz"
integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==
dependencies:
gaxios "^6.0.0"
jws "^4.0.0"
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
has-symbols@^1.0.3, has-symbols@^1.1.0:
version "1.1.0"
- resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
+ resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz"
integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
has-tostringtag@^1.0.2:
version "1.0.2"
- resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
+ resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz"
integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
dependencies:
has-symbols "^1.0.3"
hasown@^2.0.2:
version "2.0.2"
- resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
https-proxy-agent@^7.0.1:
version "7.0.6"
- resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
+ resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz"
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
dependencies:
agent-base "^7.1.2"
debug "4"
+ignore@^5.2.0:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
+ integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
+
+import-fresh@^3.2.1:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
+ integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
inherits@2.0.3:
version "2.0.3"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+ resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-glob@^4.0.0, is-glob@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-path-inside@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
is-stream@^2.0.0:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+ resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
json-bigint@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
+ resolved "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-schema-traverse@^0.4.1:
+ 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==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ 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==
+
jwa@^2.0.0:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804"
+ resolved "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz"
integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==
dependencies:
buffer-equal-constant-time "^1.0.1"
@@ -294,84 +739,234 @@ jwa@^2.0.0:
jws@^4.0.0:
version "4.0.0"
- resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
+ resolved "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz"
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
dependencies:
jwa "^2.0.0"
safe-buffer "^5.0.1"
+keyv@^4.5.3:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
math-intrinsics@^1.1.0:
version "1.1.0"
- resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
+ resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
mime-db@1.52.0:
version "1.52.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
+minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
ms@^2.1.3:
version "2.1.3"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
node-fetch-native@^1.6.4:
version "1.6.6"
- resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.6.tgz#ae1d0e537af35c2c0b0de81cbff37eedd410aa37"
+ resolved "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz"
integrity sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==
node-fetch@^2.6.9:
version "2.7.0"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+ resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
object-inspect@^1.13.3:
version "1.13.4"
- resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213"
+ resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz"
integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ 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"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ 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==
+ dependencies:
+ p-limit "^3.0.2"
+
+parent-module@^1.0.0:
+ 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==
+ dependencies:
+ callsites "^3.0.0"
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
path@^0.12.7:
version "0.12.7"
- resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
+ resolved "https://registry.npmjs.org/path/-/path-0.12.7.tgz"
integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
dependencies:
process "^0.11.1"
util "^0.10.3"
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+prettier@^3.6.2:
+ version "3.6.2"
+ resolved "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz"
+ integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
+
process@^0.11.1:
version "0.11.10"
- resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
+ resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
proxy-from-env@^1.1.0:
version "1.1.0"
- resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+punycode@^2.1.0:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
qs@^6.7.0:
version "6.14.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz"
integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==
dependencies:
side-channel "^1.1.0"
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+reusify@^1.0.4:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
+ integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
+
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
safe-buffer@^5.0.1:
version "5.2.1"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
side-channel-list@^1.0.0:
version "1.0.0"
- resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
+ resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz"
integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==
dependencies:
es-errors "^1.3.0"
@@ -379,7 +974,7 @@ side-channel-list@^1.0.0:
side-channel-map@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42"
+ resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz"
integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
dependencies:
call-bound "^1.0.2"
@@ -389,7 +984,7 @@ side-channel-map@^1.0.1:
side-channel-weakmap@^1.0.2:
version "1.0.2"
- resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea"
+ resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz"
integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
dependencies:
call-bound "^1.0.2"
@@ -400,7 +995,7 @@ side-channel-weakmap@^1.0.2:
side-channel@^1.1.0:
version "1.1.0"
- resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
+ resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz"
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
dependencies:
es-errors "^1.3.0"
@@ -409,37 +1004,102 @@ side-channel@^1.1.0:
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
+strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-json-comments@^3.1.1:
+ 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==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+text-table@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+ integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
tr46@~0.0.3:
version "0.0.3"
- resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
url-template@^2.0.8:
version "2.0.8"
- resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21"
+ resolved "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz"
integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==
util@^0.10.3:
version "0.10.4"
- resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
+ resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz"
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
dependencies:
inherits "2.0.3"
uuid@^9.0.0, uuid@^9.0.1:
version "9.0.1"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
+ resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
webidl-conversions@^3.0.0:
version "3.0.1"
- resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
whatwg-url@^5.0.0:
version "5.0.0"
- resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==