diff --git a/.env.example b/.env.example
index f96ce5b63..213356e0f 100644
--- a/.env.example
+++ b/.env.example
@@ -13,3 +13,10 @@ ANALYTICS_MODE=
# Use `trace` to get verbose logging or `info` to show less
LOG_LEVEL=debug
LOGDNA_INGESTION_KEY=
+OPENAI_API_HOST=https://api.openai.com
+OPENAI_API_KEY=
+CHATGPT_USER_PROMPT_FOR_IMPORTANT_WORDS="I need your help to find important words (e.g. unique adjectives) from github issue below and I want to parse them easily so please separate them using #(No other contexts needed). Please separate the words by # so I can parse them easily. Please answer simply as I only need the important words. Here is the issue content.\n"
+CHATGPT_USER_PROMPT_FOR_MEASURE_SIMILARITY='I have two github issues and I need to measure the possibility of the 2 issues are the same content (No other contents needed and give me only the number in %).\n Give me in number format and add % after the number.\nDo not tell other things since I only need the number (e.g. 85%). Here are two issues:\n 1. "%first%"\n2. "%second%"'
+SIMILARITY_THRESHOLD=80
+MEASURE_SIMILARITY_AI_TEMPERATURE=0
+IMPORTANT_WORDS_AI_TEMPERATURE=0
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 8b1378917..4a5a1e837 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1 +1 @@
-
+* @0xcodercrane
diff --git a/.github/ubiquibot-config.yml b/.github/ubiquibot-config.yml
index f79f61652..5d5a81c05 100644
--- a/.github/ubiquibot-config.yml
+++ b/.github/ubiquibot-config.yml
@@ -1,39 +1,5 @@
----
-evm-network-id: 100
price-multiplier: 1.5
-time-labels:
- - name: "Time: <1 Hour"
- weight: 0.125
- value: 3600
- - name: "Time: <1 Day"
- weight: 1
- value: 86400
- - name: "Time: <1 Week"
- weight: 2
- value: 604800
- - name: "Time: <2 Weeks"
- weight: 3
- value: 1209600
- - name: "Time: <1 Month"
- weight: 4
- value: 2592000
-priority-labels:
- - name: "Priority: 0 (Normal)"
- weight: 1
- - name: "Priority: 1 (Medium)"
- weight: 2
- - name: "Priority: 2 (High)"
- weight: 3
- - name: "Priority: 3 (Urgent)"
- weight: 4
- - name: "Priority: 4 (Emergency)"
- weight: 5
+
default-labels:
- "Time: <1 Hour"
- - "Priority: 0 (Normal)"
- - "Test"
-auto-pay-mode: true
-comment-incentives: true
-max-concurrent-bounties: 2
-promotion-comment: "\n
If you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot! "
-register-wallet-with-verification: false
+ - "Priority: 1 (Normal)"
diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml
index c790059a3..fa5fedab5 100644
--- a/.github/workflows/bot.yml
+++ b/.github/workflows/bot.yml
@@ -81,4 +81,4 @@ jobs:
X25519_PRIVATE_KEY: 'QCDb30UHUkwJAGhLWC-R2N0PiEbd4vQY6qH2Wloybyo'
FOLLOW_UP_TIME: '4 days'
DISQUALIFY_TIME: '7 days'
- run: yarn start:serverless
\ No newline at end of file
+ run: yarn start:serverless
diff --git a/.github/workflows/deploy-logger-page.yml b/.github/workflows/deploy-logger-page.yml
new file mode 100644
index 000000000..d827c708f
--- /dev/null
+++ b/.github/workflows/deploy-logger-page.yml
@@ -0,0 +1,37 @@
+name: Deploy Log Web App to Cloudflare Worker
+
+on:
+ workflow_run:
+ workflows: ["Conventional Commits"]
+ types:
+ - completed
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ deployments: write
+ name: Deploy to Cloudflare Worker
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: "20.3.0"
+
+ - name: Build TypeScript
+ run: cd ./log-app && npm install && npm run build
+
+ - name: Publish to Cloudflare Pages
+ uses: cloudflare/pages-action@v1
+ with:
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
+ directory: ${{ secrets.LOGGER_WEB_APP_DIRECTORY }}
+ # Enable Wrangler v3
+ wranglerVersion: "3"
diff --git a/.github/workflows/kebab-case.yml b/.github/workflows/kebab-case.yml
index 4fdb2ec11..892480a87 100644
--- a/.github/workflows/kebab-case.yml
+++ b/.github/workflows/kebab-case.yml
@@ -24,6 +24,7 @@ jobs:
"\.sql$"
"\.md$"
"\.d.ts$"
+ "^\.\/\log-app"
)
while read -r file; do
basefile=$(basename "$file")
diff --git a/.github/workflows/update-config.yml b/.github/workflows/update-config.yml
new file mode 100644
index 000000000..c93d04bfb
--- /dev/null
+++ b/.github/workflows/update-config.yml
@@ -0,0 +1,98 @@
+name: Pull Request Action
+permissions: write-all
+on:
+ workflow_dispatch:
+
+env:
+ GH_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }}
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+
+ - name: Check out repository
+ uses: actions/checkout@v3
+
+ - name: Install jq and yq
+ run: |
+ sudo apt-get -y install jq
+ sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq
+ sudo chmod +x /usr/bin/yq
+
+ - name: Get UbiquiBot Token
+ uses: tibdex/github-app-token@v1.7.0
+ id: get_installation_token
+ with:
+ app_id: ${{ secrets.UBIQUITY_BOUNTY_BOT_APP_ID }}
+ private_key: ${{ secrets.UBIQUITY_BOUNTY_BOT_PRIVATE_KEY }}
+
+ - name: Update Config Params and Create Pull Requests
+ run: |
+ urls=$(curl -sSL https://raw.githubusercontent.com/ubiquity/devpool-directory/development/projects.json | jq -r '.urls[]')
+
+ for url in $urls
+ do
+ repoName=$(basename $url)
+ ownerName=$(echo $url | awk -F/ '{print $(NF-1)}')
+
+ git clone $url $repoName
+ cd $repoName
+ defaultBranch=$(git branch --show-current)
+
+ # make a branch to update config #
+ git branch update
+ git checkout update
+
+ curl -sSL https://raw.githubusercontent.com/ubiquity/ubiquibot/development/ubiquibot-config-default.json > default.json
+ declare -A param_mapping=(
+ ["evm-network-id"]="network-id chain-id"
+ ["price-multiplier"]="base-multiplier"
+ #add more configs as needed
+ )
+
+ ### update configs ###
+ # Iterate over the mapping and perform updates using sed
+ for new_param in "${!param_mapping[@]}"
+ do
+ old_params="${param_mapping[$new_param]}"
+ for old_param in $old_params
+ do
+ # only update param if the old ones exist
+ exist_old_param=$(yq "has(\"$old_param\")" .github/ubiquibot-config.yml)
+ if $exist_old_param; then
+ yq ".$new_param = .$old_param | del(.$old_param)" .github/ubiquibot-config.yml > temp.yml
+ mv temp.yml .github/ubiquibot-config.yml
+ fi
+ done
+ # if new param still doesent exist add default from ubiquibot-config-default.json
+ exist_new_param=$(yq "has(\"$new_param\")" .github/ubiquibot-config.yml)
+ if ! $exist_new_param; then
+ echo adding
+ def_val=$(jq -r ".[\"$new_param\"]" ubiquibot-config-default.json)
+ yq ".$new_param=$def_val" .github/ubiquibot-config.yml > temp.yml
+ mv temp.yml .github/ubiquibot-config.yml
+ fi
+ done
+
+ git config user.email "113181824+UbiquiBot[bot]@users.noreply.github.com"
+ git config user.name "UbiquiBot[bot]"
+
+ git add .github/ubiquibot-config.yml
+ git commit -m "build: use latest ubiquibot config setup"
+ git remote set-url origin https://${{ secrets.ADD_TO_PROJECT_PAT }}@github.com/$ownerName/$repoName.git
+ git push -f origin update
+
+ curl -L \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ steps.get_installation_token.outputs.token }}"\
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ https://api.github.com/repos/$ownerName/$repoName/pulls \
+ -d '{
+ "title": "build: use latest ubiquibot config setup",
+ "base": "'"$defaultBranch"'",
+ "head": "update"
+ }'
+ cd ..
+ done
\ No newline at end of file
diff --git a/README.md b/README.md
index 0311933d0..4b38ed11d 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Ubiquity DAO's GitHub Bot to automate DevPool management.
git clone https://github.com/ubiquity/ubiquibot.git
cd ubiquibot
yarn
-yarn tsc
+yarn build
yarn start:watch
```
@@ -23,6 +23,15 @@ yarn start:watch
- `LOGDNA_INGESTION_KEY`: Get it from [Memzo](https://app.mezmo.com/) by creating an account, adding an organization, and copying the ingestion key on the next screen.
- `FOLLOWUP_TIME`: (optional) Set a custom follow-up time (default: 4 days).
- `DISQUALIFY_TIME`: (optional) Set a custom disqualify time (default: 7 days).
+- `OPENAI_API_HOST`: (optional) Set OpenAI host url (default: https://api.openai.com).
+- `OPENAI_API_KEY`: Set OpenAI key.
+- `CHATGPT_USER_PROMPT_FOR_IMPORTANT_WORDS`: (optional) Set a custom user prompt for finding important words
+(default: "I need your help to find important words (e.g. unique adjectives) from github issue below and I want to parse them easily so please separate them using #(No other contexts needed). Please separate the words by # so I can parse them easily. Please answer simply as I only need the important words. Here is the issue content.\n").
+- `CHATGPT_USER_PROMPT_FOR_MEASURE_SIMILARITY`: (optional) Set a custom user prompt for measuring similarity
+(default: 'I have two github issues and I need to measure the possibility of the 2 issues are the same content (No other contents needed and give me only the number in %).\n Give me in number format and add % after the number.\nDo not tell other things since I only need the number (e.g. 85%). Here are two issues:\n 1. "%first%"\n2. "%second%"').
+- `SIMILARITY_THRESHOLD`: (optional) Set similarity threshold (default: 80).
+- `MEASURE_SIMILARITY_AI_TEMPERATURE`: (optional) Set ChatGPT temperature for measuring similarity (default: 0).
+- `IMPORTANT_WORDS_AI_TEMPERATURE`: (optional) Set ChatGPT temperature for finding important words (default: 0).
`APP_ID` and `PRIVATE_KEY` are [here](https://t.me/c/1588400061/1627) for internal developers to use.
If you are an external developer, `APP_ID`and `PRIVATE_KEY` are automatically generated when you install the app on your repository.
@@ -45,38 +54,55 @@ To test the bot, you can:
1. Create a new issue
2. Add a time label, ex: `Time: <1 Day`
-3. At this point the bot should add a price label.
+3. Add a priority label, ex: `Priority: 0 (Normal)`
+4. At this point the bot should add a price label.
## Configuration
-`chain-id` is ID of the EVM-compatible network that will be used for payouts.
+`evm-network-id` is ID of the EVM-compatible network that will be used for payouts.
-`base-multiplier` is a base number that will be used to calculate bounty price based on the following formula: `price = base-multiplier * time-label-weight * priority-label-weight / 10`
+`price-multiplier` is a base number that will be used to calculate bounty price based on the following formula: `price = price-multiplier * time-label-weight * priority-label-weight * 100`
`time-labels` are labels for marking the time limit of the bounty:
- `name` is a human-readable name
-- `weight` is a number that will be used to calculate the bounty price
- `value` is number of seconds that corresponds to the time limit of the bounty
`priority-labels` are labels for marking the priority of the bounty:
- `name` is a human-readable name
-- `weight` is a number that will be used to calculate the bounty price
+
+`command-settings` are setting to enable or disable a command
+
+- `name` is the name of the command
+- `enabled` is a `true` or `false` value to enable or disable a command
`default-labels` are labels that are applied when an issue is created without any time or priority labels.
-`auto-pay-mode` can be `true` or `false` that enables or disables automatic payout of bounties when the issue is closed.
+`assistive-pricing` to create a new pricing label if it doesn't exist. Can be `true` or `false`.
+
+`disable-analytics` can be `true` or `false` that disables or enables weekly analytics collection by Ubiquity.
+
+`payment-permit-max-price` sets the max amount for automatic payout of bounties when the issue is closed.
+
+`comment-incentives` can be `true` or `false` that enable or disable comment incentives. These are payments generated for comments in the issue by contributors, excluding the assignee.
+
+`issue-creator-multiplier` is a number that defines a base multiplier for calculating incentive for the creator of the issue.
+
+`comment-element-pricing` defines how much is a part of the comment worth. For example `text: 0.1` means that any text in the comment will add 0.1
-`analytics-mode` can be `true` or `false` that enables or disables weekly analytics collection by Ubiquity.
+`incentives` defines incentive rewards:
-`incentive-mode` can be `true` or `false` that enables or disables comment incentives. These are comments in the issue by either the creator of the bounty or other users.
+- `comment` defines comment rewards:
+ - `elements` defines reward value for HTML elements such as `p`, `img`, `a`.
+ - `totals`:
+ - `word` defines reward for each word in the comment
-`issue-creator-multiplier` is a number that defines a base multiplier for calculating incentive reward for the creator of the issue.
+`max-concurrent-assigns` is the maximum number of bounties that can be assigned to a bounty hunter at once. This excludes bounties with delayed or approved pull request reviews.
-`comment-element-pricing` defines how much is a part of the comment worth. For example `text: 0.1` means that any text in the comment will be multiplied by 0.1
+`register-wallet-with-verification` can be `true` or `false`. If enabled, it requires a signed message to set wallet address. This prevents users from setting wallet address from centralized exchanges, which would make payments impossible to claim.
-`max-concurrent-bounties` is the maximum number of bounties that can be assigned to a bounty hunter at once. This excludes bounties with pending pull request reviews.
+`promotion-comment` is a message that is appended to the payment permit comment.
## How to run locally
@@ -108,7 +134,7 @@ DISQUALIFY_TIME="7 days" // 7 days
4. `yarn install`
5. Open 2 terminal instances:
- - in one instance run `yarn tsc --watch` (compiles the Typescript code)
+ - in one instance run `yarn build --watch` (compiles the Typescript code)
- in another instance run `yarn start:watch` (runs the bot locally)
6. Open `localhost:3000` and follow instructions to add the bot to one of your repositories.
@@ -119,7 +145,8 @@ You can, for example:
1. Create a new issue
2. Add a time label, ex: `Time: <1 Day`
-3. At this point the bot should add a price label, you should see event logs in one of your opened terminals
+3. Add a priority label, ex: `Priority: 0 (Normal)`
+4. At this point the bot should add a price label, you should see event logs in one of your opened terminals
## How it works
@@ -135,13 +162,31 @@ When using as a github app the flow is the following:
4. Event details are sent to your deployed bot instance (to a webhook URL that was set in github app's settings)
5. The bot handles the event
+## Payments Permits in a local instance
+
+For payment to work in your local instance, ubiquibot must be set up in a Github organization. It will not work for a ubiquibot instance set up in a personal account. Once, you have an ubiquibot instance working in an organization, follow the steps given below:
+
+1. Create a new private repository in your Github organization with name `ubiquibot-config`
+2. Add your ubiquibot app to `ubiquibot-config` repository.
+3. Create a file `.github/ubiquibot-config.yml` in it. Fill the file with contents from [this file](https://github.com/ubiquity/ubiquibot/blob/development/.github/ubiquibot-config.yml).
+4. Go to https://pay.ubq.fi/keygen and generate X25519 public/private key pair. Fill private key of your wallet's address in `PLAIN_TEXT` field and click `Encrypt`.
+5. Copy the `CIPHER_TEXT` and append it to your repo `ubiquibot-config/.github/ubiquibot-config.yml` as
+
+ `private-key-encrypted: "PASTE_YOUR_CIPHER_TEXT_HERE"`
+
+6. Copy the `X25519_PRIVATE_KEY` and append it in your local ubiquibot repository `.env` file as
+
+ `X25519_PRIVATE_KEY=PASTE_YOUR_X25519_PRIVATE_KEY_HERE`
+
## How to QA any additions to the bot
-1. Fork the ubiquibot repo and install the [ubiquibot-qa app](https://github.com/apps/ubiquibot-qa) on the forked repository.
-2. Enable github action running on the forked repo and allow `issues` on the settings tab.
-3. Create a [QA issue](https://github.com/ubiquibot/staging/issues/21) similar to this where you show the feature working in the forked repo
-4. Describe carefully the steps taken to get the feature working, this way our team can easily verify
-5. Link that QA issue to the pull request as indicated on the template before requesting a review
+Make sure you have your local instance of ubiquibot running.
+
+1. Fork the ubiquibot repo and add your local instance of ubiquibot to the forked repository.
+2. Enable Github action running on the forked repo and allow `issues` on the settings tab.
+3. Create a [QA issue](https://github.com/ubiquibot/staging/issues/21) similar to this where you show the feature working in the forked repo.
+4. Describe carefully the steps taken to get the feature working, this way our team can easily verify.
+5. Link that QA issue to the pull request as indicated on the template before requesting a review.
## How to create a new release
@@ -178,3 +223,43 @@ Bounty bot is built using the [probot](https://probot.github.io/) framework so i
|
├── utils A set of utility functions
+
+## Default Config Notes (`ubiquibot-config-default.json`)
+
+We can't use a `jsonc` file due to limitations with Netlify. Here is a snippet of some values with notes next to them.
+
+```jsonc
+{
+ "payment-permit-max-price": 9007199254740991, // Number.MAX_SAFE_INTEGER
+ "max-concurrent-assigns": 9007199254740991, // Number.MAX_SAFE_INTEGER
+ "comment-element-pricing": {
+ /* https://github.com/syntax-tree/mdast#nodes */
+ "strong": 0 // Also includes italics, unfortunately https://github.com/syntax-tree/mdast#strong
+ /* https://github.com/syntax-tree/mdast#gfm */
+ }
+}
+```
+
+## Supabase Cron Job (`logs-cleaner`)
+
+##### Dashboard > Project > Database > Extensions
+
+> Search `PG_CRON` and Enable it.
+
+
+##### Dashboard > Project > SQL Editor
+
+```sql
+-- Runs everyday at 03:00 AM to cleanup logs that are older than a week
+-- Use the cron time format to modify the trigger time if necessary
+select
+ cron.schedule (
+ 'logs-cleaner', -- Job name
+ '0 3 * * *', -- Everyday at 03:00 AM
+ $$DELETE FROM logs WHERE timestamp < now() - INTERVAL '1 week'$$
+ );
+
+
+-- Cancel the cron job
+select cron.unschedule('logs-cleaner');
+```
diff --git a/log-app/.gitignore b/log-app/.gitignore
new file mode 100644
index 000000000..9df249787
--- /dev/null
+++ b/log-app/.gitignore
@@ -0,0 +1,2 @@
+# Ignore JavaScript files
+*.js
\ No newline at end of file
diff --git a/log-app/_worker.ts b/log-app/_worker.ts
new file mode 100644
index 000000000..526cedbb3
--- /dev/null
+++ b/log-app/_worker.ts
@@ -0,0 +1,9 @@
+import { Env } from "./types/global";
+
+export default {
+ async fetch(request: Request, env: Env) {
+ // Otherwise, serve the static assets.
+ // Without this, the Worker will error and no assets will be served.
+ return env.ASSETS.fetch(request);
+ },
+};
diff --git a/log-app/index.html b/log-app/index.html
new file mode 100644
index 000000000..04ab5014d
--- /dev/null
+++ b/log-app/index.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ Log Viewer
+
+
+
+
+
+
+ All
+ Error
+ Warning
+ Info
+ Verbose
+ Debug
+
+ Clear
+
+
+
+
+ Message
+ Level
+ Timestamp
+ Comment URL
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/log-app/package.json b/log-app/package.json
new file mode 100644
index 000000000..f70c61f60
--- /dev/null
+++ b/log-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "cf-pages-functions",
+ "version": "0.0.0",
+ "scripts": {
+ "build": "tsc && tsx ./scripts/esbuild.ts"
+ },
+ "devDependencies": {
+ "@cloudflare/workers-types": "^3.2.0",
+ "@supabase/supabase-js": "^2.32.0",
+ "typescript": "^4.3.2"
+ },
+ "dependencies": {
+ "esbuild": "^0.19.2",
+ "tsx": "^3.12.7"
+ }
+}
diff --git a/log-app/scripts/constants/index.ts b/log-app/scripts/constants/index.ts
new file mode 100644
index 000000000..dbda210ab
--- /dev/null
+++ b/log-app/scripts/constants/index.ts
@@ -0,0 +1,3 @@
+export const SUPABASE_URL = "https://qzfjblxdroqrmlheoajw.supabase.co";
+export const SUPABASE_KEY =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InF6ZmpibHhkcm9xcm1saGVvYWp3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODA4NTMwMzMsImV4cCI6MTk5NjQyOTAzM30.BM_qkpX-egNdiMc0PDO_34bIaXHB7ewnr2k4U2hGFMM";
diff --git a/log-app/scripts/esbuild-config.ts b/log-app/scripts/esbuild-config.ts
new file mode 100644
index 000000000..9a0c2f714
--- /dev/null
+++ b/log-app/scripts/esbuild-config.ts
@@ -0,0 +1,19 @@
+import esbuild from "esbuild";
+const typescriptEntries = ["scripts/helpers/utils.ts", "scripts/index.ts", "scripts/constants/index.ts"];
+export const entries = [...typescriptEntries];
+
+export let esBuildContext = {
+ sourcemap: false,
+ entryPoints: entries,
+ bundle: true,
+ minify: false,
+ loader: {
+ ".png": "dataurl",
+ ".woff": "dataurl",
+ ".woff2": "dataurl",
+ ".eot": "dataurl",
+ ".ttf": "dataurl",
+ ".svg": "dataurl",
+ },
+ outdir: "scripts",
+} as esbuild.BuildOptions;
diff --git a/log-app/scripts/esbuild.ts b/log-app/scripts/esbuild.ts
new file mode 100644
index 000000000..bd358a938
--- /dev/null
+++ b/log-app/scripts/esbuild.ts
@@ -0,0 +1,3 @@
+import esbuild from "esbuild";
+import { esBuildContext } from "./esbuild-config";
+esbuild.build(esBuildContext);
diff --git a/log-app/scripts/helpers/utils.ts b/log-app/scripts/helpers/utils.ts
new file mode 100644
index 000000000..31ce87681
--- /dev/null
+++ b/log-app/scripts/helpers/utils.ts
@@ -0,0 +1,66 @@
+export enum Level {
+ ERROR = "error",
+ WARN = "warn",
+ INFO = "info",
+ HTTP = "http",
+ VERBOSE = "verbose",
+ DEBUG = "debug",
+ SILLY = "silly",
+}
+
+export const createGitHubCommentURL = (orgName: string, repoName: string, issueNumber: number, commentId: number) => {
+ return `https://github.com/${orgName}/${repoName}/issues/${issueNumber}#issuecomment-${commentId}`;
+};
+
+export const isValidJson = (jsonString) => {
+ try {
+ JSON.parse(jsonString);
+ return true;
+ } catch (error) {
+ return false;
+ }
+};
+
+export const generateRandomId = (length) => {
+ return [...Array(length)].map(() => Math.random().toString(36)[2]).join("");
+};
+
+export const containsValidJson = (message: string): [boolean, string, string] => {
+ const jsonMatches = message.match(/\{.*\}/g); // Find JSON-like substrings
+ if (!jsonMatches) {
+ return [false, "", ""];
+ }
+
+ for (const match of jsonMatches) {
+ if (isValidJson(match)) {
+ const braceIndex = message.indexOf("{");
+ if (braceIndex !== -1) {
+ return [true, match, message.substring(0, braceIndex)];
+ }
+ return [true, match, ""];
+ }
+ }
+
+ return [false, "", ""];
+};
+
+export const getLevelString = (level: number) => {
+ switch (level) {
+ case 0:
+ return Level.ERROR;
+ case 1:
+ return Level.WARN;
+ case 2:
+ return Level.INFO;
+ case 3:
+ return Level.HTTP;
+ case 4:
+ return Level.VERBOSE;
+ case 5:
+ return Level.DEBUG;
+ case 6:
+ return Level.SILLY;
+ default:
+ return -1; // Invalid level
+ }
+};
diff --git a/log-app/scripts/index.ts b/log-app/scripts/index.ts
new file mode 100644
index 000000000..a0d5c1817
--- /dev/null
+++ b/log-app/scripts/index.ts
@@ -0,0 +1,99 @@
+import { containsValidJson, createGitHubCommentURL, generateRandomId, getLevelString } from "./helpers/utils";
+import { Logs } from "./types/log";
+import { createClient } from "@supabase/supabase-js";
+import { SUPABASE_URL, SUPABASE_KEY } from "./constants/index";
+
+const filterSelect = document.getElementById("filter") as unknown as HTMLSelectElement;
+const clearButton = document.getElementById("clear") as HTMLButtonElement;
+const logBody = document.getElementById("log-body") as HTMLDivElement;
+
+const jsonModal = document.getElementById("json-modal") as HTMLDivElement;
+const closeModalButton = document.getElementById("close-modal") as HTMLButtonElement;
+const jsonContent = document.getElementById("json-content") as HTMLDivElement;
+
+const openJsonModal = (validJson) => {
+ jsonContent.textContent = validJson;
+
+ jsonModal.style.display = "flex";
+};
+
+const updateLogTable = () => {
+ const selectedFilter = filterSelect.value;
+ const filteredLogs = selectedFilter === "all" ? logs : logs.filter((log) => getLevelString(log.level) === selectedFilter);
+
+ logBody.innerHTML = "";
+ filteredLogs.forEach((log) => {
+ const classId = generateRandomId(10);
+ const commentUrl = createGitHubCommentURL(log.org_name, log.repo_name, log.issue_number, log.comment_id);
+ const row = document.createElement("tr");
+ const [validJson, match, beforeText] = containsValidJson(log.log_message);
+ row.innerHTML = `
+ ${
+ validJson
+ ? `${beforeText} - Show JSON `
+ : `${log.log_message} `
+ }
+ ${getLevelString(log.level)}
+ ${log.timestamp}
+ Comment - ${log.comment_id}
+ `;
+ logBody.appendChild(row);
+ let logCell = document.getElementsByClassName("log-cell");
+ if (validJson) {
+ // show modal button for valid json row
+ const showMoreButton = document.getElementById(`button_${classId}`) as HTMLButtonElement;
+ showMoreButton.addEventListener("click", () => {
+ if (validJson) {
+ openJsonModal(JSON.stringify(JSON.parse(match), null, 2)); // properly formatted json
+ }
+ });
+ }
+ // scroll to last added data
+ logCell[logCell.length - 1].scrollIntoView();
+ });
+};
+
+let logs: Logs[] = [];
+
+const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY);
+
+const channel = supabaseClient
+ .channel("table-db-changes")
+ .on(
+ "postgres_changes",
+ {
+ event: "INSERT",
+ schema: "public",
+ table: "logs",
+ },
+ (payload) => handlePayload(payload)
+ )
+ .subscribe();
+
+const handlePayload = (logEntry) => {
+ if (logEntry?.eventType !== "INSERT") return;
+ logs.push(logEntry.new);
+ updateLogTable();
+};
+
+filterSelect.addEventListener("change", () => {
+ updateLogTable();
+});
+
+clearButton.addEventListener("click", () => {
+ logs = [];
+ updateLogTable();
+});
+
+closeModalButton.addEventListener("click", () => {
+ jsonModal.style.display = "none";
+});
+
+window.addEventListener("click", (event) => {
+ if (event.target === jsonModal) {
+ jsonModal.style.display = "none";
+ }
+});
+
+// Initial update
+updateLogTable();
diff --git a/log-app/scripts/tsconfig.json b/log-app/scripts/tsconfig.json
new file mode 100644
index 000000000..018499861
--- /dev/null
+++ b/log-app/scripts/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "useDefineForClassFields": true,
+ "allowJs": false,
+ "esModuleInterop": true,
+ "module": "CommonJS",
+ "allowSyntheticDefaultImports": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "target": "esnext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ "lib": ["esnext", "dom", "dom.iterable"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ },
+ "include": ["./helpers", "./constants", "./index.ts"],
+ "exclude": ["./esbuild.ts", "./esbuild-config.ts"]
+}
diff --git a/log-app/scripts/types/log.ts b/log-app/scripts/types/log.ts
new file mode 100644
index 000000000..ebd2b43a2
--- /dev/null
+++ b/log-app/scripts/types/log.ts
@@ -0,0 +1,9 @@
+export type Logs = {
+ timestamp: string;
+ log_message: string;
+ level: number;
+ repo_name: string;
+ org_name: string;
+ comment_id: number;
+ issue_number: number;
+};
diff --git a/log-app/styles/index.css b/log-app/styles/index.css
new file mode 100644
index 000000000..7ba057db5
--- /dev/null
+++ b/log-app/styles/index.css
@@ -0,0 +1,156 @@
+body {
+ font-family: Arial, sans-serif;
+}
+
+.log-container {
+ margin: 20px auto;
+ width: 95%;
+}
+
+.log-cell {
+ border: 1px solid #ccc;
+ padding: 8px;
+ text-align: left;
+ max-width: 476px;
+ /* Adjust the maximum width as needed */
+ overflow: auto;
+ /* Allow scrolling if the content is too long */
+ white-space: pre-wrap;
+}
+
+#log-table {
+ margin: 20px auto;
+ width: 95%;
+ border-collapse: collapse;
+ border: 1px solid #ccc;
+ margin-top: 20px;
+ table-layout: fixed;
+}
+
+#log-table th,
+#log-table td {
+ border: 1px solid #ccc;
+ padding: 10px;
+ text-align: left;
+ color: black;
+}
+
+#log-table th {
+ background-color: #f5f5f5;
+ font-weight: bold;
+}
+
+#log-body {
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+table {
+ border-collapse: collapse;
+ width: 600px;
+}
+
+td,
+th {
+ padding: 10px;
+ text-align: left;
+ margin: 0;
+ color: black;
+}
+
+tbody tr:nth-child(2n) {
+ background-color: #eee;
+}
+
+th {
+ position: sticky;
+ top: 0;
+ background-color: #333;
+ color: white;
+}
+
+select,
+button {
+ margin-right: 10px;
+}
+
+#filter {
+ font-family: Arial, sans-serif;
+ padding: 8px 24px;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background-color: white;
+ cursor: pointer;
+ transition: border-color 0.3s;
+}
+
+#filter:hover {
+ border-color: #999;
+}
+
+/* Styling for button element */
+#clear {
+ font-family: Arial, sans-serif;
+ padding: 8px 12px;
+ background-color: #007bff;
+ border: none;
+ border-radius: 4px;
+ color: white;
+ cursor: pointer;
+ transition: background-color 0.3s;
+}
+
+#clear:hover {
+ background-color: #0056b3;
+}
+
+/* Modal styles */
+.modal {
+ display: none;
+ position: fixed;
+ z-index: 1;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.4);
+ align-items: center;
+ justify-content: center;
+ /* Center horizontally and vertically */
+}
+
+.modal-content {
+ background-color: white;
+ border: 1px solid #888;
+ width: 80%;
+ max-width: 600px;
+ height: 75vh;
+ padding: 20px;
+ text-align: left;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.close {
+ color: #aaa;
+ float: right;
+ font-size: 28px;
+ font-weight: bold;
+}
+
+.close:hover,
+.close:focus {
+ color: black;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.json-content {
+ overflow: auto;
+ height: 70vh;
+ padding: 10px;
+ background-color: #f5f5f5;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ white-space: pre-wrap;
+}
\ No newline at end of file
diff --git a/log-app/tsconfig.json b/log-app/tsconfig.json
new file mode 100644
index 000000000..d572b4033
--- /dev/null
+++ b/log-app/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "types": ["@cloudflare/workers-types", "node"],
+ "outDir": ""
+ },
+ "include": ["./_worker.ts", "./src", "./types"]
+}
diff --git a/log-app/types/global.ts b/log-app/types/global.ts
new file mode 100644
index 000000000..a664d7f42
--- /dev/null
+++ b/log-app/types/global.ts
@@ -0,0 +1,5 @@
+export interface Env {
+ ASSETS: Fetcher;
+ SUPABASE_KEY: string;
+ SUPABASE_URL: string;
+}
diff --git a/log-app/yarn.lock b/log-app/yarn.lock
new file mode 100644
index 000000000..7d3f90eda
--- /dev/null
+++ b/log-app/yarn.lock
@@ -0,0 +1,573 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@cloudflare/workers-types@^3.2.0":
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-3.19.0.tgz#f3791b80b23f7cf0072f2e67e98aa37801204b6c"
+ integrity sha512-0FRcsz7Ea3jT+gc5gKPIYciykm1bbAaTpygdzpCwGt0RL+V83zWnYN30NWDW4rIHj/FHtz+MIuBKS61C8l7AzQ==
+
+"@esbuild-kit/cjs-loader@^2.4.2":
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz#cb4dde00fbf744a68c4f20162ea15a8242d0fa54"
+ integrity sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==
+ dependencies:
+ "@esbuild-kit/core-utils" "^3.0.0"
+ get-tsconfig "^4.4.0"
+
+"@esbuild-kit/core-utils@^3.0.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@esbuild-kit/core-utils/-/core-utils-3.1.0.tgz#49945d533dbd5e1b7620aa0fc522c15e6ec089c5"
+ integrity sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==
+ dependencies:
+ esbuild "~0.17.6"
+ source-map-support "^0.5.21"
+
+"@esbuild-kit/esm-loader@^2.5.5":
+ version "2.5.5"
+ resolved "https://registry.yarnpkg.com/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz#b82da14fcee3fc1d219869756c06f43f67d1ca71"
+ integrity sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==
+ dependencies:
+ "@esbuild-kit/core-utils" "^3.0.0"
+ get-tsconfig "^4.4.0"
+
+"@esbuild/android-arm64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
+ integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==
+
+"@esbuild/android-arm64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.2.tgz#bc35990f412a749e948b792825eef7df0ce0e073"
+ integrity sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw==
+
+"@esbuild/android-arm@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d"
+ integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==
+
+"@esbuild/android-arm@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.2.tgz#edd1c8f23ba353c197f5b0337123c58ff2a56999"
+ integrity sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q==
+
+"@esbuild/android-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1"
+ integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==
+
+"@esbuild/android-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.2.tgz#2dcdd6e6f1f2d82ea1b746abd8da5b284960f35a"
+ integrity sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w==
+
+"@esbuild/darwin-arm64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276"
+ integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==
+
+"@esbuild/darwin-arm64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.2.tgz#55b36bc06d76f5c243987c1f93a11a80d8fc3b26"
+ integrity sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA==
+
+"@esbuild/darwin-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb"
+ integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==
+
+"@esbuild/darwin-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.2.tgz#982524af33a6424a3b5cb44bbd52559623ad719c"
+ integrity sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw==
+
+"@esbuild/freebsd-arm64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2"
+ integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==
+
+"@esbuild/freebsd-arm64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.2.tgz#8e478a0856645265fe79eac4b31b52193011ee06"
+ integrity sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ==
+
+"@esbuild/freebsd-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4"
+ integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==
+
+"@esbuild/freebsd-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.2.tgz#01b96604f2540db023c73809bb8ae6cd1692d6f3"
+ integrity sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw==
+
+"@esbuild/linux-arm64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb"
+ integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==
+
+"@esbuild/linux-arm64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.2.tgz#7e5d2c7864c5c83ec789b59c77cd9c20d2594916"
+ integrity sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg==
+
+"@esbuild/linux-arm@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a"
+ integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==
+
+"@esbuild/linux-arm@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.2.tgz#c32ae97bc0246664a1cfbdb4a98e7b006d7db8ae"
+ integrity sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg==
+
+"@esbuild/linux-ia32@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a"
+ integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==
+
+"@esbuild/linux-ia32@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.2.tgz#3fc4f0fa026057fe885e4a180b3956e704f1ceaa"
+ integrity sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ==
+
+"@esbuild/linux-loong64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72"
+ integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==
+
+"@esbuild/linux-loong64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.2.tgz#633bcaea443f3505fb0ed109ab840c99ad3451a4"
+ integrity sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw==
+
+"@esbuild/linux-mips64el@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289"
+ integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==
+
+"@esbuild/linux-mips64el@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.2.tgz#e0bff2898c46f52be7d4dbbcca8b887890805823"
+ integrity sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg==
+
+"@esbuild/linux-ppc64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7"
+ integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==
+
+"@esbuild/linux-ppc64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.2.tgz#d75798da391f54a9674f8c143b9a52d1dbfbfdde"
+ integrity sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw==
+
+"@esbuild/linux-riscv64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09"
+ integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==
+
+"@esbuild/linux-riscv64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.2.tgz#012409bd489ed1bb9b775541d4a46c5ded8e6dd8"
+ integrity sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw==
+
+"@esbuild/linux-s390x@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829"
+ integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==
+
+"@esbuild/linux-s390x@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.2.tgz#ece3ed75c5a150de8a5c110f02e97d315761626b"
+ integrity sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g==
+
+"@esbuild/linux-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4"
+ integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==
+
+"@esbuild/linux-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.2.tgz#dea187019741602d57aaf189a80abba261fbd2aa"
+ integrity sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ==
+
+"@esbuild/netbsd-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462"
+ integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==
+
+"@esbuild/netbsd-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.2.tgz#bbfd7cf9ab236a23ee3a41b26f0628c57623d92a"
+ integrity sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ==
+
+"@esbuild/openbsd-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691"
+ integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==
+
+"@esbuild/openbsd-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.2.tgz#fa5c4c6ee52a360618f00053652e2902e1d7b4a7"
+ integrity sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw==
+
+"@esbuild/sunos-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273"
+ integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==
+
+"@esbuild/sunos-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.2.tgz#52a2ac8ac6284c02d25df22bb4cfde26fbddd68d"
+ integrity sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw==
+
+"@esbuild/win32-arm64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f"
+ integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==
+
+"@esbuild/win32-arm64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.2.tgz#719ed5870855de8537aef8149694a97d03486804"
+ integrity sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg==
+
+"@esbuild/win32-ia32@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03"
+ integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==
+
+"@esbuild/win32-ia32@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.2.tgz#24832223880b0f581962c8660f8fb8797a1e046a"
+ integrity sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA==
+
+"@esbuild/win32-x64@0.17.19":
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
+ integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
+
+"@esbuild/win32-x64@0.19.2":
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.2.tgz#1205014625790c7ff0e471644a878a65d1e34ab0"
+ integrity sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw==
+
+"@supabase/functions-js@^2.1.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/@supabase/functions-js/-/functions-js-2.1.2.tgz#340a8d3845ef2014338b13a6d33cfa90eb745b14"
+ integrity sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==
+ dependencies:
+ cross-fetch "^3.1.5"
+
+"@supabase/gotrue-js@^2.46.1":
+ version "2.47.0"
+ resolved "https://registry.yarnpkg.com/@supabase/gotrue-js/-/gotrue-js-2.47.0.tgz#67cca8f7be726fcfcc6dd49f515bbb20b2278f74"
+ integrity sha512-3e34/vsKH/DoSZCpB85UZpFWSJ2p4GRUUlqgAgeTPagPlx4xS+Nc5v7g7ic7vp3gK0J5PsYVCn9Qu2JQUp4vXg==
+ dependencies:
+ cross-fetch "^3.1.5"
+
+"@supabase/postgrest-js@^1.8.0":
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/@supabase/postgrest-js/-/postgrest-js-1.8.0.tgz#c10b65e120c6523fc947225bcb9131869cf509f1"
+ integrity sha512-R6leDIC92NgjyG2/tCRJ42rWN7+fZY6ulTEE+c00tcnghn6cX4IYUlnTNMtrdfYC2JYNOTyM+rWj63Wdhr7Zig==
+ dependencies:
+ cross-fetch "^3.1.5"
+
+"@supabase/realtime-js@^2.7.3":
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/@supabase/realtime-js/-/realtime-js-2.7.3.tgz#cbcb84181add681ab99c87032bfe88101c6863b3"
+ integrity sha512-c7TzL81sx2kqyxsxcDduJcHL9KJdCOoKimGP6lQSqiZKX42ATlBZpWbyy9KFGFBjAP4nyopMf5JhPi2ZH9jyNw==
+ dependencies:
+ "@types/phoenix" "^1.5.4"
+ "@types/websocket" "^1.0.3"
+ websocket "^1.0.34"
+
+"@supabase/storage-js@^2.5.1":
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/@supabase/storage-js/-/storage-js-2.5.1.tgz#16c4c088996e0395034717836e626f14df63a349"
+ integrity sha512-nkR0fQA9ScAtIKA3vNoPEqbZv1k5B5HVRYEvRWdlP6mUpFphM9TwPL2jZ/ztNGMTG5xT6SrHr+H7Ykz8qzbhjw==
+ dependencies:
+ cross-fetch "^3.1.5"
+
+"@supabase/supabase-js@^2.32.0":
+ version "2.32.0"
+ resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.32.0.tgz#863c636d83232c6a2e9ba5932e0d7c1bf80bc436"
+ integrity sha512-1ShFhuOI5Du7604nlCelBsRD61daXk2O0qwjumoz35bqrYThnSPPtpJqZOHw6Mg6o7mLjIInYLh/DBlh8UvzRg==
+ dependencies:
+ "@supabase/functions-js" "^2.1.0"
+ "@supabase/gotrue-js" "^2.46.1"
+ "@supabase/postgrest-js" "^1.8.0"
+ "@supabase/realtime-js" "^2.7.3"
+ "@supabase/storage-js" "^2.5.1"
+ cross-fetch "^3.1.5"
+
+"@types/node@*":
+ version "20.5.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.1.tgz#178d58ee7e4834152b0e8b4d30cbfab578b9bb30"
+ integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==
+
+"@types/phoenix@^1.5.4":
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/@types/phoenix/-/phoenix-1.6.0.tgz#eb7536259ee695646e75c4c7b0c9a857ea174781"
+ integrity sha512-qwfpsHmFuhAS/dVd4uBIraMxRd56vwBUYQGZ6GpXnFuM2XMRFJbIyruFKKlW2daQliuYZwe0qfn/UjFCDKic5g==
+
+"@types/websocket@^1.0.3":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c"
+ integrity sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==
+ dependencies:
+ "@types/node" "*"
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+bufferutil@^4.0.1:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad"
+ integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==
+ dependencies:
+ node-gyp-build "^4.3.0"
+
+cross-fetch@^3.1.5:
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82"
+ integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==
+ dependencies:
+ node-fetch "^2.6.12"
+
+d@1, d@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
+ integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
+ dependencies:
+ es5-ext "^0.10.50"
+ type "^1.0.1"
+
+debug@^2.2.0:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+es5-ext@^0.10.35, es5-ext@^0.10.50:
+ version "0.10.62"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5"
+ integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==
+ dependencies:
+ es6-iterator "^2.0.3"
+ es6-symbol "^3.1.3"
+ next-tick "^1.1.0"
+
+es6-iterator@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+ integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==
+ dependencies:
+ d "1"
+ es5-ext "^0.10.35"
+ es6-symbol "^3.1.1"
+
+es6-symbol@^3.1.1, es6-symbol@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
+ integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
+ dependencies:
+ d "^1.0.1"
+ ext "^1.1.2"
+
+esbuild@^0.19.2:
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.2.tgz#b1541828a89dfb6f840d38538767c6130dca2aac"
+ integrity sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg==
+ optionalDependencies:
+ "@esbuild/android-arm" "0.19.2"
+ "@esbuild/android-arm64" "0.19.2"
+ "@esbuild/android-x64" "0.19.2"
+ "@esbuild/darwin-arm64" "0.19.2"
+ "@esbuild/darwin-x64" "0.19.2"
+ "@esbuild/freebsd-arm64" "0.19.2"
+ "@esbuild/freebsd-x64" "0.19.2"
+ "@esbuild/linux-arm" "0.19.2"
+ "@esbuild/linux-arm64" "0.19.2"
+ "@esbuild/linux-ia32" "0.19.2"
+ "@esbuild/linux-loong64" "0.19.2"
+ "@esbuild/linux-mips64el" "0.19.2"
+ "@esbuild/linux-ppc64" "0.19.2"
+ "@esbuild/linux-riscv64" "0.19.2"
+ "@esbuild/linux-s390x" "0.19.2"
+ "@esbuild/linux-x64" "0.19.2"
+ "@esbuild/netbsd-x64" "0.19.2"
+ "@esbuild/openbsd-x64" "0.19.2"
+ "@esbuild/sunos-x64" "0.19.2"
+ "@esbuild/win32-arm64" "0.19.2"
+ "@esbuild/win32-ia32" "0.19.2"
+ "@esbuild/win32-x64" "0.19.2"
+
+esbuild@~0.17.6:
+ version "0.17.19"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955"
+ integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==
+ optionalDependencies:
+ "@esbuild/android-arm" "0.17.19"
+ "@esbuild/android-arm64" "0.17.19"
+ "@esbuild/android-x64" "0.17.19"
+ "@esbuild/darwin-arm64" "0.17.19"
+ "@esbuild/darwin-x64" "0.17.19"
+ "@esbuild/freebsd-arm64" "0.17.19"
+ "@esbuild/freebsd-x64" "0.17.19"
+ "@esbuild/linux-arm" "0.17.19"
+ "@esbuild/linux-arm64" "0.17.19"
+ "@esbuild/linux-ia32" "0.17.19"
+ "@esbuild/linux-loong64" "0.17.19"
+ "@esbuild/linux-mips64el" "0.17.19"
+ "@esbuild/linux-ppc64" "0.17.19"
+ "@esbuild/linux-riscv64" "0.17.19"
+ "@esbuild/linux-s390x" "0.17.19"
+ "@esbuild/linux-x64" "0.17.19"
+ "@esbuild/netbsd-x64" "0.17.19"
+ "@esbuild/openbsd-x64" "0.17.19"
+ "@esbuild/sunos-x64" "0.17.19"
+ "@esbuild/win32-arm64" "0.17.19"
+ "@esbuild/win32-ia32" "0.17.19"
+ "@esbuild/win32-x64" "0.17.19"
+
+ext@^1.1.2:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
+ integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==
+ dependencies:
+ type "^2.7.2"
+
+fsevents@~2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+get-tsconfig@^4.4.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.0.tgz#06ce112a1463e93196aa90320c35df5039147e34"
+ integrity sha512-pmjiZ7xtB8URYm74PlGJozDNyhvsVLUcpBa8DZBG3bWHwaHa9bPiRpiSfovw+fjhwONSCWKRyk+JQHEGZmMrzw==
+ dependencies:
+ resolve-pkg-maps "^1.0.0"
+
+is-typedarray@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+ integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+next-tick@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
+ integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
+
+node-fetch@^2.6.12:
+ version "2.6.13"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.13.tgz#a20acbbec73c2e09f9007de5cda17104122e0010"
+ integrity sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+node-gyp-build@^4.3.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055"
+ integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==
+
+resolve-pkg-maps@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
+ integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
+
+source-map-support@^0.5.21:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.6.0:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+tsx@^3.12.7:
+ version "3.12.7"
+ resolved "https://registry.yarnpkg.com/tsx/-/tsx-3.12.7.tgz#b3b8b0fc79afc8260d1e14f9e995616c859a91e9"
+ integrity sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==
+ dependencies:
+ "@esbuild-kit/cjs-loader" "^2.4.2"
+ "@esbuild-kit/core-utils" "^3.0.0"
+ "@esbuild-kit/esm-loader" "^2.5.5"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+type@^1.0.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
+ integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
+
+type@^2.7.2:
+ version "2.7.2"
+ resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0"
+ integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==
+
+typedarray-to-buffer@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
+ integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+ dependencies:
+ is-typedarray "^1.0.0"
+
+typescript@^4.3.2:
+ version "4.9.5"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
+ integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
+
+utf-8-validate@^5.0.2:
+ version "5.0.10"
+ resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2"
+ integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
+ dependencies:
+ node-gyp-build "^4.3.0"
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+websocket@^1.0.34:
+ version "1.0.34"
+ resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111"
+ integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==
+ dependencies:
+ bufferutil "^4.0.1"
+ debug "^2.2.0"
+ es5-ext "^0.10.50"
+ typedarray-to-buffer "^3.1.5"
+ utf-8-validate "^5.0.2"
+ yaeti "^0.0.6"
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+yaeti@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
+ integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==
diff --git a/package.json b/package.json
index 8d05c1033..e64d64e99 100644
--- a/package.json
+++ b/package.json
@@ -14,27 +14,25 @@
"build:ci": "ncc build src/adapters/github/github-actions.ts -o ./",
"build:serverless": "ncc build src/index.ts -o ./",
"build": "tsc",
+ "postbuild": "copyfiles src/assets/images/* lib/",
"clean": "rimraf ./dist ./lib ./node_modules",
"format:check": "prettier -c src/**/*.ts",
"format": "prettier --write src",
"lint": "eslint --ext .ts ./src",
- "prestart:watch": "ln -s src/assets/images lib/assets/images",
"start:serverless": "tsx src/adapters/github/github-actions.ts",
"start:watch": "nodemon --exec 'yarn start'",
- "start": "probot run ./lib/index.js",
+ "start": "probot run ./lib/src/index.js",
"prepare": "husky install"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@commitlint/cli": "^17.4.3",
"@commitlint/config-conventional": "^17.4.3",
- "@logdna/logger": "^2.6.6",
"@netlify/functions": "^1.4.0",
"@probot/adapter-aws-lambda-serverless": "^3.0.2",
"@probot/adapter-github-actions": "^3.1.3",
"@sinclair/typebox": "^0.25.9",
"@supabase/supabase-js": "^2.4.0",
- "@types/mdast": "^3.0.11",
"@types/ms": "^0.7.31",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
@@ -43,19 +41,20 @@
"ajv": "^8.11.2",
"ajv-formats": "^2.1.1",
"axios": "^1.3.2",
+ "decimal.js": "^10.4.3",
+ "copyfiles": "^2.4.1",
"ethers": "^5.7.2",
+ "exponential-backoff": "^3.1.1",
"husky": "^8.0.2",
"jimp": "^0.22.4",
"js-yaml": "^4.1.0",
"libsodium-wrappers": "^0.7.11",
"lint-staged": "^13.1.0",
- "mdast-util-from-markdown": "^1.3.0",
- "mdast-util-gfm": "^2.0.2",
- "micromark-extension-gfm": "^2.0.3",
"ms": "^2.1.3",
"node-html-parser": "^6.1.5",
"node-html-to-image": "^3.3.0",
"nodemon": "^2.0.19",
+ "parse5": "^7.1.2",
"prettier": "^2.7.1",
"probot": "^12.2.4",
"telegraf": "^4.11.2",
diff --git a/src/adapters/supabase/helpers/client.ts b/src/adapters/supabase/helpers/client.ts
index f92d2e4e3..cbd0629f7 100644
--- a/src/adapters/supabase/helpers/client.ts
+++ b/src/adapters/supabase/helpers/client.ts
@@ -2,6 +2,7 @@ import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { getAdapters, getLogger } from "../../../bindings";
import { Issue, UserProfile } from "../../../types";
import { Database } from "../types";
+import { InsertPermit, Permit } from "../../../helpers";
import { BigNumber, BigNumberish } from "ethers";
/**
@@ -12,7 +13,7 @@ import { BigNumber, BigNumberish } from "ethers";
* @returns - The supabase client
*/
export const supabase = (url: string, key: string): SupabaseClient => {
- return createClient(url, key);
+ return createClient(url, key, { auth: { persistSession: false } });
};
/**
@@ -32,25 +33,45 @@ export const getMaxIssueNumber = async (): Promise => {
/**
* @dev Gets the last weekly update timestamp
*/
-export const getLastWeeklyTime = async (): Promise => {
+export const getLastWeeklyTime = async (): Promise => {
const { supabase } = getAdapters();
const { data } = await supabase.from("weekly").select("last_time").limit(1).single();
if (data) {
- return Number(data.last_time);
+ return new Date(data.last_time);
} else {
- return 0;
+ return undefined;
}
};
/**
* @dev Updates the last weekly update timestamp
*/
-export const updateLastWeeklyTime = async (time: number): Promise => {
+export const updateLastWeeklyTime = async (time: Date): Promise => {
const logger = getLogger();
const { supabase } = getAdapters();
- const { data, error } = await supabase.from("weekly").update({ last_time: time });
- logger.info(`Updating last time is done, data: ${data}, error: ${error}`);
+
+ const { data, error } = await supabase.from("weekly").select("last_time");
+ if (error) {
+ logger.error(`Checking last time failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Checking last time failed, error: ${JSON.stringify(error)}`);
+ }
+
+ if (data && data.length > 0) {
+ const { data, error } = await supabase.from("weekly").update({ last_time: time.toUTCString() }).neq("last_time", time.toUTCString());
+ if (error) {
+ logger.error(`Updating last time failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Updating last time failed, error: ${JSON.stringify(error)}`);
+ }
+ logger.info(`Updating last time is done, data: ${data}`);
+ } else {
+ const { data, error } = await supabase.from("weekly").insert({ last_time: time.toUTCString() });
+ if (error) {
+ logger.error(`Creating last time failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Creating last time failed, error: ${JSON.stringify(error)}`);
+ }
+ logger.info(`Creating last time is done, data: ${data}`);
+ }
return;
};
@@ -91,7 +112,7 @@ const getDbDataFromUserProfile = (userProfile: UserProfile, additions?: UserProf
return {
user_login: userProfile.login,
user_type: userProfile.type,
- user_name: userProfile.name,
+ user_name: userProfile.name ?? userProfile.login,
company: userProfile.company,
blog: userProfile.blog,
user_location: userProfile.location,
@@ -113,18 +134,30 @@ const getDbDataFromUserProfile = (userProfile: UserProfile, additions?: UserProf
export const upsertIssue = async (issue: Issue, additions: IssueAdditions): Promise => {
const logger = getLogger();
const { supabase } = getAdapters();
- const { data, error } = await supabase.from("issues").select("id").eq("issue_number", issue.number).single();
+ const { data, error } = await supabase.from("issues").select("id").eq("issue_number", issue.number);
+ if (error) {
+ logger.error(`Checking issue failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Checking issue failed, error: ${JSON.stringify(error)}`);
+ }
- if (data) {
- const key = data.id as number;
- await supabase
+ if (data && data.length > 0) {
+ const key = data[0].id as number;
+ const { data: _data, error: _error } = await supabase
.from("issues")
.upsert({ id: key, ...getDbDataFromIssue(issue, additions) })
.select();
- logger.info(`Upserting an issue done, data: ${data}, error: ${error}`);
- } else if (error) {
+ if (_error) {
+ logger.error(`Upserting an issue failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Upserting an issue failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Upserting an issue done, { data: ${_data}, error: ${_error}`);
+ } else {
const { data: _data, error: _error } = await supabase.from("issues").insert(getDbDataFromIssue(issue, additions));
- logger.info(`Creating a new issue done, { data: ${_data}, error: ${_error}`);
+ if (_error) {
+ logger.error(`Creating a new issue record failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Creating a new issue record failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Creating a new issue record done, { data: ${_data}, error: ${_error}`);
}
};
@@ -135,18 +168,26 @@ export const upsertIssue = async (issue: Issue, additions: IssueAdditions): Prom
export const upsertUser = async (user: UserProfile): Promise => {
const logger = getLogger();
const { supabase } = getAdapters();
- const { data, error } = await supabase.from("users").select("id").eq("user_login", user.login).single();
+ const { data, error } = await supabase.from("users").select("user_login").eq("user_login", user.login);
+ if (error) {
+ logger.error(`Checking user failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Checking user failed, error: ${JSON.stringify(error)}`);
+ }
- if (data) {
- const key = data.id as number;
- await supabase
- .from("users")
- .upsert({ id: key, ...getDbDataFromUserProfile(user) })
- .select();
- logger.info(`Upserting an user done", { data: ${data}, error: ${error} }`);
- } else if (error) {
+ if (data && data.length > 0) {
+ const { data: _data, error: _error } = await supabase.from("users").upsert(getDbDataFromUserProfile(user)).select();
+ if (_error) {
+ logger.error(`Upserting a user failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Upserting a user failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Upserting a user done, { data: ${JSON.stringify(_data)} }`);
+ } else {
const { data: _data, error: _error } = await supabase.from("users").insert(getDbDataFromUserProfile(user));
- logger.info(`Creating a new user done", { data: ${_data}, error: ${_error} }`);
+ if (_error) {
+ logger.error(`Creating a new user record failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Creating a new user record failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Creating a new user record done, { data: ${JSON.stringify(_data)} }`);
}
};
@@ -159,14 +200,23 @@ export const upsertWalletAddress = async (username: string, address: string): Pr
const logger = getLogger();
const { supabase } = getAdapters();
- const { data, error } = await supabase.from("wallets").select("user_name").eq("user_name", username).single();
- if (data) {
- await supabase.from("wallets").upsert({
+ const { data, error } = await supabase.from("wallets").select("user_name").eq("user_name", username);
+ if (error) {
+ logger.error(`Checking wallet address failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Checking wallet address failed, error: ${JSON.stringify(error)}`);
+ }
+
+ if (data && data.length > 0) {
+ const { data: _data, error: _error } = await supabase.from("wallets").upsert({
user_name: username,
wallet_address: address,
updated_at: new Date().toUTCString(),
});
- logger.info(`Upserting a wallet address done, { data: ${data}, error: ${error} }`);
+ if (_error) {
+ logger.error(`Upserting a wallet address failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Upserting a wallet address failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Upserting a wallet address done, { data: ${JSON.stringify(_data)} }`);
} else {
const { data: _data, error: _error } = await supabase.from("wallets").insert({
user_name: username,
@@ -174,7 +224,11 @@ export const upsertWalletAddress = async (username: string, address: string): Pr
created_at: new Date().toUTCString(),
updated_at: new Date().toUTCString(),
});
- logger.info(`Creating a new wallet_table record done, { data: ${_data}, error: ${_error} }`);
+ if (_error) {
+ logger.error(`Creating a new wallet_table record failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Creating a new wallet_table record failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Creating a new wallet_table record done, { data: ${JSON.stringify(_data)} }`);
}
};
@@ -187,15 +241,24 @@ export const upsertWalletMultiplier = async (username: string, multiplier: strin
const logger = getLogger();
const { supabase } = getAdapters();
- const { data, error } = await supabase.from("multiplier").select("user_id").eq("user_id", `${username}_${org_id}`).single();
- if (data) {
- await supabase.from("multiplier").upsert({
+ const { data, error } = await supabase.from("multiplier").select("user_id").eq("user_id", `${username}_${org_id}`);
+ if (error) {
+ logger.error(`Checking wallet multiplier failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Checking wallet multiplier failed, error: ${JSON.stringify(error)}`);
+ }
+
+ if (data && data.length > 0) {
+ const { data: _data, error: _error } = await supabase.from("multiplier").upsert({
user_id: `${username}_${org_id}`,
value: multiplier,
reason,
updated_at: new Date().toUTCString(),
});
- logger.info(`Upserting a wallet address done, { data: ${data}, error: ${error} }`);
+ if (_error) {
+ logger.error(`Upserting a wallet multiplier failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Upserting a wallet multiplier failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Upserting a wallet multiplier done, { data: ${JSON.stringify(_data)} }`);
} else {
const { data: _data, error: _error } = await supabase.from("multiplier").insert({
user_id: `${username}_${org_id}`,
@@ -204,7 +267,11 @@ export const upsertWalletMultiplier = async (username: string, multiplier: strin
created_at: new Date().toUTCString(),
updated_at: new Date().toUTCString(),
});
- logger.info(`Creating a new multiplier_table record done, { data: ${_data}, error: ${_error} }`);
+ if (_error) {
+ logger.error(`Creating a new multiplier record failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Creating a new multiplier record failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Creating a new multiplier record done, { data: ${JSON.stringify(_data)} }`);
}
};
@@ -219,7 +286,11 @@ export const upsertAccessControl = async (username: string, repository: string,
const logger = getLogger();
const { supabase } = getAdapters();
- const { data, error } = await supabase.from("access").select("user_name").eq("user_name", username).eq("repository", repository).single();
+ const { data, error } = await supabase.from("access").select("user_name").eq("user_name", username).eq("repository", repository);
+ if (error) {
+ logger.error(`Checking access control failed, error: ${JSON.stringify(error)}`);
+ throw new Error(`Checking access control failed, error: ${JSON.stringify(error)}`);
+ }
const properties = {
user_name: username,
@@ -228,9 +299,13 @@ export const upsertAccessControl = async (username: string, repository: string,
[access]: bool,
};
- if (data) {
- await supabase.from("access").upsert(properties);
- logger.info(`Upserting an access done, { data: ${data}, error: ${error} }`);
+ if (data && data.length > 0) {
+ const { data: _data, error: _error } = await supabase.from("access").upsert(properties);
+ if (_error) {
+ logger.error(`Upserting a access control failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Upserting a access control failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Upserting a access control done, { data: ${JSON.stringify(_data)} }`);
} else {
const { data: _data, error: _error } = await supabase.from("access").insert({
created_at: new Date().toUTCString(),
@@ -240,7 +315,11 @@ export const upsertAccessControl = async (username: string, repository: string,
priority_access: false,
...properties,
});
- logger.info(`Creating a new access record done, { data: ${_data}, error: ${_error} }`);
+ if (_error) {
+ logger.error(`Creating a new access control record failed, error: ${JSON.stringify(_error)}`);
+ throw new Error(`Creating a new access control record failed, error: ${JSON.stringify(_error)}`);
+ }
+ logger.info(`Creating a new access control record done, { data: ${JSON.stringify(_data)} }`);
}
};
@@ -366,3 +445,58 @@ export const removePenalty = async (username: string, repoName: string, tokenAdd
throw new Error(`Error removing penalty: ${error.message}`);
}
};
+
+const getDbDataFromPermit = (permit: InsertPermit): Record => {
+ return {
+ organization_id: permit.organizationId,
+ repository_id: permit.repositoryId,
+ issue_id: permit.issueId,
+ network_id: permit.networkId,
+ bounty_hunter_id: permit.bountyHunterId,
+ token_address: permit.tokenAddress,
+ payout_amount: permit.payoutAmount,
+ bounty_hunter_address: permit.bountyHunterAddress,
+ nonce: permit.nonce,
+ deadline: permit.deadline,
+ signature: permit.signature,
+ wallet_owner_address: permit.walletOwnerAddress,
+ };
+};
+
+const getPermitFromDbData = (data: Record): Permit => {
+ return {
+ id: data.id,
+ createdAt: new Date(Date.parse(data.created_at as string)),
+ organizationId: data.organization_id,
+ repositoryId: data.repository_i,
+ issueId: data.issue_id,
+ networkId: data.network_id,
+ bountyHunterId: data.bounty_hunter_id,
+ tokenAddress: data.token_address,
+ payoutAmount: data.payout_amount,
+ bountyHunterAddress: data.bounty_hunter_address,
+ nonce: data.nonce,
+ deadline: data.deadline,
+ signature: data.signature,
+ walletOwnerAddress: data.wallet_owner_address,
+ } as Permit;
+};
+
+export const savePermit = async (permit: InsertPermit): Promise => {
+ const { supabase } = getAdapters();
+ const { data, error } = await supabase
+ .from("permits")
+ .insert({
+ ...getDbDataFromPermit(permit),
+ created_at: new Date().toISOString(),
+ id: undefined, // id is auto-generated
+ })
+ .select();
+ if (error) {
+ throw new Error(error.message);
+ }
+ if (!data || data.length === 0) {
+ throw new Error("No data returned");
+ }
+ return getPermitFromDbData(data[0]);
+};
diff --git a/src/adapters/supabase/helpers/index.ts b/src/adapters/supabase/helpers/index.ts
index 5ec76921e..3bb7e5e98 100644
--- a/src/adapters/supabase/helpers/index.ts
+++ b/src/adapters/supabase/helpers/index.ts
@@ -1 +1,2 @@
export * from "./client";
+export * from "./log";
diff --git a/src/adapters/supabase/helpers/log.ts b/src/adapters/supabase/helpers/log.ts
new file mode 100644
index 000000000..e352339a8
--- /dev/null
+++ b/src/adapters/supabase/helpers/log.ts
@@ -0,0 +1,193 @@
+import { getAdapters, getBotContext, Logger } from "../../../bindings";
+import { Payload } from "../../../types";
+import { getNumericLevel } from "../../../utils/helpers";
+import { getOrgAndRepoFromPath } from "../../../utils/private";
+interface Log {
+ repo: string | null;
+ org: string | null;
+ commentId: number | undefined;
+ issueNumber: number | undefined;
+ logMessage: string;
+ level: Level;
+ timestamp: string;
+}
+
+export enum Level {
+ ERROR = "error",
+ WARN = "warn",
+ INFO = "info",
+ HTTP = "http",
+ VERBOSE = "verbose",
+ DEBUG = "debug",
+ SILLY = "silly",
+}
+
+export class GitHubLogger implements Logger {
+ private supabase;
+ private maxLevel;
+ private app;
+ private logEnvironment;
+ private logQueue: Log[] = []; // Your log queue
+ private maxConcurrency = 6; // Maximum concurrent requests
+ private retryDelay = 1000; // Delay between retries in milliseconds
+ private throttleCount = 0;
+ private retryLimit = 0; // Retries disabled by default
+
+ constructor(app: string, logEnvironment: string, maxLevel: Level, retryLimit: number) {
+ this.app = app;
+ this.logEnvironment = logEnvironment;
+ this.maxLevel = getNumericLevel(maxLevel);
+ this.retryLimit = retryLimit;
+ this.supabase = getAdapters().supabase;
+ }
+
+ async sendLogsToSupabase({ repo, org, commentId, issueNumber, logMessage, level, timestamp }: Log) {
+ const { error } = await this.supabase.from("logs").insert([
+ {
+ repo_name: repo,
+ level: getNumericLevel(level),
+ org_name: org,
+ comment_id: commentId,
+ log_message: logMessage,
+ issue_number: issueNumber,
+ timestamp,
+ },
+ ]);
+
+ if (error) {
+ console.error("Error logging to Supabase:", error.message);
+ return;
+ }
+ }
+
+ async processLogs(log: Log) {
+ try {
+ await this.sendLogsToSupabase(log);
+ } catch (error) {
+ console.error("Error sending log, retrying:", error);
+ return this.retryLimit > 0 ? await this.retryLog(log) : null;
+ }
+ }
+
+ async retryLog(log: Log, retryCount = 0) {
+ if (retryCount >= this.retryLimit) {
+ console.error("Max retry limit reached for log:", log);
+ return;
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, this.retryDelay));
+
+ try {
+ await this.sendLogsToSupabase(log);
+ } catch (error) {
+ console.error("Error sending log (after retry):", error);
+ await this.retryLog(log, retryCount + 1);
+ }
+ }
+
+ async processLogQueue() {
+ while (this.logQueue.length > 0) {
+ const log = this.logQueue.shift();
+ if (!log) {
+ continue;
+ }
+ await this.processLogs(log);
+ }
+ }
+
+ async throttle() {
+ if (this.throttleCount >= this.maxConcurrency) {
+ return;
+ }
+
+ this.throttleCount++;
+ try {
+ await this.processLogQueue();
+ } finally {
+ this.throttleCount--;
+ if (this.logQueue.length > 0) {
+ await this.throttle();
+ }
+ }
+ }
+
+ async addToQueue(log: Log) {
+ this.logQueue.push(log);
+ if (this.throttleCount < this.maxConcurrency) {
+ await this.throttle();
+ }
+ }
+
+ private save(logMessage: string | object, level: Level, errorPayload?: string | object) {
+ if (getNumericLevel(level) > this.maxLevel) return; // only return errors lower than max level
+
+ const context = getBotContext();
+ const payload = context.payload as Payload;
+ const timestamp = new Date().toUTCString();
+
+ const { comment, issue, repository } = payload;
+ const commentId = comment?.id;
+ const issueNumber = issue?.number;
+ const repoFullName = repository?.full_name;
+
+ const { org, repo } = getOrgAndRepoFromPath(repoFullName);
+
+ if (!logMessage) return;
+
+ if (typeof logMessage === "object") {
+ // pass log as json stringified
+ logMessage = JSON.stringify(logMessage);
+ }
+
+ this.addToQueue({ repo, org, commentId, issueNumber, logMessage, level, timestamp })
+ .then(() => {
+ return;
+ })
+ .catch(() => {
+ console.log("Error adding logs to queue");
+ });
+
+ if (this.logEnvironment === "development") {
+ console.log(this.app, logMessage, errorPayload, level, repo, org, commentId, issueNumber);
+ }
+ }
+
+ info(message: string | object, errorPayload?: string | object) {
+ this.save(message, Level.INFO, errorPayload);
+ }
+
+ warn(message: string | object, errorPayload?: string | object) {
+ this.save(message, Level.WARN, errorPayload);
+ }
+
+ debug(message: string | object, errorPayload?: string | object) {
+ this.save(message, Level.DEBUG, errorPayload);
+ }
+
+ error(message: string | object, errorPayload?: string | object) {
+ this.save(message, Level.ERROR, errorPayload);
+ }
+
+ async get() {
+ try {
+ const { data, error } = await this.supabase.from("logs").select("*");
+
+ if (error) {
+ console.error("Error retrieving logs from Supabase:", error.message);
+ return [];
+ }
+
+ return data;
+ } catch (error) {
+ if (error instanceof Error) {
+ // 👉️ err is type Error here
+ console.error("An error occurred:", error.message);
+
+ return;
+ }
+
+ console.log("Unexpected error", error);
+ return [];
+ }
+ }
+}
diff --git a/src/bindings/config.ts b/src/bindings/config.ts
index c2a64e6b3..9fc6f4ed0 100644
--- a/src/bindings/config.ts
+++ b/src/bindings/config.ts
@@ -1,11 +1,19 @@
import ms from "ms";
import { BotConfig, BotConfigSchema } from "../types";
-import { DEFAULT_BOT_DELAY, DEFAULT_DISQUALIFY_TIME, DEFAULT_FOLLOWUP_TIME, DEFAULT_PERMIT_BASE_URL } from "../configs";
+import {
+ DEFAULT_BOT_DELAY,
+ DEFAULT_DISQUALIFY_TIME,
+ DEFAULT_FOLLOWUP_TIME,
+ DEFAULT_PERMIT_BASE_URL,
+ DEFAULT_TIME_RANGE_FOR_MAX_ISSUE,
+ DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED,
+} from "../configs";
import { getPayoutConfigByNetworkId } from "../helpers";
import { ajv } from "../utils";
import { Context } from "probot";
import { getScalarKey, getWideConfig } from "../utils/private";
+import { Level } from "../adapters/supabase";
export const loadConfig = async (context: Context): Promise => {
const {
@@ -13,8 +21,8 @@ export const loadConfig = async (context: Context): Promise => {
timeLabels,
privateKey,
priorityLabels,
- commentElementPricing,
- autoPayMode,
+ incentives,
+ paymentPermitMaxPrice,
disableAnalytics,
bountyHunterMax,
incentiveMode,
@@ -22,6 +30,8 @@ export const loadConfig = async (context: Context): Promise => {
issueCreatorMultiplier,
defaultLabels,
promotionComment,
+ commandSettings,
+ assistivePricing,
registerWalletWithVerification,
} = await getWideConfig(context);
@@ -30,15 +40,16 @@ export const loadConfig = async (context: Context): Promise => {
const botConfig: BotConfig = {
log: {
- level: process.env.LOG_LEVEL || "debug",
- ingestionKey: process.env.LOGDNA_INGESTION_KEY ?? "",
+ logEnvironment: process.env.LOG_ENVIRONMENT || "production",
+ level: (process.env.LOG_LEVEL as Level) || Level.DEBUG,
+ retryLimit: Number(process.env.LOG_RETRY) || 0,
},
price: {
baseMultiplier,
issueCreatorMultiplier,
timeLabels,
priorityLabels,
- commentElementPricing,
+ incentives,
defaultLabels,
},
comments: {
@@ -52,6 +63,12 @@ export const loadConfig = async (context: Context): Promise => {
permitBaseUrl: process.env.PERMIT_BASE_URL || DEFAULT_PERMIT_BASE_URL,
},
unassign: {
+ timeRangeForMaxIssue: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE
+ ? Number(process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE)
+ : DEFAULT_TIME_RANGE_FOR_MAX_ISSUE,
+ timeRangeForMaxIssueEnabled: process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED
+ ? process.env.DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED == "true"
+ : DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED,
followUpTime: ms(process.env.FOLLOW_UP_TIME || DEFAULT_FOLLOWUP_TIME),
disqualifyTime: ms(process.env.DISQUALIFY_TIME || DEFAULT_DISQUALIFY_TIME),
},
@@ -64,10 +81,12 @@ export const loadConfig = async (context: Context): Promise => {
delay: process.env.TELEGRAM_BOT_DELAY ? Number(process.env.TELEGRAM_BOT_DELAY) : DEFAULT_BOT_DELAY,
},
mode: {
- autoPayMode: autoPayMode,
+ paymentPermitMaxPrice: paymentPermitMaxPrice,
disableAnalytics: disableAnalytics,
incentiveMode: incentiveMode,
+ assistivePricing: assistivePricing,
},
+ command: commandSettings,
assign: {
bountyHunterMax: bountyHunterMax,
},
@@ -80,12 +99,8 @@ export const loadConfig = async (context: Context): Promise => {
},
};
- if (botConfig.log.ingestionKey == "") {
- throw new Error("LogDNA ingestion key missing");
- }
-
if (botConfig.payout.privateKey == "") {
- botConfig.mode.autoPayMode = false;
+ botConfig.mode.paymentPermitMaxPrice = 0;
}
const validate = ajv.compile(BotConfigSchema);
diff --git a/src/bindings/event.ts b/src/bindings/event.ts
index 77518c99c..3ea42f287 100644
--- a/src/bindings/event.ts
+++ b/src/bindings/event.ts
@@ -1,5 +1,4 @@
import { Context } from "probot";
-import { createLogger } from "@logdna/logger";
import { createAdapters } from "../adapters";
import { processors, wildcardProcessors } from "../handlers/processors";
import { shouldSkip } from "../helpers";
@@ -7,6 +6,7 @@ import { BotConfig, GithubEvent, Payload, PayloadSchema } from "../types";
import { Adapters } from "../types/adapters";
import { ajv } from "../utils";
import { loadConfig } from "./config";
+import { GitHubLogger } from "../adapters/supabase";
let botContext: Context = {} as Context;
export const getBotContext = () => botContext;
@@ -23,6 +23,7 @@ export type Logger = {
warn: (msg: string | object, options?: JSON) => void;
error: (msg: string | object, options?: JSON) => void;
};
+
let logger: Logger;
export const getLogger = (): Logger => logger;
@@ -35,15 +36,21 @@ export const bindEvents = async (context: Context): Promise => {
botConfig = await loadConfig(context);
+ adapters = createAdapters(botConfig);
+
const options = {
app: "UbiquiBot",
- level: botConfig.log.level,
+ // level: botConfig.log.level,
};
- logger = createLogger(botConfig.log.ingestionKey, options) as Logger;
+
+ logger = new GitHubLogger(options.app, botConfig.log.logEnvironment, botConfig.log.level, botConfig.log.retryLimit); // contributors will see logs in console while on development env
if (!logger) {
return;
}
+ // Create adapters for telegram, supabase, twitter, discord, etc
+ logger.info("Creating adapters for supabase, telegram, twitter, etc...");
+
logger.info(
`Config loaded! config: ${JSON.stringify({
price: botConfig.price,
@@ -64,10 +71,6 @@ export const bindEvents = async (context: Context): Promise => {
return;
}
- // Create adapters for telegram, supabase, twitter, discord, etc
- logger.info("Creating adapters for supabase, telegram, twitter, etc...");
- adapters = createAdapters(botConfig);
-
// Skip validation for installation event and push
if (!NO_VALIDATION.includes(eventName)) {
// Validate payload
@@ -112,8 +115,8 @@ export const bindEvents = async (context: Context): Promise => {
await postAction();
}
- // Skip wildcard handlers for installation event
- if (eventName !== GithubEvent.INSTALLATION_ADDED_EVENT) {
+ // Skip wildcard handlers for installation event and push event
+ if (eventName !== GithubEvent.INSTALLATION_ADDED_EVENT && eventName !== GithubEvent.PUSH_EVENT) {
// Run wildcard handlers
logger.info(`Running wildcard handlers: ${wildcardProcessors.map((fn) => fn.name)}`);
for (const wildcardProcessor of wildcardProcessors) {
diff --git a/src/configs/shared.ts b/src/configs/shared.ts
index 2d817b7a0..8c287c4ff 100644
--- a/src/configs/shared.ts
+++ b/src/configs/shared.ts
@@ -5,6 +5,7 @@ export const COLORS = {
export const DEFAULT_BOT_DELAY = 100; // 100ms
export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE = 24;
export const DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED = true;
+
export const ASSIGN_COMMAND_ENABLED = true;
/**
* ms('2 days') // 172800000
diff --git a/src/configs/strings.ts b/src/configs/strings.ts
index 9bc705a20..3f3128920 100644
--- a/src/configs/strings.ts
+++ b/src/configs/strings.ts
@@ -2,4 +2,8 @@ export const GLOBAL_STRINGS = {
unassignComment: "Releasing the bounty back to dev pool because the allocated duration already ended!",
askUpdate: "Do you have any updates",
assignCommandDisabledComment: "The `/assign` command is disabled for this repository.",
+ skipPriceLabelGenerationComment: "Pricing is disabled on parent issues.",
+ ignoreStartCommandForParentIssueComment:
+ "Please select a child issue from the specification checklist to work on. The `/start` command is disabled on parent issues.",
+ autopayComment: "Automatic payment for this issue is enabled:",
};
diff --git a/src/handlers/assign/action.ts b/src/handlers/assign/action.ts
index bd71d65c3..b42312a88 100644
--- a/src/handlers/assign/action.ts
+++ b/src/handlers/assign/action.ts
@@ -1,5 +1,5 @@
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
-import { addCommentToIssue, closePullRequest, getOpenedPullRequestsForAnIssue } from "../../helpers";
+import { addCommentToIssue, closePullRequest, getOpenedPullRequestsForAnIssue, calculateWeight, calculateDuration } from "../../helpers";
import { Payload, LabelItem } from "../../types";
import { deadLinePrefix } from "../shared";
@@ -49,9 +49,9 @@ export const commentWithAssignMessage = async (): Promise => {
return;
}
- const sorted = timeLabelsAssigned.sort((a, b) => a.weight - b.weight);
+ const sorted = timeLabelsAssigned.sort((a, b) => calculateWeight(a) - calculateWeight(b));
const targetTimeLabel = sorted[0];
- const duration = targetTimeLabel.value;
+ const duration = calculateDuration(targetTimeLabel);
if (!duration) {
logger.debug(`Missing configure for timelabel: ${targetTimeLabel.name}`);
return;
diff --git a/src/handlers/assign/auto.ts b/src/handlers/assign/auto.ts
index a065327f4..7c648b5e7 100644
--- a/src/handlers/assign/auto.ts
+++ b/src/handlers/assign/auto.ts
@@ -1,5 +1,5 @@
import { getBotContext, getLogger } from "../../bindings";
-import { addAssignees, getIssueByNumber, getPullByNumber, getPullRequests } from "../../helpers";
+import { addAssignees, getAllPullRequests, getIssueByNumber, getPullByNumber } from "../../helpers";
import { gitLinkedIssueParser } from "../../helpers/parser";
import { Payload } from "../../types";
@@ -7,7 +7,7 @@ import { Payload } from "../../types";
export const checkPullRequests = async () => {
const context = getBotContext();
const logger = getLogger();
- const pulls = await getPullRequests(context);
+ const pulls = await getAllPullRequests(context);
if (pulls.length === 0) {
logger.debug(`No pull requests found at this time`);
diff --git a/src/handlers/comment/action.ts b/src/handlers/comment/action.ts
index 1857b7851..84c54e6d5 100644
--- a/src/handlers/comment/action.ts
+++ b/src/handlers/comment/action.ts
@@ -1,10 +1,12 @@
-import { getBotContext, getLogger } from "../../bindings";
+import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import { Payload } from "../../types";
+import { IssueCommentCommands } from "./commands";
import { commentParser, userCommands } from "./handlers";
import { verifyFirstCheck } from "./handlers/first";
export const handleComment = async (): Promise => {
const context = getBotContext();
+ const config = getBotConfig();
const logger = getLogger();
const payload = context.payload as Payload;
@@ -28,13 +30,21 @@ export const handleComment = async (): Promise => {
const userCommand = allCommands.find((i) => i.id == command);
if (userCommand) {
- const { handler, callback, successComment, failureComment } = userCommand;
+ const { id, handler, callback, successComment, failureComment } = userCommand;
logger.info(`Running a comment handler: ${handler.name}`);
const { payload: _payload } = getBotContext();
const issue = (_payload as Payload).issue;
if (!issue) continue;
+ const feature = config.command.find((e) => e.name === id.split("/")[1]);
+
+ if (!feature?.enabled && id !== IssueCommentCommands.HELP) {
+ logger.info(`Skipping '${id}' because it is disabled on this repo`);
+ await callback(issue.number, `Skipping \`${id}\` because it is disabled on this repo`, payload.action, payload.comment);
+ continue;
+ }
+
try {
const response = await handler(body);
const callbackComment = response ?? successComment ?? "";
diff --git a/src/handlers/comment/commands.ts b/src/handlers/comment/commands.ts
index 878727dcb..afdab0b03 100644
--- a/src/handlers/comment/commands.ts
+++ b/src/handlers/comment/commands.ts
@@ -1,7 +1,7 @@
export enum IssueCommentCommands {
HELP = "/help", // list available commands
- ASSIGN = "/assign", // assign the hunter to the issue automatically
- UNASSIGN = "/unassign", // unassign to default
+ START = "/start", // assign the hunter to the issue automatically
+ STOP = "/stop", // unassign to default
WALLET = "/wallet", // register wallet address
PAYOUT = "/payout", // request permit payout
MULTIPLIER = "/multiplier", // set bounty multiplier (for treasury)
@@ -9,4 +9,5 @@ export enum IssueCommentCommands {
// Access Controls
ALLOW = "/allow",
+ AUTOPAY = "/autopay",
}
diff --git a/src/handlers/comment/handlers/allow.ts b/src/handlers/comment/handlers/allow.ts
index 52d22e9bc..978dbbef4 100644
--- a/src/handlers/comment/handlers/allow.ts
+++ b/src/handlers/comment/handlers/allow.ts
@@ -20,7 +20,7 @@ export const setAccess = async (body: string) => {
return;
}
- const regex = /^\/allow set-(\S+)\s@(\w+)\s(true|false)$/;
+ const regex = /^\/allow\s+set-(\S+)\s+@([\w-]+)\s+(true|false)\s*$/;
const matches = body.match(regex);
diff --git a/src/handlers/comment/handlers/assign.ts b/src/handlers/comment/handlers/assign.ts
index 5b59add42..5394b3404 100644
--- a/src/handlers/comment/handlers/assign.ts
+++ b/src/handlers/comment/handlers/assign.ts
@@ -1,11 +1,12 @@
-import { addAssignees, getAssignedIssues, getAvailableOpenedPullRequests, getAllIssueComments } from "../../../helpers";
+import { addAssignees, getAssignedIssues, getAvailableOpenedPullRequests, getAllIssueComments, calculateWeight, calculateDuration } from "../../../helpers";
import { getBotConfig, getBotContext, getLogger } from "../../../bindings";
-import { Payload, LabelItem, Comment, IssueType } from "../../../types";
+import { Payload, LabelItem, Comment, IssueType, Issue } from "../../../types";
import { deadLinePrefix } from "../../shared";
import { getWalletAddress, getWalletMultiplier } from "../../../adapters/supabase";
import { tableComment } from "./table";
import { bountyInfo } from "../../wildcard";
import { ASSIGN_COMMAND_ENABLED, GLOBAL_STRINGS } from "../../../configs";
+import { isParentIssue } from "../../pricing";
export const assign = async (body: string) => {
const { payload: _payload } = getBotContext();
@@ -17,18 +18,23 @@ export const assign = async (body: string) => {
const id = organization?.id || repository?.id; // repository?.id as fallback
- logger.info(`Received '/assign' command from user: ${payload.sender.login}, body: ${body}`);
+ logger.info(`Received '/start' command from user: ${payload.sender.login}, body: ${body}`);
const issue = (_payload as Payload).issue;
if (!issue) {
- logger.info(`Skipping '/assign' because of no issue instance`);
- return "Skipping '/assign' because of no issue instance";
+ logger.info(`Skipping '/start' because of no issue instance`);
+ return "Skipping '/start' because of no issue instance";
}
if (!ASSIGN_COMMAND_ENABLED) {
- logger.info(`Ignore '/assign' command from user: ASSIGN_COMMAND_ENABLED config is set false`);
+ logger.info(`Ignore '/start' command from user: ASSIGN_COMMAND_ENABLED config is set false`);
return GLOBAL_STRINGS.assignCommandDisabledComment;
}
+ if (issue.body && isParentIssue(issue.body)) {
+ logger.info(`Ignore '/start' command from user: identified as parent issue`);
+ return GLOBAL_STRINGS.ignoreStartCommandForParentIssueComment;
+ }
+
const openedPullRequests = await getAvailableOpenedPullRequests(payload.sender.login);
logger.info(`Opened Pull Requests with approved reviews or with no reviews but over 24 hours have passed: ${JSON.stringify(openedPullRequests)}`);
@@ -41,22 +47,22 @@ export const assign = async (body: string) => {
}
if (issue.state == IssueType.CLOSED) {
- logger.info("Skipping '/assign', reason: closed ");
- return "Skipping `/assign` since the issue is closed";
+ logger.info("Skipping '/start', reason: closed ");
+ return "Skipping `/start` since the issue is closed";
}
const _assignees = payload.issue?.assignees;
const assignees = _assignees ?? [];
if (assignees.length !== 0) {
- logger.info(`Skipping '/assign', reason: already assigned. assignees: ${assignees.length > 0 ? assignees.map((i) => i.login).join() : "NoAssignee"}`);
- return "Skipping `/assign` since the issue is already assigned";
+ logger.info(`Skipping '/start', reason: already assigned. assignees: ${assignees.length > 0 ? assignees.map((i) => i.login).join() : "NoAssignee"}`);
+ return "Skipping `/start` since the issue is already assigned";
}
// get the time label from the `labels`
const labels = payload.issue?.labels;
if (!labels) {
logger.info(`No labels to calculate timeline`);
- return "Skipping `/assign` since no issue labels are set to calculate the timeline";
+ return "Skipping `/start` since no issue labels are set to calculate the timeline";
}
const timeLabelsDefined = config.price.timeLabels;
const timeLabelsAssigned: LabelItem[] = [];
@@ -72,30 +78,23 @@ export const assign = async (body: string) => {
if (timeLabelsAssigned.length == 0) {
logger.info(`No time labels to calculate timeline`);
- return "Skipping `/assign` since no time labels are set to calculate the timeline";
+ return "Skipping `/start` since no time labels are set to calculate the timeline";
}
- const sorted = timeLabelsAssigned.sort((a, b) => a.weight - b.weight);
+ const sorted = timeLabelsAssigned.sort((a, b) => calculateWeight(a) - calculateWeight(b));
const targetTimeLabel = sorted[0];
- const duration = targetTimeLabel.value;
+ const duration = calculateDuration(targetTimeLabel);
if (!duration) {
logger.info(`Missing configure for time label: ${targetTimeLabel.name}`);
- return "Skipping `/assign` since configuration is missing for the following labels";
+ return "Skipping `/start` since configuration is missing for the following labels";
}
const startTime = new Date().getTime();
const endTime = new Date(startTime + duration * 1000);
- const { reason, value } = await getWalletMultiplier(payload.sender.login, id?.toString());
-
- const multiplier = value?.toFixed(2) || "1.00";
-
const comment = {
deadline: endTime.toUTCString().replace("GMT", "UTC"),
wallet: (await getWalletAddress(payload.sender.login)) || "Please set your wallet address to use `/wallet 0x0000...0000`",
- multiplier,
- reason,
- bounty: `Permit generation skipped since price label is not set`,
commit: `@${payload.sender.login} ${deadLinePrefix} ${endTime.toUTCString()}`,
tips: `Tips:
@@ -116,11 +115,35 @@ export const assign = async (body: string) => {
const comments = issueComments.sort((a: Comment, b: Comment) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
const latestComment = comments.length > 0 ? comments[0].body : undefined;
if (latestComment && comment.commit != latestComment) {
+ const { multiplier, reason, bounty } = await getMultiplierInfoToDisplay(payload.sender.login, id?.toString(), issue);
+ return tableComment({ ...comment, multiplier, reason, bounty }) + comment.tips;
+ }
+ return;
+};
+
+const getMultiplierInfoToDisplay = async (senderLogin: string, org_id: string, issue: Issue) => {
+ const { reason, value } = await getWalletMultiplier(senderLogin, org_id);
+
+ const multiplier = value?.toFixed(2) || "1.00";
+
+ let _multiplierToDisplay, _reasonToDisplay, _bountyToDisplay;
+
+ if (value == 1) {
+ if (reason) {
+ _multiplierToDisplay = multiplier;
+ _reasonToDisplay = reason;
+ } else {
+ // default mode: normal bounty hunter with default multiplier 1 and no reason
+ // nothing to show about multiplier
+ }
+ } else {
+ _multiplierToDisplay = multiplier;
+ _reasonToDisplay = reason;
+ _bountyToDisplay = `Permit generation skipped since price label is not set`;
const issueDetailed = bountyInfo(issue);
if (issueDetailed.priceLabel) {
- comment.bounty = (+issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4) * value).toString() + " USD";
+ _bountyToDisplay = (+issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4) * value).toString() + " USD";
}
- return tableComment(comment) + comment.tips;
}
- return;
+ return { multiplier: _multiplierToDisplay, reason: _reasonToDisplay, bounty: _bountyToDisplay };
};
diff --git a/src/handlers/comment/handlers/help.ts b/src/handlers/comment/handlers/help.ts
index 9c461e84a..7e2c11bd4 100644
--- a/src/handlers/comment/handlers/help.ts
+++ b/src/handlers/comment/handlers/help.ts
@@ -20,7 +20,7 @@ export const listAvailableCommands = async (body: string) => {
}
if (issue.state == IssueType.CLOSED) {
- logger.info("Skipping '/assign', reason: closed ");
+ logger.info("Skipping '/start', reason: closed ");
return;
}
diff --git a/src/handlers/comment/handlers/index.ts b/src/handlers/comment/handlers/index.ts
index 24d5fa635..5470b3062 100644
--- a/src/handlers/comment/handlers/index.ts
+++ b/src/handlers/comment/handlers/index.ts
@@ -20,10 +20,13 @@ import {
getPayoutConfigByNetworkId,
getTokenSymbol,
getAllIssueAssignEvents,
+ calculateWeight,
} from "../../../helpers";
import { getBotConfig, getBotContext, getLogger } from "../../../bindings";
import { handleIssueClosed } from "../../payout";
import { query } from "./query";
+import { autoPay } from "./payout";
+import { getTargetPriceLabel } from "../../shared";
export * from "./assign";
export * from "./wallet";
@@ -77,24 +80,39 @@ export const issueClosedCallback = async (): Promise => {
*/
export const issueCreatedCallback = async (): Promise => {
+ const logger = getLogger();
const { payload: _payload } = getBotContext();
const config = getBotConfig();
const issue = (_payload as Payload).issue;
if (!issue) return;
const labels = issue.labels;
+
+ const { assistivePricing } = config.mode;
+
+ if (!assistivePricing) {
+ logger.info("Skipping adding label to issue because assistive pricing is disabled");
+ return;
+ }
+
try {
const timeLabels = config.price.timeLabels.filter((item) => labels.map((i) => i.name).includes(item.name));
const priorityLabels = config.price.priorityLabels.filter((item) => labels.map((i) => i.name).includes(item.name));
- if (timeLabels.length === 0 && priorityLabels.length === 0) {
- for (const label of config.price.defaultLabels) {
- const exists = await getLabel(label);
- if (!exists) await createLabel(label);
- await addLabelToIssue(label);
- }
+ const minTimeLabel =
+ timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : config.price.defaultLabels[0];
+ const minPriorityLabel =
+ priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : config.price.defaultLabels[1];
+ if (!timeLabels.length) await addLabelToIssue(minTimeLabel);
+ if (!priorityLabels.length) await addLabelToIssue(minPriorityLabel);
+
+ const targetPriceLabel = getTargetPriceLabel(minTimeLabel, minPriorityLabel);
+ if (targetPriceLabel && !labels.map((i) => i.name).includes(targetPriceLabel)) {
+ const exist = await getLabel(targetPriceLabel);
+ if (!exist) await createLabel(targetPriceLabel, "price");
+ await addLabelToIssue(targetPriceLabel);
}
} catch (err: unknown) {
- return await addCommentToIssue(`Error: ${err}`, issue.number);
+ await addCommentToIssue(`Error: ${err}`, issue.number);
}
};
@@ -192,13 +210,13 @@ export const userCommands = (): UserCommands[] => {
return [
{
- id: IssueCommentCommands.ASSIGN,
+ id: IssueCommentCommands.START,
description: "Assign the origin sender to the issue automatically.",
handler: assign,
callback: commandCallback,
},
{
- id: IssueCommentCommands.UNASSIGN,
+ id: IssueCommentCommands.STOP,
description: "Unassign the origin sender from the issue automatically.",
handler: unassign,
callback: commandCallback,
@@ -216,6 +234,12 @@ export const userCommands = (): UserCommands[] => {
handler: payout,
callback: commandCallback,
},*/
+ {
+ id: IssueCommentCommands.AUTOPAY,
+ description: "Toggle automatic payment for the completion of the current issue.",
+ handler: autoPay,
+ callback: commandCallback,
+ },
{
id: IssueCommentCommands.QUERY,
description: `Comments the users multiplier and address`,
diff --git a/src/handlers/comment/handlers/multiplier.ts b/src/handlers/comment/handlers/multiplier.ts
index 84a3e88bf..a7cc674eb 100644
--- a/src/handlers/comment/handlers/multiplier.ts
+++ b/src/handlers/comment/handlers/multiplier.ts
@@ -49,7 +49,7 @@ export const multiplier = async (body: string) => {
} else if (part.startsWith("@")) {
username = part.substring(1);
} else {
- reason += part.replace(/['"]/g, "") + " ";
+ reason += part.replace(/['"]/g, "");
}
}
username = username || sender;
@@ -68,13 +68,18 @@ export const multiplier = async (body: string) => {
return "Insufficient permissions to update the payout multiplier. You are not an `admin` or `billing_manager`";
}
}
-
logger.info(`Upserting to the wallet table, username: ${username}, bountyMultiplier: ${bountyMultiplier}, reason: ${reason}}`);
await upsertWalletMultiplier(username, bountyMultiplier?.toString(), reason, id?.toString());
- return `Successfully changed the payout multiplier for @${username} to ${bountyMultiplier}. The reason ${
- reason ? `provided is "${reason}"` : "is not provided"
- }.`;
+ if (bountyMultiplier > 1) {
+ return `Successfully changed the payout multiplier for @${username} to ${bountyMultiplier}. The reason ${
+ reason ? `provided is "${reason}"` : "is not provided"
+ }. This feature is designed to limit the contributor's compensation for any bounty on the current repository due to other compensation structures (i.e. salary.) are you sure you want to use a bounty multiplier above 1?`;
+ } else {
+ return `Successfully changed the payout multiplier for @${username} to ${bountyMultiplier}. The reason ${
+ reason ? `provided is "${reason}"` : "is not provided"
+ }.`;
+ }
} else {
logger.error("Invalid body for bountyMultiplier command");
return `Invalid syntax for wallet command \n example usage: "/multiplier @user 0.5 'Multiplier reason'"`;
diff --git a/src/handlers/comment/handlers/payout.ts b/src/handlers/comment/handlers/payout.ts
index da07f5fdb..98ce9f9ae 100644
--- a/src/handlers/comment/handlers/payout.ts
+++ b/src/handlers/comment/handlers/payout.ts
@@ -2,7 +2,8 @@ import { getBotContext, getLogger } from "../../../bindings";
import { Payload } from "../../../types";
import { IssueCommentCommands } from "../commands";
import { handleIssueClosed } from "../../payout";
-import { getAllIssueComments } from "../../../helpers";
+import { getAllIssueComments, getUserPermission } from "../../../helpers";
+import { GLOBAL_STRINGS } from "../../../configs";
export const payout = async (body: string) => {
const { payload: _payload } = getBotContext();
@@ -40,3 +41,26 @@ export const payout = async (body: string) => {
const response = await handleIssueClosed();
return response;
};
+
+export const autoPay = async (body: string) => {
+ const context = getBotContext();
+ const _payload = context.payload;
+ const logger = getLogger();
+
+ const payload = _payload as Payload;
+ logger.info(`Received '/autopay' command from user: ${payload.sender.login}`);
+
+ const pattern = /^\/autopay (true|false)$/;
+ const res = body.match(pattern);
+
+ if (res) {
+ const userPermission = await getUserPermission(payload.sender.login, context);
+ if (userPermission !== "admin" && userPermission !== "billing_manager") {
+ return "You must be an `admin` or `billing_manager` to toggle automatic payments for completed issues.";
+ }
+ if (res.length > 1) {
+ return `${GLOBAL_STRINGS.autopayComment} **${res[1]}**`;
+ }
+ }
+ return "Invalid body for autopay command: e.g. /autopay false";
+};
diff --git a/src/handlers/comment/handlers/query.ts b/src/handlers/comment/handlers/query.ts
index ea3317981..efee499d1 100644
--- a/src/handlers/comment/handlers/query.ts
+++ b/src/handlers/comment/handlers/query.ts
@@ -19,7 +19,7 @@ export const query = async (body: string) => {
return `Skipping '/query' because of no issue instance`;
}
- const regex = /^\/query\s@(\w+)$/;
+ const regex = /^\/query\s+@([\w-]+)\s*$/;
const matches = body.match(regex);
const user = matches?.[1];
diff --git a/src/handlers/comment/handlers/table.ts b/src/handlers/comment/handlers/table.ts
index fef8e181e..13bed9e88 100644
--- a/src/handlers/comment/handlers/table.ts
+++ b/src/handlers/comment/handlers/table.ts
@@ -7,9 +7,9 @@ export const tableComment = ({
}: {
deadline: string;
wallet: string;
- multiplier: string;
- reason: string;
- bounty: string;
+ multiplier?: string;
+ reason?: string;
+ bounty?: string;
}) => {
return `
@@ -27,18 +27,9 @@ export const tableComment = ({
Registered Wallet
${wallet}
-
- Payment Multiplier
- ${multiplier}
-
-
- Multiplier Reason
- ${reason}
-
-
- Total Bounty
- ${bounty}
-
+ ${multiplier ? `Payment Multiplier ${multiplier} ` : ``}
+ ${reason ? `Multiplier Reason ${reason} ` : ``}
+ ${bounty ? `Total Bounty ${bounty} ` : ``}
`;
};
diff --git a/src/handlers/comment/handlers/unassign.ts b/src/handlers/comment/handlers/unassign.ts
index 3289ed0b0..91aae184e 100644
--- a/src/handlers/comment/handlers/unassign.ts
+++ b/src/handlers/comment/handlers/unassign.ts
@@ -7,16 +7,16 @@ import { closePullRequestForAnIssue } from "../../assign/index";
export const unassign = async (body: string) => {
const { payload: _payload } = getBotContext();
const logger = getLogger();
- if (body != IssueCommentCommands.UNASSIGN && body.replace(/`/g, "") != IssueCommentCommands.UNASSIGN) {
+ if (body != IssueCommentCommands.STOP && body.replace(/`/g, "") != IssueCommentCommands.STOP) {
logger.info(`Skipping to unassign. body: ${body}`);
return;
}
const payload = _payload as Payload;
- logger.info(`Received '/unassign' command from user: ${payload.sender.login}`);
+ logger.info(`Received '/stop' command from user: ${payload.sender.login}`);
const issue = (_payload as Payload).issue;
if (!issue) {
- logger.info(`Skipping '/unassign' because of no issue instance`);
+ logger.info(`Skipping '/stop' because of no issue instance`);
return;
}
diff --git a/src/handlers/issue/index.ts b/src/handlers/issue/index.ts
new file mode 100644
index 000000000..12e1212ae
--- /dev/null
+++ b/src/handlers/issue/index.ts
@@ -0,0 +1 @@
+export * from "./pre";
diff --git a/src/handlers/issue/pre.ts b/src/handlers/issue/pre.ts
new file mode 100644
index 000000000..bebda5dce
--- /dev/null
+++ b/src/handlers/issue/pre.ts
@@ -0,0 +1,49 @@
+import { extractImportantWords, upsertCommentToIssue, measureSimilarity } from "../../helpers";
+import { getBotContext, getLogger } from "../../bindings";
+import { Issue, Payload } from "../../types";
+
+export const findDuplicateOne = async () => {
+ const logger = getLogger();
+ const context = getBotContext();
+ const payload = context.payload as Payload;
+ const issue = payload.issue;
+
+ if (!issue?.body) return;
+ const importantWords = await extractImportantWords(issue);
+ const perPage = 10;
+ let curPage = 1;
+
+ for (const importantWord of importantWords) {
+ let fetchDone = false;
+ try {
+ while (!fetchDone) {
+ const response = await context.octokit.rest.search.issuesAndPullRequests({
+ q: `${importantWord} repo:${payload.repository.owner.login}/${payload.repository.name} is:issue`,
+ sort: "created",
+ order: "desc",
+ per_page: perPage,
+ page: curPage,
+ });
+ if (response.data.items.length > 0) {
+ for (const result of response.data.items) {
+ if (!result.body) continue;
+ if (result.id === issue.id) continue;
+ const similarity = await measureSimilarity(issue, result as Issue);
+ if (similarity > parseInt(process.env.SIMILARITY_THRESHOLD || "80")) {
+ await upsertCommentToIssue(
+ issue.number,
+ `Similar issue (${result.title}) found at ${result.html_url}.\nSimilarity is about ${similarity}%`,
+ "created"
+ );
+ return;
+ }
+ }
+ }
+ if (response.data.items.length < perPage) fetchDone = true;
+ else curPage++;
+ }
+ } catch (e: unknown) {
+ logger.error(`Could not find any issues, reason: ${e}`);
+ }
+ }
+};
diff --git a/src/handlers/payout/action.ts b/src/handlers/payout/action.ts
index ba36fd52e..fd2aaaaac 100644
--- a/src/handlers/payout/action.ts
+++ b/src/handlers/payout/action.ts
@@ -3,22 +3,28 @@ import { getPenalty, getWalletAddress, getWalletMultiplier, removePenalty } from
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import {
addLabelToIssue,
+ checkUserPermissionForRepoAndOrg,
+ clearAllPriceLabelsOnIssue,
deleteLabel,
generatePermit2Signature,
- getAllIssueAssignEvents,
getAllIssueComments,
getTokenSymbol,
+ savePermitToDB,
wasIssueReopened,
+ getAllIssueAssignEvents,
} from "../../helpers";
import { UserType, Payload, StateReason } from "../../types";
import { shortenEthAddress } from "../../utils";
import { bountyInfo } from "../wildcard";
+import Decimal from "decimal.js";
+import { GLOBAL_STRINGS } from "../../configs";
+import { isParentIssue } from "../pricing";
export const handleIssueClosed = async () => {
const context = getBotContext();
const {
- payout: { paymentToken, rpc, permitBaseUrl, networkId },
- mode: { autoPayMode },
+ payout: { paymentToken, rpc, permitBaseUrl, networkId, privateKey },
+ mode: { paymentPermitMaxPrice },
} = getBotConfig();
const logger = getLogger();
const payload = context.payload as Payload;
@@ -29,11 +35,15 @@ export const handleIssueClosed = async () => {
if (!issue) return;
+ const userHasPermission = await checkUserPermissionForRepoAndOrg(payload.sender.login, context);
+
+ if (!userHasPermission) return "Permit generation skipped because this issue has been closed by an external contributor.";
+
const comments = await getAllIssueComments(issue.number);
const wasReopened = await wasIssueReopened(issue.number);
const claimUrlRegex = new RegExp(`\\((${permitBaseUrl}\\?claim=\\S+)\\)`);
- const permitCommentIdx = comments.findIndex((e) => e.user.type === "Bot" && e.body.match(claimUrlRegex));
+ const permitCommentIdx = comments.findIndex((e) => e.user.type === UserType.Bot && e.body.match(claimUrlRegex));
if (wasReopened && permitCommentIdx !== -1) {
const permitComment = comments[permitCommentIdx];
@@ -80,17 +90,43 @@ export const handleIssueClosed = async () => {
logger.info(`Penalty removed`);
return;
}
-
+ if (privateKey == "") {
+ logger.info("Permit generation skipped because wallet private key is not set");
+ return "Permit generation skipped because wallet private key is not set";
+ }
if (issue.state_reason !== StateReason.COMPLETED) {
logger.info("Permit generation skipped because the issue was not closed as completed");
return "Permit generation skipped because the issue was not closed as completed";
}
+ logger.info(`Checking if the issue is a parent issue.`);
+ if (issue.body && isParentIssue(issue.body)) {
+ logger.error("Permit generation skipped since the issue is identified as parent issue.");
+ await clearAllPriceLabelsOnIssue();
+ return "Permit generation skipped since the issue is identified as parent issue.";
+ }
+
logger.info(`Handling issues.closed event, issue: ${issue.number}`);
- if (!autoPayMode) {
- logger.info(`Skipping to generate permit2 url, reason: { autoPayMode: ${autoPayMode}}`);
- return `Permit generation skipped since autoPayMode is disabled`;
+ for (const botComment of comments.filter((cmt) => cmt.user.type === UserType.Bot).reverse()) {
+ const botCommentBody = botComment.body;
+ if (botCommentBody.includes(GLOBAL_STRINGS.autopayComment)) {
+ const pattern = /\*\*(\w+)\*\*/;
+ const res = botCommentBody.match(pattern);
+ if (res) {
+ if (res[1] === "false") {
+ logger.info(`Skipping to generate permit2 url, reason: autoPayMode for this issue: false`);
+ return `Permit generation skipped since automatic payment for this issue is disabled.`;
+ }
+ break;
+ }
+ }
+ }
+
+ if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) {
+ logger.info(`Skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`);
+ return `Permit generation skipped since paymentPermitMaxPrice is 0`;
}
+
const issueDetailed = bountyInfo(issue);
if (!issueDetailed.isBounty) {
logger.info(`Skipping... its not a bounty`);
@@ -110,26 +146,30 @@ export const handleIssueClosed = async () => {
}
const recipient = await getWalletAddress(assignee.login);
- const { value } = await getWalletMultiplier(assignee.login, id?.toString());
+ if (!recipient || recipient?.trim() === "") {
+ logger.info(`Recipient address is missing`);
+ return;
+ }
+
+ const { value: multiplier } = await getWalletMultiplier(assignee.login, id?.toString());
- if (value === 0) {
+ if (multiplier === 0) {
const errMsg = "Refusing to generate the payment permit because " + `@${assignee.login}` + "'s payment `multiplier` is `0`";
logger.info(errMsg);
return errMsg;
}
- // TODO: add multiplier to the priceInEth
- let priceInEth = (+issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4) * value).toString();
- if (!recipient || recipient?.trim() === "") {
- logger.info(`Recipient address is missing`);
- return;
+ let priceInEth = new Decimal(issueDetailed.priceLabel.substring(7, issueDetailed.priceLabel.length - 4)).mul(multiplier);
+ if (priceInEth.gt(paymentPermitMaxPrice)) {
+ logger.info("Skipping to proceed the payment because bounty payout is higher than paymentPermitMaxPrice");
+ return `Permit generation skipped since issue's bounty is higher than ${paymentPermitMaxPrice}`;
}
// if bounty hunter has any penalty then deduct it from the bounty
const penaltyAmount = await getPenalty(assignee.login, payload.repository.full_name, paymentToken, networkId.toString());
if (penaltyAmount.gt(0)) {
logger.info(`Deducting penalty from bounty`);
- const bountyAmount = ethers.utils.parseUnits(priceInEth, 18);
+ const bountyAmount = ethers.utils.parseUnits(priceInEth.toString(), 18);
const bountyAmountAfterPenalty = bountyAmount.sub(penaltyAmount);
if (bountyAmountAfterPenalty.lte(0)) {
await removePenalty(assignee.login, payload.repository.full_name, paymentToken, networkId.toString(), bountyAmount);
@@ -137,14 +177,15 @@ export const handleIssueClosed = async () => {
logger.info(msg);
return msg;
}
- priceInEth = ethers.utils.formatUnits(bountyAmountAfterPenalty, 18);
+ priceInEth = new Decimal(ethers.utils.formatUnits(bountyAmountAfterPenalty, 18));
}
- const payoutUrl = await generatePermit2Signature(recipient, priceInEth, issue.node_id);
+ const { txData, payoutUrl } = await generatePermit2Signature(recipient, priceInEth, issue.node_id);
const tokenSymbol = await getTokenSymbol(paymentToken, rpc);
const shortenRecipient = shortenEthAddress(recipient, `[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]`.length);
logger.info(`Posting a payout url to the issue, url: ${payoutUrl}`);
- const comment = `### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```";
+ const comment =
+ `#### Task Assignee Reward\n### [ **[ CLAIM ${priceInEth} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n` + "```" + shortenRecipient + "```";
const permitComments = comments.filter((content) => content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot);
if (permitComments.length > 0) {
logger.info(`Skip to generate a permit url because it has been already posted`);
@@ -152,6 +193,7 @@ export const handleIssueClosed = async () => {
}
await deleteLabel(issueDetailed.priceLabel);
await addLabelToIssue("Permitted");
+ await savePermitToDB(assignee.id, txData);
if (penaltyAmount.gt(0)) {
await removePenalty(assignee.login, payload.repository.full_name, paymentToken, networkId.toString(), penaltyAmount);
}
diff --git a/src/handlers/payout/post.ts b/src/handlers/payout/post.ts
index c4d80b408..200fe5131 100644
--- a/src/handlers/payout/post.ts
+++ b/src/handlers/payout/post.ts
@@ -1,7 +1,10 @@
import { getWalletAddress } from "../../adapters/supabase";
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
import { addCommentToIssue, generatePermit2Signature, getAllIssueComments, getIssueDescription, getTokenSymbol, parseComments } from "../../helpers";
-import { MarkdownItem, Payload, UserType, CommentElementPricing } from "../../types";
+import { Incentives, MarkdownItem, Payload, StateReason, UserType } from "../../types";
+import { commentParser } from "../comment";
+import Decimal from "decimal.js";
+import { bountyInfo } from "../wildcard";
const ItemsToExclude: string[] = [MarkdownItem.BlockQuote];
/**
@@ -11,8 +14,8 @@ const ItemsToExclude: string[] = [MarkdownItem.BlockQuote];
export const incentivizeComments = async () => {
const logger = getLogger();
const {
- mode: { incentiveMode },
- price: { baseMultiplier, commentElementPricing },
+ mode: { incentiveMode, paymentPermitMaxPrice },
+ price: { baseMultiplier, incentives },
payout: { paymentToken, rpc },
} = getBotConfig();
if (!incentiveMode) {
@@ -21,26 +24,63 @@ export const incentivizeComments = async () => {
}
const context = getBotContext();
const payload = context.payload as Payload;
- const org = payload.organization?.login;
const issue = payload.issue;
- if (!issue || !org) {
- logger.info(`Incomplete payload. issue: ${issue}, org: ${org}`);
+ if (!issue) {
+ logger.info(`Incomplete payload. issue: ${issue}`);
return;
}
+
+ if (issue.state_reason !== StateReason.COMPLETED) {
+ logger.info("incentivizeComments: comment incentives skipped because the issue was not closed as completed");
+ return;
+ }
+
+ if (!paymentPermitMaxPrice) {
+ logger.info(`incentivizeComments: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`);
+ return;
+ }
+
+ const issueDetailed = bountyInfo(issue);
+ if (!issueDetailed.isBounty) {
+ logger.info(`incentivizeComments: its not a bounty`);
+ return;
+ }
+
+ const comments = await getAllIssueComments(issue.number);
+ const permitComments = comments.filter(
+ (content) => content.body.includes("Conversation Rewards") && content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot
+ );
+ if (permitComments.length > 0) {
+ logger.info(`incentivizeComments: skip to generate a permit url because it has been already posted`);
+ return;
+ }
+
const assignees = issue?.assignees ?? [];
const assignee = assignees.length > 0 ? assignees[0] : undefined;
if (!assignee) {
- logger.info("Skipping payment permit generation because `assignee` is `undefined`.");
+ logger.info("incentivizeComments: skipping payment permit generation because `assignee` is `undefined`.");
return;
}
- const issueComments = await getAllIssueComments(issue.number);
+ const issueComments = await getAllIssueComments(issue.number, "full");
logger.info(`Getting the issue comments done. comments: ${JSON.stringify(issueComments)}`);
const issueCommentsByUser: Record = {};
for (const issueComment of issueComments) {
const user = issueComment.user;
if (user.type == UserType.Bot || user.login == assignee) continue;
- issueCommentsByUser[user.login].push(issueComment.body);
+ const commands = commentParser(issueComment.body);
+ if (commands.length > 0) {
+ logger.info(`Skipping to parse the comment because it contains commands. comment: ${JSON.stringify(issueComment)}`);
+ continue;
+ }
+ if (!issueComment.body_html) {
+ logger.info(`Skipping to parse the comment because body_html is undefined. comment: ${JSON.stringify(issueComment)}`);
+ continue;
+ }
+ if (!issueCommentsByUser[user.login]) {
+ issueCommentsByUser[user.login] = [];
+ }
+ issueCommentsByUser[user.login].push(issueComment.body_html);
}
const tokenSymbol = await getTokenSymbol(paymentToken, rpc);
logger.info(`Filtering by the user type done. commentsByUser: ${JSON.stringify(issueCommentsByUser)}`);
@@ -49,17 +89,25 @@ export const incentivizeComments = async () => {
const reward: Record = {};
// The mapping between gh handle and amount in ETH
- const fallbackReward: Record = {};
- let comment = "";
+ const fallbackReward: Record = {};
+ let comment = `#### Conversation Rewards\n`;
for (const user of Object.keys(issueCommentsByUser)) {
const comments = issueCommentsByUser[user];
const commentsByNode = await parseComments(comments, ItemsToExclude);
- const rewardValue = calculateRewardValue(commentsByNode, commentElementPricing);
+ const rewardValue = calculateRewardValue(commentsByNode, incentives);
+ if (rewardValue.equals(0)) {
+ logger.info(`Skipping to generate a permit url because the reward value is 0. user: ${user}`);
+ continue;
+ }
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
const account = await getWalletAddress(user);
- const amountInETH = ((rewardValue * baseMultiplier) / 1000).toString();
+ const amountInETH = rewardValue.mul(baseMultiplier);
+ if (amountInETH.gt(paymentPermitMaxPrice)) {
+ logger.info(`Skipping comment reward for user ${user} because reward is higher than payment permit max price`);
+ continue;
+ }
if (account) {
- const payoutUrl = await generatePermit2Signature(account, amountInETH, issue.node_id);
+ const { payoutUrl } = await generatePermit2Signature(account, amountInETH, issue.node_id);
comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`;
reward[user] = payoutUrl;
} else {
@@ -76,8 +124,8 @@ export const incentivizeComments = async () => {
export const incentivizeCreatorComment = async () => {
const logger = getLogger();
const {
- mode: { incentiveMode },
- price: { commentElementPricing, issueCreatorMultiplier },
+ mode: { incentiveMode, paymentPermitMaxPrice },
+ price: { incentives, issueCreatorMultiplier },
payout: { paymentToken, rpc },
} = getBotConfig();
if (!incentiveMode) {
@@ -86,29 +134,66 @@ export const incentivizeCreatorComment = async () => {
}
const context = getBotContext();
const payload = context.payload as Payload;
- const org = payload.organization?.login;
const issue = payload.issue;
- if (!issue || !org) {
- logger.info(`Incomplete payload. issue: ${issue}, org: ${org}`);
+ if (!issue) {
+ logger.info(`Incomplete payload. issue: ${issue}`);
return;
}
- const assignees = issue?.assignees ?? [];
+
+ if (issue.state_reason !== StateReason.COMPLETED) {
+ logger.info("incentivizeCreatorComment: comment incentives skipped because the issue was not closed as completed");
+ return;
+ }
+
+ if (paymentPermitMaxPrice == 0 || !paymentPermitMaxPrice) {
+ logger.info(`incentivizeCreatorComment: skipping to generate permit2 url, reason: { paymentPermitMaxPrice: ${paymentPermitMaxPrice}}`);
+ return;
+ }
+
+ const issueDetailed = bountyInfo(issue);
+ if (!issueDetailed.isBounty) {
+ logger.info(`incentivizeCreatorComment: its not a bounty`);
+ return;
+ }
+
+ const comments = await getAllIssueComments(issue.number);
+ const permitComments = comments.filter(
+ (content) => content.body.includes("Task Creator Reward") && content.body.includes("https://pay.ubq.fi?claim=") && content.user.type == UserType.Bot
+ );
+ if (permitComments.length > 0) {
+ logger.info(`incentivizeCreatorComment: skip to generate a permit url because it has been already posted`);
+ return;
+ }
+
+ const assignees = issue.assignees ?? [];
const assignee = assignees.length > 0 ? assignees[0] : undefined;
if (!assignee) {
- logger.info("Skipping payment permit generation because `assignee` is `undefined`.");
+ logger.info("incentivizeCreatorComment: skipping payment permit generation because `assignee` is `undefined`.");
return;
}
- const description = await getIssueDescription(issue.number);
+ const description = await getIssueDescription(issue.number, "html");
+ if (!description) {
+ logger.info(`Skipping to generate a permit url because issue description is empty. description: ${description}`);
+ return;
+ }
logger.info(`Getting the issue description done. description: ${description}`);
const creator = issue.user;
- if (creator?.type === UserType.Bot || creator?.login === issue?.assignee) {
+ if (creator.type === UserType.Bot || creator.login === issue.assignee) {
logger.info("Issue creator assigneed himself or Bot created this issue.");
return;
}
const tokenSymbol = await getTokenSymbol(paymentToken, rpc);
- const result = await generatePermitForComments(creator?.login, [description], issueCreatorMultiplier, commentElementPricing, tokenSymbol, issue.node_id);
+ const result = await generatePermitForComments(
+ creator.login,
+ [description],
+ issueCreatorMultiplier,
+ incentives,
+ tokenSymbol,
+ issue.node_id,
+ paymentPermitMaxPrice
+ );
if (result.payoutUrl) {
logger.info(`Permit url generated for creator. reward: ${result.payoutUrl}`);
@@ -123,19 +208,28 @@ const generatePermitForComments = async (
user: string,
comments: string[],
multiplier: number,
- commentElementPricing: Record,
+ incentives: Incentives,
tokenSymbol: string,
- node_id: string
-): Promise<{ comment: string; payoutUrl?: string; amountInETH?: string }> => {
+ node_id: string,
+ paymentPermitMaxPrice: number
+): Promise<{ comment: string; payoutUrl?: string; amountInETH?: Decimal }> => {
const logger = getLogger();
const commentsByNode = await parseComments(comments, ItemsToExclude);
- const rewardValue = calculateRewardValue(commentsByNode, commentElementPricing);
+ const rewardValue = calculateRewardValue(commentsByNode, incentives);
+ if (rewardValue.equals(0)) {
+ logger.info(`No reward for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
+ return { comment: "" };
+ }
logger.debug(`Comment parsed for the user: ${user}. comments: ${JSON.stringify(commentsByNode)}, sum: ${rewardValue}`);
const account = await getWalletAddress(user);
- const amountInETH = ((rewardValue * multiplier) / 1000).toString();
- let comment = "";
+ const amountInETH = rewardValue.mul(multiplier);
+ if (amountInETH.gt(paymentPermitMaxPrice)) {
+ logger.info(`Skipping issue creator reward for user ${user} because reward is higher than payment permit max price`);
+ return { comment: "" };
+ }
+ let comment = `#### Task Creator Reward\n`;
if (account) {
- const payoutUrl = await generatePermit2Signature(account, amountInETH, node_id);
+ const { payoutUrl } = await generatePermit2Signature(account, amountInETH, node_id);
comment = `${comment}### [ **${user}: [ CLAIM ${amountInETH} ${tokenSymbol.toUpperCase()} ]** ](${payoutUrl})\n`;
return { comment, payoutUrl };
} else {
@@ -146,18 +240,29 @@ const generatePermitForComments = async (
* @dev Calculates the reward values for a given comments. We'll improve the formula whenever we get the better one.
*
* @param comments - The comments to calculate the reward for
- * @param commentElementPricing - The basic price table for reward calculation
+ * @param incentives - The basic price table for reward calculation
* @returns - The reward value
*/
-const calculateRewardValue = (comments: Record, commentElementPricing: CommentElementPricing): number => {
- let sum = 0;
+const calculateRewardValue = (comments: Record, incentives: Incentives): Decimal => {
+ let sum = new Decimal(0);
for (const key of Object.keys(comments)) {
- const rewardValue = commentElementPricing[key];
const value = comments[key];
- if (key == MarkdownItem.Text || key == MarkdownItem.Paragraph) {
- sum += value.length * rewardValue;
+
+ // if it's a text node calculate word count and multiply with the reward value
+ if (key == "#text") {
+ if (!incentives.comment.totals.word) {
+ continue;
+ }
+ const wordReward = new Decimal(incentives.comment.totals.word);
+ const reward = wordReward.mul(value.map((str) => str.trim().split(" ").length).reduce((totalWords, wordCount) => totalWords + wordCount, 0));
+ sum = sum.add(reward);
} else {
- sum += rewardValue;
+ if (!incentives.comment.elements[key]) {
+ continue;
+ }
+ const rewardValue = new Decimal(incentives.comment.elements[key]);
+ const reward = rewardValue.mul(value.length);
+ sum = sum.add(reward);
}
}
diff --git a/src/handlers/pricing/action.ts b/src/handlers/pricing/action.ts
index e32b899bc..cd9dfa8a4 100644
--- a/src/handlers/pricing/action.ts
+++ b/src/handlers/pricing/action.ts
@@ -1,5 +1,6 @@
import { getBotConfig, getBotContext, getLogger } from "../../bindings";
-import { addLabelToIssue, clearAllPriceLabelsOnIssue, createLabel, getLabel } from "../../helpers";
+import { GLOBAL_STRINGS } from "../../configs";
+import { addCommentToIssue, addLabelToIssue, clearAllPriceLabelsOnIssue, createLabel, getLabel, calculateWeight } from "../../helpers";
import { Payload } from "../../types";
import { handleLabelsAccess } from "../access";
import { getTargetPriceLabel } from "../shared";
@@ -12,17 +13,28 @@ export const pricingLabelLogic = async (): Promise => {
if (!payload.issue) return;
const labels = payload.issue.labels;
+ logger.info(`Checking if the issue is a parent issue.`);
+ if (payload.issue.body && isParentIssue(payload.issue.body)) {
+ logger.error("Identified as parent issue. Disabling price label.");
+ const issuePrices = labels.filter((label) => label.name.toString().startsWith("Price:"));
+ if (issuePrices.length) {
+ await addCommentToIssue(GLOBAL_STRINGS.skipPriceLabelGenerationComment, payload.issue.number);
+ await clearAllPriceLabelsOnIssue();
+ }
+ return;
+ }
const valid = await handleLabelsAccess();
if (!valid) {
return;
}
+ const { assistivePricing } = config.mode;
const timeLabels = config.price.timeLabels.filter((item) => labels.map((i) => i.name).includes(item.name));
const priorityLabels = config.price.priorityLabels.filter((item) => labels.map((i) => i.name).includes(item.name));
- const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
- const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
+ const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;
+ const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;
const targetPriceLabel = getTargetPriceLabel(minTimeLabel, minPriorityLabel);
if (targetPriceLabel) {
@@ -33,7 +45,8 @@ export const pricingLabelLogic = async (): Promise => {
await clearAllPriceLabelsOnIssue();
const exist = await getLabel(targetPriceLabel);
- if (!exist) {
+
+ if (assistivePricing && !exist) {
logger.info(`${targetPriceLabel} doesn't exist on the repo, creating...`);
await createLabel(targetPriceLabel, "price");
}
@@ -44,3 +57,8 @@ export const pricingLabelLogic = async (): Promise => {
logger.info(`Skipping action...`);
}
};
+
+export const isParentIssue = (body: string) => {
+ const parentPattern = /-\s+\[( |x)\]\s+#\d+/;
+ return body.match(parentPattern);
+};
diff --git a/src/handlers/pricing/pre.ts b/src/handlers/pricing/pre.ts
index 29703bda7..4d048d1ca 100644
--- a/src/handlers/pricing/pre.ts
+++ b/src/handlers/pricing/pre.ts
@@ -1,5 +1,5 @@
import { getBotConfig, getLogger } from "../../bindings";
-import { createLabel, listLabelsForRepo } from "../../helpers";
+import { calculateWeight, createLabel, listLabelsForRepo } from "../../helpers";
import { calculateBountyPrice } from "../shared";
/**
@@ -10,12 +10,19 @@ export const validatePriceLabels = async (): Promise => {
const config = getBotConfig();
const logger = getLogger();
+ const { assistivePricing } = config.mode;
+
+ if (!assistivePricing) {
+ logger.info(`Assistive Pricing is disabled`);
+ return;
+ }
+
const timeLabels = config.price.timeLabels.map((i) => i.name);
const priorityLabels = config.price.priorityLabels.map((i) => i.name);
const aiLabels: string[] = [];
for (const timeLabel of config.price.timeLabels) {
for (const priorityLabel of config.price.priorityLabels) {
- const targetPrice = calculateBountyPrice(timeLabel.weight, priorityLabel.weight, config.price.baseMultiplier);
+ const targetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier);
const targetPriceLabel = `Price: ${targetPrice} USD`;
aiLabels.push(targetPriceLabel);
}
diff --git a/src/handlers/processors.ts b/src/handlers/processors.ts
index d64e07eaa..a98c65611 100644
--- a/src/handlers/processors.ts
+++ b/src/handlers/processors.ts
@@ -3,16 +3,17 @@ import { closePullRequestForAnIssue, commentWithAssignMessage } from "./assign";
import { pricingLabelLogic, validatePriceLabels } from "./pricing";
import { checkBountiesToUnassign, collectAnalytics, checkWeeklyUpdate } from "./wildcard";
import { nullHandler } from "./shared";
-import { handleComment, issueClosedCallback, issueReopenedCallback } from "./comment";
+import { handleComment, issueClosedCallback, issueCreatedCallback, issueReopenedCallback } from "./comment";
import { checkPullRequests } from "./assign/auto";
import { createDevPoolPR } from "./pull-request";
import { runOnPush } from "./push";
import { incentivizeComments, incentivizeCreatorComment } from "./payout";
+import { findDuplicateOne } from "./issue";
export const processors: Record = {
[GithubEvent.ISSUES_OPENED]: {
pre: [nullHandler],
- action: [nullHandler], // SHOULD not set `issueCreatedCallback` until the exploit issue resolved. https://github.com/ubiquity/ubiquibot/issues/535
+ action: [findDuplicateOne, issueCreatedCallback],
post: [nullHandler],
},
[GithubEvent.ISSUES_REOPENED]: {
diff --git a/src/handlers/shared/pricing.ts b/src/handlers/shared/pricing.ts
index 65c1079d0..0a543bc7c 100644
--- a/src/handlers/shared/pricing.ts
+++ b/src/handlers/shared/pricing.ts
@@ -1,4 +1,5 @@
import { getBotConfig } from "../../bindings";
+import { calculateWeight } from "../../helpers";
export const calculateBountyPrice = (timeValue: number, priorityValue: number, baseValue?: number): number => {
const botConfig = getBotConfig();
@@ -12,8 +13,8 @@ export const getTargetPriceLabel = (timeLabel: string | undefined, priorityLabel
const botConfig = getBotConfig();
let targetPriceLabel: string | undefined = undefined;
if (timeLabel && priorityLabel) {
- const timeWeight = botConfig.price.timeLabels.find((item) => item.name === timeLabel)?.weight;
- const priorityWeight = botConfig.price.priorityLabels.find((item) => item.name === priorityLabel)?.weight;
+ const timeWeight = calculateWeight(botConfig.price.timeLabels.find((item) => item.name === timeLabel));
+ const priorityWeight = calculateWeight(botConfig.price.priorityLabels.find((item) => item.name === priorityLabel));
if (timeWeight && priorityWeight) {
const bountyPrice = calculateBountyPrice(timeWeight, priorityWeight);
targetPriceLabel = `Price: ${bountyPrice} USD`;
diff --git a/src/handlers/wildcard/analytics.ts b/src/handlers/wildcard/analytics.ts
index 40e861ac7..62f24edf9 100644
--- a/src/handlers/wildcard/analytics.ts
+++ b/src/handlers/wildcard/analytics.ts
@@ -1,6 +1,6 @@
import { getMaxIssueNumber, upsertIssue, upsertUser } from "../../adapters/supabase";
import { getBotConfig, getLogger } from "../../bindings";
-import { listIssuesForRepo, getUser } from "../../helpers";
+import { listIssuesForRepo, getUser, calculateWeight } from "../../helpers";
import { Issue, IssueType, User, UserProfile } from "../../types";
import { getTargetPriceLabel } from "../shared";
@@ -24,8 +24,8 @@ export const bountyInfo = (
const isBounty = timeLabels.length > 0 && priorityLabels.length > 0;
- const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
- const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (a.weight < b.weight ? a : b)).name : undefined;
+ const minTimeLabel = timeLabels.length > 0 ? timeLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;
+ const minPriorityLabel = priorityLabels.length > 0 ? priorityLabels.reduce((a, b) => (calculateWeight(a) < calculateWeight(b) ? a : b)).name : undefined;
const priceLabel = getTargetPriceLabel(minTimeLabel, minPriorityLabel);
@@ -83,26 +83,28 @@ export const collectAnalytics = async (): Promise => {
.toString()}`
);
- await Promise.all(userProfilesToUpsert.map(async (i) => upsertUser(i)));
+ await Promise.all(userProfilesToUpsert.map((i) => upsertUser(i)));
// No need to update the record for the bounties already closed
const bountiesToUpsert = bounties.filter((bounty) => (bounty.state === IssueType.CLOSED ? bounty.number > maximumIssueNumber : true));
logger.info(`Upserting bounties: ${bountiesToUpsert.map((i) => i.title).toString()}`);
await Promise.all(
- bountiesToUpsert.map(async (i) => {
+ bountiesToUpsert.map((i) => {
const additions = bountyInfo(i as Issue);
if (additions.timelabel && additions.priorityLabel && additions.priceLabel)
- await upsertIssue(i as Issue, {
+ return upsertIssue(i as Issue, {
labels: {
timeline: additions.timelabel,
priority: additions.priorityLabel,
price: additions.priceLabel,
},
});
+ return undefined;
})
);
if (issues.length < perPage) fetchDone = true;
else curPage++;
}
+ logger.info("Collecting analytics finished...");
};
diff --git a/src/handlers/wildcard/unassign.ts b/src/handlers/wildcard/unassign.ts
index b94b2c1c7..8e06ecebd 100644
--- a/src/handlers/wildcard/unassign.ts
+++ b/src/handlers/wildcard/unassign.ts
@@ -72,7 +72,7 @@ const checkBountyToUnassign = async (issue: Issue): Promise => {
);
} else
await addCommentToIssue(
- `${askUpdate} @${assignees[0]}? If you would like to release the bounty back to the DevPool, please comment \`/unassign\` \nLast activity time: ${lastActivity}`,
+ `${askUpdate} @${assignees[0]}? If you would like to release the bounty back to the DevPool, please comment \`/stop\` \nLast activity time: ${lastActivity}`,
issue.number
);
}
diff --git a/src/handlers/wildcard/weekly.ts b/src/handlers/wildcard/weekly.ts
index 3f52bcd5b..8d9dbdc70 100644
--- a/src/handlers/wildcard/weekly.ts
+++ b/src/handlers/wildcard/weekly.ts
@@ -2,7 +2,7 @@ import { run } from "./weekly/action";
import { getLastWeeklyTime, updateLastWeeklyTime } from "../../adapters/supabase";
import { getBotConfig, getBotContext } from "../../bindings";
-const SEVEN_DAYS = 604800; // 7 days in seconds
+const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
export const checkWeeklyUpdate = async () => {
const { log } = getBotContext();
@@ -13,10 +13,12 @@ export const checkWeeklyUpdate = async () => {
log.info(`Skipping to collect the weekly analytics, reason: mode=${disableAnalytics}`);
return;
}
- const curTime = Date.now() / 1000;
+ const curTime = new Date();
const lastTime = await getLastWeeklyTime();
- if (lastTime + SEVEN_DAYS < curTime) {
+ if (lastTime == undefined || new Date(lastTime.getTime() + SEVEN_DAYS) < curTime) {
await run();
await updateLastWeeklyTime(curTime);
+ } else {
+ log.info(`Skipping to collect the weekly analytics because 7 days have not passed`);
}
};
diff --git a/src/handlers/wildcard/weekly/action.ts b/src/handlers/wildcard/weekly/action.ts
index 4a26ca9fe..09e06e878 100644
--- a/src/handlers/wildcard/weekly/action.ts
+++ b/src/handlers/wildcard/weekly/action.ts
@@ -6,7 +6,7 @@ import path from "path";
import axios from "axios";
import Jimp from "jimp";
import nodeHtmlToImage from "node-html-to-image";
-import { getBotContext } from "../../../bindings";
+import { getBotConfig, getBotContext } from "../../../bindings";
import { telegramPhotoNotifier } from "../../../adapters";
import { Context } from "probot";
import { Payload } from "../../../types";
@@ -33,31 +33,41 @@ const fetchEvents = async (context: Context): Promise => {
const perPage = 30;
while (shouldFetch) {
try {
+ let events;
if (payload.organization) {
- const { data: pubOrgEvents, headers } = await context.octokit.activity.listPublicOrgEvents({
+ events = await context.octokit.activity.listPublicOrgEvents({
org: payload.organization.login,
per_page: perPage,
page: currentPage,
});
+ } else {
+ events = await context.octokit.activity.listRepoEvents({
+ owner: payload.repository.owner.login,
+ repo: payload.repository.name,
+ per_page: perPage,
+ page: currentPage,
+ });
+ }
+ const pubEvents = events.data;
+ const headers = events.headers;
- await checkRateLimitGit(headers);
+ await checkRateLimitGit(headers);
- pubOrgEvents.forEach((elem: any) => {
- const elemTimestamp = new Date(elem.created_at as string).getTime();
- if (elemTimestamp <= startTimestamp && elemTimestamp >= endTimestamp) {
- //pass
- elemList.push(elem);
- } else if (elemTimestamp > startTimestamp) {
- //outta range
- //skip
- } else {
- //fail end
- shouldFetch = false;
- }
- });
+ pubEvents.forEach((elem: any) => {
+ const elemTimestamp = new Date(elem.created_at as string).getTime();
+ if (elemTimestamp <= startTimestamp && elemTimestamp >= endTimestamp) {
+ //pass
+ elemList.push(elem);
+ } else if (elemTimestamp > startTimestamp) {
+ //outta range
+ //skip
+ } else {
+ //fail end
+ shouldFetch = false;
+ }
+ });
- currentPage++;
- }
+ currentPage++;
} catch (error) {
shouldFetch = false;
}
@@ -339,5 +349,12 @@ export const run = async () => {
const dataPadded = await fetchSummary(repository);
await htmlImage(summaryInfo);
await compositeImage();
- await processTelegram(dataPadded);
+
+ const { telegram } = getBotConfig();
+ if (telegram.token) {
+ await processTelegram(dataPadded);
+ } else {
+ const log = context.log;
+ log.info("Skipping processTelegram because no token was set.");
+ }
};
diff --git a/src/helpers/comment.ts b/src/helpers/comment.ts
index 825958cc9..cba6c165f 100644
--- a/src/helpers/comment.ts
+++ b/src/helpers/comment.ts
@@ -1,44 +1,41 @@
-type MdastNode = {
- type: string;
- value: string;
- children: MdastNode[];
+import * as parse5 from "parse5";
+
+type Node = {
+ nodeName: string;
+ tagName?: string;
+ value?: string;
+ childNodes?: Node[];
};
-const cachedResult: Record = {};
-const traverse = (node: MdastNode, itemsToExclude: string[]): Record => {
- if (!cachedResult[node.type]) {
- cachedResult[node.type] = [];
+
+const traverse = (result: Record, node: Node, itemsToExclude: string[]): Record => {
+ if (itemsToExclude.includes(node.nodeName)) {
+ return result;
}
- if (!itemsToExclude.includes(node.type)) {
- // skip pushing if the node type has been excluded
- cachedResult[node.type].push(node.value);
- } else if (node.children.length > 0) {
- node.children.forEach((child) => traverse(child, itemsToExclude));
+ if (!result[node.nodeName]) {
+ result[node.nodeName] = [];
}
- return cachedResult;
-};
+ result[node.nodeName].push(node.value?.trim() ?? "");
+
+ if (node.childNodes && node.childNodes.length > 0) {
+ node.childNodes.forEach((child) => traverse(result, child, itemsToExclude));
+ }
-export const parseComments = async (comments: string[], itemsToExclude: string[]): Promise> => {
- const { fromMarkdown } = await import("mdast-util-from-markdown");
- const { gfmFromMarkdown } = await import("mdast-util-gfm");
- const { gfm } = await import("micromark-extension-gfm");
+ return result;
+};
+export const parseComments = (comments: string[], itemsToExclude: string[]): Record => {
const result: Record = {};
+
for (const comment of comments) {
- const tree = fromMarkdown(comment, {
- extensions: [gfm()],
- mdastExtensions: [gfmFromMarkdown()],
- });
-
- const parsedContent = traverse(tree as MdastNode, itemsToExclude);
- for (const key of Object.keys(parsedContent)) {
- if (Object.keys(result).includes(key)) {
- result[key].push(...parsedContent[key]);
- } else {
- result[key] = parsedContent[key];
- }
- }
+ const fragment = parse5.parseFragment(comment);
+ traverse(result, fragment as Node, itemsToExclude);
+ }
+
+ // remove empty values
+ if (result["#text"]) {
+ result["#text"] = result["#text"].filter((str) => str.length > 0);
}
return result;
diff --git a/src/helpers/index.ts b/src/helpers/index.ts
index 1d447003d..ad8ee0cb9 100644
--- a/src/helpers/index.ts
+++ b/src/helpers/index.ts
@@ -8,3 +8,4 @@ export * from "./contracts";
export * from "./comment";
export * from "./payout";
export * from "./file";
+export * from "./similarity";
diff --git a/src/helpers/issue.ts b/src/helpers/issue.ts
index cb07df7ee..00ccd0e93 100644
--- a/src/helpers/issue.ts
+++ b/src/helpers/issue.ts
@@ -1,13 +1,13 @@
import { Context } from "probot";
-import { getBotContext, getLogger } from "../bindings";
+import { getBotContext, getLogger, loadConfig } from "../bindings";
import { AssignEvent, Comment, IssueType, Payload } from "../types";
import { checkRateLimitGit } from "../utils";
-import { DEFAULT_TIME_RANGE_FOR_MAX_ISSUE, DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED } from "../configs";
export const clearAllPriceLabelsOnIssue = async (): Promise => {
const context = getBotContext();
const logger = getLogger();
const payload = context.payload as Payload;
+
if (!payload.issue) return;
const labels = payload.issue.labels;
@@ -48,7 +48,13 @@ export const addLabelToIssue = async (labelName: string) => {
}
};
-export const listIssuesForRepo = async (state: "open" | "closed" | "all" = "open", per_page = 30, page = 1) => {
+export const listIssuesForRepo = async (
+ state: "open" | "closed" | "all" = "open",
+ per_page = 30,
+ page = 1,
+ sort: "created" | "updated" | "comments" = "created",
+ direction: "desc" | "asc" = "desc"
+) => {
const context = getBotContext();
const payload = context.payload as Payload;
@@ -58,6 +64,8 @@ export const listIssuesForRepo = async (state: "open" | "closed" | "all" = "open
state,
per_page,
page,
+ sort,
+ direction,
});
await checkRateLimitGit(response.headers);
@@ -175,7 +183,7 @@ export const getCommentsOfIssue = async (issue_number: number): Promise => {
+export const getIssueDescription = async (issue_number: number, format: "raw" | "html" | "text" = "raw"): Promise => {
const context = getBotContext();
const logger = getLogger();
const payload = context.payload as Payload;
@@ -186,17 +194,30 @@ export const getIssueDescription = async (issue_number: number): Promise
owner: payload.repository.owner.login,
repo: payload.repository.name,
issue_number: issue_number,
+ mediaType: {
+ format,
+ },
});
await checkRateLimitGit(response?.headers);
- if (response.data.body) result = response.data.body;
+ switch (format) {
+ case "raw":
+ result = response.data.body ?? "";
+ break;
+ case "html":
+ result = response.data.body_html ?? "";
+ break;
+ case "text":
+ result = response.data.body_text ?? "";
+ break;
+ }
} catch (e: unknown) {
logger.debug(`Getting issue description failed!, reason: ${e}`);
}
return result;
};
-export const getAllIssueComments = async (issue_number: number): Promise => {
+export const getAllIssueComments = async (issue_number: number, format: "raw" | "html" | "text" | "full" = "raw"): Promise => {
const context = getBotContext();
const payload = context.payload as Payload;
@@ -211,6 +232,9 @@ export const getAllIssueComments = async (issue_number: number): Promise => {
+ const permissionForRepo = await checkUserPermissionForRepo(username, context);
+ const permissionForOrg = await checkUserPermissionForOrg(username, context);
+ const userPermission = await getUserPermission(username, context);
+
+ return permissionForOrg || permissionForRepo || userPermission === "admin" || userPermission === "billing_manager";
+};
+
+export const checkUserPermissionForRepo = async (username: string, context: Context): Promise => {
+ const logger = getLogger();
+ const payload = context.payload as Payload;
+
+ try {
+ const res = await context.octokit.rest.repos.checkCollaborator({
+ owner: payload.repository.owner.login,
+ repo: payload.repository.name,
+ username,
+ });
+
+ return res.status === 204;
+ } catch (e: unknown) {
+ logger.error(`Checking if user permisson for repo failed!, reason: ${e}`);
+ return false;
+ }
+};
+
+export const checkUserPermissionForOrg = async (username: string, context: Context): Promise => {
+ const logger = getLogger();
+ const payload = context.payload as Payload;
+ if (!payload.organization) return false;
+
+ try {
+ await context.octokit.rest.orgs.checkMembershipForUser({
+ org: payload.organization.login,
+ username,
+ });
+ // skipping status check due to type error of checkMembershipForUser function of octokit
+ return true;
+ } catch (e: unknown) {
+ logger.error(`Checking if user permisson for org failed!, reason: ${e}`);
+ return false;
+ }
+};
+
export const getUserPermission = async (username: string, context: Context): Promise => {
const logger = getLogger();
const payload = context.payload as Payload;
@@ -396,8 +464,24 @@ export const removeLabel = async (name: string) => {
}
};
+export const getAllPullRequests = async (context: Context, state: "open" | "closed" | "all" = "open") => {
+ const prArr = [];
+ let fetchDone = false;
+ const perPage = 100;
+ let curPage = 1;
+ while (!fetchDone) {
+ const prs = await getPullRequests(context, state, perPage, curPage);
+
+ // push the objects to array
+ prArr.push(...prs);
+
+ if (prs.length < perPage) fetchDone = true;
+ else curPage++;
+ }
+ return prArr;
+};
// Use `context.octokit.rest` to get the pull requests for the repository
-export const getPullRequests = async (context: Context, state: "open" | "closed" | "all" = "open") => {
+export const getPullRequests = async (context: Context, state: "open" | "closed" | "all" = "open", per_page: number, page: number) => {
const logger = getLogger();
const payload = context.payload as Payload;
try {
@@ -405,6 +489,8 @@ export const getPullRequests = async (context: Context, state: "open" | "closed"
owner: payload.repository.owner.login,
repo: payload.repository.name,
state,
+ per_page,
+ page,
});
return pulls;
} catch (e: unknown) {
@@ -532,7 +618,7 @@ export const getOpenedPullRequestsForAnIssue = async (issueNumber: number, userN
export const getOpenedPullRequests = async (username: string) => {
const context = getBotContext();
- const prs = await getPullRequests(context, "open");
+ const prs = await getAllPullRequests(context, "open");
return prs.filter((pr) => !pr.draft && (pr.user?.login === username || !username));
};
@@ -554,8 +640,10 @@ export const getCommitsOnPullRequest = async (pullNumber: number) => {
};
export const getAvailableOpenedPullRequests = async (username: string) => {
- if (!DEFAULT_TIME_RANGE_FOR_MAX_ISSUE_ENABLED) return [];
const context = getBotContext();
+ const botConfig = await loadConfig(context);
+ if (!botConfig.unassign.timeRangeForMaxIssueEnabled) return [];
+
const opened_prs = await getOpenedPullRequests(username);
const result = [];
@@ -569,7 +657,7 @@ export const getAvailableOpenedPullRequests = async (username: string) => {
if (approvedReviews) result.push(pr);
}
- if (reviews.length === 0 && (new Date().getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60) >= DEFAULT_TIME_RANGE_FOR_MAX_ISSUE) {
+ if (reviews.length === 0 && (new Date().getTime() - new Date(pr.created_at).getTime()) / (1000 * 60 * 60) >= botConfig.unassign.timeRangeForMaxIssue) {
result.push(pr);
}
}
diff --git a/src/helpers/label.ts b/src/helpers/label.ts
index 0f30bb265..a02d6c5eb 100644
--- a/src/helpers/label.ts
+++ b/src/helpers/label.ts
@@ -4,6 +4,7 @@ import { COLORS } from "../configs";
import { calculateBountyPrice } from "../handlers";
import { Label, Payload } from "../types";
import { deleteLabel } from "./issue";
+import { calculateWeight } from "../helpers";
export const listLabelsForRepo = async (per_page?: number, page?: number): Promise => {
const context = getBotContext();
@@ -67,11 +68,11 @@ export const updateLabelsFromBaseRate = async (owner: string, repo: string, cont
for (const timeLabel of config.price.timeLabels) {
for (const priorityLabel of config.price.priorityLabels) {
- const targetPrice = calculateBountyPrice(timeLabel.weight, priorityLabel.weight, config.price.baseMultiplier);
+ const targetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), config.price.baseMultiplier);
const targetPriceLabel = `Price: ${targetPrice} USD`;
newLabels.push(targetPriceLabel);
- const previousTargetPrice = calculateBountyPrice(timeLabel.weight, priorityLabel.weight, previousBaseRate);
+ const previousTargetPrice = calculateBountyPrice(calculateWeight(timeLabel), calculateWeight(priorityLabel), previousBaseRate);
const previousTargetPriceLabel = `Price: ${previousTargetPrice} USD`;
previousLabels.push(previousTargetPriceLabel);
}
diff --git a/src/helpers/permit.ts b/src/helpers/permit.ts
index de17b98eb..ba1bcaaa0 100644
--- a/src/helpers/permit.ts
+++ b/src/helpers/permit.ts
@@ -1,10 +1,49 @@
import { MaxUint256, PermitTransferFrom, SignatureTransfer } from "@uniswap/permit2-sdk";
import { BigNumber, ethers } from "ethers";
-import { getBotConfig, getLogger } from "../bindings";
+import { getBotConfig, getBotContext, getLogger } from "../bindings";
import { keccak256, toUtf8Bytes } from "ethers/lib/utils";
+import Decimal from "decimal.js";
+import { Payload } from "../types";
+import { savePermit } from "../adapters/supabase";
const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on all networks
+export type Permit = {
+ id: number;
+ createdAt: Date;
+ organizationId: number | null;
+ repositoryId: number;
+ issueId: number;
+ networkId: number;
+ bountyHunterId: number;
+ bountyHunterAddress: string;
+ tokenAddress: string;
+ payoutAmount: string;
+ nonce: string;
+ deadline: string;
+ signature: string;
+ walletOwnerAddress: string;
+};
+
+export type InsertPermit = Omit;
+
+type TxData = {
+ permit: {
+ permitted: {
+ token: string;
+ amount: string;
+ };
+ nonce: string;
+ deadline: string;
+ };
+ transferDetails: {
+ to: string;
+ requestedAmount: string;
+ };
+ owner: string;
+ signature: string;
+};
+
/**
* Generates permit2 signature data with `spender` and `amountInETH`
*
@@ -13,7 +52,7 @@ const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // same on
*
* @returns Permit2 url including base64 encocded data
*/
-export const generatePermit2Signature = async (spender: string, amountInEth: string, identifier: string): Promise => {
+export const generatePermit2Signature = async (spender: string, amountInEth: Decimal, identifier: string): Promise<{ txData: TxData; payoutUrl: string }> => {
const {
payout: { networkId, privateKey, permitBaseUrl, rpc, paymentToken },
} = getBotConfig();
@@ -26,7 +65,7 @@ export const generatePermit2Signature = async (spender: string, amountInEth: str
// token we are permitting to be transferred
token: paymentToken,
// amount we are permitting to be transferred
- amount: ethers.utils.parseUnits(amountInEth, 18),
+ amount: ethers.utils.parseUnits(amountInEth.toString(), 18),
},
// who can transfer the tokens
spender: spender,
@@ -38,7 +77,7 @@ export const generatePermit2Signature = async (spender: string, amountInEth: str
const { domain, types, values } = SignatureTransfer.getPermitData(permitTransferFromData, PERMIT2_ADDRESS, networkId);
const signature = await adminWallet._signTypedData(domain, types, values);
- const txData = {
+ const txData: TxData = {
permit: {
permitted: {
token: permitTransferFromData.permitted.token,
@@ -57,7 +96,43 @@ export const generatePermit2Signature = async (spender: string, amountInEth: str
const base64encodedTxData = Buffer.from(JSON.stringify(txData)).toString("base64");
- const result = `${permitBaseUrl}?claim=${base64encodedTxData}&network=${networkId}`;
- logger.info(`Generated permit2 url: ${result}`);
- return result;
+ const payoutUrl = `${permitBaseUrl}?claim=${base64encodedTxData}&network=${networkId}`;
+ logger.info(`Generated permit2 url: ${payoutUrl}`);
+ return { txData, payoutUrl };
+};
+
+export const savePermitToDB = async (bountyHunterId: number, txData: TxData): Promise => {
+ const logger = getLogger();
+
+ const context = getBotContext();
+ const payload = context.payload as Payload;
+ const issue = payload.issue;
+ const repository = payload.repository;
+ const organization = payload.organization;
+ if (!issue || !repository) {
+ logger.error("Cannot save permit to DB, missing issue, repository or organization");
+ throw new Error("Cannot save permit to DB, missing issue, repository or organization");
+ }
+
+ const { payout } = getBotConfig();
+ const { networkId } = payout;
+
+ const permit: InsertPermit = {
+ organizationId: organization?.id ?? null,
+ repositoryId: repository?.id,
+ issueId: issue?.id,
+ networkId: networkId,
+ bountyHunterId: bountyHunterId,
+ tokenAddress: txData.permit.permitted.token,
+ payoutAmount: txData.permit.permitted.amount,
+ bountyHunterAddress: txData.transferDetails.to,
+ nonce: txData.permit.nonce,
+ deadline: txData.permit.deadline,
+ signature: txData.signature,
+ walletOwnerAddress: txData.owner,
+ };
+
+ const savedPermit = await savePermit(permit);
+ logger.info(`Saved permit to DB: ${JSON.stringify(savedPermit)}`);
+ return savedPermit;
};
diff --git a/src/helpers/shared.ts b/src/helpers/shared.ts
index a1a0d4c6a..646e0d8e9 100644
--- a/src/helpers/shared.ts
+++ b/src/helpers/shared.ts
@@ -1,5 +1,5 @@
import { getBotContext } from "../bindings";
-import { Payload, UserType } from "../types";
+import { LabelItem, Payload, UserType } from "../types";
const contextNamesToSkip = ["workflow_run"];
@@ -19,3 +19,27 @@ export const shouldSkip = (): { skip: boolean; reason: string } => {
};
export const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
+
+export const calculateWeight = (label: LabelItem | undefined): number => {
+ if (!label) return 0;
+ const matches = label.name.match(/\d+/);
+ const number = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0;
+ if (label.name.toLowerCase().includes("priority")) return number;
+ if (label.name.toLowerCase().includes("hour")) return number * 0.125;
+ if (label.name.toLowerCase().includes("day")) return 1 + (number - 1) * 0.25;
+ if (label.name.toLowerCase().includes("week")) return number + 1;
+ if (label.name.toLowerCase().includes("month")) return 5 + (number - 1) * 8;
+ return 0;
+};
+
+export const calculateDuration = (label: LabelItem): number => {
+ if (!label) return 0;
+ const matches = label.name.match(/\d+/);
+ if (label.name.toLowerCase().includes("priority")) return 0;
+ const number = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0;
+ if (label.name.toLowerCase().includes("hour")) return number * 3600;
+ if (label.name.toLowerCase().includes("day")) return number * 86400;
+ if (label.name.toLowerCase().includes("week")) return number * 604800;
+ if (label.name.toLowerCase().includes("month")) return number * 2592000;
+ return 0;
+};
diff --git a/src/helpers/similarity.ts b/src/helpers/similarity.ts
new file mode 100644
index 000000000..a5743e562
--- /dev/null
+++ b/src/helpers/similarity.ts
@@ -0,0 +1,104 @@
+import { getLogger } from "../bindings";
+import axios, { AxiosError } from "axios";
+import { ajv } from "../utils";
+import { Static, Type } from "@sinclair/typebox";
+import { backOff } from "exponential-backoff";
+import { Issue } from "../types";
+
+export const extractImportantWords = async (issue: Issue): Promise => {
+ const res = await getAnswerFromChatGPT(
+ "",
+ `${
+ process.env.CHATGPT_USER_PROMPT_FOR_IMPORTANT_WORDS ||
+ "I need your help to find important words (e.g. unique adjectives) from github issue below and I want to parse them easily so please separate them using #(No other contexts needed). Please separate the words by # so I can parse them easily. Please answer simply as I only need the important words. Here is the issue content.\n"
+ } '${`Issue title: ${issue.title}\nIssue content: ${issue.body}`}'`,
+ parseFloat(process.env.IMPORTANT_WORDS_AI_TEMPERATURE || "0")
+ );
+ if (res === "") return [];
+ return res.split(/[,# ]/);
+};
+
+export const measureSimilarity = async (first: Issue, second: Issue): Promise => {
+ const res = await getAnswerFromChatGPT(
+ "",
+ `${(
+ process.env.CHATGPT_USER_PROMPT_FOR_MEASURE_SIMILARITY ||
+ 'I have two github issues and I need to measure the possibility of the 2 issues are the same content (I need to parse the % so other contents are not needed and give me only the number in %).\n Give me in number format and add % after the number.\nDo not tell other things since I only need the number (e.g. 85%). Here are two issues:\n 1. "%first%"\n2. "%second%"'
+ )
+ .replace("%first%", `Issue title: ${first.title}\nIssue content: ${first.body}`)
+ .replace("%second%", `Issue title: ${second.title}\nIssue content: ${second.body}`)}`,
+ parseFloat(process.env.MEASURE_SIMILARITY_AI_TEMPERATURE || "0")
+ );
+ const matches = res.match(/\d+/);
+ const percent = matches && matches.length > 0 ? parseInt(matches[0]) || 0 : 0;
+ return percent;
+};
+
+const ChatMessageSchema = Type.Object({
+ content: Type.String(),
+});
+
+const ChoiceSchema = Type.Object({
+ message: ChatMessageSchema,
+});
+
+const ChoicesSchema = Type.Object({
+ choices: Type.Array(ChoiceSchema),
+});
+
+type Choices = Static;
+
+export const getAnswerFromChatGPT = async (systemPrompt: string, userPrompt: string, temperature = 0, max_tokens = 1500): Promise => {
+ const logger = getLogger();
+ const body = JSON.stringify({
+ model: "gpt-3.5-turbo",
+ messages: [
+ {
+ role: "system",
+ content: systemPrompt,
+ },
+ {
+ role: "user",
+ content: userPrompt,
+ },
+ ],
+ max_tokens,
+ temperature,
+ stream: false,
+ });
+ const config = {
+ method: "post",
+ url: `${process.env.OPENAI_API_HOST || "https://api.openai.com"}/v1/chat/completions`,
+ headers: {
+ Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
+ "Content-Type": "application/json",
+ },
+ data: body,
+ };
+ try {
+ const response = await backOff(() => axios(config), {
+ startingDelay: 6000,
+ retry: (e: AxiosError) => {
+ if (e.response && e.response.status === 429) return true;
+ return false;
+ },
+ });
+ const data: Choices = response.data;
+ const validate = ajv.compile(ChoicesSchema);
+ const valid = validate(data);
+ if (!valid) {
+ logger.error(`Error occured from OpenAI`);
+ return "";
+ }
+ const { choices: choice } = data;
+ if (choice.length <= 0) {
+ logger.error(`No result from OpenAI`);
+ return "";
+ }
+ const answer = choice[0].message.content;
+ return answer;
+ } catch (error) {
+ logger.error(`Getting response from ChatGPT failed: ${error}`);
+ return "";
+ }
+};
diff --git a/src/types/config.ts b/src/types/config.ts
index 855ba2049..cda063811 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -1,21 +1,36 @@
import { Static, Type } from "@sinclair/typebox";
+import { Level } from "../adapters/supabase";
const LabelItemSchema = Type.Object({
name: Type.String(),
- weight: Type.Number(),
- value: Type.Optional(Type.Number()),
});
export type LabelItem = Static;
-const CommentElementPricingSchema = Type.Record(Type.String(), Type.Number());
-export type CommentElementPricing = Static;
+const CommentIncentivesSchema = Type.Object({
+ elements: Type.Record(Type.String(), Type.Number()),
+ totals: Type.Object({
+ word: Type.Number(),
+ }),
+});
+export type CommentIncentives = Static;
+
+const IncentivesSchema = Type.Object({
+ comment: CommentIncentivesSchema,
+});
+export type Incentives = Static;
+
+const CommandItemSchema = Type.Object({
+ name: Type.String(),
+ enabled: Type.Boolean(),
+});
+export type CommandItem = Static;
export const PriceConfigSchema = Type.Object({
baseMultiplier: Type.Number(),
issueCreatorMultiplier: Type.Number(),
timeLabels: Type.Array(LabelItemSchema),
priorityLabels: Type.Array(LabelItemSchema),
- commentElementPricing: CommentElementPricingSchema,
+ incentives: IncentivesSchema,
defaultLabels: Type.Array(Type.String()),
});
export type PriceConfig = Static;
@@ -41,12 +56,15 @@ export const PayoutConfigSchema = Type.Object({
export const UnassignConfigSchema = Type.Object({
followUpTime: Type.Number(),
disqualifyTime: Type.Number(),
+ timeRangeForMaxIssue: Type.Number(),
+ timeRangeForMaxIssueEnabled: Type.Boolean(),
});
export const ModeSchema = Type.Object({
- autoPayMode: Type.Boolean(),
+ paymentPermitMaxPrice: Type.Number(),
disableAnalytics: Type.Boolean(),
incentiveMode: Type.Boolean(),
+ assistivePricing: Type.Boolean(),
});
export const AssignSchema = Type.Object({
@@ -54,8 +72,9 @@ export const AssignSchema = Type.Object({
});
export const LogConfigSchema = Type.Object({
- level: Type.String(),
- ingestionKey: Type.String(),
+ logEnvironment: Type.String(),
+ level: Type.Enum(Level),
+ retryLimit: Type.Number(),
});
export const SodiumSchema = Type.Object({
@@ -67,6 +86,9 @@ export const CommentsSchema = Type.Object({
promotionComment: Type.String(),
});
+export const CommandConfigSchema = Type.Array(CommandItemSchema);
+
+export type CommandConfig = Static;
export const WalletSchema = Type.Object({
registerWalletWithVerification: Type.Boolean(),
});
@@ -82,6 +104,7 @@ export const BotConfigSchema = Type.Object({
assign: AssignSchema,
sodium: SodiumSchema,
comments: CommentsSchema,
+ command: CommandConfigSchema,
wallet: WalletSchema,
});
diff --git a/src/types/payload.ts b/src/types/payload.ts
index aa80cf454..527363b6d 100644
--- a/src/types/payload.ts
+++ b/src/types/payload.ts
@@ -97,6 +97,7 @@ const IssueSchema = Type.Object({
events_url: Type.String(),
html_url: Type.String(),
id: Type.Number(),
+ body: Type.Any(),
node_id: Type.String(),
number: Type.Number(),
title: Type.String(),
@@ -235,6 +236,8 @@ export const CommentSchema = Type.Object({
updated_at: Type.String({ format: "date-time" }),
author_association: Type.String(),
body: Type.String(),
+ body_html: Type.Optional(Type.String()),
+ body_text: Type.Optional(Type.String()),
});
export type Comment = Static;
diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts
index 415305870..e3965208b 100644
--- a/src/utils/helpers.ts
+++ b/src/utils/helpers.ts
@@ -1,5 +1,6 @@
-import { CommentElementPricing } from "../types";
-import { WideLabel, WideOrgConfig, WideRepoConfig } from "./private";
+import { Incentives } from "./private";
+import { Level } from "../adapters/supabase";
+import { CommandObj, WideLabel, WideOrgConfig, WideRepoConfig } from "./private";
interface Configs {
parsedRepo?: WideRepoConfig;
@@ -7,10 +8,31 @@ interface Configs {
parsedDefault: WideRepoConfig;
}
+export const getNumericLevel = (level: Level) => {
+ switch (level) {
+ case Level.ERROR:
+ return 0;
+ case Level.WARN:
+ return 1;
+ case Level.INFO:
+ return 2;
+ case Level.HTTP:
+ return 3;
+ case Level.VERBOSE:
+ return 4;
+ case Level.DEBUG:
+ return 5;
+ case Level.SILLY:
+ return 6;
+ default:
+ return -1; // Invalid level
+ }
+};
+
export const getNetworkId = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => {
- if (parsedRepo && parsedRepo["evm-network-id"] && !Number.isNaN(Number(parsedRepo["evm-network-id"]))) {
+ if (parsedRepo && parsedRepo["evm-network-id"] !== undefined && !Number.isNaN(Number(parsedRepo["evm-network-id"]))) {
return Number(parsedRepo["evm-network-id"]);
- } else if (parsedOrg && parsedOrg["evm-network-id"] && !Number.isNaN(Number(parsedOrg["evm-network-id"]))) {
+ } else if (parsedOrg && parsedOrg["evm-network-id"] !== undefined && !Number.isNaN(Number(parsedOrg["evm-network-id"]))) {
return Number(parsedOrg["evm-network-id"]);
} else {
return Number(parsedDefault["evm-network-id"]);
@@ -18,9 +40,9 @@ export const getNetworkId = ({ parsedRepo, parsedOrg, parsedDefault }: Configs):
};
export const getBaseMultiplier = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => {
- if (parsedRepo && parsedRepo["price-multiplier"] && !Number.isNaN(Number(parsedRepo["price-multiplier"]))) {
+ if (parsedRepo && parsedRepo["price-multiplier"] !== undefined && !Number.isNaN(Number(parsedRepo["price-multiplier"]))) {
return Number(parsedRepo["price-multiplier"]);
- } else if (parsedOrg && parsedOrg["price-multiplier"] && !Number.isNaN(Number(parsedOrg["price-multiplier"]))) {
+ } else if (parsedOrg && parsedOrg["price-multiplier"] !== undefined && !Number.isNaN(Number(parsedOrg["price-multiplier"]))) {
return Number(parsedOrg["price-multiplier"]);
} else {
return Number(parsedDefault["price-multiplier"]);
@@ -28,9 +50,9 @@ export const getBaseMultiplier = ({ parsedRepo, parsedOrg, parsedDefault }: Conf
};
export const getCreatorMultiplier = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => {
- if (parsedRepo && parsedRepo["issue-creator-multiplier"] && !Number.isNaN(Number(parsedRepo["issue-creator-multiplier"]))) {
+ if (parsedRepo && parsedRepo["issue-creator-multiplier"] !== undefined && !Number.isNaN(Number(parsedRepo["issue-creator-multiplier"]))) {
return Number(parsedRepo["issue-creator-multiplier"]);
- } else if (parsedOrg && parsedOrg["issue-creator-multiplier"] && !Number.isNaN(Number(parsedOrg["issue-creator-multiplier"]))) {
+ } else if (parsedOrg && parsedOrg["issue-creator-multiplier"] !== undefined && !Number.isNaN(Number(parsedOrg["issue-creator-multiplier"]))) {
return Number(parsedOrg["issue-creator-multiplier"]);
} else {
return Number(parsedDefault["issue-creator-multiplier"]);
@@ -38,49 +60,74 @@ export const getCreatorMultiplier = ({ parsedRepo, parsedOrg, parsedDefault }: C
};
export const getTimeLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): WideLabel[] => {
- if (parsedRepo && parsedRepo["time-labels"] && Array.isArray(parsedRepo["time-labels"]) && parsedRepo["time-labels"].length > 0) {
+ if (parsedRepo && parsedRepo["time-labels"] !== undefined && Array.isArray(parsedRepo["time-labels"]) && parsedRepo["time-labels"].length > 0) {
return parsedRepo["time-labels"];
- } else if (parsedOrg && parsedOrg["time-labels"] && Array.isArray(parsedOrg["time-labels"]) && parsedOrg["time-labels"].length > 0) {
+ } else if (parsedOrg && parsedOrg["time-labels"] !== undefined && Array.isArray(parsedOrg["time-labels"]) && parsedOrg["time-labels"].length > 0) {
return parsedOrg["time-labels"];
} else {
return parsedDefault["time-labels"] as WideLabel[];
}
};
+export const getCommandSettings = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): CommandObj[] => {
+ if (parsedRepo && parsedRepo["command-settings"] && Array.isArray(parsedRepo["command-settings"]) && parsedRepo["command-settings"].length > 0) {
+ return parsedRepo["command-settings"];
+ } else if (parsedOrg && parsedOrg["command-settings"] && Array.isArray(parsedOrg["command-settings"]) && parsedOrg["command-settings"].length > 0) {
+ return parsedOrg["command-settings"];
+ } else {
+ return parsedDefault["command-settings"] as CommandObj[];
+ }
+};
+
export const getPriorityLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): WideLabel[] => {
- if (parsedRepo && parsedRepo["priority-labels"] && Array.isArray(parsedRepo["priority-labels"]) && parsedRepo["priority-labels"].length > 0) {
+ if (parsedRepo && parsedRepo["priority-labels"] !== undefined && Array.isArray(parsedRepo["priority-labels"]) && parsedRepo["priority-labels"].length > 0) {
return parsedRepo["priority-labels"];
- } else if (parsedOrg && parsedOrg["priority-labels"] && Array.isArray(parsedOrg["priority-labels"]) && parsedOrg["priority-labels"].length > 0) {
+ } else if (
+ parsedOrg &&
+ parsedOrg["priority-labels"] !== undefined &&
+ Array.isArray(parsedOrg["priority-labels"]) &&
+ parsedOrg["priority-labels"].length > 0
+ ) {
return parsedOrg["priority-labels"];
} else {
return parsedDefault["priority-labels"] as WideLabel[];
}
};
-export const getCommentItemPrice = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): CommentElementPricing => {
- if (parsedRepo && parsedRepo["comment-element-pricing"]) {
- return parsedRepo["comment-element-pricing"];
- } else if (parsedOrg && parsedOrg["comment-element-pricing"]) {
- return parsedOrg["comment-element-pricing"];
+export const getIncentives = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): Incentives => {
+ if (parsedRepo && parsedRepo["incentives"]) {
+ return parsedRepo["incentives"];
+ } else if (parsedOrg && parsedOrg["incentives"]) {
+ return parsedOrg["incentives"];
+ } else {
+ return parsedDefault["incentives"] as Incentives;
+ }
+};
+
+export const getPaymentPermitMaxPrice = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => {
+ if (parsedRepo && parsedRepo["payment-permit-max-price"] && typeof parsedRepo["payment-permit-max-price"] === "number") {
+ return Number(parsedRepo["payment-permit-max-price"]);
+ } else if (parsedOrg && parsedOrg["payment-permit-max-price"] && typeof parsedOrg["payment-permit-max-price"] === "number") {
+ return Number(parsedOrg["payment-permit-max-price"]);
} else {
- return parsedDefault["comment-element-pricing"] as CommentElementPricing;
+ return Number(parsedDefault["payment-permit-max-price"]);
}
};
-export const getAutoPayMode = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => {
- if (parsedRepo && parsedRepo["auto-pay-mode"] && typeof parsedRepo["auto-pay-mode"] === "boolean") {
- return parsedRepo["auto-pay-mode"];
- } else if (parsedOrg && parsedOrg["auto-pay-mode"] && typeof parsedOrg["auto-pay-mode"] === "boolean") {
- return parsedOrg["auto-pay-mode"];
+export const getAssistivePricing = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => {
+ if (parsedRepo && parsedRepo["assistive-pricing"] && typeof parsedRepo["assistive-pricing"] === "boolean") {
+ return parsedRepo["assistive-pricing"];
+ } else if (parsedOrg && parsedOrg["assistive-pricing"] && typeof parsedOrg["assistive-pricing"] === "boolean") {
+ return parsedOrg["assistive-pricing"];
} else {
- return parsedDefault["auto-pay-mode"] as boolean;
+ return parsedDefault["assistive-pricing"] as boolean;
}
};
export const getAnalyticsMode = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => {
- if (parsedRepo && parsedRepo["disable-analytics"] && typeof parsedRepo["disable-analytics"] === "boolean") {
+ if (parsedRepo && parsedRepo["disable-analytics"] !== undefined && typeof parsedRepo["disable-analytics"] === "boolean") {
return parsedRepo["disable-analytics"];
- } else if (parsedOrg && parsedOrg["disable-analytics"] && typeof parsedOrg["disable-analytics"] === "boolean") {
+ } else if (parsedOrg && parsedOrg["disable-analytics"] !== undefined && typeof parsedOrg["disable-analytics"] === "boolean") {
return parsedOrg["disable-analytics"];
} else {
return parsedDefault["disable-analytics"] as boolean;
@@ -88,9 +135,9 @@ export const getAnalyticsMode = ({ parsedRepo, parsedOrg, parsedDefault }: Confi
};
export const getPromotionComment = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): string => {
- if (parsedRepo && parsedRepo["promotion-comment"] && typeof parsedRepo["promotion-comment"] === "string") {
+ if (parsedRepo && parsedRepo["promotion-comment"] !== undefined && typeof parsedRepo["promotion-comment"] === "string") {
return parsedRepo["promotion-comment"];
- } else if (parsedOrg && parsedOrg["promotion-comment"] && typeof parsedOrg["promotion-comment"] === "string") {
+ } else if (parsedOrg && parsedOrg["promotion-comment"] !== undefined && typeof parsedOrg["promotion-comment"] === "string") {
return parsedOrg["promotion-comment"];
} else {
return parsedDefault["promotion-comment"] as string;
@@ -98,9 +145,9 @@ export const getPromotionComment = ({ parsedRepo, parsedOrg, parsedDefault }: Co
};
export const getIncentiveMode = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => {
- if (parsedRepo && parsedRepo["comment-incentives"] && typeof parsedRepo["comment-incentives"] === "boolean") {
+ if (parsedRepo && parsedRepo["comment-incentives"] !== undefined && typeof parsedRepo["comment-incentives"] === "boolean") {
return parsedRepo["comment-incentives"];
- } else if (parsedOrg && parsedOrg["comment-incentives"] && typeof parsedOrg["comment-incentives"] === "boolean") {
+ } else if (parsedOrg && parsedOrg["comment-incentives"] !== undefined && typeof parsedOrg["comment-incentives"] === "boolean") {
return parsedOrg["comment-incentives"];
} else {
return parsedDefault["comment-incentives"] as boolean;
@@ -108,9 +155,9 @@ export const getIncentiveMode = ({ parsedRepo, parsedOrg, parsedDefault }: Confi
};
export const getBountyHunterMax = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): number => {
- if (parsedRepo && parsedRepo["max-concurrent-assigns"] && !Number.isNaN(Number(parsedRepo["max-concurrent-assigns"]))) {
+ if (parsedRepo && parsedRepo["max-concurrent-assigns"] !== undefined && !Number.isNaN(Number(parsedRepo["max-concurrent-assigns"]))) {
return Number(parsedRepo["max-concurrent-assigns"]);
- } else if (parsedOrg && parsedOrg["max-concurrent-assigns"] && !Number.isNaN(Number(parsedOrg["max-concurrent-assigns"]))) {
+ } else if (parsedOrg && parsedOrg["max-concurrent-assigns"] !== undefined && !Number.isNaN(Number(parsedOrg["max-concurrent-assigns"]))) {
return Number(parsedOrg["max-concurrent-assigns"]);
} else {
return Number(parsedDefault["max-concurrent-assigns"]);
@@ -118,9 +165,9 @@ export const getBountyHunterMax = ({ parsedRepo, parsedOrg, parsedDefault }: Con
};
export const getDefaultLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): string[] => {
- if (parsedRepo && parsedRepo["default-labels"]) {
+ if (parsedRepo && parsedRepo["default-labels"] !== undefined) {
return parsedRepo["default-labels"];
- } else if (parsedOrg && parsedOrg["default-labels"]) {
+ } else if (parsedOrg && parsedOrg["default-labels"] !== undefined) {
return parsedOrg["default-labels"];
} else {
return parsedDefault["default-labels"] as string[];
@@ -128,9 +175,9 @@ export const getDefaultLabels = ({ parsedRepo, parsedOrg, parsedDefault }: Confi
};
export const getRegisterWalletWithVerification = ({ parsedRepo, parsedOrg, parsedDefault }: Configs): boolean => {
- if (parsedRepo && parsedRepo["register-wallet-with-verification"] && typeof parsedRepo["register-wallet-with-verification"] === "boolean") {
+ if (parsedRepo && parsedRepo["register-wallet-with-verification"] !== undefined && typeof parsedRepo["register-wallet-with-verification"] === "boolean") {
return Boolean(parsedRepo["register-wallet-with-verification"]);
- } else if (parsedOrg && parsedOrg["register-wallet-with-verification"] && typeof parsedOrg["register-wallet-with-verification"] === "boolean") {
+ } else if (parsedOrg && parsedOrg["register-wallet-with-verification"] !== undefined && typeof parsedOrg["register-wallet-with-verification"] === "boolean") {
return Boolean(parsedOrg["register-wallet-with-verification"]);
} else {
return Boolean(parsedDefault["register-wallet-with-verification"]);
diff --git a/src/utils/private.ts b/src/utils/private.ts
index 0a6e98064..36ff92f8c 100644
--- a/src/utils/private.ts
+++ b/src/utils/private.ts
@@ -2,10 +2,9 @@ import _sodium from "libsodium-wrappers";
import YAML from "yaml";
import { Payload } from "../types";
import { Context } from "probot";
-import { readFileSync } from "fs";
import {
getAnalyticsMode,
- getAutoPayMode,
+ getPaymentPermitMaxPrice,
getBaseMultiplier,
getCreatorMultiplier,
getBountyHunterMax,
@@ -13,18 +12,22 @@ import {
getNetworkId,
getPriorityLabels,
getTimeLabels,
- getCommentItemPrice,
getDefaultLabels,
getPromotionComment,
+ getIncentives,
+ getAssistivePricing,
+ getCommandSettings,
getRegisterWalletWithVerification,
} from "./helpers";
+import DEFAULT_CONFIG_JSON from "../../ubiquibot-config-default.json";
+
const CONFIG_REPO = "ubiquibot-config";
-const KEY_PATH = ".github/ubiquibot-config.yml";
+const CONFIG_PATH = ".github/ubiquibot-config.yml";
const KEY_NAME = "private-key-encrypted";
const KEY_PREFIX = "HSK_";
-export const getConfigSuperset = async (context: Context, type: "org" | "repo"): Promise => {
+export const getConfigSuperset = async (context: Context, type: "org" | "repo", filePath: string): Promise => {
try {
const payload = context.payload as Payload;
const repo = type === "org" ? CONFIG_REPO : payload.repository.name;
@@ -33,7 +36,7 @@ export const getConfigSuperset = async (context: Context, type: "org" | "repo"):
const { data } = await context.octokit.rest.repos.getContent({
owner,
repo,
- path: KEY_PATH,
+ path: filePath,
mediaType: {
format: "raw",
},
@@ -46,8 +49,22 @@ export const getConfigSuperset = async (context: Context, type: "org" | "repo"):
export interface WideLabel {
name: string;
- weight: number;
- value?: number | undefined;
+}
+
+export interface CommentIncentives {
+ elements: Record;
+ totals: {
+ word: number;
+ };
+}
+
+export interface Incentives {
+ comment: CommentIncentives;
+}
+
+export interface CommandObj {
+ name: string;
+ enabled: boolean;
}
export interface WideConfig {
@@ -56,12 +73,14 @@ export interface WideConfig {
"issue-creator-multiplier": number;
"time-labels"?: WideLabel[];
"priority-labels"?: WideLabel[];
- "auto-pay-mode"?: boolean;
+ "payment-permit-max-price"?: number;
+ "command-settings"?: CommandObj[];
"promotion-comment"?: string;
"disable-analytics"?: boolean;
"comment-incentives"?: boolean;
+ "assistive-pricing"?: boolean;
"max-concurrent-assigns"?: number;
- "comment-element-pricing"?: Record;
+ incentives?: Incentives;
"default-labels"?: string[];
"register-wallet-with-verification"?: boolean;
}
@@ -84,9 +103,16 @@ export const parseYAML = (data?: string): WideConfig | undefined => {
}
};
-export const getDefaultConfig = (): WideRepoConfig => {
- const defaultConfig = readFileSync(`${__dirname}/../../ubiquibot-config-default.yml`, "utf8");
- return parseYAML(defaultConfig) as WideRepoConfig;
+export const getOrgAndRepoFromPath = (path: string) => {
+ const parts = path.split("/");
+
+ if (parts.length !== 2) {
+ return { org: null, repo: null };
+ }
+
+ const [org, repo] = parts;
+
+ return { org, repo };
};
export const getPrivateKey = async (cipherText: string): Promise => {
@@ -130,27 +156,29 @@ export const getScalarKey = async (X25519_PRIVATE_KEY: string | undefined): Prom
};
export const getWideConfig = async (context: Context) => {
- const orgConfig = await getConfigSuperset(context, "org");
- const repoConfig = await getConfigSuperset(context, "repo");
+ const orgConfig = await getConfigSuperset(context, "org", CONFIG_PATH);
+ const repoConfig = await getConfigSuperset(context, "repo", CONFIG_PATH);
const parsedOrg: WideOrgConfig | undefined = parseYAML(orgConfig);
const parsedRepo: WideRepoConfig | undefined = parseYAML(repoConfig);
- const parsedDefault: WideRepoConfig = getDefaultConfig();
+ const parsedDefault: WideRepoConfig = DEFAULT_CONFIG_JSON;
const privateKeyDecrypted = parsedOrg && parsedOrg[KEY_NAME] ? await getPrivateKey(parsedOrg[KEY_NAME]) : undefined;
const configs = { parsedRepo, parsedOrg, parsedDefault };
const configData = {
networkId: getNetworkId(configs),
privateKey: privateKeyDecrypted ?? "",
+ assistivePricing: getAssistivePricing(configs),
+ commandSettings: getCommandSettings(configs),
baseMultiplier: getBaseMultiplier(configs),
issueCreatorMultiplier: getCreatorMultiplier(configs),
timeLabels: getTimeLabels(configs),
priorityLabels: getPriorityLabels(configs),
- autoPayMode: getAutoPayMode(configs),
+ paymentPermitMaxPrice: getPaymentPermitMaxPrice(configs),
disableAnalytics: getAnalyticsMode(configs),
bountyHunterMax: getBountyHunterMax(configs),
incentiveMode: getIncentiveMode(configs),
- commentElementPricing: getCommentItemPrice(configs),
+ incentives: getIncentives(configs),
defaultLabels: getDefaultLabels(configs),
promotionComment: getPromotionComment(configs),
registerWalletWithVerification: getRegisterWalletWithVerification(configs),
diff --git a/supabase/README.md b/supabase/README.md
index bdfa8ab90..397affb3f 100644
--- a/supabase/README.md
+++ b/supabase/README.md
@@ -56,5 +56,6 @@ For more information about arguments, please go through [here](https://supabase.
### Database Operation
- `supabase migration new MIGRATION_NAME`: It will create a migration file in supabase/migrations folder.
-- `supabase db push -p PASSWORD`: Update database schema on supabase platform
+- `supabase migration repair --status reverted`: Revert a given migration file.
+- `supabase db push`: Update database schema on supabase platform
- `supabase gen types typescript > src/adapters/supabase/types/database.ts --linked`: Generate typescript types from the supabase project linked
diff --git a/supabase/migrations/20230730153700_permit.sql b/supabase/migrations/20230730153700_permit.sql
new file mode 100644
index 000000000..8fbfb87ed
--- /dev/null
+++ b/supabase/migrations/20230730153700_permit.sql
@@ -0,0 +1,17 @@
+
+CREATE TABLE IF NOT EXISTS permits (
+ id bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, -- auto-incrementing unique id
+ created_at timestamptz NOT NULL,
+ organization_id bigint,
+ repository_id bigint NOT NULL,
+ issue_id bigint NOT NULL,
+ network_id int NOT NULL,
+ bounty_hunter_id bigint NOT NULL,
+ bounty_hunter_address text NOT NULL,
+ token_address text NOT NULL,
+ payout_amount text NOT NULL,
+ nonce text NOT NULL,
+ deadline text NOT NULL,
+ signature text NOT NULL,
+ wallet_owner_address text NOT NULL
+);
\ No newline at end of file
diff --git a/supabase/migrations/20230803154507_setup_permits_policies.sql b/supabase/migrations/20230803154507_setup_permits_policies.sql
new file mode 100644
index 000000000..7c2828580
--- /dev/null
+++ b/supabase/migrations/20230803154507_setup_permits_policies.sql
@@ -0,0 +1,13 @@
+ALTER TABLE permits ENABLE ROW LEVEL SECURITY;
+ALTER TABLE wallets ENABLE ROW LEVEL SECURITY;
+ALTER TABLE users ENABLE ROW LEVEL SECURITY;
+ALTER TABLE issues ENABLE ROW LEVEL SECURITY;
+ALTER TABLE weekly ENABLE ROW LEVEL SECURITY;
+ALTER TABLE access ENABLE ROW LEVEL SECURITY;
+ALTER TABLE penalty ENABLE ROW LEVEL SECURITY;
+ALTER TABLE multiplier ENABLE ROW LEVEL SECURITY;
+
+CREATE POLICY "Enable read access for frontend" ON "public"."permits"
+AS PERMISSIVE FOR SELECT
+TO public
+USING (true)
\ No newline at end of file
diff --git a/supabase/migrations/20230818030324_create_logs.sql b/supabase/migrations/20230818030324_create_logs.sql
new file mode 100644
index 000000000..2bc8647c2
--- /dev/null
+++ b/supabase/migrations/20230818030324_create_logs.sql
@@ -0,0 +1,16 @@
+-- Create `logs` table
+CREATE TABLE logs (
+ id SERIAL PRIMARY KEY,
+ repo_name TEXT,
+ org_name TEXT,
+ comment_id INT,
+ issue_number INT,
+ log_message TEXT,
+ level INT,
+ timestamp TIMESTAMPTZ DEFAULT current_timestamp
+);
+
+CREATE INDEX idx_timestamp ON logs (timestamp);
+
+-- Enable RLS for `logs` table
+ALTER TABLE logs ENABLE ROW LEVEL SECURITY
\ No newline at end of file
diff --git a/supabase/migrations/20230826183500_change_wallet_type.sql b/supabase/migrations/20230826183500_change_wallet_type.sql
new file mode 100644
index 000000000..bdf90e17c
--- /dev/null
+++ b/supabase/migrations/20230826183500_change_wallet_type.sql
@@ -0,0 +1,4 @@
+ALTER TABLE wallets ALTER COLUMN wallet_address TYPE text;
+
+ALTER TABLE access DROP CONSTRAINT access_pkey;
+ALTER TABLE access ADD CONSTRAINT access_pkey PRIMARY KEY (user_name, repository);
\ No newline at end of file
diff --git a/supabase/migrations/20230829101134_setup_cron.sql b/supabase/migrations/20230829101134_setup_cron.sql
new file mode 100644
index 000000000..883436a87
--- /dev/null
+++ b/supabase/migrations/20230829101134_setup_cron.sql
@@ -0,0 +1,11 @@
+-- Enable the pg_cron extension
+CREATE EXTENSION IF NOT EXISTS pg_cron;
+
+-- Runs everyday at 03:00 AM to cleanup logs that are older than a week
+-- Use the cron time format to modify the trigger time if necessary
+SELECT
+ cron.schedule(
+ 'logs-cleaner', -- Job name
+ '0 3 * * *', -- Everyday at 03:00 AM
+ $$DELETE FROM logs WHERE timestamp < now() - INTERVAL '1 week'$$
+ );
diff --git a/tsconfig.json b/tsconfig.json
index c9c5da1c6..6dacfb57e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,7 +22,6 @@
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
-
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
@@ -32,13 +31,11 @@
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
-
/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
-
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
// "baseUrl": "./src" /* Base directory to resolve non-absolute module names. */,
@@ -50,17 +47,14 @@
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
-
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
-
/* Experimental Options */
"experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
-
/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
"resolveJsonModule": true,
diff --git a/ubiquibot-config-default.json b/ubiquibot-config-default.json
new file mode 100644
index 000000000..2cf139e15
--- /dev/null
+++ b/ubiquibot-config-default.json
@@ -0,0 +1,89 @@
+{
+ "evm-network-id": 100,
+ "price-multiplier": 1,
+ "issue-creator-multiplier": 2,
+ "payment-permit-max-price": 9007199254740991,
+ "max-concurrent-assigns": 9007199254740991,
+ "assistive-pricing": false,
+ "disable-analytics": false,
+ "comment-incentives": false,
+ "register-wallet-with-verification": false,
+ "promotion-comment": "\nIf you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot! ",
+ "default-labels": [],
+ "time-labels": [
+ {
+ "name": "Time: <1 Hour"
+ },
+ {
+ "name": "Time: <1 Day"
+ },
+ {
+ "name": "Time: <1 Week"
+ },
+ {
+ "name": "Time: <2 Weeks"
+ },
+ {
+ "name": "Time: <1 Month"
+ }
+ ],
+ "priority-labels": [
+ {
+ "name": "Priority: 1 (Normal)"
+ },
+ {
+ "name": "Priority: 2 (Medium)"
+ },
+ {
+ "name": "Priority: 3 (High)"
+ },
+ {
+ "name": "Priority: 4 (Urgent)"
+ },
+ {
+ "name": "Priority: 5 (Emergency)"
+ }
+ ],
+ "command-settings": [
+ {
+ "name": "start",
+ "enabled": false
+ },
+ {
+ "name": "stop",
+ "enabled": false
+ },
+ {
+ "name": "wallet",
+ "enabled": false
+ },
+ {
+ "name": "payout",
+ "enabled": false
+ },
+ {
+ "name": "multiplier",
+ "enabled": false
+ },
+ {
+ "name": "query",
+ "enabled": false
+ },
+ {
+ "name": "allow",
+ "enabled": false
+ },
+ {
+ "name": "autopay",
+ "enabled": false
+ }
+ ],
+ "incentives": {
+ "comment": {
+ "elements": {},
+ "totals": {
+ "word": 0
+ }
+ }
+ }
+}
diff --git a/ubiquibot-config-default.yml b/ubiquibot-config-default.yml
deleted file mode 100644
index 5b0c70ac9..000000000
--- a/ubiquibot-config-default.yml
+++ /dev/null
@@ -1,44 +0,0 @@
----
-evm-network-id: 1
-price-multiplier: 1000
-issue-creator-multiplier: 2000
-time-labels:
- - name: "Time: <1 Hour"
- weight: 0.125
- value: 3600
- - name: "Time: <1 Day"
- weight: 1
- value: 86400
- - name: "Time: <1 Week"
- weight: 2
- value: 604800
- - name: "Time: <2 Weeks"
- weight: 3
- value: 1209600
- - name: "Time: <1 Month"
- weight: 4
- value: 2592000
-priority-labels:
- - name: "Priority: 0 (Normal)"
- weight: 1
- - name: "Priority: 1 (Medium)"
- weight: 2
- - name: "Priority: 2 (High)"
- weight: 3
- - name: "Priority: 3 (Urgent)"
- weight: 4
- - name: "Priority: 4 (Emergency)"
- weight: 5
-auto-pay-mode: true
-disable-analytics: true
-comment-incentives: false
-max-concurrent-assigns: 2
-comment-element-pricing:
- text: 0.1
- link: 0.5
- list: 0.5
- code: 5
- image: 5
-default-labels: []
-promotion-comment: "\nIf you enjoy the DevPool experience, please follow Ubiquity on GitHub and star this repo to show your support. It helps a lot! "
-register-wallet-with-verification: false
diff --git a/yarn.lock b/yarn.lock
index 671f8c351..2d930cf18 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1529,22 +1529,6 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
-"@logdna/logger@^2.6.6":
- version "2.6.7"
- resolved "https://registry.yarnpkg.com/@logdna/logger/-/logger-2.6.7.tgz#9a1f5ea0c69b373f223319d956eb96042df383ec"
- integrity sha512-Jn7CLP85yjfe5cBDO/ExtjPOE5ssG7lsBtMWo4ubb3sG8yjkBufJIIerCXLh5jG+otLOfh6CKGudOmOFak7PYg==
- dependencies:
- "@logdna/stdlib" "^1.1.5"
- agentkeepalive "^4.1.3"
- axios "^0.25.0"
- https-proxy-agent "^5.0.0"
- json-stringify-safe "^5.0.1"
-
-"@logdna/stdlib@^1.1.5":
- version "1.2.3"
- resolved "https://registry.yarnpkg.com/@logdna/stdlib/-/stdlib-1.2.3.tgz#a95b4cc74f0c4378769e41c06b69151058d6a26e"
- integrity sha512-67aauh8AY395vtgAADIPZTHjDtiCJBrNb2sM13zb2lWRJWAPVdiEBcgQValDR4OuwOdkr4kAMiTMBuMziATKiA==
-
"@netlify/functions@^1.4.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@netlify/functions/-/functions-1.6.0.tgz#c373423e6fef0e6f7422ac0345e8bbf2cb692366"
@@ -1970,17 +1954,17 @@
dependencies:
cross-fetch "^3.1.5"
-"@supabase/gotrue-js@^2.31.0":
- version "2.32.0"
- resolved "https://registry.yarnpkg.com/@supabase/gotrue-js/-/gotrue-js-2.32.0.tgz#806afef38cd7a7b92300194f844fb56e6873ca74"
- integrity sha512-H9yfM1RF1wM887+5zDqITagfBaZp8qzvXF2/hoIZQMYE+jLbLiCk5ncoS4CWLY4AwKD5NnQS/8dUGFTHQcxqDA==
+"@supabase/gotrue-js@^2.46.1":
+ version "2.47.0"
+ resolved "https://registry.yarnpkg.com/@supabase/gotrue-js/-/gotrue-js-2.47.0.tgz#67cca8f7be726fcfcc6dd49f515bbb20b2278f74"
+ integrity sha512-3e34/vsKH/DoSZCpB85UZpFWSJ2p4GRUUlqgAgeTPagPlx4xS+Nc5v7g7ic7vp3gK0J5PsYVCn9Qu2JQUp4vXg==
dependencies:
cross-fetch "^3.1.5"
-"@supabase/postgrest-js@^1.7.0":
- version "1.7.1"
- resolved "https://registry.yarnpkg.com/@supabase/postgrest-js/-/postgrest-js-1.7.1.tgz#12158937783bdc750ada705d7135d01f2fc96196"
- integrity sha512-xPRYLaZrkLbXNlzmHW6Wtf9hmcBLjjI5xUz2zj8oE2hgXGaYoZBBkpN9bmW9i17Z1f6Ujxa942AqK439XOA36A==
+"@supabase/postgrest-js@^1.8.0":
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/@supabase/postgrest-js/-/postgrest-js-1.8.0.tgz#c10b65e120c6523fc947225bcb9131869cf509f1"
+ integrity sha512-R6leDIC92NgjyG2/tCRJ42rWN7+fZY6ulTEE+c00tcnghn6cX4IYUlnTNMtrdfYC2JYNOTyM+rWj63Wdhr7Zig==
dependencies:
cross-fetch "^3.1.5"
@@ -2000,14 +1984,14 @@
dependencies:
cross-fetch "^3.1.5"
-"@supabase/supabase-js@^2.4.0":
- version "2.26.0"
- resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.26.0.tgz#80eb5d7b6dc5ac7b29218f7211747e256184247a"
- integrity sha512-RXmTPTobaYAwkSobadHZmEVLmzX3SGrtRZIGfLWnLv92VzBRrjuXn0a+bJqKl50GUzsyqPA+j5pod7EwMkcH5A==
+"@supabase/supabase-js@^2.32.0":
+ version "2.32.0"
+ resolved "https://registry.yarnpkg.com/@supabase/supabase-js/-/supabase-js-2.32.0.tgz#863c636d83232c6a2e9ba5932e0d7c1bf80bc436"
+ integrity sha512-1ShFhuOI5Du7604nlCelBsRD61daXk2O0qwjumoz35bqrYThnSPPtpJqZOHw6Mg6o7mLjIInYLh/DBlh8UvzRg==
dependencies:
"@supabase/functions-js" "^2.1.0"
- "@supabase/gotrue-js" "^2.31.0"
- "@supabase/postgrest-js" "^1.7.0"
+ "@supabase/gotrue-js" "^2.46.1"
+ "@supabase/postgrest-js" "^1.8.0"
"@supabase/realtime-js" "^2.7.3"
"@supabase/storage-js" "^2.5.1"
cross-fetch "^3.1.5"
@@ -2100,13 +2084,6 @@
dependencies:
"@types/node" "*"
-"@types/debug@^4.0.0":
- version "4.1.8"
- resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317"
- integrity sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==
- dependencies:
- "@types/ms" "*"
-
"@types/eslint@^8.40.2":
version "8.40.2"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.40.2.tgz#2833bc112d809677864a4b0e7d1de4f04d7dac2d"
@@ -2213,13 +2190,6 @@
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#a6ebde70d3b4af960fd802af8d0e3c7cfe281eb2"
integrity sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA==
-"@types/mdast@^3.0.0", "@types/mdast@^3.0.11":
- version "3.0.11"
- resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.11.tgz#dc130f7e7d9306124286f6d6cee40cf4d14a3dc0"
- integrity sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==
- dependencies:
- "@types/unist" "*"
-
"@types/mime@*":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
@@ -2235,7 +2205,7 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
-"@types/ms@*", "@types/ms@^0.7.31":
+"@types/ms@^0.7.31":
version "0.7.31"
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
@@ -2345,11 +2315,6 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
-"@types/unist@*", "@types/unist@^2.0.0":
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
- integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
-
"@types/websocket@^1.0.3":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c"
@@ -2558,15 +2523,6 @@ agent-base@6:
dependencies:
debug "4"
-agentkeepalive@^4.1.3:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.3.0.tgz#bb999ff07412653c1803b3ced35e50729830a255"
- integrity sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==
- dependencies:
- debug "^4.1.0"
- depd "^2.0.0"
- humanize-ms "^1.2.1"
-
aggregate-error@^3.0.0, aggregate-error@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -2761,13 +2717,6 @@ atomic-sleep@^1.0.0:
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
-axios@^0.25.0:
- version "0.25.0"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a"
- integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==
- dependencies:
- follow-redirects "^1.14.7"
-
axios@^1.3.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.4.0.tgz#38a7bf1224cd308de271146038b551d725f0be1f"
@@ -3152,11 +3101,6 @@ capture-exit@^2.0.0:
dependencies:
rsvp "^4.8.4"
-ccount@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
- integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==
-
chalk@2.4.2, chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -3184,11 +3128,6 @@ char-regex@^1.0.2:
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
-character-entities@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
- integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
-
chokidar@^3.5.2:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -3276,6 +3215,15 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
+cliui@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+ integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
@@ -3458,6 +3406,24 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==
+copyfiles@^2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5"
+ integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==
+ dependencies:
+ glob "^7.0.5"
+ minimatch "^3.0.3"
+ mkdirp "^1.0.4"
+ noms "0.0.0"
+ through2 "^2.0.1"
+ untildify "^4.0.0"
+ yargs "^16.1.0"
+
+core-util-is@~1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
+ integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
+
cosmiconfig-typescript-loader@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz#c4259ce474c9df0f32274ed162c0447c951ef073"
@@ -3584,7 +3550,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
-debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
+debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -3611,18 +3577,11 @@ decamelize@^1.1.0, decamelize@^1.2.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
-decimal.js@^10.2.1:
+decimal.js@^10.2.1, decimal.js@^10.4.3:
version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
-decode-named-character-reference@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e"
- integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==
- dependencies:
- character-entities "^2.0.0"
-
decode-uri-component@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
@@ -3670,7 +3629,7 @@ denque@^1.1.0:
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
-depd@2.0.0, depd@^2.0.0, depd@~2.0.0:
+depd@2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@@ -3680,11 +3639,6 @@ deprecation@^2.0.0, deprecation@^2.3.1:
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
-dequal@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
- integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
-
destroy@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
@@ -3723,11 +3677,6 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
-diff@^5.0.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40"
- integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
-
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -3858,7 +3807,7 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
-entities@^4.2.0:
+entities@^4.2.0, entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
@@ -3949,11 +3898,6 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-escape-string-regexp@^5.0.0:
- version "5.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
- integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
-
escodegen@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
@@ -4502,7 +4446,7 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
-follow-redirects@^1.14.7, follow-redirects@^1.15.0:
+follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
@@ -4689,7 +4633,7 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
-glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
+glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -4948,13 +4892,6 @@ human-signals@^4.3.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2"
integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==
-humanize-ms@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
- integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==
- dependencies:
- ms "^2.0.0"
-
husky@^8.0.2:
version "8.0.3"
resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184"
@@ -5023,7 +4960,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -5274,7 +5211,12 @@ is-wsl@^2.2.0:
dependencies:
is-docker "^2.0.0"
-isarray@1.0.0:
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+ integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
+
+isarray@1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
@@ -5974,11 +5916,6 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-kleur@^4.0.3:
- version "4.1.5"
- resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
- integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
-
leven@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
@@ -6187,11 +6124,6 @@ log-update@^4.0.0:
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
-longest-streak@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4"
- integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==
-
lowercase-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
@@ -6262,126 +6194,6 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
-markdown-table@^3.0.0:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd"
- integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==
-
-mdast-util-find-and-replace@^2.0.0:
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz#cc2b774f7f3630da4bd592f61966fecade8b99b1"
- integrity sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==
- dependencies:
- "@types/mdast" "^3.0.0"
- escape-string-regexp "^5.0.0"
- unist-util-is "^5.0.0"
- unist-util-visit-parents "^5.0.0"
-
-mdast-util-from-markdown@^1.0.0, mdast-util-from-markdown@^1.3.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0"
- integrity sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==
- dependencies:
- "@types/mdast" "^3.0.0"
- "@types/unist" "^2.0.0"
- decode-named-character-reference "^1.0.0"
- mdast-util-to-string "^3.1.0"
- micromark "^3.0.0"
- micromark-util-decode-numeric-character-reference "^1.0.0"
- micromark-util-decode-string "^1.0.0"
- micromark-util-normalize-identifier "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
- unist-util-stringify-position "^3.0.0"
- uvu "^0.5.0"
-
-mdast-util-gfm-autolink-literal@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz#67a13abe813d7eba350453a5333ae1bc0ec05c06"
- integrity sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==
- dependencies:
- "@types/mdast" "^3.0.0"
- ccount "^2.0.0"
- mdast-util-find-and-replace "^2.0.0"
- micromark-util-character "^1.0.0"
-
-mdast-util-gfm-footnote@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz#ce5e49b639c44de68d5bf5399877a14d5020424e"
- integrity sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==
- dependencies:
- "@types/mdast" "^3.0.0"
- mdast-util-to-markdown "^1.3.0"
- micromark-util-normalize-identifier "^1.0.0"
-
-mdast-util-gfm-strikethrough@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz#5470eb105b483f7746b8805b9b989342085795b7"
- integrity sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==
- dependencies:
- "@types/mdast" "^3.0.0"
- mdast-util-to-markdown "^1.3.0"
-
-mdast-util-gfm-table@^1.0.0:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz#3552153a146379f0f9c4c1101b071d70bbed1a46"
- integrity sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==
- dependencies:
- "@types/mdast" "^3.0.0"
- markdown-table "^3.0.0"
- mdast-util-from-markdown "^1.0.0"
- mdast-util-to-markdown "^1.3.0"
-
-mdast-util-gfm-task-list-item@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz#b280fcf3b7be6fd0cc012bbe67a59831eb34097b"
- integrity sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==
- dependencies:
- "@types/mdast" "^3.0.0"
- mdast-util-to-markdown "^1.3.0"
-
-mdast-util-gfm@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz#e92f4d8717d74bdba6de57ed21cc8b9552e2d0b6"
- integrity sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==
- dependencies:
- mdast-util-from-markdown "^1.0.0"
- mdast-util-gfm-autolink-literal "^1.0.0"
- mdast-util-gfm-footnote "^1.0.0"
- mdast-util-gfm-strikethrough "^1.0.0"
- mdast-util-gfm-table "^1.0.0"
- mdast-util-gfm-task-list-item "^1.0.0"
- mdast-util-to-markdown "^1.0.0"
-
-mdast-util-phrasing@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz#c7c21d0d435d7fb90956038f02e8702781f95463"
- integrity sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==
- dependencies:
- "@types/mdast" "^3.0.0"
- unist-util-is "^5.0.0"
-
-mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz#c13343cb3fc98621911d33b5cd42e7d0731171c6"
- integrity sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==
- dependencies:
- "@types/mdast" "^3.0.0"
- "@types/unist" "^2.0.0"
- longest-streak "^3.0.0"
- mdast-util-phrasing "^3.0.0"
- mdast-util-to-string "^3.0.0"
- micromark-util-decode-string "^1.0.0"
- unist-util-visit "^4.0.0"
- zwitch "^2.0.0"
-
-mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz#66f7bb6324756741c5f47a53557f0cbf16b6f789"
- integrity sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==
- dependencies:
- "@types/mdast" "^3.0.0"
-
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -6424,279 +6236,6 @@ methods@^1.1.2, methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
-micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8"
- integrity sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==
- dependencies:
- decode-named-character-reference "^1.0.0"
- micromark-factory-destination "^1.0.0"
- micromark-factory-label "^1.0.0"
- micromark-factory-space "^1.0.0"
- micromark-factory-title "^1.0.0"
- micromark-factory-whitespace "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-chunked "^1.0.0"
- micromark-util-classify-character "^1.0.0"
- micromark-util-html-tag-name "^1.0.0"
- micromark-util-normalize-identifier "^1.0.0"
- micromark-util-resolve-all "^1.0.0"
- micromark-util-subtokenize "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.1"
- uvu "^0.5.0"
-
-micromark-extension-gfm-autolink-literal@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz#5853f0e579bbd8ef9e39a7c0f0f27c5a063a66e7"
- integrity sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==
- dependencies:
- micromark-util-character "^1.0.0"
- micromark-util-sanitize-uri "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-extension-gfm-footnote@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz#05e13034d68f95ca53c99679040bc88a6f92fe2e"
- integrity sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==
- dependencies:
- micromark-core-commonmark "^1.0.0"
- micromark-factory-space "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-normalize-identifier "^1.0.0"
- micromark-util-sanitize-uri "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
- uvu "^0.5.0"
-
-micromark-extension-gfm-strikethrough@^1.0.0:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz#c8212c9a616fa3bf47cb5c711da77f4fdc2f80af"
- integrity sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==
- dependencies:
- micromark-util-chunked "^1.0.0"
- micromark-util-classify-character "^1.0.0"
- micromark-util-resolve-all "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
- uvu "^0.5.0"
-
-micromark-extension-gfm-table@^1.0.0:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz#dcb46074b0c6254c3fc9cc1f6f5002c162968008"
- integrity sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==
- dependencies:
- micromark-factory-space "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
- uvu "^0.5.0"
-
-micromark-extension-gfm-tagfilter@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz#aa7c4dd92dabbcb80f313ebaaa8eb3dac05f13a7"
- integrity sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==
- dependencies:
- micromark-util-types "^1.0.0"
-
-micromark-extension-gfm-task-list-item@^1.0.0:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz#b52ce498dc4c69b6a9975abafc18f275b9dde9f4"
- integrity sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==
- dependencies:
- micromark-factory-space "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
- uvu "^0.5.0"
-
-micromark-extension-gfm@^2.0.3:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz#e517e8579949a5024a493e49204e884aa74f5acf"
- integrity sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==
- dependencies:
- micromark-extension-gfm-autolink-literal "^1.0.0"
- micromark-extension-gfm-footnote "^1.0.0"
- micromark-extension-gfm-strikethrough "^1.0.0"
- micromark-extension-gfm-table "^1.0.0"
- micromark-extension-gfm-tagfilter "^1.0.0"
- micromark-extension-gfm-task-list-item "^1.0.0"
- micromark-util-combine-extensions "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-factory-destination@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz#eb815957d83e6d44479b3df640f010edad667b9f"
- integrity sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==
- dependencies:
- micromark-util-character "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-factory-label@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz#cc95d5478269085cfa2a7282b3de26eb2e2dec68"
- integrity sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==
- dependencies:
- micromark-util-character "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
- uvu "^0.5.0"
-
-micromark-factory-space@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz#c8f40b0640a0150751d3345ed885a080b0d15faf"
- integrity sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==
- dependencies:
- micromark-util-character "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-factory-title@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz#dd0fe951d7a0ac71bdc5ee13e5d1465ad7f50ea1"
- integrity sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==
- dependencies:
- micromark-factory-space "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-factory-whitespace@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz#798fb7489f4c8abafa7ca77eed6b5745853c9705"
- integrity sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==
- dependencies:
- micromark-factory-space "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-util-character@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz#4fedaa3646db249bc58caeb000eb3549a8ca5dcc"
- integrity sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==
- dependencies:
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-util-chunked@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz#37a24d33333c8c69a74ba12a14651fd9ea8a368b"
- integrity sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==
- dependencies:
- micromark-util-symbol "^1.0.0"
-
-micromark-util-classify-character@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz#6a7f8c8838e8a120c8e3c4f2ae97a2bff9190e9d"
- integrity sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==
- dependencies:
- micromark-util-character "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-util-combine-extensions@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz#192e2b3d6567660a85f735e54d8ea6e3952dbe84"
- integrity sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==
- dependencies:
- micromark-util-chunked "^1.0.0"
- micromark-util-types "^1.0.0"
-
-micromark-util-decode-numeric-character-reference@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz#b1e6e17009b1f20bc652a521309c5f22c85eb1c6"
- integrity sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==
- dependencies:
- micromark-util-symbol "^1.0.0"
-
-micromark-util-decode-string@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz#dc12b078cba7a3ff690d0203f95b5d5537f2809c"
- integrity sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==
- dependencies:
- decode-named-character-reference "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-decode-numeric-character-reference "^1.0.0"
- micromark-util-symbol "^1.0.0"
-
-micromark-util-encode@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz#92e4f565fd4ccb19e0dcae1afab9a173bbeb19a5"
- integrity sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==
-
-micromark-util-html-tag-name@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz#48fd7a25826f29d2f71479d3b4e83e94829b3588"
- integrity sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==
-
-micromark-util-normalize-identifier@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz#7a73f824eb9f10d442b4d7f120fecb9b38ebf8b7"
- integrity sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==
- dependencies:
- micromark-util-symbol "^1.0.0"
-
-micromark-util-resolve-all@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz#4652a591ee8c8fa06714c9b54cd6c8e693671188"
- integrity sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==
- dependencies:
- micromark-util-types "^1.0.0"
-
-micromark-util-sanitize-uri@^1.0.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz#613f738e4400c6eedbc53590c67b197e30d7f90d"
- integrity sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==
- dependencies:
- micromark-util-character "^1.0.0"
- micromark-util-encode "^1.0.0"
- micromark-util-symbol "^1.0.0"
-
-micromark-util-subtokenize@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz#941c74f93a93eaf687b9054aeb94642b0e92edb1"
- integrity sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==
- dependencies:
- micromark-util-chunked "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.0"
- uvu "^0.5.0"
-
-micromark-util-symbol@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz#813cd17837bdb912d069a12ebe3a44b6f7063142"
- integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==
-
-micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz#e6676a8cae0bb86a2171c498167971886cb7e283"
- integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==
-
-micromark@^3.0.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.2.0.tgz#1af9fef3f995ea1ea4ac9c7e2f19c48fd5c006e9"
- integrity sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==
- dependencies:
- "@types/debug" "^4.0.0"
- debug "^4.0.0"
- decode-named-character-reference "^1.0.0"
- micromark-core-commonmark "^1.0.1"
- micromark-factory-space "^1.0.0"
- micromark-util-character "^1.0.0"
- micromark-util-chunked "^1.0.0"
- micromark-util-combine-extensions "^1.0.0"
- micromark-util-decode-numeric-character-reference "^1.0.0"
- micromark-util-encode "^1.0.0"
- micromark-util-normalize-identifier "^1.0.0"
- micromark-util-resolve-all "^1.0.0"
- micromark-util-sanitize-uri "^1.0.0"
- micromark-util-subtokenize "^1.0.0"
- micromark-util-symbol "^1.0.0"
- micromark-util-types "^1.0.1"
- uvu "^0.5.0"
-
micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -6778,7 +6317,7 @@ minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
-minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -6839,7 +6378,7 @@ mkdirp-classic@^0.5.2:
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
-mkdirp@1.x, mkdirp@^1.0.3:
+mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@@ -6865,7 +6404,7 @@ mri@1.1.4:
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"
integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==
-mri@^1.1.0, mri@^1.2.0:
+mri@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
@@ -6880,7 +6419,7 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
+ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -7030,6 +6569,14 @@ nodemon@^2.0.19:
touch "^3.1.0"
undefsafe "^2.0.5"
+noms@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
+ integrity sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "~1.0.31"
+
nopt@~1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
@@ -7344,6 +6891,13 @@ parse5@6.0.1:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
+parse5@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
+ integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
+ dependencies:
+ entities "^4.4.0"
+
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -7650,6 +7204,11 @@ probot@^12.1.1, probot@^12.2.1, probot@^12.2.4:
update-dotenv "^1.1.1"
uuid "^8.3.2"
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
process-warning@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616"
@@ -7856,6 +7415,29 @@ readable-stream@^4.0.0:
events "^3.3.0"
process "^0.11.10"
+readable-stream@~1.0.31:
+ version "1.0.34"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+ integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+readable-stream@~2.3.6:
+ version "2.3.8"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
+ integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
readable-web-to-node-stream@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb"
@@ -8040,14 +7622,7 @@ rxjs@^7.8.0:
dependencies:
tslib "^2.1.0"
-sade@^1.7.3:
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
- integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
- dependencies:
- mri "^1.1.0"
-
-safe-buffer@5.1.2:
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -8507,6 +8082,18 @@ string_decoder@^1.1.1:
dependencies:
safe-buffer "~5.2.0"
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+ integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -8710,6 +8297,14 @@ throat@^5.0.0:
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
+through2@^2.0.1:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+ integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
+ dependencies:
+ readable-stream "~2.3.6"
+ xtend "~4.0.1"
+
through2@^4.0.0, through2@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764"
@@ -9011,37 +8606,6 @@ union-value@^1.0.0:
is-extendable "^0.1.1"
set-value "^2.0.1"
-unist-util-is@^5.0.0:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9"
- integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==
- dependencies:
- "@types/unist" "^2.0.0"
-
-unist-util-stringify-position@^3.0.0:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d"
- integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==
- dependencies:
- "@types/unist" "^2.0.0"
-
-unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1:
- version "5.1.3"
- resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb"
- integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==
- dependencies:
- "@types/unist" "^2.0.0"
- unist-util-is "^5.0.0"
-
-unist-util-visit@^4.0.0:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2"
- integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==
- dependencies:
- "@types/unist" "^2.0.0"
- unist-util-is "^5.0.0"
- unist-util-visit-parents "^5.1.1"
-
universal-github-app-jwt@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz#d57cee49020662a95ca750a057e758a1a7190e6e"
@@ -9078,6 +8642,11 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
+untildify@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
+ integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
+
update-browserslist-db@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
@@ -9130,7 +8699,7 @@ utif2@^4.0.1:
dependencies:
pako "^1.0.11"
-util-deprecate@^1.0.1:
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
@@ -9145,16 +8714,6 @@ uuid@^8.3.0, uuid@^8.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
-uvu@^0.5.0:
- version "0.5.6"
- resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.6.tgz#2754ca20bcb0bb59b64e9985e84d2e81058502df"
- integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==
- dependencies:
- dequal "^2.0.0"
- diff "^5.0.0"
- kleur "^4.0.3"
- sade "^1.7.3"
-
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
@@ -9397,7 +8956,7 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
-xtend@^4.0.0:
+xtend@^4.0.0, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
@@ -9432,7 +8991,7 @@ yaml@^2.2.2:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
-yargs-parser@20.x, yargs-parser@^20.2.3:
+yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
@@ -9467,6 +9026,19 @@ yargs@^15.4.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
+yargs@^16.1.0:
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+ integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+ dependencies:
+ cliui "^7.0.2"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.0"
+ y18n "^5.0.5"
+ yargs-parser "^20.2.2"
+
yargs@^17.0.0:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
@@ -9497,8 +9069,3 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-
-zwitch@^2.0.0:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
- integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==