From 959992e7eefec5338a8a38faaefc112c3a65f23e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 17 Jul 2024 18:51:57 -0700 Subject: [PATCH 001/214] feat!: wrap payloads to send to a "method" with "token" or "webhook" --- .c8rc.json | 7 + .eslintignore | 2 - .eslintrc.js | 132 - .github/workflows/local/local.yml | 23 +- .github/workflows/main.yml | 219 +- .gitignore | 9 +- .mocharc.json | 11 + @types/markup-js.d.ts | 9 + README.md | 376 +- action.yml | 66 +- biome.json | 23 + .../JSON_payload.yml | 15 +- .../JSON_payload_from_file.yml | 13 +- .../default_GitHub_Trigger_payload.yml | 11 +- .../Technique_2_Slack_App/JSON_payload.yml | 22 +- .../JSON_payload_as_text.yml | 17 +- .../Technique_2_Slack_App/main.yml | 17 +- .../main.yml | 17 +- jsconfig.json | 30 + package-lock.json | 7564 ++++------------- package.json | 41 +- src/client.js | 99 + src/client.spec.js | 58 + src/config.js | 259 + src/config.spec.js | 246 + src/errors.js | 23 + src/index.js | 19 +- src/index.spec.js | 61 + src/send.js | 35 + src/send.spec.js | 21 + src/slack-send.js | 165 - src/web-client.js | 20 - src/webhook.js | 97 + src/webhook.spec.js | 110 + test/.eslintrc.js | 15 - test/resources/invalid-payload.json | 1 - test/resources/valid-payload.json | 5 - test/slack-send-test.js | 328 - test/web-client-test.js | 33 - 39 files changed, 2997 insertions(+), 7222 deletions(-) create mode 100644 .c8rc.json delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js create mode 100644 @types/markup-js.d.ts create mode 100644 biome.json create mode 100644 jsconfig.json create mode 100644 src/client.js create mode 100644 src/client.spec.js create mode 100644 src/config.js create mode 100644 src/config.spec.js create mode 100644 src/errors.js create mode 100644 src/index.spec.js create mode 100644 src/send.js create mode 100644 src/send.spec.js delete mode 100644 src/slack-send.js delete mode 100644 src/web-client.js create mode 100644 src/webhook.js create mode 100644 src/webhook.spec.js delete mode 100644 test/.eslintrc.js delete mode 100644 test/resources/invalid-payload.json delete mode 100644 test/resources/valid-payload.json delete mode 100644 test/slack-send-test.js delete mode 100644 test/web-client-test.js diff --git a/.c8rc.json b/.c8rc.json new file mode 100644 index 00000000..0c61fde4 --- /dev/null +++ b/.c8rc.json @@ -0,0 +1,7 @@ +{ + "include": ["src/*.js"], + "exclude": ["**/*.spec.js"], + "reporter": ["lcov", "text"], + "all": false, + "cache": true +} diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index f06235c4..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -dist diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 40038f0e..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,132 +0,0 @@ -// -// SlackAPI JavaScript style -// --- -// This style helps maintainers enforce safe and consistent programming practices in this project. It is not meant to be -// comprehensive on its own or vastly different from existing styles. The goal is to inherit and aggregate as many of -// the communities' recommended styles for the technologies used as we can. When, and only when, we have a stated need -// to differentiate, we add more rules (or modify options). Therefore, the fewer rules directly defined in this file, -// the better. - -module.exports = { - // This is a root of the project, ESLint should not look through parent directories to find more config - root: true, - - ignorePatterns: [ - // Ignore all build outputs and artifacts (node_modules, dotfiles, and dot directories are implicitly ignored) - '/dist', - '/coverage', - ], - - // These environments contain lists of global variables which are allowed to be accessed - env: { - // According to https://node.green, the target node version (v10) supports all important ES2018 features. But es2018 - // is not an option since it presumably doesn't introduce any new globals over ES2017. - es2017: true, - node: true, - }, - - extends: [ - // ESLint's recommended built-in rules: https://eslint.org/docs/rules/ - 'eslint:recommended', - - // Node plugin's recommended rules: https://github.com/mysticatea/eslint-plugin-node - 'plugin:node/recommended', - - // AirBnB style guide (without React) rules: https://github.com/airbnb/javascript. - 'airbnb-base', - - // JSDoc plugin's recommended rules - 'plugin:jsdoc/recommended', - ], - - rules: { - // JavaScript rules - // --- - // The top level of this configuration contains rules which apply to JavaScript (and will also be inherited for - // TypeScript). This section does not contain rules meant to override options or disable rules in the base - // configurations (ESLint, Node, AirBnb). Those rules are added in the final override. - - // Eliminate tabs to standardize on spaces for indentation. If you want to use tabs for something other than - // indentation, you may need to turn this rule off using an inline config comments. - 'no-tabs': 'error', - - // Bans use of comma as an operator because it can obscure side effects and is often an accident. - 'no-sequences': 'error', - - // This repo uses console.log, which is fine to do in GitHub actions - 'no-console': 'off', - - // Disallow the use of process.exit() - 'node/no-process-exit': 'error', - - // Allow safe references to functions before the declaration. Overrides AirBnB config. Not located in the override - // section below because a distinct override is necessary in TypeScript files. - 'no-use-before-define': ['error', 'nofunc'], - }, - - overrides: [ - { - files: ['**/*.js'], - rules: { - // Override rules - // --- - // This level of this configuration contains rules which override options or disable rules in the base - // configurations in JavaScript. - - // Increase the max line length to 120. The rest of this setting is copied from the AirBnB config. - 'max-len': ['error', 120, 2, { - ignoreUrls: true, - ignoreComments: false, - ignoreRegExpLiterals: true, - ignoreStrings: true, - ignoreTemplateLiterals: true, - }], - - // Restrict the use of backticks to declare a normal string. Template literals should only be used when the - // template string contains placeholders. The rest of this setting is copied from the AirBnb config. - quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }], - - // the server side Slack API uses snake_case for parameters often - // for mocking and override support, we need to allow snake_case - // Allow leading underscores for parameter names, which is used to acknowledge unused variables in TypeScript. - // Also, enforce camelCase naming for variables. Ideally, the leading underscore could be restricted to only - // unused parameter names, but this rule isn't capable of knowing when a variable is unused. The camelcase and - // no-underscore-dangle rules are replaced with the naming-convention rule because this single rule can serve - // both purposes, and it works fine on non-TypeScript code. - camelcase: 'off', - 'no-underscore-dangle': 'off', - - // Remove the minProperties option for enforcing line breaks between braces. The AirBnB config sets this to 4, - // which is arbitrary and not backed by anything specific in the style guide. If we just remove it, we can - // rely on the max-len rule to determine if the line is too long and then enforce line breaks. Overrides AirBnB - // styles. - 'object-curly-newline': ['error', { multiline: true, consistent: true }], - }, - }, - { - files: ['src/test/*.js'], - rules: { - // Test-specific rules - // --- - // Rules that only apply to JavaScript _test_ source files - - // With Mocha as a test framework, it is sometimes helpful to assign - // shared state to Mocha's Context object, for example in setup and - // teardown test methods. Assigning stub/mock objects to the Context - // object via `this` is a common pattern in Mocha. As such, using - // `function` over the the arrow notation binds `this` appropriately and - // should be used in tests. So: we turn off the prefer-arrow-callback - // rule. - // See https://github.com/slackapi/bolt-js/pull/1012#pullrequestreview-711232738 - // for a case of arrow-vs-function syntax coming up for the team - 'prefer-arrow-callback': 'off', - - // Using ununamed functions (e.g., null logger) in tests is fine - 'func-names': 'off', - // In tests, don't force constructing a Symbol with a descriptor, as - // it's probably just for tests - 'symbol-description': 'off', - }, - }, - ], -}; diff --git a/.github/workflows/local/local.yml b/.github/workflows/local/local.yml index 15189500..6426e0fc 100644 --- a/.github/workflows/local/local.yml +++ b/.github/workflows/local/local.yml @@ -9,20 +9,17 @@ jobs: run: runs-on: ubuntu-latest steps: - - name: Checkout action - uses: actions/checkout@v4 - - name: Build action - run: npm install && npm run build - - name: Send a message into channel - id: slack - uses: ./. - with: - payload: | - { + - name: Checkout action + uses: actions/checkout@v4 + - name: Build action + run: npm install && npm run build + - name: Send a message into channel + id: slack + uses: ./. + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + payload: | "repository": "${{ github.repository }}", "pull_request_username": "${{ github.event.pull_request.user.login }}", "pull_request_title": ${{ toJSON(github.event.pull_request.title) }}, "pull_request_url": "${{ github.event.pull_request.html_url }}" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 415f4d47..47063280 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,6 +4,7 @@ on: push: branches: - main + workflow_dispatch: jobs: tests: @@ -11,109 +12,115 @@ jobs: runs-on: ubuntu-latest environment: staging steps: - - name: "build: checkout the latest changes" - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: "build: install the required dependencies" - run: npm ci - - - name: "build: package the latest changes" - run: npm run build - - - name: "unit(test): perform unit test checks" - run: npm test - - - name: "unit(test): check unit test coverage" - run: npm run test:gen-cov - - - name: "unit(test): upload coverage to CodeCov" - uses: codecov/codecov-action@v4.5.0 - with: - directory: ./coverage - token: ${{ secrets.CODECOV_TOKEN }} - - - name: "integration(botToken): post a message to channel" - id: slackToken - uses: ./ - with: - channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - slack-message: 'CI Post from slack-send GitHub Action! Succeeded!!' - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - - - name: "integration(botToken): confirm a message was posted" - run: test -n "${{ steps.slackToken.outputs.ts }}" - - - name: "integration(botToken): post a threaded response" - id: slackThreadResponse - uses: ./ - with: - channel-id: ${{ secrets.SLACK_CHANNEL_ID }} - payload: | - { - "text": "This message should be posted as a response in thread", - "thread_ts": "${{ steps.slackToken.outputs.thread_ts }}" - } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - - - name: "integration(botToken): confirm a response was posted" - run: test -n "${{ steps.slackThreadResponse.outputs.ts }}" - - - name: "integration(wfb): save the push event trigger commit URL" - if: "contains(github.event_name, 'push')" - run: | - url=${{ github.event.head_commit.url }} - echo "URL=$url" >> "$GITHUB_ENV" - - - name: "integration(wfb): save the pull request event trigger commit URL" - if: "contains(github.event_name, 'pull_request')" - run: | - url=${{ github.event.pull_request.url }} - echo "URL=$url" >> "$GITHUB_ENV" - - - name: "integration(wfb): send a payload via workflow builder webhook" - id: slackWorkflow - uses: ./ - with: - # Workflow builder webhooks need to know the name of the keys in the payload in advance. Without normalizing, the github context payload keys can differ based on the GitHub trigger event type - # Normalized payload with info pulled out from GitHub trigger event - payload: "{\"author\":\"${{ github.event.sender.login }}\",\"url\":\"${{ env.URL}}\", \"repoName\":\"${{ github.event.repository.full_name }}\", \"status\":\"${{ job.status }}\"}" - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - - name: "integration(wfb): confirm a payload was sent" - run: test -n "${{ steps.slackWorkflow.outputs.time }}" - - - name: "integration(incoming): post a message via incoming webhook" - id: slackIncoming - uses: ./ - with: - payload: "{\"text\":\"Incoming Webhook test for slack send\", \"blocks\":[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"A post by Slack Send GitHub Action. Testing Incoming webhooks\",\"emoji\":true}}]}" - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - - - name: "integration(incoming): confirm a webhook was posted" - run: test -n "${{ steps.slackIncoming.outputs.time }}" - - - name: "integration(incoming): reveal contents of the github payload" - run: echo $JSON - env: - JSON: ${{ toJSON(github) }} - - - name: "integration(incoming): post a message via payload file" - id: slackPayloadFile - uses: ./ - with: - payload-file-path: ./.github/resources/payload-notification.json - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - JOB_STATUS: ${{ job.status }} - ATTACHMENT_COLOR: ${{ (job.status == 'success' && 'good') || (job.status == 'failure' && 'danger') || 'warning' }} - - - name: "integration(incoming): confirm a payload file was posted" - run: test -n "${{ steps.slackPayloadFile.outputs.time }}" + - name: "build: checkout the latest changes" + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: "build: install the required dependencies" + run: npm ci + + - name: "build: package the latest changes" + run: npm run build + + - name: "unit(test): perform unit test checks" + run: npm test + + - name: "unit(test): check unit test coverage" + run: npm run test:gen-cov + + - name: "unit(test): upload coverage to CodeCov" + uses: codecov/codecov-action@v4.5.0 + with: + directory: ./coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: "integration(botToken): post a message to channel" + id: slackToken + uses: ./ + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + "channel": ${{ secrets.SLACK_CHANNEL_ID }}, + "text": "CI Post from slack-send GitHub Action! Succeeded!!" + + - name: "integration(botToken): confirm a message was posted" + run: test -n "${{ steps.slackToken.outputs.ts }}" + + - name: "integration(botToken): post a threaded response" + id: slackThreadResponse + uses: ./ + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + "channel": ${{ secrets.SLACK_CHANNEL_ID }} + "text": "This message should be posted as a response in thread", + "thread_ts": "${{ steps.slackToken.outputs.thread_ts }}" + + - name: "integration(botToken): confirm a response was posted" + run: test -n "${{ steps.slackThreadResponse.outputs.ts }}" + + - name: "integration(wfb): save the push event trigger commit URL" + if: "contains(github.event_name, 'push')" + run: | + url=${{ github.event.head_commit.url }} + echo "URL=$url" >> "$GITHUB_ENV" + + - name: "integration(wfb): save the pull request event trigger commit URL" + if: "contains(github.event_name, 'pull_request')" + run: | + url=${{ github.event.pull_request.url }} + echo "URL=$url" >> "$GITHUB_ENV" + + - name: "integration(wfb): send a payload via workflow builder webhook" + id: slackWorkflow + uses: ./ + with: + # Workflow builder webhooks need to know the name of the keys in the payload in advance. Without normalizing, the github context payload keys can differ based on the GitHub trigger event type + # Normalized payload with info pulled out from GitHub trigger event + payload: '{"author":"${{ github.event.sender.login }}","url":"${{ env.URL}}", "repoName":"${{ github.event.repository.full_name }}", "status":"${{ job.status }}"}' + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + + - name: "integration(wfb): confirm a payload was sent" + run: test -n "${{ steps.slackWorkflow.outputs.time }}" + + - name: "integration(incoming): post a message via incoming webhook" + id: slackIncoming + uses: ./ + with: + webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} + payload: | + "text":"Incoming Webhook test for slack send", + "blocks":[ + { + "type":"section", + "text":{ + "type":"plain_text", + "text":"A post by Slack Send GitHub Action. Testing Incoming webhooks", + "emoji":true + } + } + ] + + - name: "integration(incoming): confirm a webhook was posted" + run: test -n "${{ steps.slackIncoming.outputs.time }}" + + - name: "integration(incoming): reveal contents of the github payload" + run: echo $JSON + env: + JSON: ${{ toJSON(github) }} + + - name: "integration(incoming): post a message via payload file" + id: slackPayloadFile + uses: ./ + with: + payload-file-path: ./.github/resources/payload-notification.json + webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} + env: + JOB_STATUS: ${{ job.status }} + ATTACHMENT_COLOR: ${{ (job.status == 'success' && 'good') || (job.status == 'failure' && 'danger') || 'warning' }} + + - name: "integration(incoming): confirm a payload file was posted" + run: test -n "${{ steps.slackPayloadFile.outputs.time }}" diff --git a/.gitignore b/.gitignore index f50d6db0..206bf637 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ +# Development dependencies node_modules -.DS_Store -.nyc_output -dist + +# Testing reminants coverage + +# Additional system files +.DS_Store diff --git a/.mocharc.json b/.mocharc.json index b64f84bc..67b41986 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,3 +1,14 @@ { + "require": [ + "@actions/core", + "@actions/github", + "@slack/web-api", + "axios", + "axios-retry", + "flat", + "https-proxy-agent", + "markup-js" + ], + "recursive": true, "timeout": 3000 } diff --git a/@types/markup-js.d.ts b/@types/markup-js.d.ts new file mode 100644 index 00000000..9d7718d0 --- /dev/null +++ b/@types/markup-js.d.ts @@ -0,0 +1,9 @@ +declare module "markup-js" { + /** + * @param template - The string with templated values. + * @param context - Values to replace in the template. + * @returns string - The template with replaced context values. + * @link https://github.com/adammark/Markup.js#usage + */ + export function up(template: string, context: object): string; +} diff --git a/README.md b/README.md index ee607ab9..9d4dbb02 100644 --- a/README.md +++ b/README.md @@ -2,54 +2,68 @@ [![codecov](https://codecov.io/gh/slackapi/slack-github-action/graph/badge.svg?token=OZNX7FHN78)](https://codecov.io/gh/slackapi/slack-github-action) -Send data into Slack using this GitHub Action! +Send data into Slack with a Slack [API method][methods] like [`chat.postMessage`][chat.postMessage] +and [`files.uploadV2`][files.uploadV2], or uses webhooks to [start workflows](#technique-1-slack-workflow-builder) in Workflow Builder and +[post messages](#technique-3-slack-incoming-webhook) with this GitHub Action! ## Sending Variables -You can send GitHub-specific data related to GitHub Action workflow events using [GitHub Contexts](https://docs.github.com/en/actions/learn-github-actions/contexts) and [Variables](https://docs.github.com/en/actions/learn-github-actions/variables) that GitHub Actions provides. +You can send GitHub-specific data related to GitHub Action workflow events using +[GitHub Contexts](https://docs.github.com/en/actions/learn-github-actions/contexts) +and +[Variables](https://docs.github.com/en/actions/learn-github-actions/variables) +that GitHub Actions provides. -For examples on how to leverage this in your workflows, check out the [example workflows we have](https://github.com/slackapi/slack-github-action/tree/main/example-workflows). +For examples on how to leverage this in your workflows, check out the +[example workflows we have](https://github.com/slackapi/slack-github-action/tree/main/example-workflows). ## How to Send Data to Slack This package has three different techniques to send data to Slack: -1) Send data to Slack's Workflow Builder (requires a paid Slack instance). -2) Send data via a Slack app to post to a specific channel (use an existing custom app or create a new one). -3) Send data via a Slack Incoming Webhook URL (use an existing custom app or create a new one). +1. Send data to Slack's Workflow Builder (requires a paid Slack instance). +2. Send data to a Slack API method using a secret token with specified scopes. +3. Send data via a Slack Incoming Webhook URL (use an existing custom app or + create a new one). -The recommended way to use this action is with Slack's Workflow Builder (if you're on a paid Slack plan). +The recommended way to use this action is with Slack's Workflow Builder (if +you're on a paid Slack plan). ### Technique 1: Slack Workflow Builder -> ❗️ This approach requires a paid Slack plan; it also doesn't support any text formatting +> ❗️ This approach requires a paid Slack plan; it also doesn't support any text +> formatting -This technique sends data into Slack via a webhook URL created using [Slack's Workflow builder](https://slack.com/features/workflow-automation). Follow [these steps to create a Slack workflow using webhooks][create-webhook]. The Slack workflow webhook URL will be in the form `https://hooks.slack.com/workflows/....`. +This technique sends data into Slack via a webhook URL created using [Slack's Workflow builder](https://slack.com/features/workflow-automation) . Follow [these steps to create a Slack workflow using webhooks][create-webhook]. The Slack workflow webhook URL will be in the form `https://hooks.slack.com/workflows/....`. As part of the [workflow setup](https://slack.com/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks#workflow-setup), you will need to define expected variables in the payload the webhook will receive (described in the "Create custom variables" section of the docs). If these variables are missing in the payload, an error is returned. -To match the webhook input format expected by Workflow Builder, the payload will be flattened and stringified (all nested keys are moved to the top level) before being sent. The default delimiter used to flatten payloads is a period (".") but should be changed to an underscore ("_") using the `payload-delimiter` parameter if you're using nested payloads as input values in your own workflows. +To match the webhook input format expected by Workflow Builder, the payload will be flattened and stringified (all nested keys are moved to the top level) before being sent. The default delimiter used to flatten payloads is a period (".") but should be changed to an underscore ("\_") using the `payload-delimiter` parameter if you're using nested payloads as input values in your own workflows. #### Setup -* [Create a Slack workflow webhook][create-webhook]. -* Copy the webhook URL (`https://hooks.slack.com/workflows/....`) and [add it as a secret in your repo settings][repo-secret] named `SLACK_WEBHOOK_URL`. -* Add a step to your GitHub action to send data to your Webhook. -* Configure your Slack workflow to use variables from the incoming payload from the GitHub Action. You can select where you want to post the data and how you want to format it in Slack's workflow builder interface. +- [Create a Slack workflow webhook][create-webhook]. +- Copy the webhook URL (`https://hooks.slack.com/workflows/....`) and + [add it as a secret in your repo settings][repo-secret] named + `SLACK_WEBHOOK_URL`. +- Add a step to your GitHub action to send data to your Webhook. +- Configure your Slack workflow to use variables from the incoming payload from + the GitHub Action. You can select where you want to post the data and how you + want to format it in Slack's workflow builder interface. #### Usage -Add this Action as a [step][job-step] to your project's GitHub Action Workflow file: +Add this Action as a [step][job-step] to your project's GitHub Action Workflow +file: ```yaml - name: Send GitHub Action trigger data to Slack workflow id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: payload-delimiter: "_" - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} ``` or @@ -57,16 +71,13 @@ or ```yaml - name: Send custom JSON data to Slack workflow id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: # This data can be any valid JSON from a previous step in the GitHub Action payload: | - { - "key": "value", - "foo": "bar" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + "key": "value", + "foo": "bar" + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} ``` or @@ -76,11 +87,10 @@ or ```yaml - name: Send custom JSON data to Slack workflow id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: payload-file-path: "./payload-slack-content.json" - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} ``` > To send the payload file JSON as is, without replacing templated values with @@ -90,229 +100,263 @@ or ```yaml - name: Send custom JSON data to Slack workflow id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: payload-file-path: "./payload-slack-content.json" payload-file-path-parsed: false - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} ``` -### Technique 2: Slack App +### Technique 2: Slack API token -By creating a new Slack app or using an existing one, this approach allows your GitHub Actions job to post a message in a Slack channel or direct message by utilizing the [chat.postMessage](https://api.slack.com/methods/chat.postMessage) API method. Using this approach you can instantly post a message without setting up Slack workflows. +A bot token or user token or [token of some other kind][tokens] can be used to +call one of many [Slack API methods][methods]! This includes [`chat.postMessage`][chat.postMessage] +and the official `@slack/web-api` implemention of [`files.uploadV2`][files.uploadV2] + +By creating a new Slack app or using an existing one, this approach allows your +GitHub Actions job to post a message in a Slack channel or direct message by +utilizing the [chat.postMessage](https://api.slack.com/methods/chat.postMessage) +API method. Using this approach you can instantly post a message without setting +up Slack workflows. #### Setup -* [Create a Slack App][apps] for your workspace (alternatively use an existing app you have already created and installed). -* Add the [`chat:write`](https://api.slack.com/scopes/chat:write) bot scope under **OAuth & Permissions**. -* Install the app to your workspace. -* Copy the app's Bot Token from the **OAuth & Permissions** page and [add it as a secret in your repo settings][repo-secret] named `SLACK_BOT_TOKEN`. -* Invite the bot user into the channel you wish to post messages to (`/invite @bot_user_name`). +- [Create a Slack App][apps] for your workspace (alternatively use an existing + app you have already created and installed). +- Add the [`chat:write`](https://api.slack.com/scopes/chat:write) bot scope + under **OAuth & Permissions**. +- Install the app to your workspace. +- Copy the app's Bot Token from the **OAuth & Permissions** page and + [add it as a secret in your repo settings][repo-secret] named + `SLACK_BOT_TOKEN`. +- Invite the bot user into the channel you wish to post messages to + (`/invite @bot_user_name`). #### Usage -Add this Action as a [step][job-step] to your project's GitHub Action Workflow file: +Add this Action as a [step][job-step] to your project's GitHub Action Workflow +file: ```yaml - name: Post to a Slack channel id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: - # Slack channel id, channel name, or user id to post message. - # See also: https://api.slack.com/methods/chat.postMessage#channels - # You can pass in multiple channels to post to by providing a comma-delimited list of channel IDs. - channel-id: 'CHANNEL_ID,ANOTHER_CHANNEL_ID' - # For posting a simple plain text message - slack-message: "GitHub build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + "channel": "C0123456789", + "text": "howdy <@channel>!" ``` -Using JSON payload for constructing a message is also available: +Posting payloads with nested JSON, like block messages with block kit, works as +the API call hopes: ```yaml - name: Post to a Slack channel id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: - # Slack channel id, channel name, or user id to post message. - # See also: https://api.slack.com/methods/chat.postMessage#channels - channel-id: 'CHANNEL_ID' - # For posting a rich message using Block Kit + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - { - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" - } + "channel": "C0123456789", + "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + } + ] + } ``` -#### Update the message +#### File uploads -If you would like to notify the real-time updates on a build status, you can modify the message your build job posted in the subsequent steps. In order to do this, the steps after the first message posting can have `update-ts: ${{ steps.slack.outputs.ts }}` in their settings. With this, the step updates the already posted channel message instead of posting a new one. +Calling web API methods with `@slack/web-api` makes uploading files just another API call, but with all of the advantages of `files.uploadV2`: + +```yaml +- name: Share a file to that channel + uses: slackapi/slack-github-action@v2 + with: + method: files.uploadV2 + payload: | + "channel_id": "C0123456789", + "initial_comment": "the results are in!", + "file": "results.out", + "filename": "results-${{ github.sha }}.out" +``` + +Using JSON payload for constructing a message is also available: + +#### Update the message -Please note that **the message update step does not accept a channel name.** Set a channel ID for the steps for the actions that update messages. +If you would like to notify the real-time updates on a build status, you can +modify the message your build job posted in the subsequent steps. In order to do +this, the steps after the first message posting can have +`update_ts: ${{ steps.slack.outputs.ts }}` in their settings. With this, the +step updates the already posted channel message instead of posting a new one. ```yaml -- id: slack - uses: slackapi/slack-github-action@v1.27.0 +- name: Initiate the deployment launch sequence + id: slack + uses: slackapi/slack-github-action@v2-development with: - # The following message update step does not accept a channel name. - # Setting a channel ID here for consistency is highly recommended. - channel-id: "CHANNEL_ID" + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - { - "text": "Deployment started (In Progress)", - "attachments": [ - { - "pretext": "Deployment started", - "color": "dbab09", - "fields": [ - { - "title": "Status", - "short": true, - "value": "In Progress" - } - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} -- uses: slackapi/slack-github-action@v1.27.0 + "channel": "C0123456789", + "text": "Deployment started (In Progress)", + "attachments": [ + { + "pretext": "Deployment started", + "color": "dbab09", + "fields": [ + { + "title": "Status", + "short": true, + "value": "In Progress" + } + ] + } + ] +- name: Countdown + run: sleep 10 +- uses: slackapi/slack-github-action@v2-development with: - # Unlike the step posting a new message, this step does not accept a channel name. - # Please use a channel ID, not a name here. - channel-id: "CHANNEL_ID" - update-ts: ${{ steps.slack.outputs.ts }} + method: chat.update + token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - { - "text": "Deployment finished (Completed)", - "attachments": [ - { - "pretext": "Deployment finished", - "color": "28a745", - "fields": [ - { - "title": "Status", - "short": true, - "value": "Completed" - } - ] - } - ] - } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + "ts": "${{ steps.slack.outputs.ts }}", + "text": "Deployment finished (Completed)", + "attachments": [ + { + "pretext": "Deployment finished", + "color": "28a745", + "fields": [ + { + "title": "Status", + "short": true, + "value": "Completed" + } + ] + } + ] ``` +Please note that **the message update step does not accept a channel name.** Set +a channel ID for the steps for the actions that update messages. + #### Reply to a message If you want to post a message as a threaded reply, you can populate the `payload` with a `thread_ts` field. This field should equal the `ts` value of the parent message of the thread. If you want to reply to a message previously posted by this Action, you can use the `ts` output provided as the `thread_ts` of a consequent threaded reply, e.g. `"thread_ts": "${{ steps.deployment_message.outputs.ts }}"`. -Please note that **reply to a message does not accept a channel name.** Set a channel ID for the actions that reply to messages in thread. - ```yaml - id: deployment_message - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: - channel-id: "CHANNEL_ID" + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - { - "text": "Deployment started (In Progress)" - } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} -- uses: slackapi/slack-github-action@v1.27.0 + "channel": "C0123456789", + "text": "Deployment started (In Progress)" +- uses: slackapi/slack-github-action@v2-development with: - # Unlike the step posting a new message, this step does not accept a channel name. - # Please use a channel ID, not a name here. - channel-id: "CHANNEL_ID" + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - { - "thread_ts": "${{ steps.deployment_message.outputs.ts }}", - "text": "Deployment finished (Completed)" - } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + "channel": "C0123456789", + "thread_ts": "${{ steps.deployment_message.outputs.ts }}", + "text": "Deployment finished (Completed)" ``` +Please note that **reply to a message does not accept a channel name.** Set a channel ID for the actions that reply to messages in thread. + ### Technique 3: Slack Incoming Webhook -This approach allows your GitHub Actions job to post a message to a Slack channel or direct message by utilizing [Incoming Webhooks](https://api.slack.com/messaging/webhooks). +This approach allows your GitHub Actions job to post a message to a Slack +channel or direct message by utilizing +[Incoming Webhooks](https://api.slack.com/messaging/webhooks). -Incoming Webhooks conform to the same rules and functionality as any of Slack's other messaging APIs. You can make your posted messages as simple as a single line of text, or make them really useful with [interactive components](https://api.slack.com/messaging/interactivity). To make the message more expressive and useful use [Block Kit](https://api.slack.com/block-kit) to build and test visual components. +Incoming Webhooks conform to the same rules and functionality as any of Slack's +other messaging APIs. You can make your posted messages as simple as a single +line of text, or make them really useful with +[interactive components](https://api.slack.com/messaging/interactivity). To make +the message more expressive and useful use +[Block Kit](https://api.slack.com/block-kit) to build and test visual +components. #### Setup -* [Create a Slack App][apps] for your workspace (alternatively use an existing app you have already created and installed). -* Add the [`incoming-webhook`](https://api.slack.com/scopes/incoming-webhook) bot scope under **OAuth & Permissions**. -* Install the app to your workspace (you will select a channel to notify). -* Activate and create a new webhook under **Incoming Webhooks**. -* Copy the Webhook URL from the Webhook you just generated [add it as a secret in your repo settings][repo-secret] named `SLACK_WEBHOOK_URL`. +- [Create a Slack App][apps] for your workspace (alternatively use an existing + app you have already created and installed). +- Add the [`incoming-webhook`](https://api.slack.com/scopes/incoming-webhook) + bot scope under **OAuth & Permissions**. +- Install the app to your workspace (you will select a channel to notify). +- Activate and create a new webhook under **Incoming Webhooks**. +- Copy the Webhook URL from the Webhook you just generated + [add it as a secret in your repo settings][repo-secret] named + `SLACK_WEBHOOK_URL`. #### Usage ```yaml - name: Send custom JSON data to Slack workflow id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: # For posting a rich message using Block Kit + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | - { - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" - } + "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" } - ] - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + } + ] ``` ### HTTPS Proxy -If you need to use a proxy to connect with Slack, you can use the `HTTPS_PROXY` (or `https_proxy`) environment variable. In this example we use the Slack App technique, but configuring a proxy works the same way for all of them: +If you need to use a proxy to connect with Slack, you can use the `HTTPS_PROXY` +(or `https_proxy`) environment variable. In this example we use the Slack App +technique, but configuring a proxy works the same way for all of them: ```yaml - name: Post to a Slack channel via a proxy id: slack - uses: slackapi/slack-github-action@v1.27.0 + uses: slackapi/slack-github-action@v2-development with: - channel-id: 'CHANNEL_ID' - slack-message: 'This message was sent through a proxy' - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} # Set the HTTPS_PROXY environment variable to whatever your policy requires - HTTPS_PROXY: 'http://proxy.example.org:8080' + proxy: "http://proxy.example.org:8080" + payload: | + "channel": "C0123456789", + "message": "This message was sent through a proxy" ``` ## Contributing -See [CONTRIBUTING](.github/contributing.md). +All contributions are encouraged! Check out the [CONTRIBUTING](.github/contributing.md) guide to learn more. ## License See [LICENSE](LICENSE). +[apps]: https://api.slack.com/apps +[chat.postMessage]: https://api.slack.com/methods/chat.postMessage [create-webhook]: https://slack.com/intl/en-ca/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks +[files.uploadV2]: https://slack.dev/node-slack-sdk/web-api/#upload-a-file [job-step]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idsteps +[methods]: https://api.slack.com/methods [repo-secret]: https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository -[apps]: https://api.slack.com/apps +[tokens]: https://api.slack.com/concepts/token-types +[wfb-triggers]: https://api.slack.com/automation/triggers/webhook diff --git a/action.yml b/action.yml index 30d98dd4..5058033d 100644 --- a/action.yml +++ b/action.yml @@ -1,37 +1,51 @@ -name: 'slack-send' -description: 'Publish a message in a channel or send a JSON payload to the Slack Workflow Builder' +name: "Slack: Send to Slack" +description: "Post messages and make other API calls or start workflows with webhooks" inputs: - channel-id: # channel id to post message when using bot token - description: 'Slack channel ID where message will be posted. Needed if using bot token' + errors: + default: "false" + description: "If the job exits with an error on errors or continues" required: false - slack-message: # message to post when using bot token - description: 'Message to post into Slack. Needed if using bot token' + method: + description: "The Slack API method to call" required: false - payload: # JSON payload to send to Slack via webhook - description: 'JSON payload to send to Slack if webhook route. If not supplied, json from GitHub event will be sent instead' + payload: + description: "Attributes that create the content of the request" required: false - payload-delimiter: # custom delimiter used to flatten nested values in the JSON payload - description: 'Custom delimiter used to flatten nested values in the JSON payload. If not supplied, defaults to a period (".").' + payload-delimiter: + description: "Field seperators for nested attributes in the payload" required: false - payload-file-path: # path to JSON payload to send to Slack via webhook - description: 'path to JSON payload to send to Slack if webhook route. If not supplied, json from GitHub event will be sent instead. If payload is provided, it will take preference over this field' + payload-file-path: + description: "Path to a file containing the valid JSON payload" required: false payload-file-path-parsed: - description: 'Replace templated variables in the JSON payload file with values from the GitHub context and environment variables' - default: true + default: "false" + description: "If templated variables in the JSON payload are replaced" required: false - update-ts: # The timestamp of a previous message posted to update it instead of posting a new message - description: 'The timestamp of a previous message posted. It will update the existing message instead of posting a new message' + proxy: + description: "An optional alternate proxied route to for HTTPS connections" + required: false + retries: + default: "5" + description: "The method to use when performing retried requests" + token: + description: "The authentication value used with the Slack API" + required: false + webhook: + description: "A location for posting request payloads" required: false outputs: - time: # id of output - description: 'The time' - thread_ts: # threaded timestamp on the message that was posted when using bot token - description: 'The timestamp on the message that was posted into Slack when using bot token' - ts: # timestamp of message posted - description: 'The timestamp on the message that was posted into Slack when using bot token' - channel_id: # Id of the channel. If channel name was used as input we can get ID from output for later use when updating message or posting to thread. - description: 'The channel id of the message that was posted into Slack when using bot token' + time: + description: "The epoch time of job completion" + ok: + description: "" + channel_id: + description: "The channel ID of a message posted using a token" + response: + description: "" + thread_ts: + description: "The threaded timestamp on a message posted using a token" + ts: + description: "The timestamp on a message posted using a token" runs: - using: 'node20' - main: 'dist/index.js' + using: "node20" + main: "src/index.js" diff --git a/biome.json b/biome.json new file mode 100644 index 00000000..8c15ed51 --- /dev/null +++ b/biome.json @@ -0,0 +1,23 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { + "ignore": ["*.json"], + "ignoreUnknown": true + }, + "formatter": { + "indentStyle": "space" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "organizeImports": { + "enabled": true + }, + "vcs": { + "enabled": true, + "clientKind": "git" + } +} diff --git a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml index e0231d61..33379793 100644 --- a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml +++ b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml @@ -5,14 +5,11 @@ jobs: runs-on: ubuntu-latest name: New push to repo steps: - - name: Send GitHub trigger payload to Slack Workflow Builder - id: slack - uses: slackapi/slack-github-action@v1.27.0 - with: - payload: | - { + - name: Send GitHub trigger payload to Slack Workflow Builder + id: slack + uses: slackapi/slack-github-action@v2-development + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + payload: | "key": "value", "foo": "bar" - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml index f5bab0c7..00ccc7f8 100644 --- a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml +++ b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml @@ -5,10 +5,9 @@ jobs: runs-on: ubuntu-latest name: New push to repo steps: - - name: Send GitHub trigger payload to Slack Workflow Builder - id: slack - uses: slackapi/slack-github-action@v1.27.0 - with: - payload-file-path: "./example-workflows/Technique_1_Slack_Workflow_Builder/payloads/example.json" - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + - name: Send GitHub trigger payload to Slack Workflow Builder + id: slack + uses: slackapi/slack-github-action@v2-development + with: + payload-file-path: "./example-workflows/Technique_1_Slack_Workflow_Builder/payloads/example.json" + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml b/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml index 6f558ebf..2a195da2 100644 --- a/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml +++ b/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml @@ -5,8 +5,9 @@ jobs: runs-on: ubuntu-latest name: New push to repo steps: - - name: Send GitHub trigger payload to Slack Workflow Builder - id: slack - uses: slackapi/slack-github-action@v1.27.0 - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + - name: Send GitHub trigger payload to Slack Workflow Builder + id: slack + uses: slackapi/slack-github-action@v2-development + with: + payload-delimiter: "_" + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/example-workflows/Technique_2_Slack_App/JSON_payload.yml b/example-workflows/Technique_2_Slack_App/JSON_payload.yml index 17a432e3..91449ad7 100644 --- a/example-workflows/Technique_2_Slack_App/JSON_payload.yml +++ b/example-workflows/Technique_2_Slack_App/JSON_payload.yml @@ -5,13 +5,15 @@ jobs: runs-on: ubuntu-latest name: New push to repo steps: - - name: Send GitHub trigger payload to Slack Workflow Builder - id: slack - uses: slackapi/slack-github-action@v1.27.0 - with: - channel-id: 'SLACK_CHANNEL_ID' # ID of Slack Channel you want to post to - payload: | - { + - name: Send GitHub trigger payload to Slack Workflow Builder + id: slack + uses: slackapi/slack-github-action@v1.26.0 + with: + token: ${{ secrets.SLACK_BOT_TOKEN }} + method: chat.postMessage + payload: | + "channel": "C0123456789", + "text": "messages make meeting" "blocks": [ { "type": "divider" }, { @@ -37,8 +39,4 @@ jobs: } ] } - ], - "channel-id": "NEW_TARGET_CHANNEL_ID" - } - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + ] diff --git a/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml b/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml index dca61976..53fcc635 100644 --- a/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml +++ b/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml @@ -5,11 +5,12 @@ jobs: runs-on: ubuntu-latest name: New push to repo steps: - - name: Send GitHub trigger payload to Slack Workflow Builder - id: slack - uses: slackapi/slack-github-action@v1.27.0 - with: - channel-id: 'SLACK_CHANNEL_ID' # ID of Slack Channel you want to post to - payload: "{\"text\": \"posting from a github action\"}" - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + - name: Send GitHub trigger payload to Slack Workflow Builder + id: slack + uses: slackapi/slack-github-action@v2-development + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + "text": "posting from a github action", + "channel": "C0123456789" diff --git a/example-workflows/Technique_2_Slack_App/main.yml b/example-workflows/Technique_2_Slack_App/main.yml index a4152d39..28d5e22e 100644 --- a/example-workflows/Technique_2_Slack_App/main.yml +++ b/example-workflows/Technique_2_Slack_App/main.yml @@ -5,11 +5,12 @@ jobs: runs-on: ubuntu-latest name: New push to repo steps: - - name: Publish to slack channel via bot token - id: slack - uses: slackapi/slack-github-action@v1.27.0 - with: - channel-id: 'SLACK_CHANNEL_ID' # ID of Slack Channel you want to post to - slack-message: 'posting from a github action!' # The message you want to post - env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + - name: Publish to slack channel via bot token + id: slack + uses: slackapi/slack-github-action@v2-development + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + "channel": "C0123456789", + "text": "posting from a github action!" diff --git a/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml b/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml index 2daae76b..6659fed4 100644 --- a/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml +++ b/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml @@ -5,12 +5,12 @@ jobs: runs-on: ubuntu-latest name: New push to repo steps: - - name: Send GitHub trigger payload to Slack Workflow Builder - id: slack - uses: slackapi/slack-github-action@v1.27.0 - with: - payload: | - { + - name: Send GitHub trigger payload to Slack Workflow Builder + id: slack + uses: slackapi/slack-github-action@v2-development + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + payload: | "text": "Danny Torrence left a 1 star review for your property.", "blocks": [ { @@ -44,8 +44,3 @@ jobs: ] } ] - } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK - diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 00000000..8403252a --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "module": "es2022", + "moduleResolution": "node", + "esModuleInterop": true, + "checkJs": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "typeRoots": [ + "./@types", + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "test", // TODO: remove + "src/slack-send.js" // TODO: remove + ], + "include": [ + "src/**/*.js" + ], + "jsdoc": { + "out": "support/jsdoc", + "access": "public" + } +} diff --git a/package-lock.json b/package-lock.json index 4025ff66..c49282cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,35 +1,36 @@ { "name": "slack-github-action", - "version": "1.27.0", + "version": "2.0.0-development", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "slack-github-action", - "version": "1.27.0", + "version": "2.0.0-development", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@slack/web-api": "^7.3.4", - "axios": "^1.7.5", + "axios": "^1.7.2", + "axios-retry": "^4.5.0", "flat": "^5.0.2", "https-proxy-agent": "^7.0.5", - "markup-js": "^1.5.21", - "whatwg-url": "^14.0.0" + "markup-js": "^1.5.21" }, "devDependencies": { + "@biomejs/biome": "1.8.3", + "@types/chai": "^4.3.19", + "@types/flat": "^5.0.5", + "@types/mocha": "^10.0.7", + "@types/node": "^20.14.10", + "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", - "chai": "^4.5.0", - "eslint": "^8.57.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.10.2", - "eslint-plugin-node": "^11.1.0", - "mocha": "^10.7.0", - "nyc": "^17.0.0", - "rewiremock": "^3.14.5", - "sinon": "^18.0.0" + "c8": "^10.1.2", + "chai": "^5.1.1", + "mocha": "^10.7.3", + "sinon": "^17.0.1", + "typescript": "^5.5.3" }, "engines": { "node": ">=20.0.0", @@ -40,7 +41,6 @@ "version": "1.10.1", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz", "integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==", - "license": "MIT", "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" @@ -50,7 +50,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz", "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", - "license": "MIT", "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", @@ -59,5078 +58,824 @@ } }, "node_modules/@actions/http-client": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", - "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", - "license": "MIT", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz", + "integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==", "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "license": "MIT" + }, + "node_modules/@biomejs/biome": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.8.3.tgz", + "integrity": "sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" }, "engines": { - "node": ">=6.0.0" + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.8.3", + "@biomejs/cli-darwin-x64": "1.8.3", + "@biomejs/cli-linux-arm64": "1.8.3", + "@biomejs/cli-linux-arm64-musl": "1.8.3", + "@biomejs/cli-linux-x64": "1.8.3", + "@biomejs/cli-linux-x64-musl": "1.8.3", + "@biomejs/cli-win32-arm64": "1.8.3", + "@biomejs/cli-win32-x64": "1.8.3" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.8.3.tgz", + "integrity": "sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "engines": { + "node": ">=14" } }, - "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, - "node_modules/@babel/generator": { - "version": "7.25.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", - "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "node": ">=12" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=12" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, - "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" - }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", "engines": { - "node": ">=6.9.0" + "node": ">= 18" } }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.1.0.tgz", + "integrity": "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.0.0", + "@octokit/request": "^8.0.2", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">= 18" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/endpoint": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", + "integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==", "dependencies": { - "color-convert": "^1.9.0" + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=4" + "node": ">= 18" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/graphql": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", + "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@octokit/request": "^8.0.1", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" + "node": ">= 18" } }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } + "node_modules/@octokit/openapi-types": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz", + "integrity": "sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==" }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz", + "integrity": "sha512-WKTQXxK+bu49qzwv4qKbMMRXej1DU2gq017euWyKVudA6MldaSSQuxtz+vGbhxV4CjxpUxjZu6rM2wfc1FiWVg==", + "dependencies": { + "@octokit/types": "^12.4.0" + }, "engines": { - "node": ">=4" + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.2.0.tgz", + "integrity": "sha512-ePbgBMYtGoRNXDyKGvr9cyHjQ163PbwD0y1MkDJCpkO2YH4OeXX40c4wYHKikHGZcpGPbcRLuy0unPUuafco8Q==", "dependencies": { - "has-flag": "^3.0.0" + "@octokit/types": "^12.3.0" }, "engines": { - "node": ">=4" + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=5" } }, - "node_modules/@babel/parser": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", - "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/request": { + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz", + "integrity": "sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==", "dependencies": { - "@babel/types": "^7.25.4" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@octokit/endpoint": "^9.0.0", + "@octokit/request-error": "^5.0.0", + "@octokit/types": "^12.0.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">= 18" } }, - "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/request-error": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz", + "integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@octokit/types": "^12.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" }, "engines": { - "node": ">=6.9.0" + "node": ">= 18" } }, - "node_modules/@babel/traverse": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", - "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", - "dev": true, - "license": "MIT", + "node_modules/@octokit/types": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.4.0.tgz", + "integrity": "sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.4", - "@babel/parser": "^7.25.4", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.4", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" + "@octokit/openapi-types": "^19.1.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", + "optional": true, "engines": { - "node": ">=4" + "node": ">=14" } }, - "node_modules/@babel/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", - "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" + "type-detect": "4.0.8" } }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", - "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "dev": true, - "license": "MIT", "dependencies": { - "comment-parser": "1.4.1", - "esquery": "^1.6.0", - "jsdoc-type-pratt-parser": "~4.0.0" - }, - "engines": { - "node": ">=16" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", "dev": true, - "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "dependencies": { + "type-detect": "4.0.8" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true + }, + "node_modules/@slack/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", "license": "MIT", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@types/node": ">=18.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">= 18", + "npm": ">= 8.6.0" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, + "node_modules/@slack/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.12.0.tgz", + "integrity": "sha512-yFewzUomYZ2BYaGJidPuIgjoYj5wqPDmi7DLSaGIkf+rCi4YZ2Z3DaiYIbz7qb/PL2NmamWjCvB7e9ArI5HkKg==", "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 12.13.0", + "npm": ">= 6.12.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "node_modules/@slack/web-api": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.3.4.tgz", + "integrity": "sha512-KwLK8dlz2lhr3NO7kbYQ7zgPTXPKrhq1JfQc0etJ0K8LSJhYYnf8GbVznvgDT/Uz1/pBXfFQnoXjrQIOKAdSuw==", "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@slack/logger": "^4.0.0", + "@slack/types": "^2.9.0", + "@types/node": ">=18.0.0", + "@types/retry": "0.12.0", + "axios": "^1.7.4", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.0", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" }, "engines": { - "node": ">=10.10.0" + "node": ">= 18", + "npm": ">= 8.6.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@types/chai": { + "version": "4.3.19", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.19.tgz", + "integrity": "sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "license": "MIT" }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@types/flat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/flat/-/flat-5.0.5.tgz", + "integrity": "sha512-nPLljZQKSnac53KDUDzuzdRfGI0TDb5qPrb+SrQyN3MtdQrOnGsKniHN1iYZsJEBIVQve94Y6gNz22sgISZq+Q==", "dev": true, - "license": "BSD-3-Clause" + "license": "MIT" }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@types/mocha": { + "version": "10.0.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", + "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "undici-types": "~5.26.4" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/sinon": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", + "integrity": "sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" + "@types/sinonjs__fake-timers": "*" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "license": "MIT" }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@vercel/ncc": { + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz", + "integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==", "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "bin": { + "ncc": "dist/ncc/cli.js" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", + "node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dependencies": { - "p-try": "^2.0.0" + "debug": "^4.3.4" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=6.0.0" + "node": ">= 8" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } + "license": "Python-2.0" }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.0.0" + "node": ">=12" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, + "node_modules/axios": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "is-retry-allowed": "^2.2.0" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "axios": "0.x || 1.x" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", - "license": "MIT", - "engines": { - "node": ">= 18" + "balanced-match": "^1.0.0" } }, - "node_modules/@octokit/core": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", - "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.1.0", - "@octokit/request": "^8.3.1", - "@octokit/request-error": "^5.1.0", - "@octokit/types": "^13.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "fill-range": "^7.1.1" }, "engines": { - "node": ">= 18" + "node": ">=8" } }, - "node_modules/@octokit/endpoint": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", - "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", - "license": "MIT", + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/c8": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.2.tgz", + "integrity": "sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==", + "dev": true, + "license": "ISC", "dependencies": { - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" }, "engines": { - "node": ">= 18" + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, - "node_modules/@octokit/graphql": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", - "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", - "license": "MIT", + "node_modules/c8/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", "dependencies": { - "@octokit/request": "^8.3.0", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">=12" } }, - "node_modules/@octokit/openapi-types": { - "version": "22.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", - "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", - "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", - "license": "MIT", + "node_modules/c8/node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "license": "ISC", "dependencies": { - "@octokit/types": "^12.6.0" + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 18" + "node": ">=14" }, - "peerDependencies": { - "@octokit/core": "5" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "license": "MIT", + "node_modules/c8/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", "dependencies": { - "@octokit/openapi-types": "^20.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", - "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", - "license": "MIT", + "node_modules/c8/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", "dependencies": { - "@octokit/types": "^12.6.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 18" + "node": ">=16 || 14 >=14.17" }, - "peerDependencies": { - "@octokit/core": "5" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", - "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", - "license": "MIT" - }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", - "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^20.0.0" - } - }, - "node_modules/@octokit/request": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", - "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^9.0.1", - "@octokit/request-error": "^5.1.0", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", - "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", - "license": "MIT", - "dependencies": { - "@octokit/types": "^13.1.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", - "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^22.2.0" - } - }, - "node_modules/@pkgr/core": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", - "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/commons/node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", - "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^2.0.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", - "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", - "dev": true, - "license": "(Unlicense OR Apache-2.0)" - }, - "node_modules/@slack/logger": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", - "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", - "license": "MIT", - "dependencies": { - "@types/node": ">=18.0.0" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@slack/types": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.12.0.tgz", - "integrity": "sha512-yFewzUomYZ2BYaGJidPuIgjoYj5wqPDmi7DLSaGIkf+rCi4YZ2Z3DaiYIbz7qb/PL2NmamWjCvB7e9ArI5HkKg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" - } - }, - "node_modules/@slack/web-api": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.3.4.tgz", - "integrity": "sha512-KwLK8dlz2lhr3NO7kbYQ7zgPTXPKrhq1JfQc0etJ0K8LSJhYYnf8GbVznvgDT/Uz1/pBXfFQnoXjrQIOKAdSuw==", - "license": "MIT", - "dependencies": { - "@slack/logger": "^4.0.0", - "@slack/types": "^2.9.0", - "@types/node": ">=18.0.0", - "@types/retry": "0.12.0", - "axios": "^1.7.4", - "eventemitter3": "^5.0.1", - "form-data": "^4.0.0", - "is-electron": "2.2.2", - "is-stream": "^2", - "p-queue": "^6", - "p-retry": "^4", - "retry": "^0.13.1" - }, - "engines": { - "node": ">= 18", - "npm": ">= 8.6.0" - } - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", - "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vercel/ncc": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz", - "integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==", - "dev": true, - "license": "MIT", - "bin": { - "ncc": "dist/ncc/cli.js" - } - }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/assert": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz", - "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "object.assign": "^4.1.4", - "util": "^0.10.4" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "license": "ISC" - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", - "license": "Apache-2.0" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true, - "license": "MIT" - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", - "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", - "dev": true, - "license": "ISC", - "dependencies": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.5", - "hash-base": "~3.0", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.7", - "readable-stream": "^2.3.8", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001653", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", - "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/comment-parser": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", - "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/compare-module-exports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/compare-module-exports/-/compare-module-exports-2.1.0.tgz", - "integrity": "sha512-3Lc0sTIuX1jmY2K2RrXRJOND6KsRTX2D4v3+eu1PDptsuJZVK4LZc852eZa9I+avj0NrUKlTNgqvccNOH6mbGg==", - "dev": true, - "license": "ISC" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true, - "license": "MIT" - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "license": "ISC" - }, - "node_modules/des.js": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", - "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", - "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.2.tgz", - "integrity": "sha512-3XnC5fDyc8M4J2E8pt8pmSVRX2M+5yWMCfI/kDZwauQeFgzQOuhcRBFKjTeJagqgk4sFKxe1mvNVnaWwImx/Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-jsdoc": { - "version": "48.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.10.2.tgz", - "integrity": "sha512-xTkf/MmEeVrTbezc6kDqCJmK9RcseIKo8X4oyoDCMvV4LY8dqrQi8kmfRrv9n0gNBkCclevaOh2Lkmu6Fs8SLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.46.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.1", - "debug": "^4.3.5", - "escape-string-regexp": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.6.0", - "parse-imports": "^2.1.1", - "semver": "^7.6.3", - "spdx-expression-parse": "^4.0.0", - "synckit": "^0.9.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", - "license": "MIT" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "license": "ISC", - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", - "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/just-extend": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", - "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha512-j7MJE+TuT51q9ggt4fSgVqro163BEFjAt3u97IqU+JA2DkWl80nFTrowzLpZ/BnpN7rrl0JA/593NAdd8p/scQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "node_modules/lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markup-js": { - "version": "1.5.21", - "resolved": "https://registry.npmjs.org/markup-js/-/markup-js-1.5.21.tgz", - "integrity": "sha512-qeUHSSwdXkcdGJv/Gd8s5l9jLEWIW5NwXDMkGkW8V1TSckB5IOdpZ/eIVzfiAOnAJU0brrSwVx6K2Ie86KGIXg==", - "bin": { - "markup-js": "src/markup.js" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true, - "license": "MIT" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", - "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" - } - }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nyc": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.0.0.tgz", - "integrity": "sha512-ISp44nqNCaPugLLGGfknzQwSwt10SSS5IMoPR7GLoMAyS18Iw5js8U7ga2VF9lYuMZ42gOHr3UddZw4WZltxKg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^6.0.2", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/c8/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "node_modules/c8/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18" } }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "node_modules/c8/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "node_modules/c8/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { "node": ">=10" }, @@ -5138,1643 +883,1326 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "license": "MIT", "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", - "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", - "dev": true, - "license": "ISC", - "dependencies": { - "asn1.js": "^4.10.1", - "browserify-aes": "^1.2.0", - "evp_bytestokey": "^1.0.3", - "hash-base": "~3.0", - "pbkdf2": "^3.1.2", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/parse-imports": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", - "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "es-module-lexer": "^1.5.3", - "slashes": "^3.0.12" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">= 18" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", - "dev": true, - "license": "MIT" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" + "node": ">=12" } }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=0.12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">= 16" + } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, "engines": { - "node": ">=8.6" + "node": ">= 8.10.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8" + "node": ">=7.0.0" } }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dependencies": { - "p-locate": "^4.1.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 8" } }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "ms": "2.1.2" }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">=6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.6.0" + "node": ">=0.3.1" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "license": "MIT" }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, - "license": "MIT", - "dependencies": { - "fromentries": "^1.2.0" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=0.6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", - "dev": true, - "engines": { - "node": ">=0.4.x" + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "license": "MIT", + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "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" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "MIT" + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" + "engines": { + "node": "*" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "picomatch": "^2.2.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "is-glob": "^4.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 6" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, - "license": "ISC", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", "dependencies": { - "es6-error": "^4.0.1" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=4" + "node": ">= 14" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "binary-extensions": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { - "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rewiremock": { - "version": "3.14.5", - "resolved": "https://registry.npmjs.org/rewiremock/-/rewiremock-3.14.5.tgz", - "integrity": "sha512-MdPutvaUd+kKVz/lcEz6N6337s4PxRUR5vhphIp2/TJRgfXIckomIkCsIAbwB53MjiSLwi7KBMdQ9lPWE5WpYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-runtime": "^6.26.0", - "compare-module-exports": "^2.1.0", - "lodash.some": "^4.6.0", - "lodash.template": "^4.4.0", - "node-libs-browser": "^2.1.0", - "path-parse": "^1.0.5", - "wipe-node-cache": "^2.1.2", - "wipe-webpack-cache": "^2.1.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=8" } }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "engines": { + "node": ">=0.12.0" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" + "engines": { + "node": ">=8" } }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, "engines": { - "node": ">=0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">=8" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "randombytes": "^2.1.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/istanbul-lib-report/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "yallist": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" } }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" + "semver": "^7.5.3" }, "engines": { - "node": ">= 0.4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true, - "license": "MIT" - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "node_modules/istanbul-lib-report/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "lru-cache": "^6.0.0" }, "bin": { - "sha.js": "bin.js" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/istanbul-lib-report/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, - "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/jackspeak": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.2.tgz", + "integrity": "sha512-qH3nOSj8q/8+Eg8LUPOq3C+6HWkpUioIjDsq1+D4zY91oZvpPttw8GwtF1nReRYKXl+1AORyFqtm2f5Q1SB6/Q==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, "engines": { - "node": ">=8" + "node": "14 >=14.21 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true }, - "node_modules/sinon": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", - "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/slashes": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", - "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/markup-js": { + "version": "1.5.21", + "resolved": "https://registry.npmjs.org/markup-js/-/markup-js-1.5.21.tgz", + "integrity": "sha512-qeUHSSwdXkcdGJv/Gd8s5l9jLEWIW5NwXDMkGkW8V1TSckB5IOdpZ/eIVzfiAOnAJU0brrSwVx6K2Ie86KGIXg==", + "bin": { + "markup-js": "src/markup.js" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "CC-BY-3.0" + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "node_modules/mocha": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "license": "BSD-3-Clause" + "license": "MIT" }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/nise": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.7.tgz", + "integrity": "sha512-wWtNUhkT7k58uvWTB/Gy26eA/EJKtPZFVAhEilN5UYVmmGRYOURbejRUyKm0Uu9XVEW7K5nBOZfR8VMB4QR2RQ==", "dev": true, - "license": "MIT", "dependencies": { - "safe-buffer": "~5.2.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, - "license": "MIT", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, + "wrappy": "1" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "p-finally": "^1.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { + "node_modules/package-json-from-dist": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "BlueOak-1.0.0" }, - "node_modules/synckit": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", - "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/core": "^0.1.0", - "tslib": "^2.6.2" - }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "node": ">=8" } }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, "engines": { "node": ">=8" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "MIT", - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } + "license": "ISC" }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", - "dev": true, - "license": "MIT" + "node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true }, - "node_modules/to-fast-properties": { + "node_modules/pathval": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 14.16" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" + "node": ">=8.6" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tr46/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "safe-buffer": "^5.1.0" } }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.0" + "picomatch": "^2.2.1" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">=8.10.0" } }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + "node": ">= 4" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" + "randombytes": "^2.1.0" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" }, - "engines": { - "node": ">= 0.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "is-typedarray": "^1.0.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", - "license": "MIT", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "@fastify/busboy": "^2.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=14.0" + "node": ">=8" } }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" - }, - "node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", - "license": "ISC" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - }, - "bin": { - "update-browserslist-db": "cli.js" + "is-number": "^7.0.0" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "engines": { + "node": ">=8.0" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, - "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/url": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", - "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.12.3" + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">= 0.4" + "node": ">=14.17" } }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "license": "MIT", "dependencies": { - "inherits": "2.0.3" + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "license": "ISC" + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, - "license": "MIT" - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.0.0.tgz", - "integrity": "sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==", - "license": "MIT", + "license": "ISC", "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=10.12.0" } }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -6785,85 +2213,32 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true, - "license": "ISC" + "license": "Apache-2.0" }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wipe-node-cache": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/wipe-node-cache/-/wipe-node-cache-2.1.2.tgz", - "integrity": "sha512-m7NXa8qSxBGMtdQilOu53ctMaIBXy93FOP04EC1Uf4bpsE+r+adfLKwIMIvGbABsznaSNxK/ErD4xXDyY5og9w==", - "dev": true, - "license": "MIT" - }, - "node_modules/wipe-webpack-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wipe-webpack-cache/-/wipe-webpack-cache-2.1.0.tgz", - "integrity": "sha512-OXzQMGpA7MnQQ8AG+uMl5mWR2ezy6fw1+DMHY+wzYP1qkF1jrek87psLBmhZEj+er4efO/GD4R8jXWFierobaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "wipe-node-cache": "^2.1.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -6884,49 +2259,17 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" - } + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, - "license": "ISC", "engines": { "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -6972,38 +2315,11 @@ "node": ">=10" } }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 63d94025..1150a436 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { "name": "slack-github-action", - "version": "1.27.0", - "description": "The official slack github action. Use this to send data into your Slack workspace", + "version": "2.0.0-development", + "description": "The official Slack Github Action. Use this to send data into your Slack workspace", "main": "dist/index.js", + "type": "module", "scripts": { - "lint": "eslint .", + "lint:fix": "biome check --write", + "lint": "biome check", + "check": "tsc --noemit --module es2022 --project ./jsconfig.json", "local": "act public --eventpath .github/workflows/local/event.json --secret-file .github/workflows/local/.env --platform ubuntu-latest=node:20-buster", - "test:mocha": "mocha --config .mocharc.json test/*-test.js", - "test:gen-cov": "nyc --reporter=lcov npm run test:mocha", - "test": "npm run lint && nyc npm run test:mocha", - "build": "npx @vercel/ncc build src/index.js --license licenses.txt" + "test": "c8 mocha src/*.spec.js" }, "repository": { "type": "git", @@ -35,23 +35,24 @@ "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@slack/web-api": "^7.3.4", - "axios": "^1.7.5", + "axios": "^1.7.2", + "axios-retry": "^4.5.0", "flat": "^5.0.2", "https-proxy-agent": "^7.0.5", - "markup-js": "^1.5.21", - "whatwg-url": "^14.0.0" + "markup-js": "^1.5.21" }, "devDependencies": { + "@biomejs/biome": "1.8.3", + "@types/chai": "^4.3.19", + "@types/flat": "^5.0.5", + "@types/mocha": "^10.0.7", + "@types/node": "^20.14.10", + "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", - "chai": "^4.5.0", - "eslint": "^8.57.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsdoc": "^48.10.2", - "eslint-plugin-node": "^11.1.0", - "mocha": "^10.7.0", - "nyc": "^17.0.0", - "rewiremock": "^3.14.5", - "sinon": "^18.0.0" + "c8": "^10.1.2", + "chai": "^5.1.1", + "mocha": "^10.7.3", + "sinon": "^17.0.1", + "typescript": "^5.5.3" } } diff --git a/src/client.js b/src/client.js new file mode 100644 index 00000000..4bc5a3e7 --- /dev/null +++ b/src/client.js @@ -0,0 +1,99 @@ +import webapi from "@slack/web-api"; +import { HttpsProxyAgent } from "https-proxy-agent"; +import Config from "./config.js"; +import SlackError from "./errors.js"; + +/** + * The Client class creates a WebClient from @slack/web-api for use when calling + * various Slack API methods. + * + * @see {@link https://slack.dev/node-slack-sdk/web-api/} + * @see {@link https://api.slack.com/methods/} + */ +export default class Client { + /** + * @typedef MessageResult - Possible message values from API methods. + * @prop {string} [threadTs] - timestamp of the top threaded message. + * @prop {string} [ts] - timestamp of the message. + * @prop {string} [channelId] - ID of the channel. + */ + + /** + * Perform the API call configured with the input payload. + * @param {Config} config + */ + async post(config) { + if (!config.inputs.method) { + throw new SlackError(config.core, "No API method was provided for use"); + } + if (!config.inputs.token) { + throw new SlackError(config.core, "No token was provided to post with"); + } + const client = new config.webapi.WebClient(config.inputs.token, { + agent: this.proxies(config)?.httpsAgent, + retryConfig: this.retries(config.inputs.retries), + }); + /** + * @type {webapi.WebAPICallResult & MessageResult} + */ + const response = await client.apiCall(config.inputs.method, config.content); + config.core.setOutput("ok", response.ok); + config.core.setOutput("response", JSON.stringify(response)); + if (!response.ok) { + throw new Error(response.error); + } + if (response.channelId) { + config.core.setOutput("channel_id", response.channelId); + } + if (response.threadTs) { + config.core.setOutput("thread_ts", response.threadTs); + } + if (response.ts) { + config.core.setOutput("ts", response.ts); + } + } + + /** + * Return configurations for https proxy options if these are set. + * @param {Config} config + * @returns {import("axios").AxiosRequestConfig | undefined} + * @see {@link https://github.com/slackapi/slack-github-action/pull/205} + */ + proxies(config) { + try { + const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; + if (!httpsProxy) { + return undefined; + } + return { + httpsAgent: new HttpsProxyAgent(httpsProxy), + }; + } catch (err) { + config.core.warning( + "Failed to configure HTTPS proxy agent for HTTP proxy so using the default axios configuration.", + ); + console.error(err); + return undefined; + } + } + + /** + * Return configurations for retry options with different delays. + * @param {string} option + * @returns {import("@slack/web-api").RetryOptions} + */ + retries(option) { + switch (option) { + case "0": + return { retries: 0 }; + case "5": + return webapi.retryPolicies.fiveRetriesInFiveMinutes; + case "10": + return webapi.retryPolicies.tenRetriesInAboutThirtyMinutes; + case "RAPID": + return webapi.retryPolicies.rapidRetryPolicy; + default: + return webapi.retryPolicies.fiveRetriesInFiveMinutes; + } + } +} diff --git a/src/client.spec.js b/src/client.spec.js new file mode 100644 index 00000000..cb5987ff --- /dev/null +++ b/src/client.spec.js @@ -0,0 +1,58 @@ +import { assert } from "chai"; +import { mocks } from "./index.spec.js"; +import send from "./send.js"; + +describe("client", () => { + beforeEach(() => { + mocks.reset(); + }); + + describe("inputs", () => { + it("requires a token is provided in inputs", async () => { + try { + await send(mocks.core); + assert.fail("Failed to throw for missing input"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Missing input! Either a token or webhook is required to take action.", + ); + } + }); + + it("requires a method is provided in inputs", async () => { + mocks.core.getInput.withArgs("token").returns("xoxb-example-001"); + try { + await send(mocks.core); + assert.fail("Failed to throw for missing input"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Missing input! A method must be decided to use the token provided.", + ); + } + }); + }); + + describe("failure", () => { + /** + * FIXME: this is calling the actual API to cause an invalid_auth error! + * + * It should be stubbing this or mocking something similar. + * + * @see {@link https://github.com/slackapi/slack-github-action/blob/5d1fb07d3c4f410b8d278134c714edff31264beb/test/web-client-test.js#L6-L17} + */ + it("calls the method with the provided token and content", async () => { + try { + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput + .withArgs("payload") + .returns(`"text": "hello", "channel": "C0123456789"`); + await send(mocks.core); + } catch (error) { + console.error(error); + } + }); + }); +}); diff --git a/src/config.js b/src/config.js new file mode 100644 index 00000000..335f5d3b --- /dev/null +++ b/src/config.js @@ -0,0 +1,259 @@ +import fs from "node:fs"; +import path from "node:path"; +import core from "@actions/core"; +import github from "@actions/github"; +import webapi from "@slack/web-api"; +import axios from "axios"; +import flatten from "flat"; +import markup from "markup-js"; +import SlackError from "./errors.js"; + +/** + * Options and settings set as inputs to this action. + * + * @see {@link ../action.yml} + */ +export default class Config { + /** + * Options of retries for failed requests. + * @readonly + * @enum {string} The option for retries. + */ + Retries = { + /** No retries, just hope that things go alright. + * @readonly + */ + ZERO: "0", + /** + * Five retries in five minutes. + * @readonly + */ + FIVE: "5", + /** + * Ten retries in about thirty minutes. + * @readonly + */ + TEN: "10", + /** + * A burst of retries to keep things running fast. + * @readonly + */ + RAPID: "RAPID", + }; + + /** + * @typedef Inputs - Values provided to this job. + * @property {boolean} errors - If the job should exit after errors or succeed. + * @property {string?} method - The Slack API method to call. + * @property {string?} payload - Request contents from the provided input. + * @property {string?} payloadDelimiter - Seperators of nested attributes. + * @property {string?} payloadFilePath - Location of a JSON request payload. + * @property {boolean} payloadFilePathParsed - If templated values are replaced. + * @property {string?} proxy - An optional proxied connection for requests. + * @property {Retries} retries - The retries method to use for failed requests. + * @property {string?} token - The authentication value used with the Slack API. + * @property {string?} webhook - A location for posting request payloads. + */ + + /** + * @type {Inputs} - The actual action input values. + */ + inputs; + + /** + * @type {import("axios").AxiosStatic} - The axios client. + */ + axios; + + /** + * @typedef Content - The provided and parsed payload object. + * @type {Record} + */ + + /** + * @type {Content} - The parsed payload data to send. + */ + content; + + /** + * Shared utilities specific to the GitHub action workflow. + * @type {import("@actions/core")} + */ + core; + + /** + * @type {import("@slack/web-api")} - Slack API client. + */ + webapi; + + /** + * Gather values from the job inputs and use defaults or error for the missing + * ones. + * + * The content of the payload is also parsed, proxies set, and a shared "core" + * kept for later use. + * + * @constructor + * @param {core} core - GitHub Actions core utilities. + */ + constructor(core) { + this.axios = axios; + this.core = core; + this.webapi = webapi; + this.inputs = { + errors: core.getBooleanInput("errors"), + method: core.getInput("method"), + payload: core.getInput("payload"), + payloadDelimiter: core.getInput("payload-delimiter"), + payloadFilePath: core.getInput("payload-file-path"), + payloadFilePathParsed: + core.getBooleanInput("payload-file-path-parsed") || false, + proxy: + core.getInput("proxy") || + process.env.HTTPS_PROXY || + process.env.https_proxy || + null, + retries: core.getInput("retries") || this.Retries.FIVE, + token: core.getInput("token") || process.env.SLACK_TOKEN || null, + webhook: + core.getInput("webhook") || process.env.SLACK_WEBHOOK_URL || null, + }; + switch (true) { + case !!this.inputs.token && !!this.inputs.webhook: + core.debug( + "Setting the provided token and webhook as secret variables.", + ); + core.setSecret(this.inputs.token); + core.setSecret(this.inputs.webhook); + throw new SlackError( + core, + "Invalid input! Either the token or webhook is required - not both.", + ); + case !!this.inputs.token: + core.debug("Setting the provided token as a secret variable."); + core.setSecret(this.inputs.token); + if (!this.inputs.method) { + throw new SlackError( + core, + "Missing input! A method must be decided to use the token provided.", + ); + } + break; + case !!this.inputs.webhook: + core.debug("Setting the provided webhook as a secret variable."); + core.setSecret(this.inputs.webhook); + break; + default: + throw new SlackError( + core, + "Missing input! Either a token or webhook is required to take action.", + ); + } + switch (true) { + case !!this.inputs.payload && !!this.inputs.payloadFilePath: + throw new SlackError( + core, + "Invalid input! Just the payload or payload file path is required.", + ); + case !!this.inputs.payload: + this.content = this.getContentPayload(core); + break; + case !!this.inputs.payloadFilePath: + this.content = this.getContentPayloadFilePath(core); + break; + default: + core.debug("Missing payload so gathering inputs from action context."); + this.content = github.context; + break; + } + if (this.inputs.payloadDelimiter) { + this.content = flatten(this.content, { + delimiter: this.inputs.payloadDelimiter, + }); + for (const key of Object.keys(this.content)) { + this.content[key] = `${this.content[key]}`; + } + } + core.debug(`Parsed request content: ${JSON.stringify(this.content)}`); + switch (this.inputs.retries) { + case this.Retries.ZERO: + case this.Retries.FIVE: + case this.Retries.TEN: + case this.Retries.RAPID: + break; + default: + core.warning( + `Invalid input! An unknown "retries" value was used: ${this.inputs.retries}`, + ); + } + core.debug(`Gathered action inputs: ${JSON.stringify(this.inputs)}`); + } + + /** + * Format request content from payload values for use in the request. + * @param {core} core - GitHub Actions core utilities. + * @throws if the input payload or payload file path is invalid JSON. + * @returns {Content} - the parsed JSON payload to use in requests. + */ + getContentPayload(core) { + if (!this.inputs.payload) { + throw new SlackError(core, "Invalid input! No payload content found"); + } + try { + const trimmed = this.inputs.payload.trim(); + if ( + !this.inputs.payload.startsWith("{") && + !this.inputs.payload.endsWith("}") + ) { + core.debug("Wrapping input payload in braces to create valid JSON"); + const comma = trimmed.replace(/,$/, ""); // remove trailing comma + const wrap = `{${comma}}`; + return JSON.parse(wrap); + } + return JSON.parse(trimmed); + } catch (error) { + if (error instanceof Error) { + core.error(error); + } + throw new SlackError( + core, + "Invalid input! Failed to parse the JSON content of the payload", + ); + } + } + + /** + * Format request content from the payload file path for use in the request. + * @param {core} core - GitHub Actions core utilities. + * @throws if the input payload or payload file path is invalid JSON. + * @returns {Content} - the parsed JSON payload to use in requests. + */ + getContentPayloadFilePath(core) { + if (!this.inputs.payloadFilePath) { + throw new SlackError(core, "Invalid input! No payload found for content"); + } + try { + const content = fs.readFileSync( + path.resolve(this.inputs.payloadFilePath), + "utf-8", + ); + if (!this.inputs.payloadFilePathParsed) { + return JSON.parse(content); + } + const template = content.replace(/\$\{\{/g, "{{"); // swap ${{ for {{ + const context = { + env: process.env, + github: github.context, + }; + return JSON.parse(markup.up(template, context)); + } catch (error) { + if (error instanceof Error) { + core.error(error); + } + throw new SlackError( + core, + "Invalid input! Failed to parse the JSON content of the payload file", + ); + } + } +} diff --git a/src/config.spec.js b/src/config.spec.js new file mode 100644 index 00000000..85c1a90f --- /dev/null +++ b/src/config.spec.js @@ -0,0 +1,246 @@ +import path from "node:path"; +import { assert } from "chai"; +import Config from "./config.js"; +import { mocks } from "./index.spec.js"; +import send from "./send.js"; + +/** + * Confirm values from the action input or environment variables are gathered + * or errors are thrown for invalid inputs. + * + * An assumption is made around these same checks and parsings being done for + * each collection of configurations, but only the edge cases of checks are done + * here. + */ +describe("config", () => { + beforeEach(() => { + mocks.reset(); + }); + + describe("inputs", () => { + it("valid values are collected from the action inputs", async () => { + mocks.core.getBooleanInput.withArgs("errors").returns(true); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("payload").returns('"hello": "world"'); + mocks.core.getInput.withArgs("proxy").returns("https://example.com"); + mocks.core.getInput.withArgs("retries").returns("0"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + const config = new Config(mocks.core); + assert.equal(config.inputs.errors, true); + assert.equal(config.inputs.method, "chat.postMessage"); + assert.equal(config.inputs.payload, '"hello": "world"'); + assert.equal(config.inputs.proxy, "https://example.com"); + assert.equal(config.inputs.retries, config.Retries.ZERO); + assert.equal(config.inputs.token, "xoxb-example"); + }); + }); + + describe("payload", () => { + describe("success", () => { + it("wraps incomplete payload in braces for valid JSON", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns(` + "message": "LGTM!", + "channel": "C0123456789", + "blocks": [ + { + "type": "section", + "text": { + "text": "LGTM! :+1:" + } + } + ] + `); + const config = new Config(mocks.core); + const expected = { + message: "LGTM!", + channel: "C0123456789", + blocks: [ + { + type: "section", + text: { + text: "LGTM! :+1:", + }, + }, + ], + }; + assert.deepEqual(config.content, expected); + }); + + it("accepts and parses complete json as payload input", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns(`{ + "message": "this is wrapped", + "channel": "C0123456789" + } + `); + const config = new Config(mocks.core); + const expected = { + message: "this is wrapped", + channel: "C0123456789", + }; + assert.deepEqual(config.content, expected); + }); + + it("parses JSON from a known file without replacements", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput + .withArgs("payload-file-path") + .returns("example.json"); + mocks.fs.readFileSync + .withArgs(path.resolve("example.json"), "utf-8") + .returns(`{ + "message": "drink water", + "channel": "C6H12O6H2O2" + }`); + const config = new Config(mocks.core); + const expected = { + message: "drink water", + channel: "C6H12O6H2O2", + }; + assert.deepEqual(config.content, expected); + }); + + it("replaces templated variables in the payload file", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput + .withArgs("payload-file-path") + .returns("example.json"); + mocks.core.getBooleanInput + .withArgs("payload-file-path-parsed") + .returns(true); + mocks.fs.readFileSync + .withArgs(path.resolve("example.json"), "utf-8") + .returns(`{ + "text": "running job #\${{ env.MOCK_JOB }} on: \${{ github.apiUrl }}" + }`); + process.env.MOCK_JOB = "12"; + const config = new Config(mocks.core); + process.env.MOCK_JOB = undefined; + const expected = { + text: "running job #12 on: https://api.github.com", + }; + assert.deepEqual(config.content, expected); + }); + + it("flattens nested payloads if a delimiter is provided", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload-delimiter").returns("_"); + mocks.core.getInput.withArgs("payload").returns(` + "apples": "tree", + "bananas": { + "truthiness": true + } + `); + const config = new Config(mocks.core); + const expected = { + apples: "tree", + bananas_truthiness: "true", + }; + assert.deepEqual(config.content, expected); + }); + }); + + describe("failure", () => { + it("errors if both a payload and file path are provided", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); + mocks.core.getInput + .withArgs("payload-file-path") + .returns("example.json"); + try { + await send(mocks.core); + assert.fail("Failed to throw for invalid input"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Invalid input! Just the payload or payload file path is required.", + ); + } + }); + + it("fails if the provided input payload is invalid JSON", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns("{"); + try { + await send(mocks.core); + assert.fail("Failed to throw for invalid JSON"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg.toString(), + "Invalid input! Failed to parse the JSON content of the payload", + ); + } + }); + + it("fails to parse a file path that does not exist", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput + .withArgs("payload-file-path") + .returns("unknown.json"); + try { + await send(mocks.core); + assert.fail("Failed to throw for nonexistent files"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg.toString(), + "Invalid input! Failed to parse the JSON content of the payload file", + ); + } + }); + }); + }); + + describe("retries", () => { + it("warns if an invalid retries option is provided", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("retries").returns("FOREVER"); + await send(mocks.core); + assert.isTrue( + mocks.core.warning.calledWith( + 'Invalid input! An unknown "retries" value was used: FOREVER', + ), + ); + }); + }); + + describe("secrets", async () => { + it("treats the provided token as a secret", async () => { + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + try { + await send(mocks.core); + } catch { + assert.isTrue(mocks.core.setSecret.withArgs("xoxb-example").called); + } + }); + + it("treats the provided webhook as a secret", async () => { + mocks.core.getInput.withArgs("webhook").returns("https://slack.com"); + try { + await send(mocks.core); + } catch { + assert.isTrue( + mocks.core.setSecret.withArgs("https://slack.com").called, + ); + } + }); + }); +}); diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 00000000..b3e78e26 --- /dev/null +++ b/src/errors.js @@ -0,0 +1,23 @@ +import core from "@actions/core"; + +/** + * SlackError is a custom error wrapper for known errors of Slack GitHub Action. + */ +export default class SlackError extends Error { + /** + * @param {core} core - GitHub Actions core utilities. + * @param {any} error - The error message to throw. + * @param {boolean} fails - if the exit should be forced. + */ + constructor(core, error, fails = true) { + if (error instanceof Error) { + super(error.message); + } else { + super(error); + } + this.name = "SlackError"; + if (fails) { + core.setFailed(error); + } + } +} diff --git a/src/index.js b/src/index.js index 694d71b2..f8b9ad0c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,17 @@ -const core = require('@actions/core'); -const slackSend = require('./slack-send'); +import core from "@actions/core"; +import send from "./send.js"; -slackSend(core); +/** + * Invoke the Slack GitHub Action job from this file but export actual logic + * from the send.js file for testing purposes. + */ +try { + send(core); +} catch (error) { + if (error instanceof Error) { + core.error(error.message); + core.debug(`${error.name} stack: ${error.stack}`); + } else { + core.error(`${error}`); + } +} diff --git a/src/index.spec.js b/src/index.spec.js new file mode 100644 index 00000000..19d2f557 --- /dev/null +++ b/src/index.spec.js @@ -0,0 +1,61 @@ +import fs from "node:fs"; +import core from "@actions/core"; +import axios, { AxiosError } from "axios"; +import sinon from "sinon"; + +/** + * Hello experimenter! These tests are here to confirm that the happy paths keep + * working and error cases are thrown. + * + * Tests are grouped with related related code using a file *.spec.js extension. + * These test related functionalities of units but a full suite of integration + * tests confirm correctness of the actual workflows in .github/workflows/*.yml. + * + * Actual tests begin in send.spec.js which has integration tests. Other modules + * test the edges of this action. + */ + +/** + * The Mock class sets expected behaviors and test listeners for dependencies. + */ +export class Mock { + /** + * @typedef Errors - A collection of mocked errors to use in tests. + * @prop {Object.} axios - The mocked axios errors. + */ + + /** + * The mocked errors. + * @type {Errors} + */ + errors = { + axios: { + network_failed: new AxiosError("network_failed"), + }, + }; + + /** + * Setup stubbed dependencies and configure default input arguments for all + * tests. + * + * @see {@link ../action.yml} + */ + constructor() { + this.sandbox = sinon.createSandbox(); + this.axios = this.sandbox.stub(axios); + this.core = this.sandbox.stub(core); + this.fs = this.sandbox.stub(fs); + this.core.getInput.withArgs("errors").returns("false"); + this.core.getInput.withArgs("retries").returns("5"); + } + + /** + * Testing interface that removes internal state from existing stubs. + */ + reset() { + this.sandbox.reset(); + this.axios.post.resetHistory(); + } +} + +export const mocks = new Mock(); diff --git a/src/send.js b/src/send.js new file mode 100644 index 00000000..51e95efd --- /dev/null +++ b/src/send.js @@ -0,0 +1,35 @@ +import core from "@actions/core"; +import Client from "./client.js"; +import Config from "./config.js"; +import SlackError from "./errors.js"; +import Webhook from "./webhook.js"; + +/** + * Orchestrate the action job happenings from inputs to logic to outputs. + * @param {core} core - GitHub Actions core utilities. + * @throws if an error happens but might not cause the job to fail. + */ +export default async function send(core) { + const config = new Config(core); + try { + await post(config); + config.core.setOutput("time", new Date().valueOf() / 1000); + } catch (error) { + throw new SlackError(core, error, config.inputs.errors); + } +} + +/** + * Perform the posting action of this workflow with configured settings. + * @param {Config} config + */ +async function post(config) { + switch (true) { + case !!config.inputs.token: + return await new Client().post(config); + case !!config.inputs.webhook: + return await new Webhook().post(config); + default: + throw new SlackError(config.core, "No method found to post content"); + } +} diff --git a/src/send.spec.js b/src/send.spec.js new file mode 100644 index 00000000..071cde87 --- /dev/null +++ b/src/send.spec.js @@ -0,0 +1,21 @@ +import { mocks } from "./index.spec.js"; +import send from "./send.js"; + +/** + * This is a collection of integration tests that make sure modules are doing + * whatever's expected. + * + * Or at least that's planned... + * + * Edge cases for inputs are checked in separate modules including the config + * specifications. + */ +describe("send", () => { + afterEach(() => { + mocks.reset(); + }); + + it("exists and can be called", async () => { + await send(mocks.core); + }); +}); diff --git a/src/slack-send.js b/src/slack-send.js deleted file mode 100644 index 7c2065e3..00000000 --- a/src/slack-send.js +++ /dev/null @@ -1,165 +0,0 @@ -const { promises: fs } = require('fs'); -const path = require('path'); -const github = require('@actions/github'); -const flatten = require('flat'); -const axios = require('axios'); -const markup = require('markup-js'); -const { HttpsProxyAgent } = require('https-proxy-agent'); -const { parseURL } = require('whatwg-url'); -const { createWebClient } = require('./web-client'); - -const SLACK_WEBHOOK_TYPES = { - WORKFLOW_TRIGGER: 'WORKFLOW_TRIGGER', - INCOMING_WEBHOOK: 'INCOMING_WEBHOOK', -}; - -module.exports = async function slackSend(core) { - try { - const botToken = process.env.SLACK_BOT_TOKEN; - const webhookUrl = process.env.SLACK_WEBHOOK_URL; - let webhookType = SLACK_WEBHOOK_TYPES.WORKFLOW_TRIGGER; - - if (process.env.SLACK_WEBHOOK_TYPE) { - // The default type is for Workflow Builder triggers. If you want to use this action for Incoming Webhooks, use - // the corresponding type instead. - webhookType = process.env.SLACK_WEBHOOK_TYPE.toUpperCase(); - } - - if ((botToken === undefined || botToken.length <= 0) && (webhookUrl === undefined || webhookUrl.length <= 0)) { - throw new Error('Need to provide at least one botToken or webhookUrl'); - } - - let payload = core.getInput('payload'); - - const payloadFilePath = core.getInput('payload-file-path'); - - /** Option to replace templated context variables in the payload file JSON */ - const payloadFilePathParsed = core.getBooleanInput('payload-file-path-parsed'); - - let webResponse; - - if (payloadFilePath && !payload) { - try { - payload = await fs.readFile(path.resolve(payloadFilePath), 'utf-8'); - if (payloadFilePathParsed) { - const context = { github: github.context, env: process.env }; - const payloadString = payload.replaceAll('${{', '{{'); - payload = markup.up(payloadString, context); - } - } catch (error) { - // passed in payload file path was invalid - console.error(error); - throw new Error(`The payload-file-path may be incorrect. Failed to load the file: ${payloadFilePath}`); - } - } - - if (payload) { - try { - // confirm it is valid json - payload = JSON.parse(payload); - } catch (e) { - // passed in payload wasn't valid json - console.error('passed in payload was invalid JSON'); - throw new Error('Need to provide valid JSON payload'); - } - } - - if (typeof botToken !== 'undefined' && botToken.length > 0) { - const message = core.getInput('slack-message') || ''; - const channelIds = core.getInput('channel-id') || ''; - const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy || ''; - const web = createWebClient(botToken, httpsProxy); - - if (channelIds.length <= 0) { - console.log('Channel ID is required to run this action. An empty one has been provided'); - throw new Error('Channel ID is required to run this action. An empty one has been provided'); - } - - if (message.length > 0 || payload) { - const ts = core.getInput('update-ts'); - await Promise.all(channelIds.split(',').map(async (channelId) => { - if (ts) { - // update message - webResponse = await web.chat.update({ ts, channel: channelId.trim(), text: message, ...(payload || {}) }); - } else { - // post message - webResponse = await web.chat.postMessage({ channel: channelId.trim(), text: message, ...(payload || {}) }); - } - })); - } else { - console.log('Missing slack-message or payload! Did not send a message via chat.postMessage with botToken', { channel: channelIds, text: message, ...(payload) }); - throw new Error('Missing message content, please input a valid payload or message to send. No Message has been send.'); - } - } - - if (typeof webhookUrl !== 'undefined' && webhookUrl.length > 0) { - if (!payload) { - // No Payload was passed in - console.log('no custom payload was passed in, using default payload that triggered the GitHub Action'); - // Get the JSON webhook payload for the event that triggered the workflow - payload = github.context.payload; - } - - if (webhookType === SLACK_WEBHOOK_TYPES.WORKFLOW_TRIGGER) { - // flatten JSON payload (no nested attributes). - const payloadDelimiter = core.getInput('payload-delimiter') || '.'; - const flatPayload = flatten(payload, { delimiter: payloadDelimiter }); - - // workflow builder requires values to be strings - // iterate over every value and convert it to string - Object.keys(flatPayload).forEach((key) => { - flatPayload[key] = `${flatPayload[key]}`; - }); - - payload = flatPayload; - } - - const axiosOpts = {}; - try { - if (parseURL(webhookUrl).scheme === 'https') { - const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy || ''; - if (httpsProxy && parseURL(httpsProxy).scheme === 'http') { - const httpsProxyAgent = new HttpsProxyAgent(httpsProxy); - axiosOpts.httpsAgent = httpsProxyAgent; - - // Use configured tunnel above instead of default axios proxy setup from env vars - axiosOpts.proxy = false; - } - } - } catch (err) { - console.log('failed to configure https proxy agent for http proxy. Using default axios configuration', err); - } - - try { - await axios.post(webhookUrl, payload, axiosOpts); - } catch (err) { - console.log('axios post failed, double check the payload being sent includes the keys Slack expects'); - if ('toJSON' in err) { - console.error(JSON.stringify(err.toJSON())); - } - console.error(`Attempted to POST payload: ${JSON.stringify(payload)}`); - - if (err.response) { - core.setFailed(err.response.data); - } else { - core.setFailed(err.message); - } - return; - } - } - - if (webResponse && webResponse.ok) { - core.setOutput('ts', webResponse.ts); - // return the thread_ts if it exists, if not return the ts - const thread_ts = webResponse.thread_ts ? webResponse.thread_ts : webResponse.ts; - core.setOutput('thread_ts', thread_ts); - // return id of the channel from the response - core.setOutput('channel_id', webResponse.channel); - } - - const time = (new Date()).toTimeString(); - core.setOutput('time', time); - } catch (error) { - core.setFailed(error); - } -}; diff --git a/src/web-client.js b/src/web-client.js deleted file mode 100644 index 7e345899..00000000 --- a/src/web-client.js +++ /dev/null @@ -1,20 +0,0 @@ -const { WebClient } = require('@slack/web-api'); -const { HttpsProxyAgent } = require('https-proxy-agent'); - -/** - * - * @param {string} botToken token used to authenticate as the Slack Bot user - * @param {string?} httpsProxy (optional) URL for the proxy to use for HTTPS requests - * @returns { WebClient } a WebClient configured with the bot token and proxy agent (if needed) - */ -function createWebClient(botToken, httpsProxy) { - if (httpsProxy) { - const httpsProxyAgent = new HttpsProxyAgent(httpsProxy); - return new WebClient(botToken, { agent: httpsProxyAgent }); - } - return new WebClient(botToken); -} - -module.exports = { - createWebClient, -}; diff --git a/src/webhook.js b/src/webhook.js new file mode 100644 index 00000000..fcdecd70 --- /dev/null +++ b/src/webhook.js @@ -0,0 +1,97 @@ +import { exponentialDelay, linearDelay } from "axios-retry"; +import { HttpsProxyAgent } from "https-proxy-agent"; +import Config from "./config.js"; +import SlackError from "./errors.js"; + +/** + * This Webhook class posts the configured payload to the provided webhook, with + * whatever additional settings set. + */ +export default class Webhook { + /** + * @param {Config} config + */ + async post(config) { + if (!config.inputs.webhook) { + throw new SlackError(config.core, "No webhook was provided to post to"); + } + /** + * @type {import("axios").AxiosRequestConfig & import("axios-retry").IAxiosRetryConfig} + */ + const options = { + ...this.retries(config.inputs.retries), + ...this.proxies(config), + }; + const response = await config.axios.post( + config.inputs.webhook, + config.content, + options, + ); + config.core.debug(JSON.stringify(response)); + } + + /** + * Return configurations for http proxy options if these are set. + * @param {Config} config + * @returns {import("axios").AxiosRequestConfig | undefined} + * @see {@link https://github.com/slackapi/slack-github-action/pull/132} + */ + proxies(config) { + try { + if (!config.inputs.webhook) { + throw new Error("No webhook was provided to proxy to"); + } + const httpsProxy = + process.env.HTTPS_PROXY || process.env.https_proxy || ""; + if (!httpsProxy) { + return undefined; + } + if (new URL(config.inputs.webhook).protocol !== "https:") { + config.core.debug( + "The webhook destination is not HTTPS so skipping the HTTPS proxy", + ); + return undefined; + } + switch (new URL(httpsProxy).protocol) { + case "https:": + return { + httpsAgent: new HttpsProxyAgent(httpsProxy), + }; + case "http:": + return { + httpsAgent: new HttpsProxyAgent(httpsProxy), + proxy: false, + }; + } + } catch (err) { + config.core.warning( + "Failed to configure HTTPS proxy agent for HTTP proxy so using the default axios configuration.", + ); + console.error(err); + } + return undefined; + } + + /** + * Return configurations for retry options with different delays. + * @param {string} option + * @returns {import("axios-retry").IAxiosRetryConfig} + */ + retries(option) { + switch (option) { + case "0": + return { retries: 0 }; + case "5": + return { retries: 5, retryDelay: linearDelay(60 * 1000) }; // 5 minutes + case "10": + return { + retries: 10, + retryDelay: (count, err) => exponentialDelay(count, err, 2 * 1000), // 34.12 minutes + }; + case "RAPID": + return { retries: 12, retryDelay: linearDelay(1 * 1000) }; // 12 seconds + default: + return { retries: 5, retryDelay: linearDelay(60 * 1000) }; // 5 minutes + } + } +} diff --git a/src/webhook.spec.js b/src/webhook.spec.js new file mode 100644 index 00000000..f3e3990a --- /dev/null +++ b/src/webhook.spec.js @@ -0,0 +1,110 @@ +import { assert } from "chai"; +import { mocks } from "./index.spec.js"; +import send from "./send.js"; +import Webhook from "./webhook.js"; + +describe("webhook", () => { + afterEach(() => { + mocks.reset(); + }); + + describe("success", () => { + it("sends the parsed payload to the provided webhook", async () => { + try { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns('"message":"hello"'); + mocks.axios.post.returns(Promise.resolve("LGTM")); + await send(mocks.core); + assert.equal(mocks.axios.post.getCalls().length, 1); + const [url, payload, options] = mocks.axios.post.getCall(0).args; + assert.equal(url, "https://hooks.slack.com"); + assert.deepEqual(payload, { message: "hello" }); + assert.equal( + /** @type {import("axios-retry").IAxiosRetryConfig} */ (options) + .retries, + 5, + ); + } catch (err) { + console.error(err); + assert.fail("Failed to send the webhook"); + } + }); + }); + + describe("failure", () => { + it("requires that a webhook is provided in inputs", async () => { + try { + await send(mocks.core); + assert.fail("Failed to throw for missing input"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Missing input! Either a token or webhook is required to take action.", + ); + } + }); + }); + + describe("retries", () => { + it("uses the configured retries in requests", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + }); + + it('does not attempt retries when "0" is set', async () => { + const webhook = new Webhook(); + const result = webhook.retries("0").retries; + assert.equal(result, 0); + }); + + it('attempts a default amount of "5" retries', async () => { + const webhook = new Webhook(); + const result = webhook.retries("5"); + assert.equal(result.retries, 5); + if (!result.retryDelay) { + assert.fail("No retry delay found!"); + } + assert.equal( + result.retryDelay(5, mocks.errors.axios.network_failed), + 300000, + "5th retry after 5 seconds", + ); + }); + + it('attempts "10" retries in around "30" minutes', async () => { + const webhook = new Webhook(); + const result = webhook.retries("10"); + assert.equal(result.retries, 10); + if (!result.retryDelay) { + assert.fail("No retry delay found!"); + } + assert.isAtLeast( + result.retryDelay(10, mocks.errors.axios.network_failed), + 1800000, + "last attempt is around 30 minutes after starting", + ); + assert.isAtMost( + result.retryDelay(10, mocks.errors.axios.network_failed), + 3600000, + "last attempt is no more than an hour later", + ); + }); + + it('attempts a "rapid" burst of "12" retries in seconds', async () => { + const webhook = new Webhook(); + const result = webhook.retries("RAPID"); + assert.equal(result.retries, 12); + if (!result.retryDelay) { + assert.fail("No retry delay found!"); + } + assert.equal( + result.retryDelay(12, mocks.errors.axios.network_failed), + 12000, + "12th retry after 12 seconds", + ); + }); + }); +}); diff --git a/test/.eslintrc.js b/test/.eslintrc.js deleted file mode 100644 index 4eeb4dff..00000000 --- a/test/.eslintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - // These environments contain lists of global variables which are allowed to be accessed - env: { - // According to https://node.green, the target node version (v10) supports all important ES2018 features. But es2018 - // is not an option since it presumably doesn't introduce any new globals over ES2017. - es2017: true, - node: true, - mocha: true, - }, - rules: { - // These rules dont like the use of devDependencies - which test code uses often. - 'node/no-unpublished-require': 0, - 'node/no-missing-require': 0, - }, -}; diff --git a/test/resources/invalid-payload.json b/test/resources/invalid-payload.json deleted file mode 100644 index c51046ef..00000000 --- a/test/resources/invalid-payload.json +++ /dev/null @@ -1 +0,0 @@ -{not-valid-json diff --git a/test/resources/valid-payload.json b/test/resources/valid-payload.json deleted file mode 100644 index 4caa4f23..00000000 --- a/test/resources/valid-payload.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "oliver": "benji", - "bonny": "clyde", - "actor":"${{github.actor}}" -} diff --git a/test/slack-send-test.js b/test/slack-send-test.js deleted file mode 100644 index 584cb27b..00000000 --- a/test/slack-send-test.js +++ /dev/null @@ -1,328 +0,0 @@ -const { assert } = require('chai'); -const sinon = require('sinon'); -const core = require('@actions/core'); -const github = require('@actions/github'); -const rewiremock = require('rewiremock/node'); - -const ChatStub = { - postMessage: sinon.fake.resolves({ ok: true, ts: '1503435957.111111', thread_ts: '1503435956.000247' }), - update: sinon.fake.resolves({ ok: true, thread_ts: '1503435956.000247' }), -}; -/* eslint-disable-next-line global-require */ -rewiremock(() => require('@slack/web-api')).with({ - WebClient: class { - constructor(token) { - this.token = token; - this.chat = ChatStub; - } - }, -}); -const AxiosMock = { - post: sinon.stub().resolves(), -}; -/* eslint-disable-next-line global-require */ -rewiremock(() => require('axios')).with(AxiosMock); -rewiremock.enable(); -const slackSend = require('../src/slack-send'); - -rewiremock.disable(); - -const ORIG_TOKEN_VAR = process.env.SLACK_BOT_TOKEN; -const ORIG_WEBHOOK_VAR = process.env.SLACK_WEBHOOK_URL; - -describe('slack-send', () => { - const fakeCore = sinon.stub(core); - const fakeGithub = sinon.stub(github); - beforeEach(() => { - sinon.reset(); - }); - after(() => { - process.env.SLACK_BOT_TOKEN = ORIG_TOKEN_VAR; - process.env.SLACK_WEBHOOK_URL = ORIG_WEBHOOK_VAR; - }); - - it('should set an error if no webhook URL or token is provided', async () => { - delete process.env.SLACK_BOT_TOKEN; - delete process.env.SLACK_WEBHOOK_URL; - await slackSend(fakeCore); - assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide at least one botToken or webhook', 'Error set specifying what env vars need to be set.'); - }); - - describe('using a bot token', () => { - beforeEach(() => { - process.env.SLACK_BOT_TOKEN = 'xoxb-xxxxx'; - delete process.env.SLACK_WEBHOOK_URL; - }); - describe('happy path', () => { - it('should send a message using the postMessage API', async () => { - fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?'); - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - await slackSend(fakeCore); - assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts'); - assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts'); - assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string'); - assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time'); - assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string'); - const chatArgs = ChatStub.postMessage.lastCall.firstArg; - assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage'); - assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage'); - }); - - it('should send a message using the update API', async () => { - fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?'); - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - fakeCore.getInput.withArgs('update-ts').returns('123456'); - await slackSend(fakeCore); - assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts'); - assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts'); - assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string'); - assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time'); - assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string'); - const chatArgs = ChatStub.update.lastCall.firstArg; - assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage'); - assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage'); - }); - - it('should send payload-file-path values with replaced context variables', async () => { - // Prepare - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/valid-payload.json'); - fakeCore.getBooleanInput.withArgs('payload-file-path-parsed').returns(true); - fakeGithub.context.actor = 'user123'; - - // Run - await slackSend(fakeCore); - - // Assert - assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts'); - assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts'); - assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string'); - assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time'); - assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string'); - const chatArgs = ChatStub.postMessage.lastCall.firstArg; - assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage'); - assert.equal(chatArgs.text, '', 'Correct message provided to postMessage'); - assert.equal(chatArgs.bonny, 'clyde', 'Correct message provided to postMessage'); - assert.equal(chatArgs.oliver, 'benji', 'Correct message provided to postMessage'); - assert.equal(chatArgs.actor, 'user123', 'Correct message provided to postMessage'); - }); - - it('should send payload-file-path values without replacing context variables', async () => { - // Prepare - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/valid-payload.json'); - fakeCore.getBooleanInput.withArgs('payload-file-path-parsed').returns(false); - fakeGithub.context.actor = 'user123'; - - // Run - await slackSend(fakeCore); - - // Assert - assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts'); - assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts'); - assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string'); - assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time'); - assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string'); - const chatArgs = ChatStub.postMessage.lastCall.firstArg; - assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage'); - assert.equal(chatArgs.text, '', 'Correct message provided to postMessage'); - assert.equal(chatArgs.bonny, 'clyde', 'Correct message provided to postMessage'); - assert.equal(chatArgs.oliver, 'benji', 'Correct message provided to postMessage'); - /* eslint-disable-next-line no-template-curly-in-string */ - assert.equal(chatArgs.actor, '${{github.actor}}', 'Correct message provided to postMessage'); - }); - - it('should send the same message to multiple channels', async () => { - fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?'); - fakeCore.getInput.withArgs('channel-id').returns('C123456,C987654'); - await slackSend(fakeCore); - const firstChatArgs = ChatStub.postMessage.firstCall.firstArg; - const secondChatArgs = ChatStub.postMessage.lastCall.firstArg; - assert.oneOf('C123456', [firstChatArgs.channel, secondChatArgs.channel], 'First comma-separated channel provided to postMessage'); - assert.oneOf('C987654', [firstChatArgs.channel, secondChatArgs.channel], 'Second comma-separated channel provided to postMessage'); - assert.equal(firstChatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage with first comma-separated channel'); - assert.equal(secondChatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage with second comma-separated channel'); - }); - - it("should send a reply-message using the postMessage API if thread_ts payload field is used'", async () => { - fakeCore.getInput - .withArgs('payload') - .returns('{"thread_ts":"123456","text":"who let the dogs out?"}'); - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - - await slackSend(fakeCore); - - assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts'); - assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts'); - assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string'); - assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time'); - assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string'); - - const chatArgs = ChatStub.postMessage.lastCall.firstArg; - assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage'); - assert.equal(chatArgs.thread_ts, '123456', 'Correct thread_ts provided to postMessage'); - assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage'); - }); - }); - describe('sad path', () => { - it('should set an error if payload cannot be JSON parsed', async () => { - fakeCore.getInput.withArgs('payload').returns('{not-valid-json'); - await slackSend(fakeCore); - assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON', 'Error set specifying JSON was invalid.'); - }); - - it('should fail if an invalid payload-file-path is provided', async () => { - // Prepare - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - fakeCore.getInput.withArgs('payload-file-path').returns('non-existing-path.json'); - - // Run - await slackSend(fakeCore); - - // Assert - assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'The payload-file-path may be incorrect. Failed to load the file: non-existing-path.json', 'Error set specifying JSON was invalid.'); - }); - - it('should fail if a valid payload-file-path with an invalid JSON is provided', async () => { - // Prepare - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/invalid-payload.json'); - - // Run - await slackSend(fakeCore); - - // Assert - assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON payload', 'Error set specifying JSON was invalid.'); - }); - - it('should fail if Channel ID is missing', async () => { - // Run - await slackSend(fakeCore); - - // Assert - assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Channel ID is required to run this action. An empty one has been provided', 'Error set specifying JSON was invalid.'); - }); - - it('should fail if payload is missing or empty', async () => { - // Prepare - fakeCore.getInput.withArgs('channel-id').returns('C123456'); - - // Run - await slackSend(fakeCore); - - // Assert - assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Missing message content, please input a valid payload or message to send. No Message has been send.', 'Error set specifying JSON was invalid.'); - }); - }); - }); - - describe('using a webhook URL', () => { - beforeEach(() => { - process.env.SLACK_WEBHOOK_URL = 'https://someurl'; - delete process.env.SLACK_BOT_TOKEN; - }); - describe('happy path', () => { - const payload = { - batman: 'robin', - thor: 'loki', - }; - beforeEach(() => { - fakeCore.getInput.withArgs('payload').returns(JSON.stringify(payload)); - }); - it('should post the payload to the webhook URL', async () => { - await slackSend(fakeCore); - assert(AxiosMock.post.calledWith('https://someurl', payload)); - }); - describe('proxy config', () => { - beforeEach(() => { - delete process.env.https_proxy; - delete process.env.HTTPS_PROXY; - }); - it('should use https proxy agent when proxy uses HTTP', async () => { - process.env.HTTPS_PROXY = 'http://test.proxy:8080/'; - await slackSend(fakeCore); - assert(AxiosMock.post.calledWith('https://someurl', payload, sinon.match.has('httpsAgent').and(sinon.match.has('proxy')))); - }); - it('should use default axios config when no proxy set', async () => { - await slackSend(fakeCore); - assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {})); - }); - it('should use default axios config when proxy uses HTTPS', async () => { - process.env.HTTPS_PROXY = 'https://test.proxy:8080/'; - await slackSend(fakeCore); - assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {})); - }); - it('should use default axios config when proxy URL is invalid', async () => { - process.env.HTTPS_PROXY = 'invalid string'; - const consoleSpy = sinon.spy(console, 'log'); - await slackSend(fakeCore); - - assert(consoleSpy.calledWith('failed to configure https proxy agent for http proxy. Using default axios configuration')); - assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {})); - }); - }); - describe('flatten', () => { - const mockPayload = { - apples: 'tree', - bananas: { truthiness: true }, - }; - beforeEach(() => { - fakeCore.getInput.withArgs('payload').returns(JSON.stringify(mockPayload)); - }); - afterEach(() => { - delete process.env.SLACK_WEBHOOK_TYPE; - }); - it('defaults to using a period to flatten nested paylods', async () => { - process.env.SLACK_WEBHOOK_TYPE = 'WORKFLOW_TRIGGER'; - await slackSend(fakeCore); - const expected = { - apples: 'tree', - 'bananas.truthiness': 'true', - }; - const count = AxiosMock.post.callCount; - assert.equal(count, 1); - const post = AxiosMock.post.getCall(0); - const [url, actual] = post.args; - assert.equal(url, 'https://someurl'); - assert.deepEqual(actual, expected); - }); - it('replaces delimiter with provided payload settings', async () => { - fakeCore.getInput.withArgs('payload-delimiter').returns('_'); - process.env.SLACK_WEBHOOK_TYPE = 'WORKFLOW_TRIGGER'; - await slackSend(fakeCore); - const expected = { - apples: 'tree', - bananas_truthiness: 'true', - }; - const count = AxiosMock.post.callCount; - assert.equal(count, 1); - const post = AxiosMock.post.getCall(0); - const [url, actual] = post.args; - assert.equal(url, 'https://someurl'); - assert.deepEqual(actual, expected); - }); - it('does not flatten nested values of incoming webhook', async () => { - process.env.SLACK_WEBHOOK_TYPE = 'INCOMING_WEBHOOK'; - await slackSend(fakeCore); - const expected = { - apples: 'tree', - bananas: { truthiness: true }, - }; - const count = AxiosMock.post.callCount; - assert.equal(count, 1); - const post = AxiosMock.post.getCall(0); - const [url, actual] = post.args; - assert.equal(url, 'https://someurl'); - assert.deepEqual(actual, expected); - }); - }); - }); - describe('sad path', () => { - it('should set an error if the POST to the webhook fails without a response', async () => { - AxiosMock.post.rejects(new Error('boom')); - await slackSend(fakeCore); - assert.include(fakeCore.setFailed.lastCall.firstArg, 'boom', 'Error set to whatever axios reports as error.'); - }); - }); - }); -}); diff --git a/test/web-client-test.js b/test/web-client-test.js deleted file mode 100644 index 89627d71..00000000 --- a/test/web-client-test.js +++ /dev/null @@ -1,33 +0,0 @@ -const { assert } = require('chai'); -const sinon = require('sinon'); -const rewiremock = require('rewiremock/node'); - -/* eslint-disable-next-line global-require */ -rewiremock(() => require('@slack/web-api')).with({ - WebClient: class { - constructor(token, options) { - this.token = token; - this.options = options || {}; - } - }, -}); -rewiremock.enable(); -const { createWebClient } = require('../src/web-client'); - -rewiremock.disable(); - -describe('web-client', () => { - beforeEach(() => { - sinon.reset(); - }); - - it('should create WebClient with an https proxy agent if given a proxy URL', async () => { - const web = createWebClient('xoxb-xxxxx', 'http://test.proxy:8080/'); - assert.equal(typeof web.options.agent, 'object'); - }); - - it('should create WebClient with default settings if not given a proxy URL', async () => { - const web = createWebClient('xoxb-xxxxx'); - assert.equal(web.options.agent, undefined); - }); -}); From 82bbd29eae33b4489344c992ddda0af725db2502 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:21:13 -0700 Subject: [PATCH 002/214] docs(fix): remove an extra trailing closing brace --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9d4dbb02..cd4f2915 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,6 @@ the API call hopes: } } ] - } ``` #### File uploads From c8916a0471790372e19d822d5b805c7ea87c10ca Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:21:39 -0700 Subject: [PATCH 003/214] fix: remove milliseconds from the returned epoch time --- src/send.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/send.js b/src/send.js index 51e95efd..2cc4c9f3 100644 --- a/src/send.js +++ b/src/send.js @@ -13,7 +13,7 @@ export default async function send(core) { const config = new Config(core); try { await post(config); - config.core.setOutput("time", new Date().valueOf() / 1000); + config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); } catch (error) { throw new SlackError(core, error, config.inputs.errors); } From efe707ebc4b17b67471c0d5cae1c332962da4373 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:22:05 -0700 Subject: [PATCH 004/214] feat: log the entire error stack for easier debugs --- src/send.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/send.js b/src/send.js index 2cc4c9f3..0eab0bdd 100644 --- a/src/send.js +++ b/src/send.js @@ -15,6 +15,7 @@ export default async function send(core) { await post(config); config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); } catch (error) { + console.error(error); throw new SlackError(core, error, config.inputs.errors); } } From c2d4e9ca35050b421a870618f1c08b041c6265f1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:23:28 -0700 Subject: [PATCH 005/214] build: remove compilation steps from the local build --- .github/workflows/local/local.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/local/local.yml b/.github/workflows/local/local.yml index 6426e0fc..3afff83d 100644 --- a/.github/workflows/local/local.yml +++ b/.github/workflows/local/local.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout action uses: actions/checkout@v4 - name: Build action - run: npm install && npm run build + run: npm install - name: Send a message into channel id: slack uses: ./. From 1da745b7a53022a18d0bcbb162c88f9b3d4b147e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:24:19 -0700 Subject: [PATCH 006/214] test(fix): resolve the posted webhook with placeholder values --- src/config.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.spec.js b/src/config.spec.js index 85c1a90f..385e05d7 100644 --- a/src/config.spec.js +++ b/src/config.spec.js @@ -209,6 +209,7 @@ describe("config", () => { describe("retries", () => { it("warns if an invalid retries option is provided", async () => { + mocks.axios.post.returns(Promise.resolve("LGTM")); mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); From 09a1d4550deca13ecbeeec2377b868a3ae74c97e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:25:03 -0700 Subject: [PATCH 007/214] ci: include chat.postMessage and files.uploadV2 in local tests --- .github/workflows/local/.env.example | 2 ++ .github/workflows/local/local.yml | 33 ++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.github/workflows/local/.env.example b/.github/workflows/local/.env.example index c7b5cdf0..813e376b 100644 --- a/.github/workflows/local/.env.example +++ b/.github/workflows/local/.env.example @@ -1,4 +1,6 @@ # Rename this file to .env and update any variables to get started SLACK_BOT_TOKEN=xoxb-01010101-abcdefgh +SLACK_CHANNEL_ID=C0123456789 +SLACK_USER_TOKEN=xoxp-22222222-example SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz diff --git a/.github/workflows/local/local.yml b/.github/workflows/local/local.yml index 3afff83d..0f04a2e7 100644 --- a/.github/workflows/local/local.yml +++ b/.github/workflows/local/local.yml @@ -13,8 +13,8 @@ jobs: uses: actions/checkout@v4 - name: Build action run: npm install - - name: Send a message into channel - id: slack + - name: Post a webhook message into channel + id: webhook uses: ./. with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -23,3 +23,32 @@ jobs: "pull_request_username": "${{ github.event.pull_request.user.login }}", "pull_request_title": ${{ toJSON(github.event.pull_request.title) }}, "pull_request_url": "${{ github.event.pull_request.html_url }}" + - name: Post a token message into channel + id: api + uses: ./. + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + "channel": "${{ secrets.SLACK_CHANNEL_ID }}", + "text": "greetings <@USLACKBOT>", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":wave: greetings <@USLACKBOT>" + } + } + ] + - name: Upload this workflow file + id: file + uses: ./. + with: + method: files.uploadV2 + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + "channel_id": "${{ secrets.SLACK_CHANNEL_ID }}", + "initial_comment": "This code exists here", + "file": "./.github/workflows/local/local.yml", + "filename": "action.yml" From b8e82b8549358224a44797d2ceb2744de133c3de Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:25:40 -0700 Subject: [PATCH 008/214] ci: include static checks of code as part of ci --- .github/workflows/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47063280..811caefd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,12 @@ jobs: - name: "build: package the latest changes" run: npm run build + - name: "unit(test): perform lints and formatting checks" + run: npm run lint + + - name: "unit(test): perform check of typings" + run: npm run check + - name: "unit(test): perform unit test checks" run: npm test From 962e0db4851890a2d03c4cde4f9805ecf7eed470 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:26:58 -0700 Subject: [PATCH 009/214] fix: debug actual response data from the webhook --- src/webhook.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webhook.js b/src/webhook.js index fcdecd70..f89e86d5 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -27,7 +27,7 @@ export default class Webhook { config.content, options, ); - config.core.debug(JSON.stringify(response)); + config.core.debug(JSON.stringify(response.data)); } /** From dbdbf9f63d11ff36f3b77523bc9740ce93d7e209 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:38:57 -0700 Subject: [PATCH 010/214] docs: include a few more words about action outputs --- action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 5058033d..c0e94f97 100644 --- a/action.yml +++ b/action.yml @@ -37,11 +37,11 @@ outputs: time: description: "The epoch time of job completion" ok: - description: "" + description: "If the requested completed without errors" channel_id: description: "The channel ID of a message posted using a token" response: - description: "" + description: "A stringified version of the Slack API response" thread_ts: description: "The threaded timestamp on a message posted using a token" ts: From a10e1f7feb0a0f841458fc5bfd75dbc2f255e495 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 01:39:16 -0700 Subject: [PATCH 011/214] fix: use the 'channel' in responses as 'channel_id' --- src/client.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/client.js b/src/client.js index 4bc5a3e7..f62cb6d0 100644 --- a/src/client.js +++ b/src/client.js @@ -12,10 +12,12 @@ import SlackError from "./errors.js"; */ export default class Client { /** + * Possible response values related to messages from the API. * @typedef MessageResult - Possible message values from API methods. * @prop {string} [threadTs] - timestamp of the top threaded message. * @prop {string} [ts] - timestamp of the message. - * @prop {string} [channelId] - ID of the channel. + * @prop {string} [channel] - ID of the channel. + * @see {@link https://api.slack.com/methods/chat.postMessage#examples} */ /** @@ -42,8 +44,8 @@ export default class Client { if (!response.ok) { throw new Error(response.error); } - if (response.channelId) { - config.core.setOutput("channel_id", response.channelId); + if (response.channel) { + config.core.setOutput("channel_id", response.channel); } if (response.threadTs) { config.core.setOutput("thread_ts", response.threadTs); From 9fb3e9ed9ca5ecb3071a43d675f67e13cfd4970b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 02:43:19 -0700 Subject: [PATCH 012/214] build(fix): remove removed files from the jsdocs --- jsconfig.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jsconfig.json b/jsconfig.json index 8403252a..04678f5b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -16,9 +16,7 @@ ] }, "exclude": [ - "node_modules", - "test", // TODO: remove - "src/slack-send.js" // TODO: remove + "node_modules" ], "include": [ "src/**/*.js" From 57ab9a416dbcf5aebe3c61f4aaffdd1605b5bea2 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 02:44:00 -0700 Subject: [PATCH 013/214] refactor: parse payload content in a new "content" module also fix: were the inputs not being parsed in tests idkperhaps --- src/client.js | 5 +- src/config.js | 140 ++++++----------------------------- src/config.spec.js | 175 +------------------------------------------ src/content.js | 129 ++++++++++++++++++++++++++++++++ src/content.spec.js | 176 ++++++++++++++++++++++++++++++++++++++++++++ src/send.spec.js | 2 + src/webhook.js | 2 +- 7 files changed, 337 insertions(+), 292 deletions(-) create mode 100644 src/content.js create mode 100644 src/content.spec.js diff --git a/src/client.js b/src/client.js index f62cb6d0..d06ffc13 100644 --- a/src/client.js +++ b/src/client.js @@ -38,7 +38,10 @@ export default class Client { /** * @type {webapi.WebAPICallResult & MessageResult} */ - const response = await client.apiCall(config.inputs.method, config.content); + const response = await client.apiCall( + config.inputs.method, + config.content.values, + ); config.core.setOutput("ok", response.ok); config.core.setOutput("response", JSON.stringify(response)); if (!response.ok) { diff --git a/src/config.js b/src/config.js index 335f5d3b..f93dcbde 100644 --- a/src/config.js +++ b/src/config.js @@ -1,11 +1,7 @@ -import fs from "node:fs"; -import path from "node:path"; import core from "@actions/core"; -import github from "@actions/github"; import webapi from "@slack/web-api"; import axios from "axios"; -import flatten from "flat"; -import markup from "markup-js"; +import Content from "./content.js"; import SlackError from "./errors.js"; /** @@ -65,11 +61,6 @@ export default class Config { */ axios; - /** - * @typedef Content - The provided and parsed payload object. - * @type {Record} - */ - /** * @type {Content} - The parsed payload data to send. */ @@ -100,6 +91,8 @@ export default class Config { this.axios = axios; this.core = core; this.webapi = webapi; + core.setSecret(core.getInput("token")); + core.setSecret(core.getInput("webhook")); this.inputs = { errors: core.getBooleanInput("errors"), method: core.getInput("method"), @@ -118,6 +111,27 @@ export default class Config { webhook: core.getInput("webhook") || process.env.SLACK_WEBHOOK_URL || null, }; + this.validate(); + core.debug(`Gathered action inputs: ${JSON.stringify(this.inputs)}`); + this.content = new Content(this); + core.debug(`Parsed request content: ${JSON.stringify(this.content)}`); + } + + /** + * Confirm the configurations are correct enough to continue. + */ + validate() { + switch (this.inputs.retries) { + case this.Retries.ZERO: + case this.Retries.FIVE: + case this.Retries.TEN: + case this.Retries.RAPID: + break; + default: + core.warning( + `Invalid input! An unknown "retries" value was used: ${this.inputs.retries}`, + ); + } switch (true) { case !!this.inputs.token && !!this.inputs.webhook: core.debug( @@ -149,111 +163,5 @@ export default class Config { "Missing input! Either a token or webhook is required to take action.", ); } - switch (true) { - case !!this.inputs.payload && !!this.inputs.payloadFilePath: - throw new SlackError( - core, - "Invalid input! Just the payload or payload file path is required.", - ); - case !!this.inputs.payload: - this.content = this.getContentPayload(core); - break; - case !!this.inputs.payloadFilePath: - this.content = this.getContentPayloadFilePath(core); - break; - default: - core.debug("Missing payload so gathering inputs from action context."); - this.content = github.context; - break; - } - if (this.inputs.payloadDelimiter) { - this.content = flatten(this.content, { - delimiter: this.inputs.payloadDelimiter, - }); - for (const key of Object.keys(this.content)) { - this.content[key] = `${this.content[key]}`; - } - } - core.debug(`Parsed request content: ${JSON.stringify(this.content)}`); - switch (this.inputs.retries) { - case this.Retries.ZERO: - case this.Retries.FIVE: - case this.Retries.TEN: - case this.Retries.RAPID: - break; - default: - core.warning( - `Invalid input! An unknown "retries" value was used: ${this.inputs.retries}`, - ); - } - core.debug(`Gathered action inputs: ${JSON.stringify(this.inputs)}`); - } - - /** - * Format request content from payload values for use in the request. - * @param {core} core - GitHub Actions core utilities. - * @throws if the input payload or payload file path is invalid JSON. - * @returns {Content} - the parsed JSON payload to use in requests. - */ - getContentPayload(core) { - if (!this.inputs.payload) { - throw new SlackError(core, "Invalid input! No payload content found"); - } - try { - const trimmed = this.inputs.payload.trim(); - if ( - !this.inputs.payload.startsWith("{") && - !this.inputs.payload.endsWith("}") - ) { - core.debug("Wrapping input payload in braces to create valid JSON"); - const comma = trimmed.replace(/,$/, ""); // remove trailing comma - const wrap = `{${comma}}`; - return JSON.parse(wrap); - } - return JSON.parse(trimmed); - } catch (error) { - if (error instanceof Error) { - core.error(error); - } - throw new SlackError( - core, - "Invalid input! Failed to parse the JSON content of the payload", - ); - } - } - - /** - * Format request content from the payload file path for use in the request. - * @param {core} core - GitHub Actions core utilities. - * @throws if the input payload or payload file path is invalid JSON. - * @returns {Content} - the parsed JSON payload to use in requests. - */ - getContentPayloadFilePath(core) { - if (!this.inputs.payloadFilePath) { - throw new SlackError(core, "Invalid input! No payload found for content"); - } - try { - const content = fs.readFileSync( - path.resolve(this.inputs.payloadFilePath), - "utf-8", - ); - if (!this.inputs.payloadFilePathParsed) { - return JSON.parse(content); - } - const template = content.replace(/\$\{\{/g, "{{"); // swap ${{ for {{ - const context = { - env: process.env, - github: github.context, - }; - return JSON.parse(markup.up(template, context)); - } catch (error) { - if (error instanceof Error) { - core.error(error); - } - throw new SlackError( - core, - "Invalid input! Failed to parse the JSON content of the payload file", - ); - } } } diff --git a/src/config.spec.js b/src/config.spec.js index 385e05d7..8996610c 100644 --- a/src/config.spec.js +++ b/src/config.spec.js @@ -1,4 +1,3 @@ -import path from "node:path"; import { assert } from "chai"; import Config from "./config.js"; import { mocks } from "./index.spec.js"; @@ -35,179 +34,7 @@ describe("config", () => { }); }); - describe("payload", () => { - describe("success", () => { - it("wraps incomplete payload in braces for valid JSON", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload").returns(` - "message": "LGTM!", - "channel": "C0123456789", - "blocks": [ - { - "type": "section", - "text": { - "text": "LGTM! :+1:" - } - } - ] - `); - const config = new Config(mocks.core); - const expected = { - message: "LGTM!", - channel: "C0123456789", - blocks: [ - { - type: "section", - text: { - text: "LGTM! :+1:", - }, - }, - ], - }; - assert.deepEqual(config.content, expected); - }); - - it("accepts and parses complete json as payload input", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload").returns(`{ - "message": "this is wrapped", - "channel": "C0123456789" - } - `); - const config = new Config(mocks.core); - const expected = { - message: "this is wrapped", - channel: "C0123456789", - }; - assert.deepEqual(config.content, expected); - }); - - it("parses JSON from a known file without replacements", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput - .withArgs("payload-file-path") - .returns("example.json"); - mocks.fs.readFileSync - .withArgs(path.resolve("example.json"), "utf-8") - .returns(`{ - "message": "drink water", - "channel": "C6H12O6H2O2" - }`); - const config = new Config(mocks.core); - const expected = { - message: "drink water", - channel: "C6H12O6H2O2", - }; - assert.deepEqual(config.content, expected); - }); - - it("replaces templated variables in the payload file", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput - .withArgs("payload-file-path") - .returns("example.json"); - mocks.core.getBooleanInput - .withArgs("payload-file-path-parsed") - .returns(true); - mocks.fs.readFileSync - .withArgs(path.resolve("example.json"), "utf-8") - .returns(`{ - "text": "running job #\${{ env.MOCK_JOB }} on: \${{ github.apiUrl }}" - }`); - process.env.MOCK_JOB = "12"; - const config = new Config(mocks.core); - process.env.MOCK_JOB = undefined; - const expected = { - text: "running job #12 on: https://api.github.com", - }; - assert.deepEqual(config.content, expected); - }); - - it("flattens nested payloads if a delimiter is provided", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload-delimiter").returns("_"); - mocks.core.getInput.withArgs("payload").returns(` - "apples": "tree", - "bananas": { - "truthiness": true - } - `); - const config = new Config(mocks.core); - const expected = { - apples: "tree", - bananas_truthiness: "true", - }; - assert.deepEqual(config.content, expected); - }); - }); - - describe("failure", () => { - it("errors if both a payload and file path are provided", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); - mocks.core.getInput - .withArgs("payload-file-path") - .returns("example.json"); - try { - await send(mocks.core); - assert.fail("Failed to throw for invalid input"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Invalid input! Just the payload or payload file path is required.", - ); - } - }); - - it("fails if the provided input payload is invalid JSON", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload").returns("{"); - try { - await send(mocks.core); - assert.fail("Failed to throw for invalid JSON"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg.toString(), - "Invalid input! Failed to parse the JSON content of the payload", - ); - } - }); - - it("fails to parse a file path that does not exist", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput - .withArgs("payload-file-path") - .returns("unknown.json"); - try { - await send(mocks.core); - assert.fail("Failed to throw for nonexistent files"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg.toString(), - "Invalid input! Failed to parse the JSON content of the payload file", - ); - } - }); - }); - }); - - describe("retries", () => { + describe("validate", () => { it("warns if an invalid retries option is provided", async () => { mocks.axios.post.returns(Promise.resolve("LGTM")); mocks.core.getInput diff --git a/src/content.js b/src/content.js new file mode 100644 index 00000000..22d76c83 --- /dev/null +++ b/src/content.js @@ -0,0 +1,129 @@ +import fs from "node:fs"; +import path from "node:path"; +import github from "@actions/github"; +import flatten from "flat"; +import markup from "markup-js"; +import Config from "./config.js"; +import SlackError from "./errors.js"; + +/** + * The parsed payload provided to the action and passed to the preferred method + * of sending a payload. + */ +export default class Content { + /** + * @type {Record} + */ + values; + + /** + * @param {Config} config + */ + constructor(config) { + switch (true) { + case !!config.inputs.payload && !!config.inputs.payloadFilePath: + console.error(config.inputs.payload); + console.error(config.inputs.payloadFilePath); + throw new SlackError( + config.core, + "Invalid input! Just the payload or payload file path is required.", + ); + case !!config.inputs.payload: + this.values = this.getContentPayload(config); + break; + case !!config.inputs.payloadFilePath: + this.values = this.getContentPayloadFilePath(config); + break; + default: + config.core.debug( + "Missing payload so gathering inputs from action context.", + ); + this.values = github.context; + break; + } + if (config.inputs.payloadDelimiter) { + this.values = flatten(this.values, { + delimiter: config.inputs.payloadDelimiter, + }); + for (const key of Object.keys(this.values)) { + this.values[key] = `${this.values[key]}`; + } + } + } + + /** + * Format request content from payload values for use in the request. + * @param {Config} config - GitHub Actions core utilities. + * @throws if the input payload or payload file path is invalid JSON. + * @returns {Content} - the parsed JSON payload to use in requests. + */ + getContentPayload(config) { + if (!config.inputs.payload) { + throw new SlackError( + config.core, + "Invalid input! No payload content found", + ); + } + try { + const trimmed = config.inputs.payload.trim(); + if ( + !config.inputs.payload.startsWith("{") && + !config.inputs.payload.endsWith("}") + ) { + config.core.debug( + "Wrapping input payload in braces to create valid JSON", + ); + const comma = trimmed.replace(/,$/, ""); // remove trailing comma + const wrap = `{${comma}}`; + return JSON.parse(wrap); + } + return JSON.parse(trimmed); + } catch (error) { + if (error instanceof Error) { + config.core.error(error); + } + throw new SlackError( + config.core, + "Invalid input! Failed to parse the JSON content of the payload", + ); + } + } + + /** + * Format request content from the payload file path for use in the request. + * @param {Config} config - GitHub Actions core utilities. + * @throws if the input payload or payload file path is invalid JSON. + * @returns {Content} - the parsed JSON payload to use in requests. + */ + getContentPayloadFilePath(config) { + if (!config.inputs.payloadFilePath) { + throw new SlackError( + config.core, + "Invalid input! No payload found for content", + ); + } + try { + const content = fs.readFileSync( + path.resolve(config.inputs.payloadFilePath), + "utf-8", + ); + if (!config.inputs.payloadFilePathParsed) { + return JSON.parse(content); + } + const template = content.replace(/\$\{\{/g, "{{"); // swap ${{ for {{ + const context = { + env: process.env, + github: github.context, + }; + return JSON.parse(markup.up(template, context)); + } catch (error) { + if (error instanceof Error) { + config.core.error(error); + } + throw new SlackError( + config.core, + "Invalid input! Failed to parse the JSON content of the payload file", + ); + } + } +} diff --git a/src/content.spec.js b/src/content.spec.js new file mode 100644 index 00000000..88bac602 --- /dev/null +++ b/src/content.spec.js @@ -0,0 +1,176 @@ +import path from "node:path"; +import { assert } from "chai"; +import Config from "./config.js"; +import { mocks } from "./index.spec.js"; +import send from "./send.js"; + +/** + * Confirm values from the action input or environment variables are gathered + */ +describe("content", () => { + afterEach(() => { + mocks.reset(); + }); + + describe("success", () => { + it("wraps incomplete payload in braces for valid JSON", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns(` + "message": "LGTM!", + "channel": "C0123456789", + "blocks": [ + { + "type": "section", + "text": { + "text": "LGTM! :+1:" + } + } + ] + `); + const config = new Config(mocks.core); + const expected = { + message: "LGTM!", + channel: "C0123456789", + blocks: [ + { + type: "section", + text: { + text: "LGTM! :+1:", + }, + }, + ], + }; + assert.deepEqual(config.content.values, expected); + }); + + it("accepts and parses complete json as payload input", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns(`{ + "message": "this is wrapped", + "channel": "C0123456789" + } + `); + const config = new Config(mocks.core); + const expected = { + message: "this is wrapped", + channel: "C0123456789", + }; + assert.deepEqual(config.content.values, expected); + }); + + it("parses JSON from a known file without replacements", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + mocks.fs.readFileSync + .withArgs(path.resolve("example.json"), "utf-8") + .returns(`{ + "message": "drink water", + "channel": "C6H12O6H2O2" + }`); + const config = new Config(mocks.core); + const expected = { + message: "drink water", + channel: "C6H12O6H2O2", + }; + assert.deepEqual(config.content.values, expected); + }); + + it("replaces templated variables in the payload file", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + mocks.core.getBooleanInput + .withArgs("payload-file-path-parsed") + .returns(true); + mocks.fs.readFileSync + .withArgs(path.resolve("example.json"), "utf-8") + .returns(`{ + "text": "running job #\${{ env.MOCK_JOB }} on: \${{ github.apiUrl }}" + }`); + process.env.MOCK_JOB = "12"; + const config = new Config(mocks.core); + process.env.MOCK_JOB = undefined; + const expected = { + text: "running job #12 on: https://api.github.com", + }; + assert.deepEqual(config.content.values, expected); + }); + + it("flattens nested payloads if a delimiter is provided", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload-delimiter").returns("_"); + mocks.core.getInput.withArgs("payload").returns(` + "apples": "tree", + "bananas": { + "truthiness": true + } + `); + const config = new Config(mocks.core); + const expected = { + apples: "tree", + bananas_truthiness: "true", + }; + assert.deepEqual(config.content.values, expected); + }); + }); + + describe("failure", () => { + it("errors if both a payload and file path are provided", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); + mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + try { + await send(mocks.core); + assert.fail("Failed to throw for invalid input"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Invalid input! Just the payload or payload file path is required.", + ); + } + }); + + it("fails if the provided input payload is invalid JSON", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns("{"); + try { + await send(mocks.core); + assert.fail("Failed to throw for invalid JSON"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg.toString(), + "Invalid input! Failed to parse the JSON content of the payload", + ); + } + }); + + it("fails to parse a file path that does not exist", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload-file-path").returns("unknown.json"); + try { + await send(mocks.core); + assert.fail("Failed to throw for nonexistent files"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg.toString(), + "Invalid input! Failed to parse the JSON content of the payload file", + ); + } + }); + }); +}); diff --git a/src/send.spec.js b/src/send.spec.js index 071cde87..776590dc 100644 --- a/src/send.spec.js +++ b/src/send.spec.js @@ -16,6 +16,8 @@ describe("send", () => { }); it("exists and can be called", async () => { + mocks.core.getInput.withArgs("webhook").returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); await send(mocks.core); }); }); diff --git a/src/webhook.js b/src/webhook.js index f89e86d5..e051dbb6 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -24,7 +24,7 @@ export default class Webhook { }; const response = await config.axios.post( config.inputs.webhook, - config.content, + config.content.values, options, ); config.core.debug(JSON.stringify(response.data)); From 6ca60134a5a5581ffd0f65dbf8f641a8914aec60 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 14:38:37 -0700 Subject: [PATCH 014/214] style: remove extra spacing from the pull request template --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 22391f16..7945c926 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -### Summary +### Summary Describe the goal of this PR. Mention any related Issue numbers. From 5875c610e883234f3db216ea372a15801a23195a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 14:39:08 -0700 Subject: [PATCH 015/214] fix: remove addtional error logging from erroring inputs --- src/content.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/content.js b/src/content.js index 22d76c83..cd7925f1 100644 --- a/src/content.js +++ b/src/content.js @@ -22,8 +22,6 @@ export default class Content { constructor(config) { switch (true) { case !!config.inputs.payload && !!config.inputs.payloadFilePath: - console.error(config.inputs.payload); - console.error(config.inputs.payloadFilePath); throw new SlackError( config.core, "Invalid input! Just the payload or payload file path is required.", From 5b4b7b8fa54cf288c7e17c7ddb261cc405c636b6 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 14:48:46 -0700 Subject: [PATCH 016/214] feat: parse action payload inputs as possible yaml values --- package-lock.json | 11 +++++++++-- package.json | 2 ++ src/content.js | 29 ++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c49282cf..88a28607 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,14 @@ "axios-retry": "^4.5.0", "flat": "^5.0.2", "https-proxy-agent": "^7.0.5", + "js-yaml": "^4.1.0", "markup-js": "^1.5.21" }, "devDependencies": { "@biomejs/biome": "1.8.3", "@types/chai": "^4.3.19", "@types/flat": "^5.0.5", + "@types/js-yaml": "^4.0.9", "@types/mocha": "^10.0.7", "@types/node": "^20.14.10", "@types/sinon": "^17.0.3", @@ -504,6 +506,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", @@ -615,7 +624,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/assertion-error": { @@ -1533,7 +1541,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" diff --git a/package.json b/package.json index 1150a436..de58ba07 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,14 @@ "axios-retry": "^4.5.0", "flat": "^5.0.2", "https-proxy-agent": "^7.0.5", + "js-yaml": "^4.1.0", "markup-js": "^1.5.21" }, "devDependencies": { "@biomejs/biome": "1.8.3", "@types/chai": "^4.3.19", "@types/flat": "^5.0.5", + "@types/js-yaml": "^4.0.9", "@types/mocha": "^10.0.7", "@types/node": "^20.14.10", "@types/sinon": "^17.0.3", diff --git a/src/content.js b/src/content.js index cd7925f1..4a31e421 100644 --- a/src/content.js +++ b/src/content.js @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import github from "@actions/github"; import flatten from "flat"; +import yaml from "js-yaml"; import markup from "markup-js"; import Config from "./config.js"; import SlackError from "./errors.js"; @@ -62,6 +63,17 @@ export default class Content { "Invalid input! No payload content found", ); } + try { + const content = yaml.load(config.inputs.payload, { + schema: yaml.JSON_SCHEMA, + }); + return /** @type {Content} */ (content); + } catch (error) { + if (error instanceof Error) { + config.core.debug("Failed to parse input payload as YAML"); + config.core.debug(error.message); + } + } try { const trimmed = config.inputs.payload.trim(); if ( @@ -106,7 +118,22 @@ export default class Content { "utf-8", ); if (!config.inputs.payloadFilePathParsed) { - return JSON.parse(content); + if ( + config.inputs.payloadFilePath.endsWith("yaml") || + config.inputs.payloadFilePath.endsWith("yml") + ) { + const load = yaml.load(content, { + schema: yaml.JSON_SCHEMA, + }); + return /** @type {Content} */ (load); + } + if (config.inputs.payloadFilePath.endsWith("json")) { + return JSON.parse(content); + } + throw new SlackError( + config.core, + `Failed to parse file extension ${config.inputs.payloadFilePath}`, + ); } const template = content.replace(/\$\{\{/g, "{{"); // swap ${{ for {{ const context = { From ee9e2002d2c890987a89a6f3885aa3d5236fe7b3 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 14:49:08 -0700 Subject: [PATCH 017/214] docs: update examples and wordings to use the latest changes --- README.md | 427 ++++++++++-------- .../JSON_payload.yml | 4 +- .../Technique_2_Slack_App/JSON_payload.yml | 46 +- .../JSON_payload_as_text.yml | 4 +- .../Technique_2_Slack_App/main.yml | 4 +- .../main.yml | 53 +-- 6 files changed, 276 insertions(+), 262 deletions(-) diff --git a/README.md b/README.md index cd4f2915..1b853e9a 100644 --- a/README.md +++ b/README.md @@ -1,131 +1,148 @@ -# Slack Send GitHub Action +# Slack Send + +> the GitHub Action for sending data to Slack [![codecov](https://codecov.io/gh/slackapi/slack-github-action/graph/badge.svg?token=OZNX7FHN78)](https://codecov.io/gh/slackapi/slack-github-action) -Send data into Slack with a Slack [API method][methods] like [`chat.postMessage`][chat.postMessage] -and [`files.uploadV2`][files.uploadV2], or uses webhooks to [start workflows](#technique-1-slack-workflow-builder) in Workflow Builder and -[post messages](#technique-3-slack-incoming-webhook) with this GitHub Action! +Send data into Slack with a Slack [API method](#technique-2-slack-api-method) +like [`chat.postMessage`][chat.postMessage] and +[`files.uploadV2`][files.uploadV2], or use webhooks to +[start workflows](#technique-1-slack-workflow-builder) in Workflow Builder and +[post messages](#technique-3-slack-incoming-webhook) with webhooks using this +GitHub Action! -## Sending Variables +## Sending data -You can send GitHub-specific data related to GitHub Action workflow events using -[GitHub Contexts](https://docs.github.com/en/actions/learn-github-actions/contexts) -and -[Variables](https://docs.github.com/en/actions/learn-github-actions/variables) -that GitHub Actions provides. +Different ways to send data share overlapping styles of gathering the data to be +sent, intersecting between the `payload` and `payload-file-path` inputs and the +`method` and `webook` techniques. -For examples on how to leverage this in your workflows, check out the -[example workflows we have](https://github.com/slackapi/slack-github-action/tree/main/example-workflows). +Either payload input option can be used with either technique, with both YAML +and JSON formats being accepted. -## How to Send Data to Slack +**Examples** -This package has three different techniques to send data to Slack: +```yaml +- name: Post a message to a channel using a token + uses: slackapi/slack-github-action@v2-development + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + text: "Actions happen at " + channel: ${{ secrets.SLACK_CHANNEL_ID }} +``` -1. Send data to Slack's Workflow Builder (requires a paid Slack instance). -2. Send data to a Slack API method using a secret token with specified scopes. -3. Send data via a Slack Incoming Webhook URL (use an existing custom app or - create a new one). +```yaml +- name: Start a Slack workflow using a webhook URL + uses: slackapi/slack-github-action@v2-development + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + payload-file-path: ./build-artifacts.json +``` -The recommended way to use this action is with Slack's Workflow Builder (if -you're on a paid Slack plan). +If neither payload input option is provided, the +[GitHub context][github-context] is used which has details specific to the +repository and run of the workflow. -### Technique 1: Slack Workflow Builder +The final payload content doesn't have to be fixed either, with +[additional configurations](#additional-configurations) available for +customization. + +### Example workflows -> ❗️ This approach requires a paid Slack plan; it also doesn't support any text -> formatting +For examples on how to leverage this in your workflows, check out the +[example workflows we have][examples] available. + +## Sending techniques + +This Action offers three different techniques to send data to Slack: -This technique sends data into Slack via a webhook URL created using [Slack's Workflow builder](https://slack.com/features/workflow-automation) . Follow [these steps to create a Slack workflow using webhooks][create-webhook]. The Slack workflow webhook URL will be in the form `https://hooks.slack.com/workflows/....`. +1. Send data with a webhook to start a worflow in Workflow Builder. +2. Send data using a Slack API method and a secret token with required scopes. +3. Send data as a message with a Slack Incoming Webhook URL. + +### Technique 1: Slack Workflow Builder -As part of the [workflow setup](https://slack.com/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks#workflow-setup), -you will need to define expected variables in the payload the webhook will receive (described in the "Create custom variables" section of the docs). If these variables are missing in the payload, an error is returned. +> ❗️ This technique requires [a Slack paid plan][plans] to use Workflow Builder. -To match the webhook input format expected by Workflow Builder, the payload will be flattened and stringified (all nested keys are moved to the top level) before being sent. The default delimiter used to flatten payloads is a period (".") but should be changed to an underscore ("\_") using the `payload-delimiter` parameter if you're using nested payloads as input values in your own workflows. +This technique sends data to Slack using a webhook to start a workflow created +using the [Slack Workflow Builder][wfb]. Follow +[these steps to create a Slack workflow using webhooks][wfb-create]. #### Setup -- [Create a Slack workflow webhook][create-webhook]. -- Copy the webhook URL (`https://hooks.slack.com/workflows/....`) and - [add it as a secret in your repo settings][repo-secret] named - `SLACK_WEBHOOK_URL`. -- Add a step to your GitHub action to send data to your Webhook. -- Configure your Slack workflow to use variables from the incoming payload from - the GitHub Action. You can select where you want to post the data and how you - want to format it in Slack's workflow builder interface. +Starting in Slack, some prerequisite preparations are necessary: -#### Usage +1. [Create a Slack workflow][wfb-create] that starts with a webhook. +2. Copy the webhook URL and [add it as a repository secret][repo-secret] called + `SLACK_WEBHOOK_URL`. +3. Add a step to your GitHub Action to send data to your webhook. +4. Configure your Slack workflow to use the incoming payload variables from the + the GitHub Action. You can then adjust the steps of the workflow to use these + values in creative and clever ways. -Add this Action as a [step][job-step] to your project's GitHub Action Workflow -file: +Note: The webhook URL will resemble something like so: -```yaml -- name: Send GitHub Action trigger data to Slack workflow - id: slack - uses: slackapi/slack-github-action@v2-development - with: - payload-delimiter: "_" - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} +```txt +https://hooks.slack.com/triggers/T0123456789/3141592653589/c6e6c0d868b3054ca0f4611a5dbadaf ``` -or +#### Usage -```yaml -- name: Send custom JSON data to Slack workflow - id: slack - uses: slackapi/slack-github-action@v2-development - with: - # This data can be any valid JSON from a previous step in the GitHub Action - payload: | - "key": "value", - "foo": "bar" - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} -``` +Add this Action as a [step][job-step] to your project's GitHub Action workflow +file with the configurations you want. -or +##### Sending values from the GitHub default context -> If the `payload` is provided it will take preference over `payload-file-path` +In the example below, no payload input values are being provided so values from +the [GitHub context][github-context] specific to the job are used: ```yaml -- name: Send custom JSON data to Slack workflow - id: slack +- name: Send GitHub Action data to a Slack workflow uses: slackapi/slack-github-action@v2-development with: - payload-file-path: "./payload-slack-content.json" webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + payload-delimiter: "_" ``` -> To send the payload file JSON as is, without replacing templated values with -> `github.context` or `github.env`, set `payload-file-path-parsed` to `false`. -> Default: `true`. +While also using `payload-delimiter` the payload is flattened and stringified to +match the webhook input format expected by Workflow Builder. + +##### Providing parsed payload information as strings + +Provided input values for payload information are sent to the webhook URL after +parsing the workflow: ```yaml - name: Send custom JSON data to Slack workflow - id: slack uses: slackapi/slack-github-action@v2-development with: - payload-file-path: "./payload-slack-content.json" - payload-file-path-parsed: false webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + payload: | + status: "${{ job.status }}" + option: "false" ``` -### Technique 2: Slack API token +### Technique 2: Slack API method A bot token or user token or [token of some other kind][tokens] can be used to -call one of many [Slack API methods][methods]! This includes [`chat.postMessage`][chat.postMessage] -and the official `@slack/web-api` implemention of [`files.uploadV2`][files.uploadV2] +call one of many [Slack API methods][methods]! This includes +[`chat.postMessage`][chat.postMessage] and the official `@slack/web-api` +implemention of [`files.uploadV2`][files.uploadV2]. -By creating a new Slack app or using an existing one, this approach allows your -GitHub Actions job to post a message in a Slack channel or direct message by -utilizing the [chat.postMessage](https://api.slack.com/methods/chat.postMessage) -API method. Using this approach you can instantly post a message without setting -up Slack workflows. +Setting up a workflow with this technique allows you to instantly interact with +the Slack API methods without setting up a Slack workflow. #### Setup -- [Create a Slack App][apps] for your workspace (alternatively use an existing - app you have already created and installed). -- Add the [`chat:write`](https://api.slack.com/scopes/chat:write) bot scope - under **OAuth & Permissions**. -- Install the app to your workspace. +The exact [API method][methods] used will change the required [scopes][scopes], +but setup should be similar for all methods: + +- [Create a Slack App][apps] for your workspace or use an existing one. +- Add the [`chat:write`][chat:write] bot scope under the **OAuth & Permissions** + page. +- Install the app to your workspace using the **Install App** page. - Copy the app's Bot Token from the **OAuth & Permissions** page and [add it as a secret in your repo settings][repo-secret] named `SLACK_BOT_TOKEN`. @@ -134,48 +151,51 @@ up Slack workflows. #### Usage -Add this Action as a [step][job-step] to your project's GitHub Action Workflow -file: +Choosing inputs for these steps is left as an exercise for the actioneer, but +these snippets might be helpful when starting. + +##### Posting a message with text + +An introductory call to the `chat.postMessage` method can be done by +[adding this step][job-step] to a job in your workflow: ```yaml - name: Post to a Slack channel - id: slack uses: slackapi/slack-github-action@v2-development with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": "C0123456789", - "text": "howdy <@channel>!" + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "howdy <@channel>!" ``` -Posting payloads with nested JSON, like block messages with block kit, works as -the API call hopes: +##### Posting a message with blocks + +More detailed messages with nested JSON, like block messages made with block +kit, work as the API call might hope: ```yaml - name: Post to a Slack channel - id: slack uses: slackapi/slack-github-action@v2-development with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": "C0123456789", - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" - } - } - ] + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" + blocks: + - type: "section" + text: + type: "mrkdwn" + text: "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" ``` -#### File uploads +##### Uploading a file -Calling web API methods with `@slack/web-api` makes uploading files just another API call, but with all of the advantages of `files.uploadV2`: +Calling web API methods with [`@slack/web-api`][slack-web-api] makes uploading +files just another API call, but with all of the advantages of +[`files.uploadV2`][files.uploadV2]: ```yaml - name: Share a file to that channel @@ -183,21 +203,17 @@ Calling web API methods with `@slack/web-api` makes uploading files just another with: method: files.uploadV2 payload: | - "channel_id": "C0123456789", - "initial_comment": "the results are in!", - "file": "results.out", - "filename": "results-${{ github.sha }}.out" + channel: ${{ secrets.SLACK_CHANNEL_ID }} + initial_comment: "the results are in!" + file: "results.out" + filename: "results-${{ github.sha }}.out" ``` -Using JSON payload for constructing a message is also available: +##### Updating a message -#### Update the message - -If you would like to notify the real-time updates on a build status, you can -modify the message your build job posted in the subsequent steps. In order to do -this, the steps after the first message posting can have -`update_ts: ${{ steps.slack.outputs.ts }}` in their settings. With this, the -step updates the already posted channel message instead of posting a new one. +Following up on a message after it's posted, such as updates for a build status, +can be done by chaining multiple steps together using outputs from past steps as +inputs to current ones: ```yaml - name: Initiate the deployment launch sequence @@ -207,93 +223,80 @@ step updates the already posted channel message instead of posting a new one. method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": "C0123456789", - "text": "Deployment started (In Progress)", - "attachments": [ - { - "pretext": "Deployment started", - "color": "dbab09", - "fields": [ - { - "title": "Status", - "short": true, - "value": "In Progress" - } - ] - } - ] -- name: Countdown + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "Deployment started :eyes:" + attachments: + - color: "dbab09" + fields: + - title: "Status" + short: true + value: "In Progress" +- name: Countdown until launch run: sleep 10 -- uses: slackapi/slack-github-action@v2-development +- name: Update the original message with success + uses: slackapi/slack-github-action@v2-development with: method: chat.update token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "ts": "${{ steps.slack.outputs.ts }}", - "text": "Deployment finished (Completed)", - "attachments": [ - { - "pretext": "Deployment finished", - "color": "28a745", - "fields": [ - { - "title": "Status", - "short": true, - "value": "Completed" - } - ] - } - ] + channel: ${{ secrets.SLACK_CHANNEL_ID }} + ts: ${{ steps.slack.outputs.ts }} + text: "Deployment finished! :rocket:" + attachments: + - color: "28a745" + fields: + - title: "Status" + short: true + value: "Completed" ``` -Please note that **the message update step does not accept a channel name.** Set -a channel ID for the steps for the actions that update messages. +##### Replying to a message -#### Reply to a message - -If you want to post a message as a threaded reply, you can populate the `payload` with a `thread_ts` field. This field should equal the `ts` value of the parent message of the thread. If you want to reply to a message previously posted by this Action, you can use the `ts` output provided as the `thread_ts` of a consequent threaded reply, e.g. `"thread_ts": "${{ steps.deployment_message.outputs.ts }}"`. +Posting threaded replies to a message from a past job can be done by including +the `thread_ts` attribute of the **parent** message in the `payload`: ```yaml -- id: deployment_message +- name: Initiate a deployment uses: slackapi/slack-github-action@v2-development + id: deployment_message with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": "C0123456789", - "text": "Deployment started (In Progress)" -- uses: slackapi/slack-github-action@v2-development + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "Deployment started :eyes:" +- name: Conclude the deployment + uses: slackapi/slack-github-action@v2-development with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": "C0123456789", - "thread_ts": "${{ steps.deployment_message.outputs.ts }}", - "text": "Deployment finished (Completed)" + channel: ${{ secrets.SLACK_CHANNEL_ID }} + thread_ts: ${{ steps.deployment_message.outputs.ts }} + text: "Deployment finished! :rocket:" ``` -Please note that **reply to a message does not accept a channel name.** Set a channel ID for the actions that reply to messages in thread. - -### Technique 3: Slack Incoming Webhook +### Technique 3: Slack incoming webhook -This approach allows your GitHub Actions job to post a message to a Slack -channel or direct message by utilizing -[Incoming Webhooks](https://api.slack.com/messaging/webhooks). +This technique uses GitHub Actions to post messages to a channel or direct +message using [incoming webhooks][incoming-webhook] from a Slack app. Incoming Webhooks conform to the same rules and functionality as any of Slack's other messaging APIs. You can make your posted messages as simple as a single line of text, or make them really useful with -[interactive components](https://api.slack.com/messaging/interactivity). To make -the message more expressive and useful use -[Block Kit](https://api.slack.com/block-kit) to build and test visual -components. +[interactive components][interactivity]. To make the message more expressive and +useful use [Block Kit][block-kit] to build and test visual components. #### Setup +A similar approach to [Technique 1](#technique-1-slack-workflow-builder) is +taken to create apps and setup the workflow, but webhooks are gathered from a +different source: + - [Create a Slack App][apps] for your workspace (alternatively use an existing app you have already created and installed). -- Add the [`incoming-webhook`](https://api.slack.com/scopes/incoming-webhook) - bot scope under **OAuth & Permissions**. +- Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & + Permissions**. - Install the app to your workspace (you will select a channel to notify). - Activate and create a new webhook under **Incoming Webhooks**. - Copy the Webhook URL from the Webhook you just generated @@ -302,60 +305,94 @@ components. #### Usage +Add the collected webhook from above to a workflow and configure the job using +[`mrkdwn`][mrkdwn] formatting values for a message or [Block Kit][block-kit] +blocks: + ```yaml - name: Send custom JSON data to Slack workflow - id: slack uses: slackapi/slack-github-action@v2-development with: - # For posting a rich message using Block Kit webhook: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" - } - } - ] + text: "*GitHub Action build result*: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" + blocks: + - type: "section" + text: + type: "mrkdwn" + text: "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" +``` + +## Additional configurations + +Not all of the above settings serve every customization of a workflow, so these +options might be useful. + +### Parsing templated variables + +Additional [variables][github-variables] provided by Github can be used to +replace templated variables in the `payload-file-path` file using the option +to parse payloads with the `payload-file-path-parsed` option: + +```yaml +- name: Send custom JSON data to Slack workflow + id: slack + uses: slackapi/slack-github-action@v2-development + with: + payload-file-path: "./payload-slack-content.json" + payload-file-path-parsed: true + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} ``` -### HTTPS Proxy +This replaces variables templated with as `${{ github.repository }}` with the +values found in the action context. + +### HTTPS proxy If you need to use a proxy to connect with Slack, you can use the `HTTPS_PROXY` -(or `https_proxy`) environment variable. In this example we use the Slack App +or `https_proxy` environment variable. In this example we use the Slack App technique, but configuring a proxy works the same way for all of them: ```yaml - name: Post to a Slack channel via a proxy - id: slack uses: slackapi/slack-github-action@v2-development with: method: chat.postMessage + proxy: "http://proxy.example.org:8080" # Change this to a custom value token: ${{ secrets.SLACK_BOT_TOKEN }} - # Set the HTTPS_PROXY environment variable to whatever your policy requires - proxy: "http://proxy.example.org:8080" payload: | - "channel": "C0123456789", - "message": "This message was sent through a proxy" + channel: ${{ secrets.SLACK_CHANNEL_ID }} + message: "This message was sent through a proxy" ``` -## Contributing +## License -All contributions are encouraged! Check out the [CONTRIBUTING](.github/contributing.md) guide to learn more. +This project is licensed under the [MIT license](LICENSE). -## License +## Contributing -See [LICENSE](LICENSE). +All contributions are encouraged! Check out the +[contributor's guide][contributing] to learn more. [apps]: https://api.slack.com/apps +[block-kit]: https://api.slack.com/surfaces/messages#complex_layouts [chat.postMessage]: https://api.slack.com/methods/chat.postMessage -[create-webhook]: https://slack.com/intl/en-ca/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks +[chat:write]: https://api.slack.com/scopes/chat:write +[contributing]: .github/contributing.md +[examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows [files.uploadV2]: https://slack.dev/node-slack-sdk/web-api/#upload-a-file +[github-context]: https://docs.github.com/en/actions/learn-github-actions/contexts +[github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables +[incoming-webhook]: https://api.slack.com/messaging/webhooks +[incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook +[interactivity]: https://api.slack.com/messaging/interactivity [job-step]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idsteps [methods]: https://api.slack.com/methods +[mrkdwn]: https://api.slack.com/reference/surfaces/formatting +[plans]: https://slack.com/pricing [repo-secret]: https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository +[scopes]: https://api.slack.com/scopes +[slack-web-api]: https://slack.dev/node-slack-sdk/web-api [tokens]: https://api.slack.com/concepts/token-types -[wfb-triggers]: https://api.slack.com/automation/triggers/webhook +[wfb]: https://slack.com/features/workflow-automation +[wfb-create]: https://slack.com/intl/en-ca/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks diff --git a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml index 33379793..0c1dc9aa 100644 --- a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml +++ b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml @@ -11,5 +11,5 @@ jobs: with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | - "key": "value", - "foo": "bar" + key: value + foo: bar diff --git a/example-workflows/Technique_2_Slack_App/JSON_payload.yml b/example-workflows/Technique_2_Slack_App/JSON_payload.yml index 91449ad7..95d075b9 100644 --- a/example-workflows/Technique_2_Slack_App/JSON_payload.yml +++ b/example-workflows/Technique_2_Slack_App/JSON_payload.yml @@ -12,31 +12,21 @@ jobs: token: ${{ secrets.SLACK_BOT_TOKEN }} method: chat.postMessage payload: | - "channel": "C0123456789", - "text": "messages make meeting" - "blocks": [ - { "type": "divider" }, - { - "type": "image", - "title": { - "type": "plain_text", - "text": "Slack Slack Slack", - "emoji": true - }, - "image_url": "https://media.makeameme.org/created/a-slack-this.jpg", - "alt_text": "marg" - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "${{ github.sha }}" - }, - "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" - } - ] - } - ] + channel: "C0123456789" + text: "messages make meeting" + blocks: + - type: "divider" + type: "image" + title: + type: "plain_text" + text: "Slack Slack Slack" + emoji: true + image_url: "https://media.makeameme.org/created/a-slack-this.jpg" + alt_text: "marg" + - type: "actions" + elements: + - type": "button" + text: + type: "plain_text" + text: "${{ github.sha }}" + url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" diff --git a/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml b/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml index 53fcc635..0ac8f1d5 100644 --- a/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml +++ b/example-workflows/Technique_2_Slack_App/JSON_payload_as_text.yml @@ -12,5 +12,5 @@ jobs: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "text": "posting from a github action", - "channel": "C0123456789" + text: "posting from a github action" + channel: "C0123456789" diff --git a/example-workflows/Technique_2_Slack_App/main.yml b/example-workflows/Technique_2_Slack_App/main.yml index 28d5e22e..8029a5ed 100644 --- a/example-workflows/Technique_2_Slack_App/main.yml +++ b/example-workflows/Technique_2_Slack_App/main.yml @@ -12,5 +12,5 @@ jobs: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": "C0123456789", - "text": "posting from a github action!" + channel: "C0123456789" + text: "posting from a github action!" diff --git a/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml b/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml index 6659fed4..0c30dce5 100644 --- a/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml +++ b/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml @@ -11,36 +11,23 @@ jobs: with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | - "text": "Danny Torrence left a 1 star review for your property.", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Danny Torrence left the following review for your property:" - } - }, - { - "type": "section", - "block_id": "section567", - "text": { - "type": "mrkdwn", - "text": " \\n :star: \\n Doors had too many axe holes, guest in room 237 was far too rowdy, whole place felt stuck in the 1920s." - }, - "accessory": { - "type": "image", - "image_url": "https://is5-ssl.mzstatic.com/image/thumb/Purple3/v4/d3/72/5c/d3725c8f-c642-5d69-1904-aa36e4297885/source/256x256bb.jpg", - "alt_text": "Haunted hotel image" - } - }, - { - "type": "section", - "block_id": "section789", - "fields": [ - { - "type": "mrkdwn", - "text": "*Average Rating*\\n1.0" - } - ] - } - ] + "text": "Danny Torrence left a 1 star review for your property." + "blocks": + - type: "section" + text: + type: "mrkdwn" + text: "Danny Torrence left the following review for your property:" + - type: "section" + block_id: "section567" + text: { + type: "mrkdwn" + text: " \\n :star: \\n Doors had too many axe holes, guest in room 237 was far too rowdy, whole place felt stuck in the 1920s." + accessory: + type: "image" + image_url: "https://is5-ssl.mzstatic.com/image/thumb/Purple3/v4/d3/72/5c/d3725c8f-c642-5d69-1904-aa36e4297885/source/256x256bb.jpg" + alt_text: "Haunted hotel image" + - type: "section" + block_id: "section789" + fields: + - type: "mrkdwn" + text: "*Average Rating*\\n1.0" From 4c57022640ae50f0e0bce4fc8d173658359699f7 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 14:49:42 -0700 Subject: [PATCH 018/214] test: update tests in ci to use the yaml inputs --- .github/workflows/main.yml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 811caefd..beea9f17 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,9 +20,6 @@ jobs: - name: "build: install the required dependencies" run: npm ci - - name: "build: package the latest changes" - run: npm run build - - name: "unit(test): perform lints and formatting checks" run: npm run lint @@ -48,8 +45,8 @@ jobs: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": ${{ secrets.SLACK_CHANNEL_ID }}, - "text": "CI Post from slack-send GitHub Action! Succeeded!!" + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "CI Post from slack-send GitHub Action! Succeeded!!" - name: "integration(botToken): confirm a message was posted" run: test -n "${{ steps.slackToken.outputs.ts }}" @@ -61,9 +58,9 @@ jobs: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": ${{ secrets.SLACK_CHANNEL_ID }} - "text": "This message should be posted as a response in thread", - "thread_ts": "${{ steps.slackToken.outputs.thread_ts }}" + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "This message should be posted as a response in thread" + thread_ts: ${{ steps.slackToken.outputs.thread_ts }} - name: "integration(botToken): confirm a response was posted" run: test -n "${{ steps.slackThreadResponse.outputs.ts }}" @@ -98,17 +95,13 @@ jobs: with: webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} payload: | - "text":"Incoming Webhook test for slack send", - "blocks":[ - { - "type":"section", - "text":{ - "type":"plain_text", - "text":"A post by Slack Send GitHub Action. Testing Incoming webhooks", - "emoji":true - } - } - ] + text: "Incoming Webhook test for slack send" + blocks: + - type: section + text: + type: plain_text + text: "A post by Slack Send GitHub Action. Testing Incoming webhooks" + emoji: true - name: "integration(incoming): confirm a webhook was posted" run: test -n "${{ steps.slackIncoming.outputs.time }}" From 72734a95eac19e8265ccd94cfac27d482021fb7f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 14:50:38 -0700 Subject: [PATCH 019/214] feat: increase methods for quick use in the local workflow --- .github/workflows/local/README.md | 47 +++++++++++++++++---- .github/workflows/local/local.yml | 70 ++++++++++++++++++++++--------- 2 files changed, 91 insertions(+), 26 deletions(-) diff --git a/.github/workflows/local/README.md b/.github/workflows/local/README.md index 1cdd9e50..d462dc54 100644 --- a/.github/workflows/local/README.md +++ b/.github/workflows/local/README.md @@ -1,25 +1,58 @@ # Local runs -For a simple development experience, a local version of this action can be used in experiments. +For a simple development experience, a local version of this action can be used +in experiments: + +```sh +$ npm run local # Test techniques +``` **Requirements**: - An installation of [nektos/act](https://github.com/nektos/act) +- A running instance of [Docker](https://www.docker.com) + +## Setting up an app + +The saved `local.yml` workflow uses `webhook` and `method` for a combination of +features, which expects the following scopes: + +- `chat:write` +- `files:write` +- `reactions:write` -## Configuring secrets +These scopes aren't required if the methods used change, but others might be if +different API methods are called! -To use `${{ secrets.* }}` in the workflow, move `.env.example` to `.env` and update any variables. +This app will have useful variables that can be configured as values in this +next section. -## Mocking event payloads +## Configuring values -Different event payloads can be mocked directly with changes to the `event.json` file. +### Changing secrets + +To use `${{ secrets.* }}` in the workflow, move `.env.example` to `.env` and +update any variables. + +### Mocking event payloads + +Different event payloads can be mocked directly with changes to the `event.json` +file. Reference: https://docs.github.com/en/webhooks/webhook-events-and-payloads ## Updating the workflow -The `local.yml` file contains the workflow used for testing. Updates to these steps can be made to test various functionalities. +The `local.yml` file contains the workflow used for testing. Updates to these +steps can be made to test various functionalities. ## Running an experiment -Run the workflow using `npm run local`. The above configurations will be used to simulate an actual workflow run. +Run the workflow using `act` with the `npm run local` script. The above settings +will be used to simulate an actual workflow run: + +```sh +$ npm run local +... +[Local run/run] 🏁 Job succeeded +``` diff --git a/.github/workflows/local/local.yml b/.github/workflows/local/local.yml index 0f04a2e7..530e32ee 100644 --- a/.github/workflows/local/local.yml +++ b/.github/workflows/local/local.yml @@ -19,10 +19,10 @@ jobs: with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} payload: | - "repository": "${{ github.repository }}", - "pull_request_username": "${{ github.event.pull_request.user.login }}", - "pull_request_title": ${{ toJSON(github.event.pull_request.title) }}, - "pull_request_url": "${{ github.event.pull_request.html_url }}" + repository: ${{ github.repository }} + pull_request_username: ${{ github.event.pull_request.user.login }} + pull_request_title: ${{ toJSON(github.event.pull_request.title) }} + pull_request_url: ${{ github.event.pull_request.html_url }} - name: Post a token message into channel id: api uses: ./. @@ -30,17 +30,8 @@ jobs: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel": "${{ secrets.SLACK_CHANNEL_ID }}", - "text": "greetings <@USLACKBOT>", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ":wave: greetings <@USLACKBOT>" - } - } - ] + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "Action happens at " - name: Upload this workflow file id: file uses: ./. @@ -48,7 +39,48 @@ jobs: method: files.uploadV2 token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - "channel_id": "${{ secrets.SLACK_CHANNEL_ID }}", - "initial_comment": "This code exists here", - "file": "./.github/workflows/local/local.yml", - "filename": "action.yml" + channel_id: ${{ secrets.SLACK_CHANNEL_ID }} + initial_comment: "This code exists here" + file: .github/workflows/local/local.yml + filename: action.yml + - name: Initiate the deployment sequence + id: slack + uses: ./. + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "Deployment started :eyes:" + attachments: + - color: "dbab09" + fields: + - title: "Status" + short: true + value: "In Progress" + - name: Countdown + run: sleep 10 + - name: Launch time is now + uses: ./. + with: + method: chat.update + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + ts: "${{ steps.slack.outputs.ts }}" + text: "Deployment finished :rocket:" + attachments: + - color: "28a745" + fields: + - title: "Status" + short: true + value: "Completed" + - name: Celebrate wins + uses: ./. + with: + method: reactions.add + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + timestamp: ${{ steps.slack.outputs.ts }} + name: "tada" From 4fa18f2152e9dd7953c81807cb70ef07e0d066ed Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 15:01:31 -0700 Subject: [PATCH 020/214] ci(temp): use the changes of the pull request when running tests --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index beea9f17..27ff67c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,6 @@ name: Tests on: - pull_request_target: + pull_request: push: branches: - main From 32c4ddb1ccd32ba06a751c32ad60dd6cf50c3c3c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 15:05:28 -0700 Subject: [PATCH 021/214] ci: workaround broken biome installations in ci --- .github/workflows/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 27ff67c7..b09687d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,8 +20,13 @@ jobs: - name: "build: install the required dependencies" run: npm ci + - name: "build: setup the linter for formatting checks" + uses: biomejs/setup-biome@v2 + with: + version: latest + - name: "unit(test): perform lints and formatting checks" - run: npm run lint + run: biome ci . - name: "unit(test): perform check of typings" run: npm run check From 5ffcbc8e2a82e5a32781284cabcc3c980be165b9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 15:10:31 -0700 Subject: [PATCH 022/214] ci(fix): remove additional steps for checking test coverage --- .github/workflows/main.yml | 3 -- package-lock.json | 60 +++++++++++++------------------------- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b09687d6..101b6ae5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,9 +34,6 @@ jobs: - name: "unit(test): perform unit test checks" run: npm test - - name: "unit(test): check unit test coverage" - run: npm run test:gen-cov - - name: "unit(test): upload coverage to CodeCov" uses: codecov/codecov-action@v4.5.0 with: diff --git a/package-lock.json b/package-lock.json index 88a28607..84eccf0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -256,10 +256,11 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1092,10 +1093,11 @@ "dev": true }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1457,18 +1459,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report/node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -1484,27 +1474,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/istanbul-reports": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", @@ -1979,6 +1948,19 @@ ], "license": "MIT" }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", From 0d8d92957e1773778532ce132f53ff7620409e57 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 30 Aug 2024 18:44:18 -0700 Subject: [PATCH 023/214] fix: avoid setting setting secrets twice for possible missing values --- src/config.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/config.js b/src/config.js index f93dcbde..be753cd4 100644 --- a/src/config.js +++ b/src/config.js @@ -91,8 +91,6 @@ export default class Config { this.axios = axios; this.core = core; this.webapi = webapi; - core.setSecret(core.getInput("token")); - core.setSecret(core.getInput("webhook")); this.inputs = { errors: core.getBooleanInput("errors"), method: core.getInput("method"), From e7f7020c11df19bf2a88ee87b682491aaea60017 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 11:23:34 -0700 Subject: [PATCH 024/214] docs: reference the complete 'incoming webhooks' feature --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b853e9a..e111fe7b 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ Send data into Slack with a Slack [API method](#technique-2-slack-api-method) like [`chat.postMessage`][chat.postMessage] and [`files.uploadV2`][files.uploadV2], or use webhooks to [start workflows](#technique-1-slack-workflow-builder) in Workflow Builder and -[post messages](#technique-3-slack-incoming-webhook) with webhooks using this -GitHub Action! +[post messages](#technique-3-slack-incoming-webhook) with incoming webhooks +using this GitHub Action! ## Sending data From 7f42936382d9b5048260c47cd4f5332de3250b64 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 11:23:58 -0700 Subject: [PATCH 025/214] docs: replace the link to repo secrets with another page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e111fe7b..b9f7f4d4 100644 --- a/README.md +++ b/README.md @@ -390,7 +390,7 @@ All contributions are encouraged! Check out the [methods]: https://api.slack.com/methods [mrkdwn]: https://api.slack.com/reference/surfaces/formatting [plans]: https://slack.com/pricing -[repo-secret]: https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository +[repo-secret]: https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository [scopes]: https://api.slack.com/scopes [slack-web-api]: https://slack.dev/node-slack-sdk/web-api [tokens]: https://api.slack.com/concepts/token-types From 78e74e6d9a18cc78382f75053e9f35b63c39c174 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 15:09:17 -0700 Subject: [PATCH 026/214] feat: require a webhook type as part of webhook inputs --- .github/workflows/local/local.yml | 1 + .github/workflows/main.yml | 5 ++- README.md | 9 +++-- action.yml | 3 ++ .../JSON_payload.yml | 1 + .../JSON_payload_from_file.yml | 1 + .../default_GitHub_Trigger_payload.yml | 1 + .../main.yml | 1 + src/client.spec.js | 2 +- src/config.js | 17 ++++++++++ src/config.spec.js | 34 ++++++++++--------- src/content.spec.js | 18 +++++++--- src/send.spec.js | 1 + src/webhook.spec.js | 26 +++++++++++--- 14 files changed, 90 insertions(+), 30 deletions(-) diff --git a/.github/workflows/local/local.yml b/.github/workflows/local/local.yml index 530e32ee..e05088f3 100644 --- a/.github/workflows/local/local.yml +++ b/.github/workflows/local/local.yml @@ -18,6 +18,7 @@ jobs: uses: ./. with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger payload: | repository: ${{ github.repository }} pull_request_username: ${{ github.event.pull_request.user.login }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 101b6ae5..a1e76e5b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -87,6 +87,7 @@ jobs: # Normalized payload with info pulled out from GitHub trigger event payload: '{"author":"${{ github.event.sender.login }}","url":"${{ env.URL}}", "repoName":"${{ github.event.repository.full_name }}", "status":"${{ job.status }}"}' webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger - name: "integration(wfb): confirm a payload was sent" run: test -n "${{ steps.slackWorkflow.outputs.time }}" @@ -96,8 +97,9 @@ jobs: uses: ./ with: webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} + webhook-type: incoming-webhook payload: | - text: "Incoming Webhook test for slack send" + text: "Incoming webhook test for slack send" blocks: - type: section text: @@ -119,6 +121,7 @@ jobs: with: payload-file-path: ./.github/resources/payload-notification.json webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} + webhook-type: incoming-webhook env: JOB_STATUS: ${{ job.status }} ATTACHMENT_COLOR: ${{ (job.status == 'success' && 'good') || (job.status == 'failure' && 'danger') || 'warning' }} diff --git a/README.md b/README.md index b9f7f4d4..1f017f7c 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,9 @@ and JSON formats being accepted. - name: Start a Slack workflow using a webhook URL uses: slackapi/slack-github-action@v2-development with: - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} payload-file-path: ./build-artifacts.json + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger ``` If neither payload input option is provided, the @@ -102,8 +103,9 @@ the [GitHub context][github-context] specific to the job are used: - name: Send GitHub Action data to a Slack workflow uses: slackapi/slack-github-action@v2-development with: - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} payload-delimiter: "_" + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger ``` While also using `payload-delimiter` the payload is flattened and stringified to @@ -119,6 +121,7 @@ parsing the workflow: uses: slackapi/slack-github-action@v2-development with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger payload: | status: "${{ job.status }}" option: "false" @@ -314,6 +317,7 @@ blocks: uses: slackapi/slack-github-action@v2-development with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook payload: | text: "*GitHub Action build result*: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" blocks: @@ -342,6 +346,7 @@ to parse payloads with the `payload-file-path-parsed` option: payload-file-path: "./payload-slack-content.json" payload-file-path-parsed: true webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger ``` This replaces variables templated with as `${{ github.repository }}` with the diff --git a/action.yml b/action.yml index c0e94f97..a2ae8544 100644 --- a/action.yml +++ b/action.yml @@ -33,6 +33,9 @@ inputs: webhook: description: "A location for posting request payloads" required: false + webhook-type: + description: "Posting with either an incoming webhook or webhook trigger" + required: false outputs: time: description: "The epoch time of job completion" diff --git a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml index 0c1dc9aa..24139f4d 100644 --- a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml +++ b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload.yml @@ -10,6 +10,7 @@ jobs: uses: slackapi/slack-github-action@v2-development with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger payload: | key: value foo: bar diff --git a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml index 00ccc7f8..52d36cf6 100644 --- a/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml +++ b/example-workflows/Technique_1_Slack_Workflow_Builder/JSON_payload_from_file.yml @@ -11,3 +11,4 @@ jobs: with: payload-file-path: "./example-workflows/Technique_1_Slack_Workflow_Builder/payloads/example.json" webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger diff --git a/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml b/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml index 2a195da2..292cb32f 100644 --- a/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml +++ b/example-workflows/Technique_1_Slack_Workflow_Builder/default_GitHub_Trigger_payload.yml @@ -11,3 +11,4 @@ jobs: with: payload-delimiter: "_" webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger diff --git a/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml b/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml index 0c30dce5..699e2422 100644 --- a/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml +++ b/example-workflows/Technique_3_Slack_Incoming_Webhook/main.yml @@ -10,6 +10,7 @@ jobs: uses: slackapi/slack-github-action@v2-development with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger payload: | "text": "Danny Torrence left a 1 star review for your property." "blocks": diff --git a/src/client.spec.js b/src/client.spec.js index cb5987ff..5cba149e 100644 --- a/src/client.spec.js +++ b/src/client.spec.js @@ -44,8 +44,8 @@ describe("client", () => { */ it("calls the method with the provided token and content", async () => { try { - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput .withArgs("payload") .returns(`"text": "hello", "channel": "C0123456789"`); diff --git a/src/config.js b/src/config.js index be753cd4..8227e1ab 100644 --- a/src/config.js +++ b/src/config.js @@ -49,6 +49,7 @@ export default class Config { * @property {Retries} retries - The retries method to use for failed requests. * @property {string?} token - The authentication value used with the Slack API. * @property {string?} webhook - A location for posting request payloads. + * @property {string?} webhookType - Posting method to use with the webhook. */ /** @@ -108,6 +109,7 @@ export default class Config { token: core.getInput("token") || process.env.SLACK_TOKEN || null, webhook: core.getInput("webhook") || process.env.SLACK_WEBHOOK_URL || null, + webhookType: core.getInput("webhook-type"), }; this.validate(); core.debug(`Gathered action inputs: ${JSON.stringify(this.inputs)}`); @@ -154,6 +156,21 @@ export default class Config { case !!this.inputs.webhook: core.debug("Setting the provided webhook as a secret variable."); core.setSecret(this.inputs.webhook); + if (!this.inputs.webhookType) { + throw new SlackError( + core, + "Missing input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + ); + } + if ( + this.inputs.webhookType !== "incoming-webhook" && + this.inputs.webhookType !== "webhook-trigger" + ) { + throw new SlackError( + core, + "Invalid input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + ); + } break; default: throw new SlackError( diff --git a/src/config.spec.js b/src/config.spec.js index 8996610c..8e242584 100644 --- a/src/config.spec.js +++ b/src/config.spec.js @@ -34,22 +34,6 @@ describe("config", () => { }); }); - describe("validate", () => { - it("warns if an invalid retries option is provided", async () => { - mocks.axios.post.returns(Promise.resolve("LGTM")); - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("retries").returns("FOREVER"); - await send(mocks.core); - assert.isTrue( - mocks.core.warning.calledWith( - 'Invalid input! An unknown "retries" value was used: FOREVER', - ), - ); - }); - }); - describe("secrets", async () => { it("treats the provided token as a secret", async () => { mocks.core.getInput.withArgs("token").returns("xoxb-example"); @@ -62,6 +46,7 @@ describe("config", () => { it("treats the provided webhook as a secret", async () => { mocks.core.getInput.withArgs("webhook").returns("https://slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); try { await send(mocks.core); } catch { @@ -71,4 +56,21 @@ describe("config", () => { } }); }); + + describe("validate", () => { + it("warns if an invalid retries option is provided", async () => { + mocks.axios.post.returns(Promise.resolve("LGTM")); + mocks.core.getInput.withArgs("retries").returns("FOREVER"); + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + await send(mocks.core); + assert.isTrue( + mocks.core.warning.calledWith( + 'Invalid input! An unknown "retries" value was used: FOREVER', + ), + ); + }); + }); }); diff --git a/src/content.spec.js b/src/content.spec.js index 88bac602..97b6bedb 100644 --- a/src/content.spec.js +++ b/src/content.spec.js @@ -17,6 +17,7 @@ describe("content", () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); mocks.core.getInput.withArgs("payload").returns(` "message": "LGTM!", "channel": "C0123456789", @@ -49,6 +50,7 @@ describe("content", () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); mocks.core.getInput.withArgs("payload").returns(`{ "message": "this is wrapped", "channel": "C0123456789" @@ -66,6 +68,7 @@ describe("content", () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); mocks.fs.readFileSync .withArgs(path.resolve("example.json"), "utf-8") @@ -82,13 +85,14 @@ describe("content", () => { }); it("replaces templated variables in the payload file", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); mocks.core.getBooleanInput .withArgs("payload-file-path-parsed") .returns(true); + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); mocks.fs.readFileSync .withArgs(path.resolve("example.json"), "utf-8") .returns(`{ @@ -104,10 +108,11 @@ describe("content", () => { }); it("flattens nested payloads if a delimiter is provided", async () => { + mocks.core.getInput.withArgs("payload-delimiter").returns("_"); mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload-delimiter").returns("_"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); mocks.core.getInput.withArgs("payload").returns(` "apples": "tree", "bananas": { @@ -128,6 +133,7 @@ describe("content", () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); try { @@ -145,6 +151,7 @@ describe("content", () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); mocks.core.getInput.withArgs("payload").returns("{"); try { await send(mocks.core); @@ -158,10 +165,11 @@ describe("content", () => { }); it("fails to parse a file path that does not exist", async () => { + mocks.core.getInput.withArgs("payload-file-path").returns("unknown.json"); mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload-file-path").returns("unknown.json"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); try { await send(mocks.core); assert.fail("Failed to throw for nonexistent files"); diff --git a/src/send.spec.js b/src/send.spec.js index 776590dc..bf195048 100644 --- a/src/send.spec.js +++ b/src/send.spec.js @@ -17,6 +17,7 @@ describe("send", () => { it("exists and can be called", async () => { mocks.core.getInput.withArgs("webhook").returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); await send(mocks.core); }); diff --git a/src/webhook.spec.js b/src/webhook.spec.js index f3e3990a..97602c58 100644 --- a/src/webhook.spec.js +++ b/src/webhook.spec.js @@ -10,12 +10,13 @@ describe("webhook", () => { describe("success", () => { it("sends the parsed payload to the provided webhook", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("payload").returns('"message":"hello"'); + mocks.axios.post.returns(Promise.resolve("LGTM")); try { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("payload").returns('"message":"hello"'); - mocks.axios.post.returns(Promise.resolve("LGTM")); await send(mocks.core); assert.equal(mocks.axios.post.getCalls().length, 1); const [url, payload, options] = mocks.axios.post.getCall(0).args; @@ -45,6 +46,21 @@ describe("webhook", () => { ); } }); + + it("requires that a webhook type is provided in input", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + try { + await send(mocks.core); + assert.fail("Failed to throw for missing input"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Missing input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + ); + } + }); }); describe("retries", () => { From 31b6ed1ec6243b014539e7e1c12dc1cf6ab7b4af Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 15:36:07 -0700 Subject: [PATCH 027/214] feat: open the option to templatize input payloads to action input strings too https://github.com/slackapi/slack-github-action/issues/330 --- README.md | 6 +++--- action.yml | 4 ++-- src/config.js | 5 ++--- src/content.js | 2 +- src/content.spec.js | 4 +--- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1f017f7c..ab3bd300 100644 --- a/README.md +++ b/README.md @@ -335,8 +335,8 @@ options might be useful. ### Parsing templated variables Additional [variables][github-variables] provided by Github can be used to -replace templated variables in the `payload-file-path` file using the option -to parse payloads with the `payload-file-path-parsed` option: +replace templated variables in the `payload-file-path` file using the option to +parse payloads with the `payload-templated` option: ```yaml - name: Send custom JSON data to Slack workflow @@ -344,7 +344,7 @@ to parse payloads with the `payload-file-path-parsed` option: uses: slackapi/slack-github-action@v2-development with: payload-file-path: "./payload-slack-content.json" - payload-file-path-parsed: true + payload-templated: true webhook: ${{ secrets.SLACK_WEBHOOK_URL }} webhook-type: webhook-trigger ``` diff --git a/action.yml b/action.yml index a2ae8544..7fac3ad1 100644 --- a/action.yml +++ b/action.yml @@ -17,9 +17,9 @@ inputs: payload-file-path: description: "Path to a file containing the valid JSON payload" required: false - payload-file-path-parsed: + payload-templated: default: "false" - description: "If templated variables in the JSON payload are replaced" + description: "If templated variables in the input payloads should be replaced" required: false proxy: description: "An optional alternate proxied route to for HTTPS connections" diff --git a/src/config.js b/src/config.js index 8227e1ab..4aca8927 100644 --- a/src/config.js +++ b/src/config.js @@ -44,7 +44,7 @@ export default class Config { * @property {string?} payload - Request contents from the provided input. * @property {string?} payloadDelimiter - Seperators of nested attributes. * @property {string?} payloadFilePath - Location of a JSON request payload. - * @property {boolean} payloadFilePathParsed - If templated values are replaced. + * @property {boolean} payloadTemplated - If templated values are replaced. * @property {string?} proxy - An optional proxied connection for requests. * @property {Retries} retries - The retries method to use for failed requests. * @property {string?} token - The authentication value used with the Slack API. @@ -98,8 +98,7 @@ export default class Config { payload: core.getInput("payload"), payloadDelimiter: core.getInput("payload-delimiter"), payloadFilePath: core.getInput("payload-file-path"), - payloadFilePathParsed: - core.getBooleanInput("payload-file-path-parsed") || false, + payloadTemplated: core.getBooleanInput("payload-templated") || false, proxy: core.getInput("proxy") || process.env.HTTPS_PROXY || diff --git a/src/content.js b/src/content.js index 4a31e421..28a91131 100644 --- a/src/content.js +++ b/src/content.js @@ -117,7 +117,7 @@ export default class Content { path.resolve(config.inputs.payloadFilePath), "utf-8", ); - if (!config.inputs.payloadFilePathParsed) { + if (!config.inputs.payloadTemplated) { if ( config.inputs.payloadFilePath.endsWith("yaml") || config.inputs.payloadFilePath.endsWith("yml") diff --git a/src/content.spec.js b/src/content.spec.js index 97b6bedb..bc34c5ba 100644 --- a/src/content.spec.js +++ b/src/content.spec.js @@ -86,9 +86,7 @@ describe("content", () => { it("replaces templated variables in the payload file", async () => { mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); - mocks.core.getBooleanInput - .withArgs("payload-file-path-parsed") - .returns(true); + mocks.core.getBooleanInput.withArgs("payload-templated").returns(true); mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); From 5f3b855b3aa0cd70c43a5515b693441181550b81 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 16:05:22 -0700 Subject: [PATCH 028/214] feat: link the outputs of the action to log streams of the webapi --- src/client.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index d06ffc13..7d167ca0 100644 --- a/src/client.js +++ b/src/client.js @@ -1,4 +1,4 @@ -import webapi from "@slack/web-api"; +import webapi, { LogLevel } from "@slack/web-api"; import { HttpsProxyAgent } from "https-proxy-agent"; import Config from "./config.js"; import SlackError from "./errors.js"; @@ -34,6 +34,17 @@ export default class Client { const client = new config.webapi.WebClient(config.inputs.token, { agent: this.proxies(config)?.httpsAgent, retryConfig: this.retries(config.inputs.retries), + logger: { + debug: config.core.debug, + info: config.core.info, + warn: config.core.warning, + error: config.core.error, + getLevel: () => { + return config.core.isDebug() ? LogLevel.DEBUG : LogLevel.INFO; + }, + setLevel: (_level) => { }, + setName: (_name) => { }, + }, }); /** * @type {webapi.WebAPICallResult & MessageResult} From bc9c18b71be0d4002167c5ace37922e2314151e1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 16:07:04 -0700 Subject: [PATCH 029/214] fix: link without spacing when creating noop functions --- src/client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index 7d167ca0..dd690292 100644 --- a/src/client.js +++ b/src/client.js @@ -42,8 +42,8 @@ export default class Client { getLevel: () => { return config.core.isDebug() ? LogLevel.DEBUG : LogLevel.INFO; }, - setLevel: (_level) => { }, - setName: (_name) => { }, + setLevel: (_level) => {}, + setName: (_name) => {}, }, }); /** From 8c35f789758381951593c5dc6a75136de0e155da Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 16:49:51 -0700 Subject: [PATCH 030/214] ci(fix): use the original message timestamp when threading a message --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a1e76e5b..aeb358f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,7 +62,7 @@ jobs: payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} text: "This message should be posted as a response in thread" - thread_ts: ${{ steps.slackToken.outputs.thread_ts }} + thread_ts: ${{ steps.slackToken.outputs.ts }} - name: "integration(botToken): confirm a response was posted" run: test -n "${{ steps.slackThreadResponse.outputs.ts }}" From b1453daa524e52b2fbb1032a57190efb8a3a9c35 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 16:54:24 -0700 Subject: [PATCH 031/214] fix: use a stringified thread ts to avoid float madness --- .github/workflows/main.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aeb358f2..714dbbb2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,7 +62,7 @@ jobs: payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} text: "This message should be posted as a response in thread" - thread_ts: ${{ steps.slackToken.outputs.ts }} + thread_ts: "${{ steps.slackToken.outputs.ts }}" - name: "integration(botToken): confirm a response was posted" run: test -n "${{ steps.slackThreadResponse.outputs.ts }}" diff --git a/README.md b/README.md index ab3bd300..17ba2c98 100644 --- a/README.md +++ b/README.md @@ -275,7 +275,7 @@ the `thread_ts` attribute of the **parent** message in the `payload`: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - thread_ts: ${{ steps.deployment_message.outputs.ts }} + thread_ts: "${{ steps.deployment_message.outputs.ts }}" text: "Deployment finished! :rocket:" ``` From 518946debc64fab4bbf7783d35a232724f68e800 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 25 Sep 2024 17:01:06 -0700 Subject: [PATCH 032/214] test: templatize the payload file inputs for variable replacements --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 714dbbb2..1e71a937 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -120,6 +120,7 @@ jobs: uses: ./ with: payload-file-path: ./.github/resources/payload-notification.json + payload-templated: true webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} webhook-type: incoming-webhook env: From 1a77eed948d8294fb6d86f8dba9941938c9dc75e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 26 Sep 2024 15:37:57 -0700 Subject: [PATCH 033/214] refactor: move tests into a test directory and away from src --- package.json | 2 +- {src => test}/client.spec.js | 2 +- {src => test}/config.spec.js | 4 ++-- {src => test}/content.spec.js | 4 ++-- {src => test}/index.spec.js | 0 {src => test}/send.spec.js | 2 +- {src => test}/webhook.spec.js | 6 +++--- 7 files changed, 10 insertions(+), 10 deletions(-) rename {src => test}/client.spec.js (98%) rename {src => test}/config.spec.js (97%) rename {src => test}/content.spec.js (98%) rename {src => test}/index.spec.js (100%) rename {src => test}/send.spec.js (95%) rename {src => test}/webhook.spec.js (97%) diff --git a/package.json b/package.json index de58ba07..cc38c140 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "biome check", "check": "tsc --noemit --module es2022 --project ./jsconfig.json", "local": "act public --eventpath .github/workflows/local/event.json --secret-file .github/workflows/local/.env --platform ubuntu-latest=node:20-buster", - "test": "c8 mocha src/*.spec.js" + "test": "c8 mocha test/*.spec.js" }, "repository": { "type": "git", diff --git a/src/client.spec.js b/test/client.spec.js similarity index 98% rename from src/client.spec.js rename to test/client.spec.js index 5cba149e..e9957ddd 100644 --- a/src/client.spec.js +++ b/test/client.spec.js @@ -1,6 +1,6 @@ import { assert } from "chai"; import { mocks } from "./index.spec.js"; -import send from "./send.js"; +import send from "../src/send.js"; describe("client", () => { beforeEach(() => { diff --git a/src/config.spec.js b/test/config.spec.js similarity index 97% rename from src/config.spec.js rename to test/config.spec.js index 8e242584..931c231d 100644 --- a/src/config.spec.js +++ b/test/config.spec.js @@ -1,7 +1,7 @@ import { assert } from "chai"; -import Config from "./config.js"; +import Config from "../src/config.js"; import { mocks } from "./index.spec.js"; -import send from "./send.js"; +import send from "../src/send.js"; /** * Confirm values from the action input or environment variables are gathered diff --git a/src/content.spec.js b/test/content.spec.js similarity index 98% rename from src/content.spec.js rename to test/content.spec.js index bc34c5ba..2bbb0192 100644 --- a/src/content.spec.js +++ b/test/content.spec.js @@ -1,8 +1,8 @@ import path from "node:path"; import { assert } from "chai"; -import Config from "./config.js"; +import Config from "../src/config.js"; import { mocks } from "./index.spec.js"; -import send from "./send.js"; +import send from "../src/send.js"; /** * Confirm values from the action input or environment variables are gathered diff --git a/src/index.spec.js b/test/index.spec.js similarity index 100% rename from src/index.spec.js rename to test/index.spec.js diff --git a/src/send.spec.js b/test/send.spec.js similarity index 95% rename from src/send.spec.js rename to test/send.spec.js index bf195048..62a65f4c 100644 --- a/src/send.spec.js +++ b/test/send.spec.js @@ -1,5 +1,5 @@ import { mocks } from "./index.spec.js"; -import send from "./send.js"; +import send from "../src/send.js"; /** * This is a collection of integration tests that make sure modules are doing diff --git a/src/webhook.spec.js b/test/webhook.spec.js similarity index 97% rename from src/webhook.spec.js rename to test/webhook.spec.js index 97602c58..b8718987 100644 --- a/src/webhook.spec.js +++ b/test/webhook.spec.js @@ -1,7 +1,7 @@ import { assert } from "chai"; import { mocks } from "./index.spec.js"; -import send from "./send.js"; -import Webhook from "./webhook.js"; +import send from "../src/send.js"; +import Webhook from "../src/webhook.js"; describe("webhook", () => { afterEach(() => { @@ -23,7 +23,7 @@ describe("webhook", () => { assert.equal(url, "https://hooks.slack.com"); assert.deepEqual(payload, { message: "hello" }); assert.equal( - /** @type {import("axios-retry").IAxiosRetryConfig} */ (options) + /** @type {import("axios-retry").IAxiosRetryConfig} */(options) .retries, 5, ); From eea2eb1403830f07941c94555d82046102dd4dbb Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 26 Sep 2024 15:38:56 -0700 Subject: [PATCH 034/214] fix: avoid debug logging undefined data of an undefined response --- src/webhook.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webhook.js b/src/webhook.js index e051dbb6..26c6c2fd 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -27,7 +27,7 @@ export default class Webhook { config.content.values, options, ); - config.core.debug(JSON.stringify(response.data)); + config.core.debug(JSON.stringify(response?.data)); } /** From 38b6f1e827e9f58db575319884af003bda74d6f5 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 26 Sep 2024 15:41:18 -0700 Subject: [PATCH 035/214] style: lint fixes that are required for ci to pass --- test/client.spec.js | 2 +- test/config.spec.js | 2 +- test/content.spec.js | 2 +- test/send.spec.js | 2 +- test/webhook.spec.js | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/client.spec.js b/test/client.spec.js index e9957ddd..69be4f27 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { mocks } from "./index.spec.js"; import send from "../src/send.js"; +import { mocks } from "./index.spec.js"; describe("client", () => { beforeEach(() => { diff --git a/test/config.spec.js b/test/config.spec.js index 931c231d..3ef479f6 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -1,7 +1,7 @@ import { assert } from "chai"; import Config from "../src/config.js"; -import { mocks } from "./index.spec.js"; import send from "../src/send.js"; +import { mocks } from "./index.spec.js"; /** * Confirm values from the action input or environment variables are gathered diff --git a/test/content.spec.js b/test/content.spec.js index 2bbb0192..f8f52f61 100644 --- a/test/content.spec.js +++ b/test/content.spec.js @@ -1,8 +1,8 @@ import path from "node:path"; import { assert } from "chai"; import Config from "../src/config.js"; -import { mocks } from "./index.spec.js"; import send from "../src/send.js"; +import { mocks } from "./index.spec.js"; /** * Confirm values from the action input or environment variables are gathered diff --git a/test/send.spec.js b/test/send.spec.js index 62a65f4c..e157efd0 100644 --- a/test/send.spec.js +++ b/test/send.spec.js @@ -1,5 +1,5 @@ -import { mocks } from "./index.spec.js"; import send from "../src/send.js"; +import { mocks } from "./index.spec.js"; /** * This is a collection of integration tests that make sure modules are doing diff --git a/test/webhook.spec.js b/test/webhook.spec.js index b8718987..c8686929 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -1,7 +1,7 @@ import { assert } from "chai"; -import { mocks } from "./index.spec.js"; import send from "../src/send.js"; import Webhook from "../src/webhook.js"; +import { mocks } from "./index.spec.js"; describe("webhook", () => { afterEach(() => { @@ -23,7 +23,7 @@ describe("webhook", () => { assert.equal(url, "https://hooks.slack.com"); assert.deepEqual(payload, { message: "hello" }); assert.equal( - /** @type {import("axios-retry").IAxiosRetryConfig} */(options) + /** @type {import("axios-retry").IAxiosRetryConfig} */ (options) .retries, 5, ); From e3f0238359faac42c27ae779d389ffbabc5e8b91 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 26 Sep 2024 15:54:11 -0700 Subject: [PATCH 036/214] docs: polish and findings from past readings of the readme --- README.md | 6 +++--- example-workflows/Technique_2_Slack_App/JSON_payload.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 17ba2c98..c0204cdc 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ files just another API call, but with all of the advantages of ```yaml - name: Share a file to that channel - uses: slackapi/slack-github-action@v2 + uses: slackapi/slack-github-action@v2-development with: method: files.uploadV2 payload: | @@ -335,8 +335,8 @@ options might be useful. ### Parsing templated variables Additional [variables][github-variables] provided by Github can be used to -replace templated variables in the `payload-file-path` file using the option to -parse payloads with the `payload-templated` option: +replace templated variables in the provided payloads using the option to parse +payloads with the `payload-templated` option: ```yaml - name: Send custom JSON data to Slack workflow diff --git a/example-workflows/Technique_2_Slack_App/JSON_payload.yml b/example-workflows/Technique_2_Slack_App/JSON_payload.yml index 95d075b9..7cdcf23b 100644 --- a/example-workflows/Technique_2_Slack_App/JSON_payload.yml +++ b/example-workflows/Technique_2_Slack_App/JSON_payload.yml @@ -7,7 +7,7 @@ jobs: steps: - name: Send GitHub trigger payload to Slack Workflow Builder id: slack - uses: slackapi/slack-github-action@v1.26.0 + uses: slackapi/slack-github-action@v2-development with: token: ${{ secrets.SLACK_BOT_TOKEN }} method: chat.postMessage From 7aee1306ed1f86346f9e3afc9b36f26859d9a8da Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 26 Sep 2024 18:58:17 -0700 Subject: [PATCH 037/214] fix: correct webclient proxy warning to be the https axios agent --- src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index dd690292..2cd33de8 100644 --- a/src/client.js +++ b/src/client.js @@ -86,7 +86,7 @@ export default class Client { }; } catch (err) { config.core.warning( - "Failed to configure HTTPS proxy agent for HTTP proxy so using the default axios configuration.", + "Failed to configure the HTTPS proxy agent so using the default axios configuration.", ); console.error(err); return undefined; From abda089994ee10fa203a0401e5b0086c85dd4f62 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 27 Sep 2024 00:17:12 -0700 Subject: [PATCH 038/214] fix: use the nested message response to gather thread ts --- .github/workflows/local/local.yml | 22 +++++++++++++++++++++- src/client.js | 12 +++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.github/workflows/local/local.yml b/.github/workflows/local/local.yml index e05088f3..1ca88288 100644 --- a/.github/workflows/local/local.yml +++ b/.github/workflows/local/local.yml @@ -59,9 +59,20 @@ jobs: - title: "Status" short: true value: "In Progress" + - name: Write starting statistics + id: stats + uses: ./. + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ steps.slack.outputs.channel_id }} + text: "Started at `${{ steps.slack.outputs.time }}`" + thread_ts: ${{ steps.slack.outputs.ts }} - name: Countdown - run: sleep 10 + run: sleep 3 - name: Launch time is now + id: finished uses: ./. with: method: chat.update @@ -85,3 +96,12 @@ jobs: channel: ${{ secrets.SLACK_CHANNEL_ID }} timestamp: ${{ steps.slack.outputs.ts }} name: "tada" + - name: Include ending statistics + uses: ./. + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ steps.slack.outputs.channel_id }} + text: "Finished at `${{ steps.finished.outputs.time }}`" + thread_ts: ${{ steps.stats.outputs.thread_ts }} diff --git a/src/client.js b/src/client.js index 2cd33de8..f9e0a844 100644 --- a/src/client.js +++ b/src/client.js @@ -11,10 +11,16 @@ import SlackError from "./errors.js"; * @see {@link https://api.slack.com/methods/} */ export default class Client { + /** + * Known response values from messages that are included in outputs. + * @typedef MessageResultDetails + * @prop {string} [thread_ts] - timestamp of the top threaded message. + */ + /** * Possible response values related to messages from the API. * @typedef MessageResult - Possible message values from API methods. - * @prop {string} [threadTs] - timestamp of the top threaded message. + * @prop {MessageResultDetails} [message] - additional message details. * @prop {string} [ts] - timestamp of the message. * @prop {string} [channel] - ID of the channel. * @see {@link https://api.slack.com/methods/chat.postMessage#examples} @@ -61,8 +67,8 @@ export default class Client { if (response.channel) { config.core.setOutput("channel_id", response.channel); } - if (response.threadTs) { - config.core.setOutput("thread_ts", response.threadTs); + if (response.message?.thread_ts) { + config.core.setOutput("thread_ts", response.message?.thread_ts); } if (response.ts) { config.core.setOutput("ts", response.ts); From 6b14b3233e27df86ee4de9f4cea2aa7905e71da0 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 27 Sep 2024 00:18:53 -0700 Subject: [PATCH 039/214] test: fail tests that are expected to have thrown but dont --- test/config.spec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/config.spec.js b/test/config.spec.js index 3ef479f6..9c52b0c5 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -39,6 +39,7 @@ describe("config", () => { mocks.core.getInput.withArgs("token").returns("xoxb-example"); try { await send(mocks.core); + assert.fail("Failed to error for incomplete inputs while testing"); } catch { assert.isTrue(mocks.core.setSecret.withArgs("xoxb-example").called); } @@ -49,6 +50,7 @@ describe("config", () => { mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); try { await send(mocks.core); + assert.fail("Failed to error for incomplete inputs while testing"); } catch { assert.isTrue( mocks.core.setSecret.withArgs("https://slack.com").called, From 23913a6e65f101fb751aadc2157ca48f0d2d01a0 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 27 Sep 2024 00:51:05 -0700 Subject: [PATCH 040/214] test: confirm requests are sent with arguments to the slack web api --- test/client.spec.js | 140 ++++++++++++++++++++++++++++++++++++++++---- test/index.spec.js | 4 ++ 2 files changed, 132 insertions(+), 12 deletions(-) diff --git a/test/client.spec.js b/test/client.spec.js index 69be4f27..aecc2a50 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -34,24 +34,140 @@ describe("client", () => { }); }); - describe("failure", () => { - /** - * FIXME: this is calling the actual API to cause an invalid_auth error! - * - * It should be stubbing this or mocking something similar. - * - * @see {@link https://github.com/slackapi/slack-github-action/blob/5d1fb07d3c4f410b8d278134c714edff31264beb/test/web-client-test.js#L6-L17} - */ - it("calls the method with the provided token and content", async () => { + describe("success", () => { + it("calls 'chat.postMessage' with the given token and content", async () => { try { + const args = { + channel: "C0123456789", + text: "hello", + thread_ts: "1234567890.000001", + }; + const response = { + ok: true, + channel: "C0123456789", + ts: "1234567890.000002", + message: { + thread_ts: "1234567890.000001", + }, + }; mocks.core.getInput.withArgs("method").returns("chat.postMessage"); mocks.core.getInput.withArgs("token").returns("xoxb-example"); - mocks.core.getInput - .withArgs("payload") - .returns(`"text": "hello", "channel": "C0123456789"`); + mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args)); + mocks.api.resolves(response); + await send(mocks.core); + assert.deepEqual(mocks.api.getCall(0).firstArg, "chat.postMessage"); + assert.deepEqual(mocks.api.getCall(0).lastArg, args); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "channel_id"); + assert.equal(mocks.core.setOutput.getCall(2).lastArg, "C0123456789"); + assert.equal(mocks.core.setOutput.getCall(3).firstArg, "thread_ts"); + assert.equal( + mocks.core.setOutput.getCall(3).lastArg, + "1234567890.000001", + ); + assert.equal(mocks.core.setOutput.getCall(4).firstArg, "ts"); + assert.equal( + mocks.core.setOutput.getCall(4).lastArg, + "1234567890.000002", + ); + assert.equal(mocks.core.setOutput.getCall(5).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 6); + } catch (error) { + console.error(error); + assert.fail("Unexpected error when calling the method"); + } + }); + + it("calls 'files.uploadV2' with the provided token and content", async () => { + try { + const args = { + channel: "C0000000001", + initial_comment: "the results are in!", + file: "results.out", + filename: "results-888888.out", + }; + const response = { + ok: true, + files: [{ id: "F0000000001", created: 1234567890 }], + }; + mocks.core.getInput.withArgs("method").returns("files.uploadV2"); + mocks.core.getInput.withArgs("token").returns("xoxp-example"); + mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args)); + mocks.api.resolves(response); await send(mocks.core); + assert.deepEqual(mocks.api.getCall(0).lastArg, args); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 3); } catch (error) { console.error(error); + assert.fail("Unexpected error when calling the method"); + } + }); + }); + + describe("failure", () => { + it("errors when the payload arguments are invalid for the api", async () => { + const response = { + ok: false, + error: "missing_channel", + }; + try { + mocks.core.getInput.reset(); + mocks.core.getBooleanInput.withArgs("errors").returns(true); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`); + mocks.api.resolves(response); + await send(mocks.core); + assert.fail("Expected an error but none was found"); + } catch (error) { + assert.isTrue(mocks.core.setFailed.called); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.deepEqual( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); + } + }); + + it("returns the api error and details without a exit failing", async () => { + const response = { + ok: false, + error: "missing_channel", + }; + try { + mocks.core.getInput.reset(); + mocks.core.getBooleanInput.withArgs("errors").returns(false); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`); + mocks.api.resolves(response); + await send(mocks.core); + assert.fail("Expected an error but none was found"); + } catch (error) { + assert.isFalse(mocks.core.setFailed.called); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.deepEqual( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); } }); }); diff --git a/test/index.spec.js b/test/index.spec.js index 19d2f557..98cfab63 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -1,7 +1,9 @@ import fs from "node:fs"; import core from "@actions/core"; +import webapi from "@slack/web-api"; import axios, { AxiosError } from "axios"; import sinon from "sinon"; +import SlackError from "../src/errors.js"; /** * Hello experimenter! These tests are here to confirm that the happy paths keep @@ -42,6 +44,7 @@ export class Mock { */ constructor() { this.sandbox = sinon.createSandbox(); + this.api = sinon.stub(webapi.WebClient.prototype, "apiCall"); this.axios = this.sandbox.stub(axios); this.core = this.sandbox.stub(core); this.fs = this.sandbox.stub(fs); @@ -54,6 +57,7 @@ export class Mock { */ reset() { this.sandbox.reset(); + this.api.resetHistory(); this.axios.post.resetHistory(); } } From 92fd8d8beec82c664b39af5d0f9424d002e73b92 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 27 Sep 2024 00:51:57 -0700 Subject: [PATCH 041/214] fix: include the time of action completion when erroring too --- src/send.js | 2 +- test/client.spec.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/send.js b/src/send.js index 0eab0bdd..9f2c852b 100644 --- a/src/send.js +++ b/src/send.js @@ -15,7 +15,7 @@ export default async function send(core) { await post(config); config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); } catch (error) { - console.error(error); + config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); throw new SlackError(core, error, config.inputs.errors); } } diff --git a/test/client.spec.js b/test/client.spec.js index aecc2a50..63e97f22 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -142,6 +142,8 @@ describe("client", () => { mocks.core.setOutput.getCall(1).lastArg, JSON.stringify(response), ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 3); } }); @@ -168,6 +170,8 @@ describe("client", () => { mocks.core.setOutput.getCall(1).lastArg, JSON.stringify(response), ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 3); } }); }); From 856cb18c04281fb0b0491627a491a7106c98888d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 27 Sep 2024 23:11:48 -0700 Subject: [PATCH 042/214] chore: setup a manifest app with workflow and scopes and webhooks some maintenance buffs in this pr - the cli is used to create and manage the webhook trigger and workflow for more stable testing! it might soon be nice to incorporate this into other ci, but i want to avoid too many more changes... --- .github/workflows/local/.env.example | 8 +- .github/workflows/local/.gitignore | 3 + .github/workflows/local/README.md | 50 ++++++++++-- .github/workflows/local/local.yml | 12 ++- .github/workflows/local/manifest.json | 77 +++++++++++++++++++ .github/workflows/local/slack.json | 9 +++ .github/workflows/local/triggers/webhook.json | 23 ++++++ 7 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/local/manifest.json create mode 100644 .github/workflows/local/slack.json create mode 100644 .github/workflows/local/triggers/webhook.json diff --git a/.github/workflows/local/.env.example b/.github/workflows/local/.env.example index 813e376b..89650f6d 100644 --- a/.github/workflows/local/.env.example +++ b/.github/workflows/local/.env.example @@ -1,6 +1,6 @@ # Rename this file to .env and update any variables to get started -SLACK_BOT_TOKEN=xoxb-01010101-abcdefgh -SLACK_CHANNEL_ID=C0123456789 -SLACK_USER_TOKEN=xoxp-22222222-example -SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz +export SLACK_BOT_TOKEN=xoxb-01010101-abcdefgh +export SLACK_CHANNEL_ID=C0123456789 +export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz +export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz diff --git a/.github/workflows/local/.gitignore b/.github/workflows/local/.gitignore index 4ce635d8..d0ef15f7 100644 --- a/.github/workflows/local/.gitignore +++ b/.github/workflows/local/.gitignore @@ -1,2 +1,5 @@ # Secret variables .env + +# Application settings +.slack diff --git a/.github/workflows/local/README.md b/.github/workflows/local/README.md index d462dc54..fbd059d2 100644 --- a/.github/workflows/local/README.md +++ b/.github/workflows/local/README.md @@ -15,17 +15,51 @@ $ npm run local # Test techniques ## Setting up an app The saved `local.yml` workflow uses `webhook` and `method` for a combination of -features, which expects the following scopes: +features, and expects the values found in `manifest.json`. -- `chat:write` -- `files:write` -- `reactions:write` - -These scopes aren't required if the methods used change, but others might be if +These values aren't required if the methods used change, but others might be if different API methods are called! -This app will have useful variables that can be configured as values in this -next section. +### Installing with the CLI + +Quick setup for the saved workflow can be achieved using the provided manifest +and these [Slack CLI](https://api.slack.com/automation/cli) commands: + +```sh +$ slack install +$ vim .env +export SLACK_CHANNEL_ID=C0123456789 # Invite the bot to this channel! +``` + +Find and store the bot token: + +```sh +$ open https://api.slack.com/apps/A0123456789/oauth +vim .env +export SLACK_BOT_TOKEN=xoxb-0123456789-example-0000000001 +``` + +Create the webhook trigger for use in Workflow Builder: + +```sh +$ slack trigger create --trigger-def triggers/webhook.json +$ vim .env +export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/... +``` + +Gather and save an incoming webhook for a workspace: + +```sh +$ open https://api.slack.com/apps/A0123456789/incoming-webhooks +$ vim .env +export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/... +``` + +Once variables are set for new app, start the workflow with the same command: + +```sh +$ npm run local # Test techniques +``` ## Configuring values diff --git a/.github/workflows/local/local.yml b/.github/workflows/local/local.yml index 1ca88288..d4aa7003 100644 --- a/.github/workflows/local/local.yml +++ b/.github/workflows/local/local.yml @@ -17,13 +17,14 @@ jobs: id: webhook uses: ./. with: - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook: ${{ secrets.SLACK_WEBHOOK_TRIGGER }} webhook-type: webhook-trigger payload: | + channel_id: ${{ secrets.SLACK_CHANNEL_ID }} repository: ${{ github.repository }} - pull_request_username: ${{ github.event.pull_request.user.login }} pull_request_title: ${{ toJSON(github.event.pull_request.title) }} pull_request_url: ${{ github.event.pull_request.html_url }} + pull_request_username: ${{ github.event.pull_request.user.login }} - name: Post a token message into channel id: api uses: ./. @@ -105,3 +106,10 @@ jobs: channel: ${{ steps.slack.outputs.channel_id }} text: "Finished at `${{ steps.finished.outputs.time }}`" thread_ts: ${{ steps.stats.outputs.thread_ts }} + - name: Write one final webhook + uses: ./. + with: + webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} + webhook-type: incoming-webhook + payload: | + text: "end." diff --git a/.github/workflows/local/manifest.json b/.github/workflows/local/manifest.json new file mode 100644 index 00000000..46b37032 --- /dev/null +++ b/.github/workflows/local/manifest.json @@ -0,0 +1,77 @@ +{ + "_metadata": { + "major_version": 2 + }, + "display_information": { + "name": "slacktion", + "description": "an application for developing the Slack GitHub Action" + }, + "features": { + "app_home": { + "messages_tab_enabled": true, + "messages_tab_read_only_enabled": true + }, + "bot_user": { + "display_name": "slacktion" + } + }, + "oauth_config": { + "scopes": { + "bot": [ + "chat:write", + "chat:write.public", + "files:write", + "incoming-webhook", + "reactions:write" + ] + } + }, + "settings": { + "org_deploy_enabled": true, + "incoming_webhooks": { + "incoming_webhooks_enabled": true + } + }, + "workflows": { + "pr_notifications": { + "title": "Pull request notifier", + "description": "A new changeset has been submitted", + "input_parameters": { + "properties": { + "channel_id": { + "type": "slack#/types/channel_id" + }, + "repository": { + "type": "string" + }, + "pull_request_username": { + "type": "string" + }, + "pull_request_title": { + "type": "string" + }, + "pull_request_url": { + "type": "string" + } + }, + "required": [ + "channel_id", + "repository", + "pull_request_username", + "pull_request_title", + "pull_request_url" + ] + }, + "steps": [ + { + "id": "0", + "function_id": "slack#/functions/send_message", + "inputs": { + "channel_id": "{{inputs.channel_id}}", + "message": "{{inputs.repository}}(@{{inputs.pull_request_username}}): <{{inputs.pull_request_url}}|{{inputs.pull_request_title}}>" + } + } + ] + } + } +} diff --git a/.github/workflows/local/slack.json b/.github/workflows/local/slack.json new file mode 100644 index 00000000..0b3248df --- /dev/null +++ b/.github/workflows/local/slack.json @@ -0,0 +1,9 @@ +{ + "runtime": "actions", + "hooks": { + "get-manifest": "cat manifest.json #" + }, + "config": { + "protocol-version": ["default"] + } +} diff --git a/.github/workflows/local/triggers/webhook.json b/.github/workflows/local/triggers/webhook.json new file mode 100644 index 00000000..c7b59e72 --- /dev/null +++ b/.github/workflows/local/triggers/webhook.json @@ -0,0 +1,23 @@ +{ + "type": "webhook", + "name": "Post the latest pull request", + "description": "Share information to a channel", + "workflow": "#/workflows/pr_notifications", + "inputs": { + "channel_id": { + "value": "{{data.channel_id}}" + }, + "repository": { + "value": "{{data.repository}}" + }, + "pull_request_title": { + "value": "{{data.pull_request_title}}" + }, + "pull_request_url": { + "value": "{{data.pull_request_url}}" + }, + "pull_request_username": { + "value": "{{data.pull_request_username}}" + } + } +} From 3d0eb1e10288d2e86c75d0997d07cdfee8f24ea9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 27 Sep 2024 23:17:03 -0700 Subject: [PATCH 043/214] docs: improve setup instructions with more correct wordings --- .github/workflows/local/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/local/README.md b/.github/workflows/local/README.md index fbd059d2..020d0c31 100644 --- a/.github/workflows/local/README.md +++ b/.github/workflows/local/README.md @@ -35,7 +35,7 @@ Find and store the bot token: ```sh $ open https://api.slack.com/apps/A0123456789/oauth -vim .env +$ vim .env export SLACK_BOT_TOKEN=xoxb-0123456789-example-0000000001 ``` @@ -47,7 +47,7 @@ $ vim .env export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/... ``` -Gather and save an incoming webhook for a workspace: +Gather and save an incoming webhook for a channel in a workspace: ```sh $ open https://api.slack.com/apps/A0123456789/incoming-webhooks From 28dee887e13faf8418bbe064a0c0ce0031cebe41 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 13:41:38 -0700 Subject: [PATCH 044/214] ci: revert removal of the build step for packaged distribution --- .github/workflows/main.yml | 3 +++ .github/workflows/{publish.yaml => publish.yml} | 0 .gitignore | 3 +++ package.json | 3 ++- 4 files changed, 8 insertions(+), 1 deletion(-) rename .github/workflows/{publish.yaml => publish.yml} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1e71a937..35e1b707 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,6 +34,9 @@ jobs: - name: "unit(test): perform unit test checks" run: npm test + - name: "build: package code for distribution" + run: npm run build + - name: "unit(test): upload coverage to CodeCov" uses: codecov/codecov-action@v4.5.0 with: diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yml similarity index 100% rename from .github/workflows/publish.yaml rename to .github/workflows/publish.yml diff --git a/.gitignore b/.gitignore index 206bf637..df503e67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Build artifacts +dist + # Development dependencies node_modules diff --git a/package.json b/package.json index cc38c140..178bf18c 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,10 @@ "main": "dist/index.js", "type": "module", "scripts": { + "build": "ncc build src/index.js --license licenses.txt", + "check": "tsc --noemit --module es2022 --project ./jsconfig.json", "lint:fix": "biome check --write", "lint": "biome check", - "check": "tsc --noemit --module es2022 --project ./jsconfig.json", "local": "act public --eventpath .github/workflows/local/event.json --secret-file .github/workflows/local/.env --platform ubuntu-latest=node:20-buster", "test": "c8 mocha test/*.spec.js" }, From a9b40ba20925bef56cf62c1bdb884cae41f286d8 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 13:52:37 -0700 Subject: [PATCH 045/214] build: include the source map in build outputs for error debugs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 178bf18c..c1cb1b7c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "dist/index.js", "type": "module", "scripts": { - "build": "ncc build src/index.js --license licenses.txt", + "build": "ncc build src/index.js --license licenses.txt --source-map", "check": "tsc --noemit --module es2022 --project ./jsconfig.json", "lint:fix": "biome check --write", "lint": "biome check", From 29c7ea6d715d12489dadf388742da68ea6696ea5 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 15:40:18 -0700 Subject: [PATCH 046/214] ci(chore): run check ups of the health score on changes --- .github/workflows/main.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35e1b707..59b476de 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -132,3 +132,10 @@ jobs: - name: "integration(incoming): confirm a payload file was posted" run: test -n "${{ steps.slackPayloadFile.outputs.time }}" + + - name: "chore(health): check up on recent changes to the health score" + uses: slackapi/slack-health-score@v0.1.1 + with: + codecov_token: ${{ secrets.CODECOV_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + extension: js From 1a8d212b9ee5dbdaa05721b2035f5eb906b8b38a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 15:42:16 -0700 Subject: [PATCH 047/214] ci(fix): include the include of included paths of check up --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 59b476de..f8105de0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -139,3 +139,4 @@ jobs: codecov_token: ${{ secrets.CODECOV_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} extension: js + include: "." From 01ca5836ca717c078ff162dafad7a3e8c93cc299 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 15:51:34 -0700 Subject: [PATCH 048/214] revert(ci): oh oops the error is from codecov setups in this pr --- .github/workflows/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f8105de0..59b476de 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -139,4 +139,3 @@ jobs: codecov_token: ${{ secrets.CODECOV_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} extension: js - include: "." From b88b87aa6ff6cf3e1cc3e80cbda1227f9b2e01ce Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 16:01:45 -0700 Subject: [PATCH 049/214] refactor: rename the testing workflow into a single test file --- .github/workflows/{main.yml => test.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{main.yml => test.yml} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/test.yml similarity index 100% rename from .github/workflows/main.yml rename to .github/workflows/test.yml From 6f23616a89773f42768bc4f01423a09b5ecf40cc Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 16:02:45 -0700 Subject: [PATCH 050/214] refactor: move the local workflow into a development area --- .github/workflows/{local => develop}/.env.example | 0 .github/workflows/{local => develop}/.gitignore | 0 .github/workflows/{local => develop}/README.md | 2 +- .github/workflows/{local => develop}/event.json | 0 .github/workflows/{local => develop}/local.yml | 0 .github/workflows/{local => develop}/manifest.json | 0 .github/workflows/{local => develop}/slack.json | 0 .github/workflows/{local => develop}/triggers/webhook.json | 0 8 files changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{local => develop}/.env.example (100%) rename .github/workflows/{local => develop}/.gitignore (100%) rename .github/workflows/{local => develop}/README.md (98%) rename .github/workflows/{local => develop}/event.json (100%) rename .github/workflows/{local => develop}/local.yml (100%) rename .github/workflows/{local => develop}/manifest.json (100%) rename .github/workflows/{local => develop}/slack.json (100%) rename .github/workflows/{local => develop}/triggers/webhook.json (100%) diff --git a/.github/workflows/local/.env.example b/.github/workflows/develop/.env.example similarity index 100% rename from .github/workflows/local/.env.example rename to .github/workflows/develop/.env.example diff --git a/.github/workflows/local/.gitignore b/.github/workflows/develop/.gitignore similarity index 100% rename from .github/workflows/local/.gitignore rename to .github/workflows/develop/.gitignore diff --git a/.github/workflows/local/README.md b/.github/workflows/develop/README.md similarity index 98% rename from .github/workflows/local/README.md rename to .github/workflows/develop/README.md index 020d0c31..4785593c 100644 --- a/.github/workflows/local/README.md +++ b/.github/workflows/develop/README.md @@ -1,4 +1,4 @@ -# Local runs +# Development workflows For a simple development experience, a local version of this action can be used in experiments: diff --git a/.github/workflows/local/event.json b/.github/workflows/develop/event.json similarity index 100% rename from .github/workflows/local/event.json rename to .github/workflows/develop/event.json diff --git a/.github/workflows/local/local.yml b/.github/workflows/develop/local.yml similarity index 100% rename from .github/workflows/local/local.yml rename to .github/workflows/develop/local.yml diff --git a/.github/workflows/local/manifest.json b/.github/workflows/develop/manifest.json similarity index 100% rename from .github/workflows/local/manifest.json rename to .github/workflows/develop/manifest.json diff --git a/.github/workflows/local/slack.json b/.github/workflows/develop/slack.json similarity index 100% rename from .github/workflows/local/slack.json rename to .github/workflows/develop/slack.json diff --git a/.github/workflows/local/triggers/webhook.json b/.github/workflows/develop/triggers/webhook.json similarity index 100% rename from .github/workflows/local/triggers/webhook.json rename to .github/workflows/develop/triggers/webhook.json From 815c519f3a52bd6d5238260e0ea8cfd04da0d9d1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 30 Sep 2024 16:12:31 -0700 Subject: [PATCH 051/214] style: format json with separate spacing for individual array items --- .github/resources/payload-notification.json | 56 ++++++++++++--------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/.github/resources/payload-notification.json b/.github/resources/payload-notification.json index 83908bdf..ac24f37e 100644 --- a/.github/resources/payload-notification.json +++ b/.github/resources/payload-notification.json @@ -1,27 +1,35 @@ { "text": "A GitHub Action Event ${{ github.eventName }} has ${{ env.JOB_STATUS }}", - "attachments": [{ - "color": "${{ env.ATTACHMENT_COLOR }}", - "fields": [{ - "title": "Repository", - "short": false, - "value": "<${{ github.payload.repository.html_url }}|${{ github.payload.repository.full_name }}>" - }, { - "title": "Ref", - "short": false, - "value": "${{ github.ref }}" - }, { - "title": "Commit", - "short": false, - "value": "<${{ github.payload.repository.html_url }}/commit/${{ github.sha }}|${{ github.sha }}>" - }, { - "title": "Author", - "short": false, - "value": "" - }, { - "title": "Workflow", - "short": false, - "value": "<${{ github.payload.repository.html_url }}/actions/runs/${{ github.runId }}|${{ github.workflow }}>" - }] - }] + "attachments": [ + { + "color": "${{ env.ATTACHMENT_COLOR }}", + "fields": [ + { + "title": "Repository", + "short": false, + "value": "<${{ github.payload.repository.html_url }}|${{ github.payload.repository.full_name }}>" + }, + { + "title": "Ref", + "short": false, + "value": "${{ github.ref }}" + }, + { + "title": "Commit", + "short": false, + "value": "<${{ github.payload.repository.html_url }}/commit/${{ github.sha }}|${{ github.sha }}>" + }, + { + "title": "Author", + "short": false, + "value": "" + }, + { + "title": "Workflow", + "short": false, + "value": "<${{ github.payload.repository.html_url }}/actions/runs/${{ github.runId }}|${{ github.workflow }}>" + } + ] + } + ] } From 8003d03ce6726bad7e2de1419fb1041e42cca744 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 1 Oct 2024 02:00:31 -0700 Subject: [PATCH 052/214] test: confirm changes match between develop and test and docs --- .../develop => resources/.actions}/event.json | 9 +- .github/resources/.gitignore | 6 + .github/resources/.slack/config.json | 5 + .../incoming-webhook.json} | 0 .../.slack}/manifest.json | 28 ++-- .github/resources/.slack/webhook-trigger.json | 23 +++ .github/resources/README.md | 104 +++++++++++++ .github/resources/slack.json | 11 ++ .../{develop/local.yml => develop.yml} | 70 ++++----- .github/workflows/develop/.env.example | 6 - .github/workflows/develop/.gitignore | 5 - .github/workflows/develop/README.md | 92 ------------ .github/workflows/develop/slack.json | 9 -- .../workflows/develop/triggers/webhook.json | 23 --- .github/workflows/test.yml | 140 +++++++++++++----- .gitignore | 6 +- README.md | 38 ++--- package.json | 2 +- 18 files changed, 327 insertions(+), 250 deletions(-) rename .github/{workflows/develop => resources/.actions}/event.json (56%) create mode 100644 .github/resources/.gitignore create mode 100644 .github/resources/.slack/config.json rename .github/resources/{payload-notification.json => .slack/incoming-webhook.json} (100%) rename .github/{workflows/develop => resources/.slack}/manifest.json (68%) create mode 100644 .github/resources/.slack/webhook-trigger.json create mode 100644 .github/resources/README.md create mode 100644 .github/resources/slack.json rename .github/workflows/{develop/local.yml => develop.yml} (79%) delete mode 100644 .github/workflows/develop/.env.example delete mode 100644 .github/workflows/develop/.gitignore delete mode 100644 .github/workflows/develop/README.md delete mode 100644 .github/workflows/develop/slack.json delete mode 100644 .github/workflows/develop/triggers/webhook.json diff --git a/.github/workflows/develop/event.json b/.github/resources/.actions/event.json similarity index 56% rename from .github/workflows/develop/event.json rename to .github/resources/.actions/event.json index 383b8140..67fb546d 100644 --- a/.github/workflows/develop/event.json +++ b/.github/resources/.actions/event.json @@ -1,9 +1,12 @@ { "pull_request": { - "user": { - "login": "dependabot" - }, "title": "Bump actions/checkout from 3 to 4", "html_url": "https://github.com/slackapi/slack-github-action/pull/238" + }, + "repository": { + "full_name": "slackapi/slack-github-action" + }, + "sender": { + "login": "dependabot" } } diff --git a/.github/resources/.gitignore b/.github/resources/.gitignore new file mode 100644 index 00000000..9d06fbd9 --- /dev/null +++ b/.github/resources/.gitignore @@ -0,0 +1,6 @@ +# Application values +!.env.example +.env +.env.* +.slack/apps.json +.slack/apps.*.json diff --git a/.github/resources/.slack/config.json b/.github/resources/.slack/config.json new file mode 100644 index 00000000..6de199bc --- /dev/null +++ b/.github/resources/.slack/config.json @@ -0,0 +1,5 @@ +{ + "experiments": ["bolt"], + "project_id": "c4805b41-d1ce-4ea0-b297-ed2f8c64c267" +} + diff --git a/.github/resources/payload-notification.json b/.github/resources/.slack/incoming-webhook.json similarity index 100% rename from .github/resources/payload-notification.json rename to .github/resources/.slack/incoming-webhook.json diff --git a/.github/workflows/develop/manifest.json b/.github/resources/.slack/manifest.json similarity index 68% rename from .github/workflows/develop/manifest.json rename to .github/resources/.slack/manifest.json index 46b37032..df7e6c0c 100644 --- a/.github/workflows/develop/manifest.json +++ b/.github/resources/.slack/manifest.json @@ -33,34 +33,28 @@ } }, "workflows": { - "pr_notifications": { - "title": "Pull request notifier", - "description": "A new changeset has been submitted", + "event_notifications": { + "title": "GitHub event notifications", + "description": "A new changeset has been discovered", "input_parameters": { "properties": { + "author": { + "type": "string" + }, "channel_id": { "type": "slack#/types/channel_id" }, - "repository": { - "type": "string" - }, - "pull_request_username": { + "repo_name": { "type": "string" }, - "pull_request_title": { + "status": { "type": "string" }, - "pull_request_url": { + "event_url": { "type": "string" } }, - "required": [ - "channel_id", - "repository", - "pull_request_username", - "pull_request_title", - "pull_request_url" - ] + "required": ["author", "channel_id", "repo_name", "status", "event_url"] }, "steps": [ { @@ -68,7 +62,7 @@ "function_id": "slack#/functions/send_message", "inputs": { "channel_id": "{{inputs.channel_id}}", - "message": "{{inputs.repository}}(@{{inputs.pull_request_username}}): <{{inputs.pull_request_url}}|{{inputs.pull_request_title}}>" + "message": "{{inputs.repo_name}}(@{{inputs.author}}): `{{inputs.status}}` <{{inputs.event_url}}|Check it out here>!" } } ] diff --git a/.github/resources/.slack/webhook-trigger.json b/.github/resources/.slack/webhook-trigger.json new file mode 100644 index 00000000..8223c78c --- /dev/null +++ b/.github/resources/.slack/webhook-trigger.json @@ -0,0 +1,23 @@ +{ + "type": "webhook", + "name": "Post the latest actions from GitHub", + "description": "Share event information to a channel", + "workflow": "#/workflows/event_notifications", + "inputs": { + "author": { + "value": "{{data.author}}" + }, + "channel_id": { + "value": "{{data.channel_id}}" + }, + "repo_name": { + "value": "{{data.repo_name}}" + }, + "status": { + "value": "{{data.status}}" + }, + "event_url": { + "value": "{{data.event_url}}" + } + } +} diff --git a/.github/resources/README.md b/.github/resources/README.md new file mode 100644 index 00000000..4f368fc4 --- /dev/null +++ b/.github/resources/README.md @@ -0,0 +1,104 @@ +# Resources during testing and development + +For a quick development experience and fast testing setup, the app needed with +this action is configured using the [app manifest][manifest] and can be used in +experiments with the [Slack CLI][cli]. + +Start by gathering credentials of an application equipped for action: + +```sh +$ slack install +$ slack trigger create # SLACK_WEBHOOK_TRIGGER +$ slack deploy # SLACK_BOT_TOKEN and SLACK_INCOMING_WEBHOOK +``` + +Where these are stored will depend on the configurations to run and will follow +in the next sections. + +Also be sure to add the new bot to a channel for posting messages or errors +happen. + +## Running the workflows + +Both setups share the same starting test suite to make sure the similar examples +as in the `README.md` are correct, but changes while testing are encouraged! + +The app uses the values stored in `.slack` and can also be adjusted for changing +scopes or workflows. + +### Testing in CI + +Run the workflow as a complete CI check for the changes upstream. + +**Requirements**: + +- The credentials collected above +- Access to secrets for the repo + +The saved `test.yml` include common workflows from the `README.md` and other +code checks including linting and tests. + +Add gathered credentials and [secrets to save][secrets] to the repository being +tested. Required values include: + +- `SLACK_BOT_TOKEN`: xoxb-01010101-example +- `SLACK_CHANNEL_ID`: C0123456789 +- `SLACK_INCOMING_WEBHOOK`: https://hooks.slack.com/services/T0123456789/... +- `SLACK_WEBHOOK_TRIGGER`: https://hooks.slack.com/triggers/T0123456789/... + +### Experimenting for development + +Run the workflow to post messages without pushing changes upstream. + +**Requirements**: + +- The credentials collected above +- An installation of [`nektos/act`](https://github.com/nektos/act) +- A running instance of [Docker](https://www.docker.com) + +The saved `develop.yml` workflows use the same `webhook` and `method` examples +but skip tests that happen during upstream checks: + +```sh +$ cp .env.example .env # Set credentials +$ vim .env +export SLACK_BOT_TOKEN=xoxb-01010101-example +export SLACK_CHANNEL_ID=C0123456789 +export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz +export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz +``` + +Once credentials are configured and workflows updated, the following command +runs the workflow using `act` and the above settings: + +```sh +$ npm run dev # Test techniques +... +[Local run/run] 🏁 Job succeeded +``` + +## Configuring different things + +### Updating the workflow + +The `develop.yml` file contains the workflow used for testing. Updates to these +steps can be made to test various functionalities and edge cases. + +The same applies to test workflows in the `test.yml` file! However, some hope of +keeping examples in the `README.md` similar to the tests could be neat. + +### Changing secrets + +To use `${{ secrets.* }}` in the workflow, add more environment variable values +to the `.env` file or update the secrets saved to the repository. + +### Mocking event payloads + +Different event payloads can be mocked in development with changes to the values +of the `.github/resources/.actions/event.json` file. + +Reference: https://docs.github.com/en/webhooks/webhook-events-and-payloads + +[cli]: https://api.slack.com/automation/cli/commands +[manifest]: https://api.slack.com/concepts/manifests +[secrets]: https://github.com/slackapi/slack-github-action/settings/secrets/actions diff --git a/.github/resources/slack.json b/.github/resources/slack.json new file mode 100644 index 00000000..89e6f776 --- /dev/null +++ b/.github/resources/slack.json @@ -0,0 +1,11 @@ +{ + "runtime": "actions", + "hooks": { + "deploy": "open https://api.slack.com/apps || true", + "get-manifest": "cat ./.slack/manifest.json #" + }, + "config": { + "protocol-version": ["default"], + "trigger-paths": ["./.slack/webhook-trigger.json"] + } +} diff --git a/.github/workflows/develop/local.yml b/.github/workflows/develop.yml similarity index 79% rename from .github/workflows/develop/local.yml rename to .github/workflows/develop.yml index d4aa7003..7222c8fe 100644 --- a/.github/workflows/develop/local.yml +++ b/.github/workflows/develop.yml @@ -1,4 +1,4 @@ -name: Local run +name: Development run # Requires mocking the "public" event to begin this workflow and avoid actual runs # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows @@ -11,43 +11,32 @@ jobs: steps: - name: Checkout action uses: actions/checkout@v4 - - name: Build action + - name: Install dependencies run: npm install - - name: Post a webhook message into channel - id: webhook - uses: ./. + - name: Start a workflow with a webhook trigger + id: wfb + uses: ./ with: webhook: ${{ secrets.SLACK_WEBHOOK_TRIGGER }} webhook-type: webhook-trigger payload: | + author: ${{ github.event.sender.login }} channel_id: ${{ secrets.SLACK_CHANNEL_ID }} - repository: ${{ github.repository }} - pull_request_title: ${{ toJSON(github.event.pull_request.title) }} - pull_request_url: ${{ github.event.pull_request.html_url }} - pull_request_username: ${{ github.event.pull_request.user.login }} + event_url: ${{ github.event.pull_request.html_url }} + repo_name: ${{ github.event.repository.full_name }} + status: ${{ job.status }} - name: Post a token message into channel id: api - uses: ./. + uses: ./ with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - text: "Action happens at " - - name: Upload this workflow file - id: file - uses: ./. - with: - method: files.uploadV2 - token: ${{ secrets.SLACK_BOT_TOKEN }} - payload: | - channel_id: ${{ secrets.SLACK_CHANNEL_ID }} - initial_comment: "This code exists here" - file: .github/workflows/local/local.yml - filename: action.yml + text: ":checkered_flag: Action happens at " - name: Initiate the deployment sequence id: slack - uses: ./. + uses: ./ with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} @@ -62,7 +51,7 @@ jobs: value: "In Progress" - name: Write starting statistics id: stats - uses: ./. + uses: ./ with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} @@ -74,40 +63,51 @@ jobs: run: sleep 3 - name: Launch time is now id: finished - uses: ./. + uses: ./ with: method: chat.update token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} ts: "${{ steps.slack.outputs.ts }}" - text: "Deployment finished :rocket:" + text: "Deployment finished :tada:" attachments: - color: "28a745" fields: - title: "Status" short: true value: "Completed" + - name: Include ending statistics + uses: ./ + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ steps.slack.outputs.channel_id }} + text: "Finished at `${{ steps.finished.outputs.time }}`" + thread_ts: ${{ steps.stats.outputs.thread_ts }} - name: Celebrate wins - uses: ./. + uses: ./ with: method: reactions.add token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} timestamp: ${{ steps.slack.outputs.ts }} - name: "tada" - - name: Include ending statistics - uses: ./. + name: "ship" + - name: Upload this workflow file + id: file + uses: ./ with: - method: chat.postMessage + method: files.uploadV2 token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - channel: ${{ steps.slack.outputs.channel_id }} - text: "Finished at `${{ steps.finished.outputs.time }}`" - thread_ts: ${{ steps.stats.outputs.thread_ts }} + channel_id: ${{ secrets.SLACK_CHANNEL_ID }} + initial_comment: ":robot_face: The codes exists here" + file: .github/workflows/develop.yml + filename: action.yml - name: Write one final webhook - uses: ./. + uses: ./ with: webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} webhook-type: incoming-webhook diff --git a/.github/workflows/develop/.env.example b/.github/workflows/develop/.env.example deleted file mode 100644 index 89650f6d..00000000 --- a/.github/workflows/develop/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -# Rename this file to .env and update any variables to get started - -export SLACK_BOT_TOKEN=xoxb-01010101-abcdefgh -export SLACK_CHANNEL_ID=C0123456789 -export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz -export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz diff --git a/.github/workflows/develop/.gitignore b/.github/workflows/develop/.gitignore deleted file mode 100644 index d0ef15f7..00000000 --- a/.github/workflows/develop/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Secret variables -.env - -# Application settings -.slack diff --git a/.github/workflows/develop/README.md b/.github/workflows/develop/README.md deleted file mode 100644 index 4785593c..00000000 --- a/.github/workflows/develop/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Development workflows - -For a simple development experience, a local version of this action can be used -in experiments: - -```sh -$ npm run local # Test techniques -``` - -**Requirements**: - -- An installation of [nektos/act](https://github.com/nektos/act) -- A running instance of [Docker](https://www.docker.com) - -## Setting up an app - -The saved `local.yml` workflow uses `webhook` and `method` for a combination of -features, and expects the values found in `manifest.json`. - -These values aren't required if the methods used change, but others might be if -different API methods are called! - -### Installing with the CLI - -Quick setup for the saved workflow can be achieved using the provided manifest -and these [Slack CLI](https://api.slack.com/automation/cli) commands: - -```sh -$ slack install -$ vim .env -export SLACK_CHANNEL_ID=C0123456789 # Invite the bot to this channel! -``` - -Find and store the bot token: - -```sh -$ open https://api.slack.com/apps/A0123456789/oauth -$ vim .env -export SLACK_BOT_TOKEN=xoxb-0123456789-example-0000000001 -``` - -Create the webhook trigger for use in Workflow Builder: - -```sh -$ slack trigger create --trigger-def triggers/webhook.json -$ vim .env -export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/... -``` - -Gather and save an incoming webhook for a channel in a workspace: - -```sh -$ open https://api.slack.com/apps/A0123456789/incoming-webhooks -$ vim .env -export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/... -``` - -Once variables are set for new app, start the workflow with the same command: - -```sh -$ npm run local # Test techniques -``` - -## Configuring values - -### Changing secrets - -To use `${{ secrets.* }}` in the workflow, move `.env.example` to `.env` and -update any variables. - -### Mocking event payloads - -Different event payloads can be mocked directly with changes to the `event.json` -file. - -Reference: https://docs.github.com/en/webhooks/webhook-events-and-payloads - -## Updating the workflow - -The `local.yml` file contains the workflow used for testing. Updates to these -steps can be made to test various functionalities. - -## Running an experiment - -Run the workflow using `act` with the `npm run local` script. The above settings -will be used to simulate an actual workflow run: - -```sh -$ npm run local -... -[Local run/run] 🏁 Job succeeded -``` diff --git a/.github/workflows/develop/slack.json b/.github/workflows/develop/slack.json deleted file mode 100644 index 0b3248df..00000000 --- a/.github/workflows/develop/slack.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtime": "actions", - "hooks": { - "get-manifest": "cat manifest.json #" - }, - "config": { - "protocol-version": ["default"] - } -} diff --git a/.github/workflows/develop/triggers/webhook.json b/.github/workflows/develop/triggers/webhook.json deleted file mode 100644 index c7b59e72..00000000 --- a/.github/workflows/develop/triggers/webhook.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "type": "webhook", - "name": "Post the latest pull request", - "description": "Share information to a channel", - "workflow": "#/workflows/pr_notifications", - "inputs": { - "channel_id": { - "value": "{{data.channel_id}}" - }, - "repository": { - "value": "{{data.repository}}" - }, - "pull_request_title": { - "value": "{{data.pull_request_title}}" - }, - "pull_request_url": { - "value": "{{data.pull_request_url}}" - }, - "pull_request_username": { - "value": "{{data.pull_request_username}}" - } - } -} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 59b476de..e63495e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,63 +43,129 @@ jobs: directory: ./coverage token: ${{ secrets.CODECOV_TOKEN }} + - name: "pretest(inputs): save the push event trigger commit URL" + if: "contains(github.event_name, 'push')" + run: | + url=${{ github.event.head_commit.url }} + echo "EVENT_URL=$url" >> "$GITHUB_ENV" + + - name: "pretest(inputs): save the pull request event trigger commit URL" + if: "contains(github.event_name, 'pull_request')" + run: | + url=${{ github.event.pull_request.html_url }} + echo "EVENT_URL=$url" >> "$GITHUB_ENV" + + - name: "integration(wfb): send a payload to workflow builder via webhook trigger" + id: wfb + uses: ./ + with: + webhook: ${{ secrets.SLACK_WEBHOOK_TRIGGER }} + webhook-type: webhook-trigger + payload: | + author: ${{ github.event.sender.login }} + channel_id: ${{ secrets.SLACK_CHANNEL_ID }} + event_url: ${{ env.EVENT_URL}} + repo_name: ${{ github.event.repository.full_name }} + status: ${{ job.status }} + + - name: "integration(wfb): confirm a payload was sent" + run: test -n "${{ steps.wfb.outputs.time }}" + - name: "integration(botToken): post a message to channel" - id: slackToken + id: message uses: ./ with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - text: "CI Post from slack-send GitHub Action! Succeeded!!" + text: ":checkered_flag: Action happens at " - - name: "integration(botToken): confirm a message was posted" - run: test -n "${{ steps.slackToken.outputs.ts }}" + - name: "integration(method): confirm a message was posted" + run: test -n "${{ steps.message.outputs.ts }}" - - name: "integration(botToken): post a threaded response" - id: slackThreadResponse + - name: "integration(method): post a message with blocks" + id: blocks uses: ./ with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - text: "This message should be posted as a response in thread" - thread_ts: "${{ steps.slackToken.outputs.ts }}" - - - name: "integration(botToken): confirm a response was posted" - run: test -n "${{ steps.slackThreadResponse.outputs.ts }}" + text: "Event received :eyes:" + attachments: + - color: "dbab09" + fields: + - title: "Status" + short: true + value: "Processing" + + - name: "integration(method): confirm the blocks were posted" + run: test -n "${{ steps.blocks.outputs.ts }}" + + - name: "integration(method): post a threaded message" + id: timer + uses: ./ + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "Started at `${{ steps.blocks.outputs.time }}`" + thread_ts: "${{ steps.blocks.outputs.ts }}" - - name: "integration(wfb): save the push event trigger commit URL" - if: "contains(github.event_name, 'push')" - run: | - url=${{ github.event.head_commit.url }} - echo "URL=$url" >> "$GITHUB_ENV" + - name: "integration(incoming): confirm the thread started" + run: test -n "${{ steps.timer.outputs.time }}" - - name: "integration(wfb): save the pull request event trigger commit URL" - if: "contains(github.event_name, 'pull_request')" - run: | - url=${{ github.event.pull_request.url }} - echo "URL=$url" >> "$GITHUB_ENV" + - name: "integration(method): wait to mock event processing" + run: sleep 3 - - name: "integration(wfb): send a payload via workflow builder webhook" - id: slackWorkflow + - name: "integration(method): update the original message" + id: finished uses: ./ with: - # Workflow builder webhooks need to know the name of the keys in the payload in advance. Without normalizing, the github context payload keys can differ based on the GitHub trigger event type - # Normalized payload with info pulled out from GitHub trigger event - payload: '{"author":"${{ github.event.sender.login }}","url":"${{ env.URL}}", "repoName":"${{ github.event.repository.full_name }}", "status":"${{ job.status }}"}' - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} - webhook-type: webhook-trigger + method: chat.update + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + ts: ${{ steps.blocks.outputs.ts }} + text: "Event processed! :gear:" + attachments: + - color: "28a745" + fields: + - title: "Status" + short: true + value: "Completed" + + - name: "integration(method): post another threaded message" + id: done + uses: ./ + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ steps.blocks.outputs.channel_id }} + text: "Finished at `${{ steps.finished.outputs.time }}`" + thread_ts: ${{ steps.stats.outputs.thread_ts }} - - name: "integration(wfb): confirm a payload was sent" - run: test -n "${{ steps.slackWorkflow.outputs.time }}" + - name: "integration(incoming): confirm the thread ended" + run: test -n "${{ steps.done.outputs.time }}" + + - name: Celebrate wins + uses: ./ + with: + method: reactions.add + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + timestamp: ${{ steps.blocks.outputs.ts }} + name: "tada" - name: "integration(incoming): post a message via incoming webhook" - id: slackIncoming + id: incoming uses: ./ with: - webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} + webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} webhook-type: incoming-webhook payload: | text: "Incoming webhook test for slack send" @@ -111,7 +177,7 @@ jobs: emoji: true - name: "integration(incoming): confirm a webhook was posted" - run: test -n "${{ steps.slackIncoming.outputs.time }}" + run: test -n "${{ steps.incoming.outputs.time }}" - name: "integration(incoming): reveal contents of the github payload" run: echo $JSON @@ -119,19 +185,19 @@ jobs: JSON: ${{ toJSON(github) }} - name: "integration(incoming): post a message via payload file" - id: slackPayloadFile + id: payload_file uses: ./ with: - payload-file-path: ./.github/resources/payload-notification.json + payload-file-path: ./.github/resources/.actions/payload-notification.json payload-templated: true - webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }} + webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} webhook-type: incoming-webhook env: JOB_STATUS: ${{ job.status }} ATTACHMENT_COLOR: ${{ (job.status == 'success' && 'good') || (job.status == 'failure' && 'danger') || 'warning' }} - name: "integration(incoming): confirm a payload file was posted" - run: test -n "${{ steps.slackPayloadFile.outputs.time }}" + run: test -n "${{ steps.payload_file.outputs.time }}" - name: "chore(health): check up on recent changes to the health score" uses: slackapi/slack-health-score@v0.1.1 diff --git a/.gitignore b/.gitignore index df503e67..3b477d8f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,8 @@ dist # Development dependencies node_modules +# Extra files +.DS_Store + # Testing reminants coverage - -# Additional system files -.DS_Store diff --git a/README.md b/README.md index c0204cdc..f17b872e 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ match the webhook input format expected by Workflow Builder. ##### Providing parsed payload information as strings Provided input values for payload information are sent to the webhook URL after -parsing the workflow: +the job is started: ```yaml - name: Send custom JSON data to Slack workflow @@ -194,24 +194,6 @@ kit, work as the API call might hope: text: "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}" ``` -##### Uploading a file - -Calling web API methods with [`@slack/web-api`][slack-web-api] makes uploading -files just another API call, but with all of the advantages of -[`files.uploadV2`][files.uploadV2]: - -```yaml -- name: Share a file to that channel - uses: slackapi/slack-github-action@v2-development - with: - method: files.uploadV2 - payload: | - channel: ${{ secrets.SLACK_CHANNEL_ID }} - initial_comment: "the results are in!" - file: "results.out" - filename: "results-${{ github.sha }}.out" -``` - ##### Updating a message Following up on a message after it's posted, such as updates for a build status, @@ -279,6 +261,24 @@ the `thread_ts` attribute of the **parent** message in the `payload`: text: "Deployment finished! :rocket:" ``` +##### Uploading a file + +Calling web API methods with [`@slack/web-api`][slack-web-api] makes uploading +files just another API call, but with all of the advantages of +[`files.uploadV2`][files.uploadV2]: + +```yaml +- name: Share a file to that channel + uses: slackapi/slack-github-action@v2-development + with: + method: files.uploadV2 + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + initial_comment: "the results are in!" + file: "results.out" + filename: "results-${{ github.sha }}.out" +``` + ### Technique 3: Slack incoming webhook This technique uses GitHub Actions to post messages to a channel or direct diff --git a/package.json b/package.json index c1cb1b7c..0cb9e3f4 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "scripts": { "build": "ncc build src/index.js --license licenses.txt --source-map", "check": "tsc --noemit --module es2022 --project ./jsconfig.json", + "dev": "act public --eventpath .github/resources/.actions/event.json --secret-file .github/resources/.env --platform ubuntu-latest=node:20-buster", "lint:fix": "biome check --write", "lint": "biome check", - "local": "act public --eventpath .github/workflows/local/event.json --secret-file .github/workflows/local/.env --platform ubuntu-latest=node:20-buster", "test": "c8 mocha test/*.spec.js" }, "repository": { From 19c1ee58804fa4661e4fbed3a97084202fb6de57 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 1 Oct 2024 02:03:11 -0700 Subject: [PATCH 053/214] ci: fix inputs that didnt match changes made in the last commit --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e63495e6..2268f662 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -146,7 +146,7 @@ jobs: payload: | channel: ${{ steps.blocks.outputs.channel_id }} text: "Finished at `${{ steps.finished.outputs.time }}`" - thread_ts: ${{ steps.stats.outputs.thread_ts }} + thread_ts: "${{ steps.stats.outputs.thread_ts }}" - name: "integration(incoming): confirm the thread ended" run: test -n "${{ steps.done.outputs.time }}" @@ -188,7 +188,7 @@ jobs: id: payload_file uses: ./ with: - payload-file-path: ./.github/resources/.actions/payload-notification.json + payload-file-path: ./.github/resources/.actions/incoming-webhook.json payload-templated: true webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} webhook-type: incoming-webhook From ecb8404a623129361d9591b5fc1cec9d6fd8ca8b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 1 Oct 2024 02:05:47 -0700 Subject: [PATCH 054/214] ci: fix the path to slack resources to be in slack paths --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2268f662..3fc1d0d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -188,7 +188,7 @@ jobs: id: payload_file uses: ./ with: - payload-file-path: ./.github/resources/.actions/incoming-webhook.json + payload-file-path: ./.github/resources/.slack/incoming-webhook.json payload-templated: true webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} webhook-type: incoming-webhook From 0777d30a2494d8a2729083be405025377c82d1eb Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 1 Oct 2024 02:08:47 -0700 Subject: [PATCH 055/214] ci: fix the gathered thread ts to be from another threaded message --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3fc1d0d6..c477b072 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -146,7 +146,7 @@ jobs: payload: | channel: ${{ steps.blocks.outputs.channel_id }} text: "Finished at `${{ steps.finished.outputs.time }}`" - thread_ts: "${{ steps.stats.outputs.thread_ts }}" + thread_ts: "${{ steps.timer.outputs.thread_ts }}" - name: "integration(incoming): confirm the thread ended" run: test -n "${{ steps.done.outputs.time }}" From 574e5f9d9aa8d8f19de013fe91315578cc67232f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 1 Oct 2024 11:19:46 -0700 Subject: [PATCH 056/214] test: ensure a file can be uploaded from the action too --- .github/workflows/test.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c477b072..3b3f7f7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -148,6 +148,18 @@ jobs: text: "Finished at `${{ steps.finished.outputs.time }}`" thread_ts: "${{ steps.timer.outputs.thread_ts }}" + - name: "integration(method): post a file into a channel" + id: file + uses: ./ + with: + method: files.uploadV2 + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel_id: ${{ secrets.SLACK_CHANNEL_ID }} + initial_comment: ":robot_face: The codes exists here" + file: .github/workflows/test.yml + filename: action.yml + - name: "integration(incoming): confirm the thread ended" run: test -n "${{ steps.done.outputs.time }}" @@ -173,7 +185,7 @@ jobs: - type: section text: type: plain_text - text: "A post by Slack Send GitHub Action. Testing Incoming webhooks" + text: ":link: A message was received via incoming webhook" emoji: true - name: "integration(incoming): confirm a webhook was posted" From a10a04718ab5ed0bfc759c11de1f9508273eb5ab Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 1 Oct 2024 11:22:25 -0700 Subject: [PATCH 057/214] style: format the incoming webhook blocks to read inline --- .github/resources/.slack/incoming-webhook.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/resources/.slack/incoming-webhook.json b/.github/resources/.slack/incoming-webhook.json index ac24f37e..91d5cef6 100644 --- a/.github/resources/.slack/incoming-webhook.json +++ b/.github/resources/.slack/incoming-webhook.json @@ -1,5 +1,5 @@ { - "text": "A GitHub Action Event ${{ github.eventName }} has ${{ env.JOB_STATUS }}", + "text": ":email: A GitHub Action `${{ github.eventName }}` event status is ${{ env.JOB_STATUS }}", "attachments": [ { "color": "${{ env.ATTACHMENT_COLOR }}", From 3abfdb6d3f142d4a07cc628640995a6844e96e89 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 1 Oct 2024 11:29:36 -0700 Subject: [PATCH 058/214] style: write emojis at the start of lines for consistency --- .github/resources/.slack/manifest.json | 2 +- .github/workflows/develop.yml | 6 +++--- .github/workflows/test.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/resources/.slack/manifest.json b/.github/resources/.slack/manifest.json index df7e6c0c..0f8f9322 100644 --- a/.github/resources/.slack/manifest.json +++ b/.github/resources/.slack/manifest.json @@ -62,7 +62,7 @@ "function_id": "slack#/functions/send_message", "inputs": { "channel_id": "{{inputs.channel_id}}", - "message": "{{inputs.repo_name}}(@{{inputs.author}}): `{{inputs.status}}` <{{inputs.event_url}}|Check it out here>!" + "message": ":ship: {{inputs.repo_name}}(@{{inputs.author}}): `{{inputs.status}}` <{{inputs.event_url}}|Check it out here>!" } } ] diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 7222c8fe..0c1953df 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -42,7 +42,7 @@ jobs: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - text: "Deployment started :eyes:" + text: ":eyes: Testing started..." attachments: - color: "dbab09" fields: @@ -70,7 +70,7 @@ jobs: payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} ts: "${{ steps.slack.outputs.ts }}" - text: "Deployment finished :tada:" + text: ":microscope: Testing finished!" attachments: - color: "28a745" fields: @@ -94,7 +94,7 @@ jobs: payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} timestamp: ${{ steps.slack.outputs.ts }} - name: "ship" + name: "eye-in-speech-bubble" - name: Upload this workflow file id: file uses: ./ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b3f7f7c..203b8719 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,7 @@ jobs: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - text: "Event received :eyes:" + text: ":eyes: Event received..." attachments: - color: "dbab09" fields: @@ -129,7 +129,7 @@ jobs: payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} ts: ${{ steps.blocks.outputs.ts }} - text: "Event processed! :gear:" + text: ":gear: Event processed!" attachments: - color: "28a745" fields: From 7165edc47555718af66d0d78af9d6ac2ce8d8188 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 2 Oct 2024 11:44:30 -0700 Subject: [PATCH 059/214] ci: react to with a method before testing incoming message --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 203b8719..34d029d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -160,10 +160,7 @@ jobs: file: .github/workflows/test.yml filename: action.yml - - name: "integration(incoming): confirm the thread ended" - run: test -n "${{ steps.done.outputs.time }}" - - - name: Celebrate wins + - name: "integration(method): react to the completed update message" uses: ./ with: method: reactions.add @@ -173,6 +170,9 @@ jobs: timestamp: ${{ steps.blocks.outputs.ts }} name: "tada" + - name: "integration(incoming): confirm the thread ended" + run: test -n "${{ steps.done.outputs.time }}" + - name: "integration(incoming): post a message via incoming webhook" id: incoming uses: ./ From 504fb3149ac36717d6c42da441c00fc21d55a29c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 2 Oct 2024 11:45:34 -0700 Subject: [PATCH 060/214] fix: use the config input proxy to set the webhook proxy --- src/webhook.js | 17 ++++++------ test/webhook.spec.js | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/webhook.js b/src/webhook.js index 26c6c2fd..1fbdbfca 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -37,35 +37,34 @@ export default class Webhook { * @see {@link https://github.com/slackapi/slack-github-action/pull/132} */ proxies(config) { + const { webhook, proxy } = config.inputs; try { - if (!config.inputs.webhook) { + if (!webhook) { throw new Error("No webhook was provided to proxy to"); } - const httpsProxy = - process.env.HTTPS_PROXY || process.env.https_proxy || ""; - if (!httpsProxy) { + if (!proxy) { return undefined; } - if (new URL(config.inputs.webhook).protocol !== "https:") { + if (new URL(webhook).protocol !== "https:") { config.core.debug( "The webhook destination is not HTTPS so skipping the HTTPS proxy", ); return undefined; } - switch (new URL(httpsProxy).protocol) { + switch (new URL(proxy).protocol) { case "https:": return { - httpsAgent: new HttpsProxyAgent(httpsProxy), + httpsAgent: new HttpsProxyAgent(proxy), }; case "http:": return { - httpsAgent: new HttpsProxyAgent(httpsProxy), + httpsAgent: new HttpsProxyAgent(proxy), proxy: false, }; } } catch (err) { config.core.warning( - "Failed to configure HTTPS proxy agent for HTTP proxy so using the default axios configuration.", + "Failed to configure the HTTPS proxy agent so using default configurations.", ); console.error(err); } diff --git a/test/webhook.spec.js b/test/webhook.spec.js index c8686929..a2c8d853 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -1,4 +1,6 @@ import { assert } from "chai"; +import { HttpsProxyAgent } from "https-proxy-agent"; +import Config from "../src/config.js"; import send from "../src/send.js"; import Webhook from "../src/webhook.js"; import { mocks } from "./index.spec.js"; @@ -63,6 +65,66 @@ describe("webhook", () => { }); }); + describe("proxies", () => { + it("skips proxying an http webhook url altogether", async () => { + mocks.core.getInput.withArgs("webhook").returns("http://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("proxy").returns("https://example.com"); + const config = new Config(mocks.core); + const webhook = new Webhook(); + const request = webhook.proxies(config); + assert.isUndefined(request); + }); + + it("sets up the proxy agent for the provided https proxy", async () => { + const proxy = "https://example.com"; + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("proxy").returns(proxy); + const config = new Config(mocks.core); + const webhook = new Webhook(); + const { httpsAgent, proxy: proxying } = webhook.proxies(config); + assert.deepEqual(httpsAgent.proxy, new URL(proxy)); + assert.isNotFalse(proxying); + }); + + it("sets up the agent without proxy for http proxies", async () => { + const proxy = "http://example.com"; + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("proxy").returns(proxy); + const config = new Config(mocks.core); + const webhook = new Webhook(); + const { httpsAgent, proxy: proxying } = webhook.proxies(config); + assert.deepEqual(httpsAgent.proxy, new URL(proxy)); + assert.isFalse(proxying); + }); + + it("fails to configure proxies with an invalid proxied url", async () => { + const proxy = "https://"; + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("proxy").returns(proxy); + try { + const config = new Config(mocks.core); + const webhook = new Webhook(); + webhook.proxies(config); + assert.fail("An invalid proxy URL was not thrown as error!"); + } catch { + assert.include( + mocks.core.warning.lastCall.firstArg, + "Failed to configure the HTTPS proxy agent so using default configurations.", + ); + } + }); + }); + describe("retries", () => { it("uses the configured retries in requests", async () => { mocks.core.getInput From 72d552d4e74f83b0972770f4e927181fc591ed91 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 2 Oct 2024 11:46:10 -0700 Subject: [PATCH 061/214] test: remove console logs from output when test succeeds --- .mocharc.json | 3 ++- package-lock.json | 21 +++++++++++++++++++++ package.json | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.mocharc.json b/.mocharc.json index 67b41986..ce5d6526 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -7,7 +7,8 @@ "axios-retry", "flat", "https-proxy-agent", - "markup-js" + "markup-js", + "mocha-suppress-logs" ], "recursive": true, "timeout": 3000 diff --git a/package-lock.json b/package-lock.json index 35b83e22..f38e063e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "c8": "^10.1.2", "chai": "^5.1.1", "mocha": "^10.7.3", + "mocha-suppress-logs": "^0.5.1", "sinon": "^19.0.2", "typescript": "^5.5.3" }, @@ -978,6 +979,16 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1665,6 +1676,16 @@ "node": ">= 14.0.0" } }, + "node_modules/mocha-suppress-logs": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mocha-suppress-logs/-/mocha-suppress-logs-0.5.1.tgz", + "integrity": "sha512-f4BhMiCABgCt3tlXiOcRydWreNCkfvgXgNL2ZclfXPdLNcY7jfyNy3Oi5wwPuxx++UyuNiIx3F7orvckAfrKzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2" + } + }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 6d716f48..9815f7dd 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "c8": "^10.1.2", "chai": "^5.1.1", "mocha": "^10.7.3", + "mocha-suppress-logs": "^0.5.1", "sinon": "^19.0.2", "typescript": "^5.5.3" } From 84c034ad1039b0d8b2b558aac4aaeb7521e6373f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 2 Oct 2024 12:48:02 -0700 Subject: [PATCH 062/214] fix: include proxied https urls with the web client methods --- src/client.js | 10 +++++----- test/client.spec.js | 34 ++++++++++++++++++++++++++++++++++ test/webhook.spec.js | 1 - 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/client.js b/src/client.js index f9e0a844..6f67c19b 100644 --- a/src/client.js +++ b/src/client.js @@ -82,21 +82,21 @@ export default class Client { * @see {@link https://github.com/slackapi/slack-github-action/pull/205} */ proxies(config) { + const proxy = config.inputs.proxy; try { - const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; - if (!httpsProxy) { + if (!proxy) { return undefined; } return { - httpsAgent: new HttpsProxyAgent(httpsProxy), + httpsAgent: new HttpsProxyAgent(proxy), }; } catch (err) { config.core.warning( - "Failed to configure the HTTPS proxy agent so using the default axios configuration.", + "Failed to configure the HTTPS proxy agent so using default configurations.", ); console.error(err); - return undefined; } + return undefined; } /** diff --git a/test/client.spec.js b/test/client.spec.js index 63e97f22..e72be30d 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -1,4 +1,6 @@ import { assert } from "chai"; +import Client from "../src/client.js"; +import Config from "../src/config.js"; import send from "../src/send.js"; import { mocks } from "./index.spec.js"; @@ -175,4 +177,36 @@ describe("client", () => { } }); }); + + describe("proxies", () => { + it("sets up the proxy agent for the provided https proxy", async () => { + const proxy = "https://example.com"; + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("proxy").returns(proxy); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + const config = new Config(mocks.core); + const client = new Client(); + const { httpsAgent, proxy: proxying } = client.proxies(config); + assert.deepEqual(httpsAgent.proxy, new URL(proxy)); + assert.isNotFalse(proxying); + }); + + it("fails to configure proxies with an invalid proxied url", async () => { + const proxy = "https://"; + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("proxy").returns(proxy); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + try { + const config = new Config(mocks.core); + const client = new Client(); + client.proxies(config); + assert.fail("An invalid proxy URL was not thrown as error!"); + } catch { + assert.include( + mocks.core.warning.lastCall.firstArg, + "Failed to configure the HTTPS proxy agent so using default configurations.", + ); + } + }); + }); }); diff --git a/test/webhook.spec.js b/test/webhook.spec.js index a2c8d853..78c0c8e9 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -1,5 +1,4 @@ import { assert } from "chai"; -import { HttpsProxyAgent } from "https-proxy-agent"; import Config from "../src/config.js"; import send from "../src/send.js"; import Webhook from "../src/webhook.js"; From 67471c1c98828493c3510cc794ef464767ea6d26 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 2 Oct 2024 14:57:01 -0700 Subject: [PATCH 063/214] test: confirm retries happen at expected intervals for the client --- test/client.spec.js | 54 ++++++++++++++++++++++++++++++++++++++++++++ test/webhook.spec.js | 12 +++++----- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/test/client.spec.js b/test/client.spec.js index e72be30d..183e0de2 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -1,3 +1,4 @@ +import webapi from "@slack/web-api"; import { assert } from "chai"; import Client from "../src/client.js"; import Config from "../src/config.js"; @@ -209,4 +210,57 @@ describe("client", () => { } }); }); + + describe("retries", () => { + it("uses a default of five retries in requests", async () => { + const client = new Client(); + const result = client.retries(); + assert.equal( + result.retries, + webapi.retryPolicies.fiveRetriesInFiveMinutes.retries, + ); + }); + + it('does not attempt retries when "0" is set', async () => { + const webhook = new Client(); + const result = webhook.retries("0"); + assert.equal(result.retries, 0); + }); + + it('attempts a default amount of "5" retries', async () => { + const webhook = new Client(); + const result = webhook.retries("5"); + assert.equal( + result.retries, + webapi.retryPolicies.fiveRetriesInFiveMinutes.retries, + ); + assert.equal( + result.factor, + webapi.retryPolicies.fiveRetriesInFiveMinutes.factor, + ); + }); + + it('attempts "10" retries in around "30" minutes', async () => { + const webhook = new Client(); + const result = webhook.retries("10"); + assert.equal( + result.retries, + webapi.retryPolicies.tenRetriesInAboutThirtyMinutes.retries, + ); + assert.equal( + result.factor, + webapi.retryPolicies.tenRetriesInAboutThirtyMinutes.factor, + ); + }); + + it('attempts a "rapid" burst of "12" retries in seconds', async () => { + const webhook = new Client(); + const result = webhook.retries("RAPID"); + assert.equal( + result.retries, + webapi.retryPolicies.rapidRetryPolicy.retries, + ); + assert.equal(result.factor, webapi.retryPolicies.rapidRetryPolicy.factor); + }); + }); }); diff --git a/test/webhook.spec.js b/test/webhook.spec.js index 78c0c8e9..b678160d 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -125,16 +125,16 @@ describe("webhook", () => { }); describe("retries", () => { - it("uses the configured retries in requests", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); + it("uses a default of five retries in requests", async () => { + const webhook = new Webhook(); + const result = webhook.retries(); + assert.equal(result.retries, 5); }); it('does not attempt retries when "0" is set', async () => { const webhook = new Webhook(); - const result = webhook.retries("0").retries; - assert.equal(result, 0); + const result = webhook.retries("0"); + assert.equal(result.retries, 0); }); it('attempts a default amount of "5" retries', async () => { From c7112342a3e96b8b8ac2706bd6e26bbdb575f26f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 2 Oct 2024 15:12:09 -0700 Subject: [PATCH 064/214] test: ensure errors happen during invalid config setup --- test/config.spec.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/config.spec.js b/test/config.spec.js index 9c52b0c5..80a08b03 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -32,6 +32,39 @@ describe("config", () => { assert.equal(config.inputs.retries, config.Retries.ZERO); assert.equal(config.inputs.token, "xoxb-example"); }); + + it("errors when both the token and webhook is provided", async () => { + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("webhook").returns("https://example.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + try { + new Config(mocks.core); + assert.fail("Failed to error when invalid inputs are provided"); + } catch (err) { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Invalid input! Either the token or webhook is required - not both.", + ); + assert.isTrue(mocks.core.setSecret.withArgs("xoxb-example").called); + assert.isTrue( + mocks.core.setSecret.withArgs("https://example.com").called, + ); + } + }); + + it("errors if the webhook type does not match techniques", async () => { + mocks.core.getInput.withArgs("webhook").returns("https://example.com"); + mocks.core.getInput.withArgs("webhook-type").returns("post"); + try { + new Config(mocks.core); + assert.fail("Failed to error when invalid inputs are provided"); + } catch (err) { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Invalid input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + ); + } + }); }); describe("secrets", async () => { From 4c6260b3e724f7018778dcbe5c2ba57846eba1d9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 01:04:43 -0700 Subject: [PATCH 065/214] fix: templatize payload content from all input sources --- src/config.js | 2 +- src/content.js | 88 +++++++++------- test/content.spec.js | 245 ++++++++++++++++++++++++++++--------------- test/index.spec.js | 4 +- test/send.spec.js | 2 +- test/webhook.spec.js | 2 +- 6 files changed, 219 insertions(+), 124 deletions(-) diff --git a/src/config.js b/src/config.js index 4aca8927..4894da9f 100644 --- a/src/config.js +++ b/src/config.js @@ -112,7 +112,7 @@ export default class Config { }; this.validate(); core.debug(`Gathered action inputs: ${JSON.stringify(this.inputs)}`); - this.content = new Content(this); + this.content = new Content().get(this); core.debug(`Parsed request content: ${JSON.stringify(this.content)}`); } diff --git a/src/content.js b/src/content.js index 28a91131..89e87c58 100644 --- a/src/content.js +++ b/src/content.js @@ -13,14 +13,11 @@ import SlackError from "./errors.js"; */ export default class Content { /** - * @type {Record} - */ - values; - - /** + * Gather content from the provided payload or payload file path with parsings. * @param {Config} config + * @returns {this} - An instance of this Content class. */ - constructor(config) { + get(config) { switch (true) { case !!config.inputs.payload && !!config.inputs.payloadFilePath: throw new SlackError( @@ -48,11 +45,12 @@ export default class Content { this.values[key] = `${this.values[key]}`; } } + return this; } /** * Format request content from payload values for use in the request. - * @param {Config} config - GitHub Actions core utilities. + * @param {Config} config * @throws if the input payload or payload file path is invalid JSON. * @returns {Content} - the parsed JSON payload to use in requests. */ @@ -64,9 +62,12 @@ export default class Content { ); } try { - const content = yaml.load(config.inputs.payload, { - schema: yaml.JSON_SCHEMA, - }); + const input = this.templatize(config, config.inputs.payload); + const content = /** @type {Content} */ ( + yaml.load(input, { + schema: yaml.JSON_SCHEMA, + }) + ); return /** @type {Content} */ (content); } catch (error) { if (error instanceof Error) { @@ -92,16 +93,17 @@ export default class Content { if (error instanceof Error) { config.core.error(error); } + console.error(error); throw new SlackError( config.core, - "Invalid input! Failed to parse the JSON content of the payload", + "Invalid input! Failed to parse contents of the provided payload", ); } } /** * Format request content from the payload file path for use in the request. - * @param {Config} config - GitHub Actions core utilities. + * @param {Config} config * @throws if the input payload or payload file path is invalid JSON. * @returns {Content} - the parsed JSON payload to use in requests. */ @@ -113,42 +115,54 @@ export default class Content { ); } try { - const content = fs.readFileSync( + const input = fs.readFileSync( path.resolve(config.inputs.payloadFilePath), "utf-8", ); - if (!config.inputs.payloadTemplated) { - if ( - config.inputs.payloadFilePath.endsWith("yaml") || - config.inputs.payloadFilePath.endsWith("yml") - ) { - const load = yaml.load(content, { - schema: yaml.JSON_SCHEMA, - }); - return /** @type {Content} */ (load); - } - if (config.inputs.payloadFilePath.endsWith("json")) { - return JSON.parse(content); - } - throw new SlackError( - config.core, - `Failed to parse file extension ${config.inputs.payloadFilePath}`, - ); + const content = this.templatize(config, input); + if ( + config.inputs.payloadFilePath.endsWith("yaml") || + config.inputs.payloadFilePath.endsWith("yml") + ) { + const load = yaml.load(content, { + schema: yaml.JSON_SCHEMA, + }); + return /** @type {Content} */ (load); + } + if (config.inputs.payloadFilePath.endsWith("json")) { + return JSON.parse(content); } - const template = content.replace(/\$\{\{/g, "{{"); // swap ${{ for {{ - const context = { - env: process.env, - github: github.context, - }; - return JSON.parse(markup.up(template, context)); + throw new SlackError( + config.core, + `Failed to parse file extension ${config.inputs.payloadFilePath}`, + ); } catch (error) { if (error instanceof Error) { config.core.error(error); } + console.error(error); throw new SlackError( config.core, - "Invalid input! Failed to parse the JSON content of the payload file", + "Invalid input! Failed to parse contents of the provided payload file", ); } } + + /** + * Replace templated variables in the provided content if requested. + * @param {Config} config + * @param {string} input - The initial value of the content. + * @returns {string} Content with templatized variables replaced. + */ + templatize(config, input) { + if (!config.inputs.payloadTemplated) { + return input; + } + const template = input.replace(/\$\{\{/g, "{{"); // swap ${{ for {{ + const context = { + env: process.env, + github: github.context, + }; + return markup.up(template, context); + } } diff --git a/test/content.spec.js b/test/content.spec.js index f8f52f61..a7c526a1 100644 --- a/test/content.spec.js +++ b/test/content.spec.js @@ -1,6 +1,8 @@ import path from "node:path"; +import core from "@actions/core"; import { assert } from "chai"; import Config from "../src/config.js"; +import Content from "../src/content.js"; import send from "../src/send.js"; import { mocks } from "./index.spec.js"; @@ -8,16 +10,63 @@ import { mocks } from "./index.spec.js"; * Confirm values from the action input or environment variables are gathered */ describe("content", () => { - afterEach(() => { + beforeEach(() => { mocks.reset(); }); - describe("success", () => { + describe("get", () => { + it("errors if both a payload and file path are provided", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); + mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + try { + await send(mocks.core); + assert.fail("Failed to throw for invalid input"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Invalid input! Just the payload or payload file path is required.", + ); + } + }); + }); + + describe("content", async () => { + it("accepts and parses yaml as a valid payload format", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(` + message: "this is wrapped" + channel: "C0123456789" + `); + const config = new Config(mocks.core); + const expected = { + message: "this is wrapped", + channel: "C0123456789", + }; + assert.deepEqual(config.content.values, expected); + }); + + it("accepts and parses complete json as payload input", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(`{ + "message": "this is wrapped", + "channel": "C0123456789" + } + `); + const config = new Config(mocks.core); + const expected = { + message: "this is wrapped", + channel: "C0123456789", + }; + assert.deepEqual(config.content.values, expected); + }); + it("wraps incomplete payload in braces for valid JSON", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(` "message": "LGTM!", "channel": "C0123456789", @@ -46,29 +95,47 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); - it("accepts and parses complete json as payload input", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); - mocks.core.getInput.withArgs("payload").returns(`{ - "message": "this is wrapped", - "channel": "C0123456789" - } - `); - const config = new Config(mocks.core); - const expected = { - message: "this is wrapped", - channel: "C0123456789", + it("fails if no payload content is provided as input", async () => { + /** + * @type {Config} + */ + const config = { + core: core, + inputs: { + payloadFilePath: "unknown.json", + }, }; - assert.deepEqual(config.content.values, expected); + try { + new Content().getContentPayload(config); + assert.fail("Failed to throw for missing payload content"); + } catch (err) { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Invalid input! No payload content found", + ); + } }); + it("fails if the provided input payload is invalid JSON", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns("{"); + try { + await send(mocks.core); + assert.fail("Failed to throw for invalid JSON"); + } catch { + assert.include( + mocks.core.setFailed.lastCall.firstArg.toString(), + "Invalid input! Failed to parse contents of the provided payload", + ); + } + }); + }); + + describe("file", async () => { it("parses JSON from a known file without replacements", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); mocks.fs.readFileSync .withArgs(path.resolve("example.json"), "utf-8") @@ -84,99 +151,111 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); - it("replaces templated variables in the payload file", async () => { - mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); - mocks.core.getBooleanInput.withArgs("payload-templated").returns(true); - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + it("parses YAML from a known file without replacements", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload-file-path").returns("example.yml"); mocks.fs.readFileSync - .withArgs(path.resolve("example.json"), "utf-8") - .returns(`{ - "text": "running job #\${{ env.MOCK_JOB }} on: \${{ github.apiUrl }}" - }`); - process.env.MOCK_JOB = "12"; + .withArgs(path.resolve("example.yml"), "utf-8") + .returns(` + message: "drink water" + channel: "C6H12O6H2O2" + `); const config = new Config(mocks.core); - process.env.MOCK_JOB = undefined; const expected = { - text: "running job #12 on: https://api.github.com", + message: "drink water", + channel: "C6H12O6H2O2", }; assert.deepEqual(config.content.values, expected); }); - it("flattens nested payloads if a delimiter is provided", async () => { - mocks.core.getInput.withArgs("payload-delimiter").returns("_"); - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); - mocks.core.getInput.withArgs("payload").returns(` - "apples": "tree", - "bananas": { - "truthiness": true - } - `); + it("parses YAML from the extended file extension", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload-file-path").returns("example.yaml"); + mocks.fs.readFileSync + .withArgs(path.resolve("example.yaml"), "utf-8") + .returns(` + message: "drink coffee" + channel: "C0FFEEEEEEEE" + `); const config = new Config(mocks.core); const expected = { - apples: "tree", - bananas_truthiness: "true", + message: "drink coffee", + channel: "C0FFEEEEEEEE", }; assert.deepEqual(config.content.values, expected); }); - }); - describe("failure", () => { - it("errors if both a payload and file path are provided", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); - mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); - mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + it("fails if no payload file is provided in the input", async () => { + /** + * @type {Config} + */ + const config = { + core: core, + inputs: { + payload: "LGTM", + }, + }; try { - await send(mocks.core); - assert.fail("Failed to throw for invalid input"); - } catch { + new Content().getContentPayloadFilePath(config); + assert.fail("Failed to throw for the wrong payload type"); + } catch (err) { assert.include( mocks.core.setFailed.lastCall.firstArg, - "Invalid input! Just the payload or payload file path is required.", + "Invalid input! No payload found for content", ); } }); - it("fails if the provided input payload is invalid JSON", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); - mocks.core.getInput.withArgs("payload").returns("{"); + it("fails to parse a file path that does not exist", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload-file-path").returns("unknown.json"); try { await send(mocks.core); - assert.fail("Failed to throw for invalid JSON"); - } catch { + assert.fail("Failed to throw for nonexistent files"); + } catch (err) { assert.include( mocks.core.setFailed.lastCall.firstArg.toString(), - "Invalid input! Failed to parse the JSON content of the payload", + "Invalid input! Failed to parse contents of the provided payload file", ); } }); - it("fails to parse a file path that does not exist", async () => { - mocks.core.getInput.withArgs("payload-file-path").returns("unknown.json"); - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); + it("fails to parse a file with an unknown extension", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload-file-path").returns("unknown.md"); try { await send(mocks.core); assert.fail("Failed to throw for nonexistent files"); - } catch { + } catch (err) { assert.include( mocks.core.setFailed.lastCall.firstArg.toString(), - "Invalid input! Failed to parse the JSON content of the payload file", + "Invalid input! Failed to parse contents of the provided payload file", ); } }); }); + + describe("flatten", () => { + it("flattens nested payloads if a delimiter is provided", async () => { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(` + "apples": "tree", + "bananas": { + "truthiness": true + } + `); + mocks.core.getInput.withArgs("payload-delimiter").returns("_"); + const config = new Config(mocks.core); + const expected = { + apples: "tree", + bananas_truthiness: "true", + }; + assert.deepEqual(config.content.values, expected); + }); + }); }); diff --git a/test/index.spec.js b/test/index.spec.js index 98cfab63..3138c4a4 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -3,7 +3,6 @@ import core from "@actions/core"; import webapi from "@slack/web-api"; import axios, { AxiosError } from "axios"; import sinon from "sinon"; -import SlackError from "../src/errors.js"; /** * Hello experimenter! These tests are here to confirm that the happy paths keep @@ -59,6 +58,9 @@ export class Mock { this.sandbox.reset(); this.api.resetHistory(); this.axios.post.resetHistory(); + this.core.getInput.reset(); + this.core.getInput.withArgs("errors").returns("false"); + this.core.getInput.withArgs("retries").returns("5"); } } diff --git a/test/send.spec.js b/test/send.spec.js index e157efd0..beee34d2 100644 --- a/test/send.spec.js +++ b/test/send.spec.js @@ -11,7 +11,7 @@ import { mocks } from "./index.spec.js"; * specifications. */ describe("send", () => { - afterEach(() => { + beforeEach(() => { mocks.reset(); }); diff --git a/test/webhook.spec.js b/test/webhook.spec.js index b678160d..144b7d5f 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -5,7 +5,7 @@ import Webhook from "../src/webhook.js"; import { mocks } from "./index.spec.js"; describe("webhook", () => { - afterEach(() => { + beforeEach(() => { mocks.reset(); }); From 1b4979ea857e9724d278d8d3d4952f1d18c197d8 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 01:11:01 -0700 Subject: [PATCH 066/214] test: setup the same input before each test before each test --- test/content.spec.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/test/content.spec.js b/test/content.spec.js index a7c526a1..f4f74142 100644 --- a/test/content.spec.js +++ b/test/content.spec.js @@ -12,12 +12,12 @@ import { mocks } from "./index.spec.js"; describe("content", () => { beforeEach(() => { mocks.reset(); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); }); describe("get", () => { it("errors if both a payload and file path are provided", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); try { @@ -34,8 +34,6 @@ describe("content", () => { describe("content", async () => { it("accepts and parses yaml as a valid payload format", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(` message: "this is wrapped" channel: "C0123456789" @@ -49,8 +47,6 @@ describe("content", () => { }); it("accepts and parses complete json as payload input", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(`{ "message": "this is wrapped", "channel": "C0123456789" @@ -65,8 +61,6 @@ describe("content", () => { }); it("wraps incomplete payload in braces for valid JSON", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(` "message": "LGTM!", "channel": "C0123456789", @@ -117,8 +111,6 @@ describe("content", () => { }); it("fails if the provided input payload is invalid JSON", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns("{"); try { await send(mocks.core); @@ -134,8 +126,6 @@ describe("content", () => { describe("file", async () => { it("parses JSON from a known file without replacements", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); mocks.fs.readFileSync .withArgs(path.resolve("example.json"), "utf-8") @@ -152,8 +142,6 @@ describe("content", () => { }); it("parses YAML from a known file without replacements", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload-file-path").returns("example.yml"); mocks.fs.readFileSync .withArgs(path.resolve("example.yml"), "utf-8") @@ -170,8 +158,6 @@ describe("content", () => { }); it("parses YAML from the extended file extension", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload-file-path").returns("example.yaml"); mocks.fs.readFileSync .withArgs(path.resolve("example.yaml"), "utf-8") @@ -209,8 +195,6 @@ describe("content", () => { }); it("fails to parse a file path that does not exist", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload-file-path").returns("unknown.json"); try { await send(mocks.core); @@ -224,8 +208,6 @@ describe("content", () => { }); it("fails to parse a file with an unknown extension", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload-file-path").returns("unknown.md"); try { await send(mocks.core); @@ -241,8 +223,6 @@ describe("content", () => { describe("flatten", () => { it("flattens nested payloads if a delimiter is provided", async () => { - mocks.core.getInput.withArgs("method").returns("chat.postMessage"); - mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(` "apples": "tree", "bananas": { From 48d4ccffedb459ce6c307dbbda0ab0fb8f0ef6ef Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 01:33:13 -0700 Subject: [PATCH 067/214] test: match naming of spec its for more reasoning --- test/content.spec.js | 105 ++++++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/test/content.spec.js b/test/content.spec.js index f4f74142..b990a597 100644 --- a/test/content.spec.js +++ b/test/content.spec.js @@ -16,6 +16,24 @@ describe("content", () => { mocks.core.getInput.withArgs("token").returns("xoxb-example"); }); + describe("flatten", () => { + it("flattens nested payloads provided with delimiter", async () => { + mocks.core.getInput.withArgs("payload").returns(` + "apples": "tree", + "bananas": { + "truthiness": true + } + `); + mocks.core.getInput.withArgs("payload-delimiter").returns("_"); + const config = new Config(mocks.core); + const expected = { + apples: "tree", + bananas_truthiness: "true", + }; + assert.deepEqual(config.content.values, expected); + }); + }); + describe("get", () => { it("errors if both a payload and file path are provided", async () => { mocks.core.getInput.withArgs("payload").returns(`"message"="hello"`); @@ -32,8 +50,8 @@ describe("content", () => { }); }); - describe("content", async () => { - it("accepts and parses yaml as a valid payload format", async () => { + describe("payload", async () => { + it("parses complete YAML from the input payload", async () => { mocks.core.getInput.withArgs("payload").returns(` message: "this is wrapped" channel: "C0123456789" @@ -46,7 +64,7 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); - it("accepts and parses complete json as payload input", async () => { + it("parses complete JSON from the input payload", async () => { mocks.core.getInput.withArgs("payload").returns(`{ "message": "this is wrapped", "channel": "C0123456789" @@ -60,7 +78,20 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); - it("wraps incomplete payload in braces for valid JSON", async () => { + it("trims last comma JSON with the input payload", async () => { + mocks.core.getInput.withArgs("payload").returns(` + "message": "LGTM!", + "channel": "C0123456789", + `); + const config = new Config(mocks.core); + const expected = { + message: "LGTM!", + channel: "C0123456789", + }; + assert.deepEqual(config.content.values, expected); + }); + + it("wraps incomplete JSON from the input payload", async () => { mocks.core.getInput.withArgs("payload").returns(` "message": "LGTM!", "channel": "C0123456789", @@ -89,7 +120,7 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); - it("fails if no payload content is provided as input", async () => { + it("fails if no payload content is provided in input", async () => { /** * @type {Config} */ @@ -110,7 +141,7 @@ describe("content", () => { } }); - it("fails if the provided input payload is invalid JSON", async () => { + it("fails if invalid JSON exists in the input payload", async () => { mocks.core.getInput.withArgs("payload").returns("{"); try { await send(mocks.core); @@ -124,15 +155,15 @@ describe("content", () => { }); }); - describe("file", async () => { - it("parses JSON from a known file without replacements", async () => { - mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + describe("payload file", async () => { + it("parses complete YAML from the input payload file", async () => { + mocks.core.getInput.withArgs("payload-file-path").returns("example.yaml"); mocks.fs.readFileSync - .withArgs(path.resolve("example.json"), "utf-8") - .returns(`{ - "message": "drink water", - "channel": "C6H12O6H2O2" - }`); + .withArgs(path.resolve("example.yaml"), "utf-8") + .returns(` + message: "drink water" + channel: "C6H12O6H2O2" + `); const config = new Config(mocks.core); const expected = { message: "drink water", @@ -141,34 +172,34 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); - it("parses YAML from a known file without replacements", async () => { + it("parses complete YML from the input payload file", async () => { mocks.core.getInput.withArgs("payload-file-path").returns("example.yml"); mocks.fs.readFileSync .withArgs(path.resolve("example.yml"), "utf-8") .returns(` - message: "drink water" - channel: "C6H12O6H2O2" + message: "drink coffee" + channel: "C0FFEEEEEEEE" `); const config = new Config(mocks.core); const expected = { - message: "drink water", - channel: "C6H12O6H2O2", + message: "drink coffee", + channel: "C0FFEEEEEEEE", }; assert.deepEqual(config.content.values, expected); }); - it("parses YAML from the extended file extension", async () => { - mocks.core.getInput.withArgs("payload-file-path").returns("example.yaml"); + it("parses complete JSON from the input payload file", async () => { + mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); mocks.fs.readFileSync - .withArgs(path.resolve("example.yaml"), "utf-8") - .returns(` - message: "drink coffee" - channel: "C0FFEEEEEEEE" - `); + .withArgs(path.resolve("example.json"), "utf-8") + .returns(`{ + "message": "drink water", + "channel": "C6H12O6H2O2" + }`); const config = new Config(mocks.core); const expected = { - message: "drink coffee", - channel: "C0FFEEEEEEEE", + message: "drink water", + channel: "C6H12O6H2O2", }; assert.deepEqual(config.content.values, expected); }); @@ -220,22 +251,4 @@ describe("content", () => { } }); }); - - describe("flatten", () => { - it("flattens nested payloads if a delimiter is provided", async () => { - mocks.core.getInput.withArgs("payload").returns(` - "apples": "tree", - "bananas": { - "truthiness": true - } - `); - mocks.core.getInput.withArgs("payload-delimiter").returns("_"); - const config = new Config(mocks.core); - const expected = { - apples: "tree", - bananas_truthiness: "true", - }; - assert.deepEqual(config.content.values, expected); - }); - }); }); From 16ea7d7d211321cc68fb86e98b2f2acf18dd8ee1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 02:11:07 -0700 Subject: [PATCH 068/214] test: check for replacements of templatized variables --- test/content.spec.js | 64 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/test/content.spec.js b/test/content.spec.js index b990a597..cbda9e68 100644 --- a/test/content.spec.js +++ b/test/content.spec.js @@ -78,6 +78,35 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); + it("templatizes variables with matching variables", async () => { + mocks.core.getInput + .withArgs("payload") + .returns("message: Served ${{ env.NUMBER }} from ${{ github.apiUrl }}"); + mocks.core.getBooleanInput.withArgs("payload-templated").returns(true); + process.env.NUMBER = 12; + const config = new Config(mocks.core); + process.env.NUMBER = undefined; + const expected = { + message: "Served 12 from https://api.github.com", + }; + assert.deepEqual(config.content.values, expected); + }); + + /** + * @see {@link https://github.com/slackapi/slack-github-action/issues/203} + */ + it("templatizes variables with missing variables", async () => { + mocks.core.getInput + .withArgs("payload") + .returns("message: What makes ${{ env.TREASURE }} a secret"); + mocks.core.getBooleanInput.withArgs("payload-templated").returns(true); + const config = new Config(mocks.core); + const expected = { + message: "What makes ??? a secret", + }; + assert.deepEqual(config.content.values, expected); + }); + it("trims last comma JSON with the input payload", async () => { mocks.core.getInput.withArgs("payload").returns(` "message": "LGTM!", @@ -204,6 +233,41 @@ describe("content", () => { assert.deepEqual(config.content.values, expected); }); + it("templatizes variables with matching variables", async () => { + mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + mocks.fs.readFileSync + .withArgs(path.resolve("example.json"), "utf-8") + .returns(`{ + "message": "Served $\{\{ env.NUMBER }} from $\{\{ github.apiUrl }}" + }`); + mocks.core.getBooleanInput.withArgs("payload-templated").returns(true); + process.env.NUMBER = 12; + const config = new Config(mocks.core); + process.env.NUMBER = undefined; + const expected = { + message: "Served 12 from https://api.github.com", + }; + assert.deepEqual(config.content.values, expected); + }); + + /** + * @see {@link https://github.com/slackapi/slack-github-action/issues/203} + */ + it("templatizes variables with missing variables", async () => { + mocks.core.getInput.withArgs("payload-file-path").returns("example.json"); + mocks.fs.readFileSync + .withArgs(path.resolve("example.json"), "utf-8") + .returns(`{ + "message": "What makes $\{\{ env.TREASURE }} a secret" + }`); + mocks.core.getBooleanInput.withArgs("payload-templated").returns(true); + const config = new Config(mocks.core); + const expected = { + message: "What makes ??? a secret", + }; + assert.deepEqual(config.content.values, expected); + }); + it("fails if no payload file is provided in the input", async () => { /** * @type {Config} From 05b1c7cede440975d00ef14cdc574f8d8c3a1d02 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 02:13:36 -0700 Subject: [PATCH 069/214] chore: replace hardcoding types with an upstream alternative --- @types/markup-js.d.ts | 9 --------- package-lock.json | 7 +++++++ package.json | 1 + 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 @types/markup-js.d.ts diff --git a/@types/markup-js.d.ts b/@types/markup-js.d.ts deleted file mode 100644 index 9d7718d0..00000000 --- a/@types/markup-js.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare module "markup-js" { - /** - * @param template - The string with templated values. - * @param context - Values to replace in the template. - * @returns string - The template with replaced context values. - * @link https://github.com/adammark/Markup.js#usage - */ - export function up(template: string, context: object): string; -} diff --git a/package-lock.json b/package-lock.json index f38e063e..b7798c9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@slack/web-api": "^7.5.0", + "@types/markup-js": "^1.5.0", "axios": "^1.7.7", "axios-retry": "^4.5.0", "flat": "^5.0.2", @@ -520,6 +521,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/markup-js": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/markup-js/-/markup-js-1.5.0.tgz", + "integrity": "sha512-yUPklfeB8Sk72gp0D0A/xjAR0QTKwVAY6hr1ORj44uSnqXIp7aEkWFZrAMf7md0PgVIMEAZtiEaNVFGaSyVA+A==", + "license": "MIT" + }, "node_modules/@types/mocha": { "version": "10.0.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", diff --git a/package.json b/package.json index 9815f7dd..d7cdecdb 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@slack/web-api": "^7.5.0", + "@types/markup-js": "^1.5.0", "axios": "^1.7.7", "axios-retry": "^4.5.0", "flat": "^5.0.2", From 8618c4d33bee5a373d031aecc4e0e7eb722024a8 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 02:40:22 -0700 Subject: [PATCH 070/214] test: confirm the expected techniques work as expected --- test/client.spec.js | 31 +++++++++++++++++++++++++------ test/send.spec.js | 45 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/test/client.spec.js b/test/client.spec.js index 183e0de2..ec802724 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -1,3 +1,4 @@ +import core from "@actions/core"; import webapi from "@slack/web-api"; import { assert } from "chai"; import Client from "../src/client.js"; @@ -11,27 +12,45 @@ describe("client", () => { }); describe("inputs", () => { - it("requires a token is provided in inputs", async () => { + it("requires a method is provided in inputs", async () => { + /** + * @type {Config} + */ + const config = { + core: core, + inputs: { + token: "xoxb-example", + }, + }; try { - await send(mocks.core); + await new Client().post(config); assert.fail("Failed to throw for missing input"); } catch { assert.include( mocks.core.setFailed.lastCall.firstArg, - "Missing input! Either a token or webhook is required to take action.", + "No API method was provided for use", ); } }); - it("requires a method is provided in inputs", async () => { + it("requires a token is provided in inputs", async () => { + /** + * @type {Config} + */ + const config = { + core: core, + inputs: { + method: "chat.postMessage", + }, + }; mocks.core.getInput.withArgs("token").returns("xoxb-example-001"); try { - await send(mocks.core); + await new Client().post(config); assert.fail("Failed to throw for missing input"); } catch { assert.include( mocks.core.setFailed.lastCall.firstArg, - "Missing input! A method must be decided to use the token provided.", + "No token was provided to post with", ); } }); diff --git a/test/send.spec.js b/test/send.spec.js index beee34d2..5bc708b6 100644 --- a/test/send.spec.js +++ b/test/send.spec.js @@ -1,3 +1,4 @@ +import { assert } from "chai"; import send from "../src/send.js"; import { mocks } from "./index.spec.js"; @@ -15,10 +16,44 @@ describe("send", () => { mocks.reset(); }); - it("exists and can be called", async () => { - mocks.core.getInput.withArgs("webhook").returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); - mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); - await send(mocks.core); + describe("techniques", async () => { + it("incoming webhook", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); + await send(mocks.core); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "time"); + assert.isAtLeast(mocks.core.setOutput.getCall(0).lastArg, 0); + }); + + it("token", async () => { + mocks.api.resolves({ ok: true }); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); + await send(mocks.core); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify({ ok: true }), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.isAtLeast(mocks.core.setOutput.getCall(2).lastArg, 0); + }); + + it("webhook trigger", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); + mocks.core.getInput.withArgs("payload").returns('"greetings": "hello"'); + await send(mocks.core); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "time"); + assert.isAtLeast(mocks.core.setOutput.getCall(0).lastArg, 0); + }); }); }); From 8066bf93591d01c5605974a474de39076b1949d8 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 02:51:58 -0700 Subject: [PATCH 071/214] test: confirm errors are returned when missing webhook inputs --- src/webhook.js | 5 ++++- test/config.spec.js | 25 +++++++++++++++++++++++++ test/webhook.spec.js | 32 ++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/webhook.js b/src/webhook.js index 1fbdbfca..69ac74a0 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -40,7 +40,10 @@ export default class Webhook { const { webhook, proxy } = config.inputs; try { if (!webhook) { - throw new Error("No webhook was provided to proxy to"); + throw new SlackError( + config.core, + "No webhook was provided to proxy to", + ); } if (!proxy) { return undefined; diff --git a/test/config.spec.js b/test/config.spec.js index 80a08b03..1d2dc77e 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -52,6 +52,31 @@ describe("config", () => { } }); + it("errors if neither the token or webhook is provided", async () => { + try { + new Config(mocks.core); + assert.fail("Failed to error when invalid inputs are provided"); + } catch (err) { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Missing input! Either a token or webhook is required to take action.", + ); + } + }); + + it("errors if a webhook is provided without the type", async () => { + mocks.core.getInput.withArgs("webhook").returns("https://example.com"); + try { + new Config(mocks.core); + assert.fail("Failed to error when invalid inputs are provided"); + } catch (err) { + assert.include( + mocks.core.setFailed.lastCall.firstArg, + "Missing input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + ); + } + }); + it("errors if the webhook type does not match techniques", async () => { mocks.core.getInput.withArgs("webhook").returns("https://example.com"); mocks.core.getInput.withArgs("webhook-type").returns("post"); diff --git a/test/webhook.spec.js b/test/webhook.spec.js index 144b7d5f..336550f2 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -1,3 +1,4 @@ +import core from "@actions/core"; import { assert } from "chai"; import Config from "../src/config.js"; import send from "../src/send.js"; @@ -37,34 +38,45 @@ describe("webhook", () => { describe("failure", () => { it("requires that a webhook is provided in inputs", async () => { + /** + * @type {Config} + */ + const config = { + core: core, + inputs: {}, + }; try { - await send(mocks.core); + await new Webhook().post(config); assert.fail("Failed to throw for missing input"); } catch { assert.include( mocks.core.setFailed.lastCall.firstArg, - "Missing input! Either a token or webhook is required to take action.", + "No webhook was provided to post to", ); } }); + }); - it("requires that a webhook type is provided in input", async () => { - mocks.core.getInput - .withArgs("webhook") - .returns("https://hooks.slack.com"); + describe("proxies", () => { + it("requires a webhook is included in the inputs", async () => { + /** + * @type {Config} + */ + const config = { + core: core, + inputs: {}, + }; try { - await send(mocks.core); + new Webhook().proxies(config); assert.fail("Failed to throw for missing input"); } catch { assert.include( mocks.core.setFailed.lastCall.firstArg, - "Missing input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + "No webhook was provided to proxy to", ); } }); - }); - describe("proxies", () => { it("skips proxying an http webhook url altogether", async () => { mocks.core.getInput.withArgs("webhook").returns("http://hooks.slack.com"); mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); From 103ae854f51574a748c81148c18f5ce2ebeea770 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 10:45:58 -0700 Subject: [PATCH 072/214] fix: use the built distribution files when running this action --- .github/workflows/develop.yml | 2 ++ action.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 0c1953df..5f72a05a 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -13,6 +13,8 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies run: npm install + - name: Package the build + run: npm run build - name: Start a workflow with a webhook trigger id: wfb uses: ./ diff --git a/action.yml b/action.yml index 7fac3ad1..d90a1473 100644 --- a/action.yml +++ b/action.yml @@ -51,4 +51,4 @@ outputs: description: "The timestamp on a message posted using a token" runs: using: "node20" - main: "src/index.js" + main: "dist/index.js" From be375536f8f60381161bda360ed5cbac078764fd Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 10:47:03 -0700 Subject: [PATCH 073/214] docs: make it clear that the unix epoch is returned in time --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index d90a1473..a8473382 100644 --- a/action.yml +++ b/action.yml @@ -38,7 +38,7 @@ inputs: required: false outputs: time: - description: "The epoch time of job completion" + description: "The unix epoch time that the job completed" ok: description: "If the requested completed without errors" channel_id: From 75d7165eedf565d5a881a0b843f99065c3f5b6c3 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 10:52:17 -0700 Subject: [PATCH 074/214] chore: remove additional type roots from past compilation --- jsconfig.json | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/jsconfig.json b/jsconfig.json index 04678f5b..64512e1b 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,19 +1,15 @@ { "compilerOptions": { + "strict": true, + "baseUrl": ".", + "checkJs": true, + "esModuleInterop": true, "module": "es2022", "moduleResolution": "node", - "esModuleInterop": true, - "checkJs": true, - "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "baseUrl": ".", - "typeRoots": [ - "./@types", - "./node_modules/@types" - ] + "noFallthroughCasesInSwitch": true }, "exclude": [ "node_modules" From bc887ca077f4714b6e8eb50a1dd1210cffb0267c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 10:53:36 -0700 Subject: [PATCH 075/214] build: include the markup-js types as a development depenency --- package-lock.json | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b7798c9c..01a2d668 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@slack/web-api": "^7.5.0", - "@types/markup-js": "^1.5.0", "axios": "^1.7.7", "axios-retry": "^4.5.0", "flat": "^5.0.2", @@ -25,6 +24,7 @@ "@types/chai": "^4.3.19", "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", + "@types/markup-js": "^1.5.0", "@types/mocha": "^10.0.7", "@types/node": "^20.14.10", "@types/sinon": "^17.0.3", @@ -525,6 +525,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@types/markup-js/-/markup-js-1.5.0.tgz", "integrity": "sha512-yUPklfeB8Sk72gp0D0A/xjAR0QTKwVAY6hr1ORj44uSnqXIp7aEkWFZrAMf7md0PgVIMEAZtiEaNVFGaSyVA+A==", + "dev": true, "license": "MIT" }, "node_modules/@types/mocha": { diff --git a/package.json b/package.json index d7cdecdb..25ae5edb 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "@actions/core": "^1.10.1", "@actions/github": "^6.0.0", "@slack/web-api": "^7.5.0", - "@types/markup-js": "^1.5.0", "axios": "^1.7.7", "axios-retry": "^4.5.0", "flat": "^5.0.2", @@ -49,6 +48,7 @@ "@types/chai": "^4.3.19", "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", + "@types/markup-js": "^1.5.0", "@types/mocha": "^10.0.7", "@types/node": "^20.14.10", "@types/sinon": "^17.0.3", From 90d5d06558e13ab550b872089d6bcbd6439e7127 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 3 Oct 2024 10:59:20 -0700 Subject: [PATCH 076/214] style: remove additional undefined chaining after an initial definiton check --- src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 6f67c19b..b0fafe26 100644 --- a/src/client.js +++ b/src/client.js @@ -68,7 +68,7 @@ export default class Client { config.core.setOutput("channel_id", response.channel); } if (response.message?.thread_ts) { - config.core.setOutput("thread_ts", response.message?.thread_ts); + config.core.setOutput("thread_ts", response.message.thread_ts); } if (response.ts) { config.core.setOutput("ts", response.ts); From 12e162546e78aedc7b536ab1637f2be5f6e5c75a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 15 Oct 2024 14:20:38 -0700 Subject: [PATCH 077/214] docs(fix): add a step for showing the revealed credetials --- .github/resources/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/resources/README.md b/.github/resources/README.md index 4f368fc4..16083258 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -60,14 +60,18 @@ The saved `develop.yml` workflows use the same `webhook` and `method` examples but skip tests that happen during upstream checks: ```sh -$ cp .env.example .env # Set credentials -$ vim .env +$ cp .env.example .env # Create credentials +$ vim .env # Update credentials +$ cat .env # Reveal credentials export SLACK_BOT_TOKEN=xoxb-01010101-example export SLACK_CHANNEL_ID=C0123456789 export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz ``` +Environment variables and credentials should be set in the created `.env` file +for use in workflows and actions. + Once credentials are configured and workflows updated, the following command runs the workflow using `act` and the above settings: From c3a19f36c428c6f73cbe56e65643109568afd05f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 15 Oct 2024 14:21:16 -0700 Subject: [PATCH 078/214] docs: include complete example links in the ci workflow variable values --- .github/resources/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/resources/README.md b/.github/resources/README.md index 16083258..0085d348 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -43,8 +43,8 @@ tested. Required values include: - `SLACK_BOT_TOKEN`: xoxb-01010101-example - `SLACK_CHANNEL_ID`: C0123456789 -- `SLACK_INCOMING_WEBHOOK`: https://hooks.slack.com/services/T0123456789/... -- `SLACK_WEBHOOK_TRIGGER`: https://hooks.slack.com/triggers/T0123456789/... +- `SLACK_INCOMING_WEBHOOK`: https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz +- `SLACK_WEBHOOK_TRIGGER`: https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz ### Experimenting for development From e909226034a9c6a4c548d90135ac55ab659d1e99 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 15 Oct 2024 14:26:57 -0700 Subject: [PATCH 079/214] build: set the node version to match the runner runtime --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..209e3ef4 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 From a2b32d6d902b7cb6acbd9c1b55aadd5125f0f06f Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 15 Oct 2024 14:35:18 -0700 Subject: [PATCH 080/214] docs(fix): tyypo reminants -> remnants Co-authored-by: Fil Maj --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3b477d8f..0b4c043f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ node_modules # Extra files .DS_Store -# Testing reminants +# Testing remnants coverage From 607573463d6b5f68312729a1165be8bd3141be4c Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Tue, 15 Oct 2024 14:37:27 -0700 Subject: [PATCH 081/214] docs: give the action context for the github context Co-authored-by: Fil Maj --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f17b872e..72155b62 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ and JSON formats being accepted. ``` If neither payload input option is provided, the -[GitHub context][github-context] is used which has details specific to the +[GitHub Actions context][github-context] is used which has details specific to the repository and run of the workflow. The final payload content doesn't have to be fixed either, with From 8be2d3a6279d89a796e6f1845e84b68a2b6a71ff Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 12:14:48 -0700 Subject: [PATCH 082/214] docs: locate secret values from either outputs or the app settings --- .github/resources/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/resources/README.md b/.github/resources/README.md index 0085d348..75a78ee7 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -12,6 +12,9 @@ $ slack trigger create # SLACK_WEBHOOK_TRIGGER $ slack deploy # SLACK_BOT_TOKEN and SLACK_INCOMING_WEBHOOK ``` +Gather the **webhook trigger** from the output of the `trigger create` command +and collect the **bot token** and **incoming webhook** from app settings. + Where these are stored will depend on the configurations to run and will follow in the next sections. From fa91f44a08a4782d21e4dedf2238373d5568c8bf Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 12:15:23 -0700 Subject: [PATCH 083/214] docs: include the requirement of a channel ID where the bot is added --- .github/resources/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/resources/README.md b/.github/resources/README.md index 75a78ee7..a5a13de8 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -18,8 +18,8 @@ and collect the **bot token** and **incoming webhook** from app settings. Where these are stored will depend on the configurations to run and will follow in the next sections. -Also be sure to add the new bot to a channel for posting messages or errors -happen. +Also be sure to add the new bot to a channel, and while keeping track of that +**channel ID** for posting messages or errors happen. ## Running the workflows From 540f02dd735dee7f00fd353d685ee31142073490 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 12:16:13 -0700 Subject: [PATCH 084/214] docs: include an overview of the app found in resources for development --- .github/resources/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/resources/README.md b/.github/resources/README.md index a5a13de8..5c4d0555 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -4,6 +4,32 @@ For a quick development experience and fast testing setup, the app needed with this action is configured using the [app manifest][manifest] and can be used in experiments with the [Slack CLI][cli]. +## Overview + +This app showcases all three techniques of sending data into Slack and follows +patterns found in the integration tests. + +- **Technique 1** Slack Workflow Builder: Use a Slack webhook trigger to start a + workflow in Slack Workflow Builder. +- **Technique 2** Slack API method: Call a Slack API method using a token and + data provided through the GitHub Workflow. +- **Technique 3** Incoming webhook: Post a message to a Slack channel using an + incoming webhook. + +Configurations for the Slack app and workflow, and the GitHub Actions workflow +are found in the following files: + +- Slack app settings: [`.github/resources/.slack/manifest.json`][slacktion] +- GitHub Actions steps: [`.github/workflows/develop.yml`][develop] + +Either the techniques or app settings and workflow setups can be adjusted during +testing and development. For experimenting with new changes, we recommend using +the [steps for development](#experimenting-for-development) while the +[steps for CI](#testing-in-ci) is useful when using this app in an actual GitHub +Actions workflow. + +### Getting started + Start by gathering credentials of an application equipped for action: ```sh @@ -107,5 +133,7 @@ of the `.github/resources/.actions/event.json` file. Reference: https://docs.github.com/en/webhooks/webhook-events-and-payloads [cli]: https://api.slack.com/automation/cli/commands +[develop]: ../workflows/develop.yml [manifest]: https://api.slack.com/concepts/manifests [secrets]: https://github.com/slackapi/slack-github-action/settings/secrets/actions +[slacktion]: ./.slack/manifest.json From c21fea270474165654a22058c745d037863d7b36 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 14:04:01 -0700 Subject: [PATCH 085/214] build: include an example environment variable file for testing --- .github/resources/.env.example | 7 +++++++ .github/resources/.gitignore | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .github/resources/.env.example diff --git a/.github/resources/.env.example b/.github/resources/.env.example new file mode 100644 index 00000000..c638f894 --- /dev/null +++ b/.github/resources/.env.example @@ -0,0 +1,7 @@ +# Move this file to .env before starting + +# https://api.slack.com/apps/A0123456789 +export SLACK_BOT_TOKEN=xoxb-01010101-example +export SLACK_CHANNEL_ID=C0123456789 +export SLACK_INCOMING_WEBHOOK=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz +export SLACK_WEBHOOK_TRIGGER=https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz diff --git a/.github/resources/.gitignore b/.github/resources/.gitignore index 9d06fbd9..1a4adbbf 100644 --- a/.github/resources/.gitignore +++ b/.github/resources/.gitignore @@ -1,6 +1,6 @@ # Application values -!.env.example -.env .env.* +.env +!.env.example .slack/apps.json .slack/apps.*.json From a8affaaafb67a3bbcd27ef5949cdbc6245f38ea9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 14:04:33 -0700 Subject: [PATCH 086/214] docs: reference the resources for testing experimental changes in development --- .github/maintainers_guide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index 57141f19..2ab71aab 100644 --- a/.github/maintainers_guide.md +++ b/.github/maintainers_guide.md @@ -13,8 +13,8 @@ All you need to work with this project is a supported version of [Node.js](https ### Developing -Iterate quickly by developing and testing a local version of this action using `npm run local`. -Information on setting up and configuring mocked events can be found in [`.github/workflows/local/README.md`](./workflows/local/README.md). +Iterate quickly by developing and testing all techniques of this action with a local version of this action using `npm run dev`. +Information on setting up and configuring mocked events can be found in [`.github/resources/README.md`](./resources/README.md). ### Testing From 21334815469ffa3597eaa9451272a3901c243a99 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 14:05:25 -0700 Subject: [PATCH 087/214] docs(style): wrap lines that exceed the length of the deno fmt --- .github/resources/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/resources/README.md b/.github/resources/README.md index 5c4d0555..7086ac15 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -72,8 +72,10 @@ tested. Required values include: - `SLACK_BOT_TOKEN`: xoxb-01010101-example - `SLACK_CHANNEL_ID`: C0123456789 -- `SLACK_INCOMING_WEBHOOK`: https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz -- `SLACK_WEBHOOK_TRIGGER`: https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz +- `SLACK_INCOMING_WEBHOOK`: + https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz +- `SLACK_WEBHOOK_TRIGGER`: + https://hooks.slack.com/triggers/T0123456789/00000000000/abcdefghijklmnopqrstuvwxyz ### Experimenting for development From fd5313b0f4ae53e604fbd59c916b9ce8c7767b43 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 14:26:10 -0700 Subject: [PATCH 088/214] docs: make wording of step setups more percise --- .github/resources/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/resources/README.md b/.github/resources/README.md index 7086ac15..4b5dfeb2 100644 --- a/.github/resources/README.md +++ b/.github/resources/README.md @@ -12,17 +12,17 @@ patterns found in the integration tests. - **Technique 1** Slack Workflow Builder: Use a Slack webhook trigger to start a workflow in Slack Workflow Builder. - **Technique 2** Slack API method: Call a Slack API method using a token and - data provided through the GitHub Workflow. + data provided through the GitHub workflow. - **Technique 3** Incoming webhook: Post a message to a Slack channel using an incoming webhook. Configurations for the Slack app and workflow, and the GitHub Actions workflow are found in the following files: -- Slack app settings: [`.github/resources/.slack/manifest.json`][slacktion] +- Slack app setup: [`.github/resources/.slack/manifest.json`][slacktion] - GitHub Actions steps: [`.github/workflows/develop.yml`][develop] -Either the techniques or app settings and workflow setups can be adjusted during +Either the techniques or app setup and workflow steps can be adjusted during testing and development. For experimenting with new changes, we recommend using the [steps for development](#experimenting-for-development) while the [steps for CI](#testing-in-ci) is useful when using this app in an actual GitHub From da7d2a1d9fe69da36a7f2fec43fd6717ef45e904 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 17:23:35 -0700 Subject: [PATCH 089/214] docs: revert the changed title to that of the original readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72155b62..66503086 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Slack Send +# Slack Send GitHub Action > the GitHub Action for sending data to Slack From 7816a2c763736305b74b20b42b8f598169a64f25 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 17 Oct 2024 17:28:50 -0700 Subject: [PATCH 090/214] docs(fix): replace a redirected link to contextual workflow info --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66503086..5dc6c925 100644 --- a/README.md +++ b/README.md @@ -386,7 +386,7 @@ All contributions are encouraged! Check out the [contributing]: .github/contributing.md [examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows [files.uploadV2]: https://slack.dev/node-slack-sdk/web-api/#upload-a-file -[github-context]: https://docs.github.com/en/actions/learn-github-actions/contexts +[github-context]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs [github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables [incoming-webhook]: https://api.slack.com/messaging/webhooks [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook From ad5d39994f3fe71bcd71fec7e61b6c7961fd71f1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 18 Oct 2024 10:36:49 -0700 Subject: [PATCH 091/214] temp: print the octokit context in a pull request setting --- .github/workflows/test.yml | 6 ++++++ src/content.js | 1 + 2 files changed, 7 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34d029d9..0ccda4c1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,12 @@ jobs: url=${{ github.event.pull_request.html_url }} echo "EVENT_URL=$url" >> "$GITHUB_ENV" + - name: "temp(inputs): use and print the github octokit context" + uses: ./ + with: + webhook: ${{ secrets.SLACK_WEBHOOK_TRIGGER }} + webhook-type: webhook-trigger + - name: "integration(wfb): send a payload to workflow builder via webhook trigger" id: wfb uses: ./ diff --git a/src/content.js b/src/content.js index 89e87c58..4c47c70a 100644 --- a/src/content.js +++ b/src/content.js @@ -34,6 +34,7 @@ export default class Content { config.core.debug( "Missing payload so gathering inputs from action context.", ); + console.log(github.context); this.values = github.context; break; } From 23838b2930e31c10ea7850ce9435961bebb782da Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 13:58:20 -0700 Subject: [PATCH 092/214] docs: introduce concepts of sending variables with more context --- README.md | 63 +++++++++++++++++-------------------------------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 7558996d..1ada6463 100644 --- a/README.md +++ b/README.md @@ -4,56 +4,29 @@ [![codecov](https://codecov.io/gh/slackapi/slack-github-action/graph/badge.svg?token=OZNX7FHN78)](https://codecov.io/gh/slackapi/slack-github-action) -Send data into Slack with a Slack [API method](#technique-2-slack-api-method) -like [`chat.postMessage`][chat.postMessage] and -[`files.uploadV2`][files.uploadV2], or use webhooks to -[start workflows](#technique-1-slack-workflow-builder) in Workflow Builder and -[post messages](#technique-3-slack-incoming-webhook) with incoming webhooks -using this GitHub Action! +## Example workflows -## Sending data +For examples on how to leverage this in your workflows, check out the +[example workflows we have][examples] available. -Different ways to send data share overlapping styles of gathering the data to be -sent, intersecting between the `payload` and `payload-file-path` inputs and the -`method` and `webook` techniques. +## Sending variables -Either payload input option can be used with either technique, with both YAML -and JSON formats being accepted. +There are different [techniques to send data](#sending-techniques) into Slack +and whichever one is chosen will require a certain set of customized inputs, as +described later. -**Examples** +You can provide data to send to Slack from this GitHub Action and either source: -```yaml -- name: Post a message to a channel using a token - uses: slackapi/slack-github-action@v2-development - with: - method: chat.postMessage - token: ${{ secrets.SLACK_BOT_TOKEN }} - payload: | - text: "Actions happen at " - channel: ${{ secrets.SLACK_CHANNEL_ID }} -``` +- The default event [context][event-context] with a [payload][event-payload] + matching the GitHub event. +- A custom JSON payload with optional [variables][variables] provided in the + GitHub Action step. -```yaml -- name: Start a Slack workflow using a webhook URL - uses: slackapi/slack-github-action@v2-development - with: - payload-file-path: ./build-artifacts.json - webhook: ${{ secrets.SLACK_WEBHOOK_URL }} - webhook-type: webhook-trigger -``` +These input options are valid for all techniques, but some techniques require +specific constraints with certain requirements for valid inputs. -If neither payload input option is provided, the -[GitHub Actions context][github-context] is used which has details specific to the -repository and run of the workflow. - -The final payload content doesn't have to be fixed either, with -[additional configurations](#additional-configurations) available for -customization. - -### Example workflows - -For examples on how to leverage this in your workflows, check out the -[example workflows we have][examples] available. +Additional [configurations](#additional-configurations) are also available for +more customizations to the provided payload. ## Sending techniques @@ -385,9 +358,10 @@ All contributions are encouraged! Check out the [chat.postMessage]: https://api.slack.com/methods/chat.postMessage [chat:write]: https://api.slack.com/scopes/chat:write [contributing]: .github/contributing.md +[event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6 +[event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads [examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows [files.uploadV2]: https://slack.dev/node-slack-sdk/web-api/#upload-a-file -[github-context]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs [github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables [incoming-webhook]: https://api.slack.com/messaging/webhooks [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook @@ -400,5 +374,6 @@ All contributions are encouraged! Check out the [scopes]: https://api.slack.com/scopes [slack-web-api]: https://slack.dev/node-slack-sdk/web-api [tokens]: https://api.slack.com/concepts/token-types +[variables]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables [wfb]: https://slack.com/features/workflow-automation [wfb-create]: https://slack.com/intl/en-ca/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks From aab54513e0838eecec6771c2523965afada88bec Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 14:03:38 -0700 Subject: [PATCH 093/214] docs: reword the exampliary sentance to reference 'action' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ada6463..9f88d4c8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## Example workflows -For examples on how to leverage this in your workflows, check out the +For examples on how to leverage this Action in workflows, check out [example workflows we have][examples] available. ## Sending variables From aceca257e47a283ee60cc0706b724cb062681b5e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 14:04:02 -0700 Subject: [PATCH 094/214] docs(fix): remove the json requirement from input payloads --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f88d4c8..51c1701d 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ You can provide data to send to Slack from this GitHub Action and either source: - The default event [context][event-context] with a [payload][event-payload] matching the GitHub event. -- A custom JSON payload with optional [variables][variables] provided in the - GitHub Action step. +- A custom payload with optional [variables][variables] provided in the GitHub + Action step. These input options are valid for all techniques, but some techniques require specific constraints with certain requirements for valid inputs. From b773f12e3ffddf3e327011f281e1e9fdb9a0b8e9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 14:05:24 -0700 Subject: [PATCH 095/214] docs(style): include a few more words for easier reading --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 51c1701d..d8808e22 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ You can provide data to send to Slack from this GitHub Action and either source: These input options are valid for all techniques, but some techniques require specific constraints with certain requirements for valid inputs. -Additional [configurations](#additional-configurations) are also available for -more customizations to the provided payload. +Additional [configurations](#additional-configurations) and other details are +also available for more customizations to the provided payload. ## Sending techniques From e0e254b7c78da3bc246c4f60a9b2086dc2bb3e15 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 14:09:32 -0700 Subject: [PATCH 096/214] revert "temp: print the octokit context in a pull request setting" --- .github/workflows/test.yml | 6 ------ src/content.js | 1 - 2 files changed, 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0ccda4c1..34d029d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,12 +55,6 @@ jobs: url=${{ github.event.pull_request.html_url }} echo "EVENT_URL=$url" >> "$GITHUB_ENV" - - name: "temp(inputs): use and print the github octokit context" - uses: ./ - with: - webhook: ${{ secrets.SLACK_WEBHOOK_TRIGGER }} - webhook-type: webhook-trigger - - name: "integration(wfb): send a payload to workflow builder via webhook trigger" id: wfb uses: ./ diff --git a/src/content.js b/src/content.js index 4c47c70a..89e87c58 100644 --- a/src/content.js +++ b/src/content.js @@ -34,7 +34,6 @@ export default class Content { config.core.debug( "Missing payload so gathering inputs from action context.", ); - console.log(github.context); this.values = github.context; break; } From e37146c4d1c56cd28fe609efcdcde1c1ec8b4726 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 17:11:33 -0700 Subject: [PATCH 097/214] docs: link to outbound resources with more information --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d8808e22..61d6efa5 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ also available for more customizations to the provided payload. This Action offers three different techniques to send data to Slack: -1. Send data with a webhook to start a worflow in Workflow Builder. -2. Send data using a Slack API method and a secret token with required scopes. -3. Send data as a message with a Slack Incoming Webhook URL. +1. Send data with a webhook to start a workflow in [Workflow Builder][wfb]. +2. Send data using [a Slack API method][methods] and a secret token with required scopes. +3. Send data as a message with a Slack [incoming webhook][incoming-webhook] URL. ### Technique 1: Slack Workflow Builder From 9df54bbb7bcf12deb5f3528dd558439dd7fa1319 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 17:48:22 -0700 Subject: [PATCH 098/214] docs: include more explicit detail on which workflow is updated --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 61d6efa5..75176419 100644 --- a/README.md +++ b/README.md @@ -33,30 +33,32 @@ also available for more customizations to the provided payload. This Action offers three different techniques to send data to Slack: 1. Send data with a webhook to start a workflow in [Workflow Builder][wfb]. -2. Send data using [a Slack API method][methods] and a secret token with required scopes. +2. Send data using [a Slack API method][methods] and a secret token with + required scopes. 3. Send data as a message with a Slack [incoming webhook][incoming-webhook] URL. ### Technique 1: Slack Workflow Builder -> ❗️ This technique requires [a Slack paid plan][plans] to use Workflow Builder. +> :memo: This technique requires [a Slack paid plan][plans] to use Workflow +> Builder. This technique sends data to Slack using a webhook to start a workflow created -using the [Slack Workflow Builder][wfb]. Follow -[these steps to create a Slack workflow using webhooks][wfb-create]. +using Slack [Workflow Builder][wfb]. #### Setup -Starting in Slack, some prerequisite preparations are necessary: +Start in Slack to create a Slack workflow: -1. [Create a Slack workflow][wfb-create] that starts with a webhook. +1. [Create a Slack workflow][wfb-create] that starts from a webhook. 2. Copy the webhook URL and [add it as a repository secret][repo-secret] called `SLACK_WEBHOOK_URL`. -3. Add a step to your GitHub Action to send data to your webhook. -4. Configure your Slack workflow to use the incoming payload variables from the - the GitHub Action. You can then adjust the steps of the workflow to use these +3. Add this Action as a step to your GitHub workflow and set the input payload + to send. +4. Configure your Slack workflow to use the payload variables sent from the + GitHub Action. You can then update the steps of the Slack workflow to use these values in creative and clever ways. -Note: The webhook URL will resemble something like so: +The webhook URL will resemble something like so: ```txt https://hooks.slack.com/triggers/T0123456789/3141592653589/c6e6c0d868b3054ca0f4611a5dbadaf From e80e77b9c73a875874d42de8c25148829b99a49b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 21 Oct 2024 17:49:55 -0700 Subject: [PATCH 099/214] docs: quick link to the sections of different techniques --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 75176419..e978d2e0 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,12 @@ also available for more customizations to the provided payload. This Action offers three different techniques to send data to Slack: -1. Send data with a webhook to start a workflow in [Workflow Builder][wfb]. -2. Send data using [a Slack API method][methods] and a secret token with - required scopes. -3. Send data as a message with a Slack [incoming webhook][incoming-webhook] URL. +1. [**Technique 1**](#technique-1-slack-workflow-builder): Send data with a + webhook to start a workflow in [Workflow Builder][wfb]. +2. [**Technique 2**](#technique-2-slack-api-method): Send data using + [a Slack API method][methods] and a secret token with required scopes. +3. [**Technique 3**](#technique-3-slack-incoming-webhook): Send data as a + message with a Slack [incoming webhook][incoming-webhook] URL. ### Technique 1: Slack Workflow Builder @@ -55,8 +57,8 @@ Start in Slack to create a Slack workflow: 3. Add this Action as a step to your GitHub workflow and set the input payload to send. 4. Configure your Slack workflow to use the payload variables sent from the - GitHub Action. You can then update the steps of the Slack workflow to use these - values in creative and clever ways. + GitHub Action. You can then update the steps of the Slack workflow to use + these values in creative and clever ways. The webhook URL will resemble something like so: From 99dd6aadd910d372acc042a7b05aa6adb92af800 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 22 Oct 2024 12:00:36 -0700 Subject: [PATCH 100/214] docs: include information on the reason for deliminating payloads --- README.md | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e978d2e0..a3d64315 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ file with the configurations you want. ##### Sending values from the default GitHub event context -In the example below, no payload input values are being provided so values from -the GitHub [context][event-context] and [payload][event-payload] specific to the -job are used: +In the example below, the default GitHub event [context][event-context] and +event [payload][event-payload] associated with the job that started the GitHub +workflow are sent to the provided webhook URL: ```yaml - name: Send GitHub Action data to a Slack workflow @@ -86,8 +86,12 @@ job are used: webhook-type: webhook-trigger ``` -While also using `payload-delimiter` the payload is flattened and stringified to -match the webhook input format expected by Workflow Builder. +Accessing variables sent to [Workflow Builder][wfb] with a webhook require that +the payload variables are flattened with stringified values. Nested variables in +the provided payload can be both flattened and also stringified with the +`payload-delimiter` option or changed with other +[configurations](#additional-configurations) to match this format expected from +Workflow Builder. ##### Providing parsed payload information as strings @@ -310,6 +314,25 @@ blocks: Not all of the above settings serve every customization of a workflow, so these options might be useful. +### Flattening nested payloads + +Variables and data provided in the payload might contain nested fields that need +to be flattened before being +[sent with a webhook trigger](#technique-1-slack-workflow-builder) to match the +expected input format of [Workflow Builder][wfb]. + +The `payload-delimiter` option will flatten the input payload using the provided +delimiter and will make values stringified: + +```yaml +- name: Send GitHub Action data to a Slack workflow + uses: slackapi/slack-github-action@v2-development + with: + payload-delimiter: "_" + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger +``` + ### Parsing templated variables Additional [variables][github-variables] provided by Github can be used to From ac9a37e31bcca7def070bb8806e08949c8de882b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 22 Oct 2024 12:02:06 -0700 Subject: [PATCH 101/214] docs(style): change a few words for spacing setups --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a3d64315..1a9fc16c 100644 --- a/README.md +++ b/README.md @@ -317,12 +317,12 @@ options might be useful. ### Flattening nested payloads Variables and data provided in the payload might contain nested fields that need -to be flattened before being -[sent with a webhook trigger](#technique-1-slack-workflow-builder) to match the -expected input format of [Workflow Builder][wfb]. +to be flattened before being sent with a +[webhook trigger](#technique-1-slack-workflow-builder) to match the expected +input format of [Workflow Builder][wfb]. The `payload-delimiter` option will flatten the input payload using the provided -delimiter and will make values stringified: +delimiter and will also make values stringified: ```yaml - name: Send GitHub Action data to a Slack workflow From f19da8361ee13c7fd92b0859bfd8e33d54d88555 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 22 Oct 2024 12:16:06 -0700 Subject: [PATCH 102/214] docs(fix): recommend a templated variable found in the octokit payload --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1a9fc16c..acb8c348 100644 --- a/README.md +++ b/README.md @@ -335,9 +335,9 @@ delimiter and will also make values stringified: ### Parsing templated variables -Additional [variables][github-variables] provided by Github can be used to -replace templated variables in the provided payloads using the option to parse -payloads with the `payload-templated` option: +Additional variables provided in the Github event [context][event-context] and +event [payload][event-payload] can be used to replace templated variables in the +input payload with the `payload-templated` option: ```yaml - name: Send custom JSON data to Slack workflow @@ -350,8 +350,8 @@ payloads with the `payload-templated` option: webhook-type: webhook-trigger ``` -This replaces variables templated with as `${{ github.repository }}` with the -values found in the action context. +This replaces variables templated as `${{ github.payload.repository.html_url }}` +with the values found in the Action context. ### HTTPS proxy From 9bc0ad9069a77872b5c2cc287753bfae6a1f8a1a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 22 Oct 2024 12:16:57 -0700 Subject: [PATCH 103/214] docs(fix): wrap a numeric float in quotations to make it a string --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acb8c348..9d7d0c2e 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ inputs to current ones: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - ts: ${{ steps.slack.outputs.ts }} + ts: "${{ steps.slack.outputs.ts }}" text: "Deployment finished! :rocket:" attachments: - color: "28a745" From 3a414605784443e8a4f5c019c0d86e200dadc2fd Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 13:14:20 -0700 Subject: [PATCH 104/214] docs: reference the complete implementation of file upload --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d7d0c2e..10a7baa9 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,10 @@ the job is started: ### Technique 2: Slack API method A bot token or user token or [token of some other kind][tokens] can be used to -call one of many [Slack API methods][methods]! This includes -[`chat.postMessage`][chat.postMessage] and the official `@slack/web-api` -implemention of [`files.uploadV2`][files.uploadV2]. +call one of many [Slack API methods][methods]! This includes the +[`chat.postMessage`][chat.postMessage] method for posting messages or +[uploading a file][files.upload] with the convenience of the `@slack/web-api` +implementation for the [`files.uploadV2`][files.uploadV2] method. Setting up a workflow with this technique allows you to instantly interact with the Slack API methods without setting up a Slack workflow. @@ -388,6 +389,7 @@ All contributions are encouraged! Check out the [event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6 [event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads [examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows +[files.upload]: https://api.slack.com/messaging/files#upload [files.uploadV2]: https://slack.dev/node-slack-sdk/web-api/#upload-a-file [github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables [incoming-webhook]: https://api.slack.com/messaging/webhooks From 0972b00de999251b67966463cfa8ac86944b8629 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 13:14:46 -0700 Subject: [PATCH 105/214] docs: express capabilities of technique without negative wording --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 10a7baa9..ce57bc1d 100644 --- a/README.md +++ b/README.md @@ -117,8 +117,8 @@ call one of many [Slack API methods][methods]! This includes the [uploading a file][files.upload] with the convenience of the `@slack/web-api` implementation for the [`files.uploadV2`][files.uploadV2] method. -Setting up a workflow with this technique allows you to instantly interact with -the Slack API methods without setting up a Slack workflow. +Setting up a GitHub workflow with this technique unlocks instant interactions +with the Slack API methods using just a Slack API token. #### Setup From 619f3b3fbdd155da59caeccd7716b1941edd5882 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 14:36:58 -0700 Subject: [PATCH 106/214] docs: remove repeated sentance and break a paragraph into lines --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ce57bc1d..f9a77419 100644 --- a/README.md +++ b/README.md @@ -112,13 +112,12 @@ the job is started: ### Technique 2: Slack API method A bot token or user token or [token of some other kind][tokens] can be used to -call one of many [Slack API methods][methods]! This includes the -[`chat.postMessage`][chat.postMessage] method for posting messages or -[uploading a file][files.upload] with the convenience of the `@slack/web-api` -implementation for the [`files.uploadV2`][files.uploadV2] method. +call one of [the Slack API methods][methods] with this technique. -Setting up a GitHub workflow with this technique unlocks instant interactions -with the Slack API methods using just a Slack API token. +This includes the [`chat.postMessage`][chat.postMessage] method for posting +messages or [uploading a file][files.upload] with the convenience of the +`@slack/web-api` implementation for the [`files.uploadV2`][files.uploadV2] +method. #### Setup From 725d89a073c1caaf987f604147998ba2cac12cb4 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 14:38:10 -0700 Subject: [PATCH 107/214] docs: write that the api methods used are the slack api methods --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9a77419..a6bcce53 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,8 @@ method. #### Setup -The exact [API method][methods] used will change the required [scopes][scopes], -but setup should be similar for all methods: +The exact [Slack API method][methods] used will require setting various sets of +[scopes][scopes], but setup should be similar for all methods: - [Create a Slack App][apps] for your workspace or use an existing one. - Add the [`chat:write`][chat:write] bot scope under the **OAuth & Permissions** From 452188c3a08144b3803f5a96f0639b99ed2e300d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 14:39:48 -0700 Subject: [PATCH 108/214] docs: link to the @slack/web-api landing page of tools.slack.dev --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a6bcce53..e6c5095b 100644 --- a/README.md +++ b/README.md @@ -116,8 +116,8 @@ call one of [the Slack API methods][methods] with this technique. This includes the [`chat.postMessage`][chat.postMessage] method for posting messages or [uploading a file][files.upload] with the convenience of the -`@slack/web-api` implementation for the [`files.uploadV2`][files.uploadV2] -method. +[`@slack/web-api`][slack-web-api] implementation for the +[`files.uploadV2`][files.uploadV2] method. #### Setup From cedefeb979ee39654ccef74c1897563dc6f0784d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 14:40:47 -0700 Subject: [PATCH 109/214] docs: redirect links to the tools subdomain of slack.dev --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e6c5095b..97406005 100644 --- a/README.md +++ b/README.md @@ -389,7 +389,7 @@ All contributions are encouraged! Check out the [event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads [examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows [files.upload]: https://api.slack.com/messaging/files#upload -[files.uploadV2]: https://slack.dev/node-slack-sdk/web-api/#upload-a-file +[files.uploadV2]: https://tools.slack.dev/node-slack-sdk/web-api/#upload-a-file [github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables [incoming-webhook]: https://api.slack.com/messaging/webhooks [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook @@ -400,7 +400,7 @@ All contributions are encouraged! Check out the [plans]: https://slack.com/pricing [repo-secret]: https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository [scopes]: https://api.slack.com/scopes -[slack-web-api]: https://slack.dev/node-slack-sdk/web-api +[slack-web-api]: https://tools.slack.dev/node-slack-sdk/web-api [tokens]: https://api.slack.com/concepts/token-types [variables]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables [wfb]: https://slack.com/features/workflow-automation From 3a63569b71cd88059b8fe6b350a8135865cbbc01 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 14:52:14 -0700 Subject: [PATCH 110/214] docs: update the title of the link on creating wfb workflow with webhook --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97406005..5b4ca795 100644 --- a/README.md +++ b/README.md @@ -404,4 +404,4 @@ All contributions are encouraged! Check out the [tokens]: https://api.slack.com/concepts/token-types [variables]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables [wfb]: https://slack.com/features/workflow-automation -[wfb-create]: https://slack.com/intl/en-ca/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks +[wfb-create]: https://slack.com/help/articles/360041352714-Build-a-workflow--Create-a-workflow-that-starts-outside-of-Slack From 63a34158deaf29fc859bc556676861f83a07802b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 15:00:49 -0700 Subject: [PATCH 111/214] docs: link to action documentation before recommending input updates --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5b4ca795..a3852139 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ Start in Slack to create a Slack workflow: 1. [Create a Slack workflow][wfb-create] that starts from a webhook. 2. Copy the webhook URL and [add it as a repository secret][repo-secret] called `SLACK_WEBHOOK_URL`. -3. Add this Action as a step to your GitHub workflow and set the input payload - to send. +3. [Add this Action as a step][job-step] to your GitHub workflow and set the + input payload to send. 4. Configure your Slack workflow to use the payload variables sent from the GitHub Action. You can then update the steps of the Slack workflow to use these values in creative and clever ways. @@ -68,8 +68,8 @@ https://hooks.slack.com/triggers/T0123456789/3141592653589/c6e6c0d868b3054ca0f46 #### Usage -Add this Action as a [step][job-step] to your project's GitHub Action workflow -file with the configurations you want. +Update the input payloads sent from this GitHub Action to your Slack workflow +using the following options: ##### Sending values from the default GitHub event context From 06555c35fdaf7a895d32bad07f2cba0ce4e1a16b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 15:02:22 -0700 Subject: [PATCH 112/214] docs: include information on the payload file path argument --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index a3852139..f2d5bb10 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,20 @@ the job is started: option: "false" ``` +##### Gathering details of the payload from a saved file + +Input values for the payload to be sent can also be provided in a file, either +in JSON or YAML format: + +```yaml +- name: Send custom JSON data to Slack workflow + uses: slackapi/slack-github-action@v2-development + with: + payload-file-path: "./artifacts.json" + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: webhook-trigger +``` + ### Technique 2: Slack API method A bot token or user token or [token of some other kind][tokens] can be used to From a6b4974d41d3680fe0862991669ec6da8b412c04 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 15:05:17 -0700 Subject: [PATCH 113/214] docs: share a similar link length for method and scope docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2d5bb10..afa454f2 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ messages or [uploading a file][files.upload] with the convenience of the #### Setup -The exact [Slack API method][methods] used will require setting various sets of +The exact Slack API [method][methods] used will require setting various sets of [scopes][scopes], but setup should be similar for all methods: - [Create a Slack App][apps] for your workspace or use an existing one. From b89ded2cfdd667fc4c669b073187acc017fdd7b2 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 15:08:01 -0700 Subject: [PATCH 114/214] docs: improve the naming of example snippets --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index afa454f2..72032ae6 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ Provided input values for payload information are sent to the webhook URL after the job is started: ```yaml -- name: Send custom JSON data to Slack workflow +- name: Send custom event details to Slack workflow uses: slackapi/slack-github-action@v2-development with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -115,7 +115,7 @@ Input values for the payload to be sent can also be provided in a file, either in JSON or YAML format: ```yaml -- name: Send custom JSON data to Slack workflow +- name: Send a saved artifact to a Slack workflow uses: slackapi/slack-github-action@v2-development with: payload-file-path: "./artifacts.json" From f1e2d319b1a9ea63331063f843d7b6cd151e76b4 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 15:09:07 -0700 Subject: [PATCH 115/214] docs(style): use a to suggest a singular slack workflow --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72032ae6..0788398e 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ Provided input values for payload information are sent to the webhook URL after the job is started: ```yaml -- name: Send custom event details to Slack workflow +- name: Send custom event details to a Slack workflow uses: slackapi/slack-github-action@v2-development with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} From 5397443fb916a27f5f48012853342a09188de594 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 15:28:56 -0700 Subject: [PATCH 116/214] docs: remove a preference for the chat.postMessage method --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0788398e..ea630e75 100644 --- a/README.md +++ b/README.md @@ -138,15 +138,16 @@ messages or [uploading a file][files.upload] with the convenience of the The exact Slack API [method][methods] used will require setting various sets of [scopes][scopes], but setup should be similar for all methods: -- [Create a Slack App][apps] for your workspace or use an existing one. -- Add the [`chat:write`][chat:write] bot scope under the **OAuth & Permissions** - page. +- [Create a Slack app][apps] for your workspace or use an existing app. +- Select a [method][methods] to call and add the required **scopes** to your app + under the **OAuth & Permissions** page on app settings. - Install the app to your workspace using the **Install App** page. -- Copy the app's Bot Token from the **OAuth & Permissions** page and - [add it as a secret in your repo settings][repo-secret] named - `SLACK_BOT_TOKEN`. -- Invite the bot user into the channel you wish to post messages to - (`/invite @bot_user_name`). +- Find the [token][tokens] minted for the Slack API method being called from the + **OAuth & Permissions** page. +- Add the token as [a repository secret][repo-secret] called `SLACK_BOT_TOKEN` + or something similar and memorable. +- [Add this Action as a step][job-step] to your GitHub workflow and set the + input payload to send. #### Usage From 74d76d2e39ee5a00b793ab5166ac22d31796ef79 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 15:29:25 -0700 Subject: [PATCH 117/214] docs: enumerate the steps in setting up a slack api method --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ea630e75..4cf68cca 100644 --- a/README.md +++ b/README.md @@ -138,16 +138,16 @@ messages or [uploading a file][files.upload] with the convenience of the The exact Slack API [method][methods] used will require setting various sets of [scopes][scopes], but setup should be similar for all methods: -- [Create a Slack app][apps] for your workspace or use an existing app. -- Select a [method][methods] to call and add the required **scopes** to your app - under the **OAuth & Permissions** page on app settings. -- Install the app to your workspace using the **Install App** page. -- Find the [token][tokens] minted for the Slack API method being called from the - **OAuth & Permissions** page. -- Add the token as [a repository secret][repo-secret] called `SLACK_BOT_TOKEN` - or something similar and memorable. -- [Add this Action as a step][job-step] to your GitHub workflow and set the - input payload to send. +1. [Create a Slack app][apps] for your workspace or use an existing app. +2. Select a [method][methods] to call and add the required **scopes** to your + app under the **OAuth & Permissions** page on app settings. +3. Install the app to your workspace using the **Install App** page. +4. Find the [token][tokens] minted for the Slack API method being called from + the **OAuth & Permissions** page. +5. Add the token as [a repository secret][repo-secret] called `SLACK_BOT_TOKEN` + or something similar and memorable. +6. [Add this Action as a step][job-step] to your GitHub workflow and set the + input payload to send. #### Usage From b2ba444e064dac16885e1671c3696edc023327b0 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 16:25:04 -0700 Subject: [PATCH 118/214] docs: include reference for finding app configuration tokens --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 4cf68cca..f381ffbe 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,10 @@ The exact Slack API [method][methods] used will require setting various sets of 6. [Add this Action as a step][job-step] to your GitHub workflow and set the input payload to send. +Methods that require an app configuration token should gather this token +from the [app configuration token][config-tokens] settings instead of from a +specific app since this token is associated with the **workspace**. + #### Usage Choosing inputs for these steps is left as an exercise for the actioneer, but @@ -399,6 +403,7 @@ All contributions are encouraged! Check out the [block-kit]: https://api.slack.com/surfaces/messages#complex_layouts [chat.postMessage]: https://api.slack.com/methods/chat.postMessage [chat:write]: https://api.slack.com/scopes/chat:write +[config-tokens]: https://api.slack.com/reference/manifests#config-tokens [contributing]: .github/contributing.md [event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6 [event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads From 1b38aa1b15c299d4dadaf85856d3091a736d2a57 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 16:26:26 -0700 Subject: [PATCH 119/214] docs(fix): style the makrdown with lines that wrap --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f381ffbe..150118f2 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,9 @@ The exact Slack API [method][methods] used will require setting various sets of 6. [Add this Action as a step][job-step] to your GitHub workflow and set the input payload to send. -Methods that require an app configuration token should gather this token -from the [app configuration token][config-tokens] settings instead of from a -specific app since this token is associated with the **workspace**. +Methods that require an app configuration token should gather this token from +the [app configuration token][config-tokens] settings instead of from a specific +app since this token is associated with the **workspace**. #### Usage From cd00cf25c8181c51411db4bf2fe4e52799327636 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 16:26:58 -0700 Subject: [PATCH 120/214] docs: suggest a reason for selecting different inputs for http methods --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 150118f2..27f0fa9c 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,9 @@ app since this token is associated with the **workspace**. #### Usage -Choosing inputs for these steps is left as an exercise for the actioneer, but -these snippets might be helpful when starting. +Choosing inputs for these steps is left as an exercise for the actioneer since +each of the Slack API methods requires certain values and parameters, but these +snippets might be helpful when starting. ##### Posting a message with text From b72c275ea05672f58de4719a98de9b20e507e861 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 16:29:38 -0700 Subject: [PATCH 121/214] docs: remove a setup link from usage but detail a common edge --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27f0fa9c..911bf43d 100644 --- a/README.md +++ b/README.md @@ -161,8 +161,9 @@ snippets might be helpful when starting. ##### Posting a message with text -An introductory call to the `chat.postMessage` method can be done by -[adding this step][job-step] to a job in your workflow: +A call to the `chat.postMessage` method can be accomplished by adding this step +to a job in your GitHub workflow and inviting the bot associated with your app +to the channel for posting: ```yaml - name: Post to a Slack channel From ad486c75307a0845bb193195d1d66a0685f6e5b4 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 16:30:42 -0700 Subject: [PATCH 122/214] docs: reveal the end goal of chat.postMessage in documentation --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 911bf43d..38acde12 100644 --- a/README.md +++ b/README.md @@ -161,9 +161,9 @@ snippets might be helpful when starting. ##### Posting a message with text -A call to the `chat.postMessage` method can be accomplished by adding this step -to a job in your GitHub workflow and inviting the bot associated with your app -to the channel for posting: +Posting a message with the `chat.postMessage` method can be achieved by adding +this step to a job in your GitHub workflow and inviting the bot associated with +your app to the channel for posting: ```yaml - name: Post to a Slack channel From 229e3042f57d577abf77145168841d3ceb0e1b83 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 17:24:35 -0700 Subject: [PATCH 123/214] docs: make it clear that parameters must be specific --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 38acde12..0380bc80 100644 --- a/README.md +++ b/README.md @@ -156,8 +156,8 @@ app since this token is associated with the **workspace**. #### Usage Choosing inputs for these steps is left as an exercise for the actioneer since -each of the Slack API methods requires certain values and parameters, but these -snippets might be helpful when starting. +each of the Slack API methods requires certain values and specific parameters, +but these snippets might be helpful when starting. ##### Posting a message with text From 15e3647c4d802f75dff2018b27ddc0ade6acb6e5 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 17:24:47 -0700 Subject: [PATCH 124/214] docs: link to the chat.postmessage method when referecing it --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0380bc80..66d04c89 100644 --- a/README.md +++ b/README.md @@ -161,9 +161,9 @@ but these snippets might be helpful when starting. ##### Posting a message with text -Posting a message with the `chat.postMessage` method can be achieved by adding -this step to a job in your GitHub workflow and inviting the bot associated with -your app to the channel for posting: +Posting a message with the [`chat.postMessage`][chat.postMessage] method can be +achieved by adding this step to a job in your GitHub workflow and inviting the +bot associated with your app to the channel for posting: ```yaml - name: Post to a Slack channel From 01fd19849dcda1edb6c2f9c7b4dcbadd1a3b491c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 17:31:28 -0700 Subject: [PATCH 125/214] docs: reference the block kit documentatoin in complex layouts --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66d04c89..5f9f9e01 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,8 @@ bot associated with your app to the channel for posting: ##### Posting a message with blocks -More detailed messages with nested JSON, like block messages made with block -kit, work as the API call might hope: +More complex message layouts, like messages made with [Block Kit][block-kit] +blocks, can also be sent with Slack API methods: ```yaml - name: Post to a Slack channel From 03eca4071da742a2df8fcc2959846819c1075c88 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 17:34:17 -0700 Subject: [PATCH 126/214] docs: reference the chat.update method for updating messages --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5f9f9e01..852a84ec 100644 --- a/README.md +++ b/README.md @@ -199,9 +199,10 @@ blocks, can also be sent with Slack API methods: ##### Updating a message -Following up on a message after it's posted, such as updates for a build status, -can be done by chaining multiple steps together using outputs from past steps as -inputs to current ones: +Following up on a message after it's posted, such as using the +[`chat.update`][chat.update] method to share updates for a build status, can be +done by chaining multiple steps together using outputs from past steps as inputs +to current ones: ```yaml - name: Initiate the deployment launch sequence @@ -404,6 +405,7 @@ All contributions are encouraged! Check out the [apps]: https://api.slack.com/apps [block-kit]: https://api.slack.com/surfaces/messages#complex_layouts [chat.postMessage]: https://api.slack.com/methods/chat.postMessage +[chat.update]: https://api.slack.com/methods/chat.update [chat:write]: https://api.slack.com/scopes/chat:write [config-tokens]: https://api.slack.com/reference/manifests#config-tokens [contributing]: .github/contributing.md From e3e3da68b6b10ce504ffaf0879319cc22a4799e0 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 17:37:47 -0700 Subject: [PATCH 127/214] docs: rewrite words to leave examples in the snippets --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 852a84ec..dca0e24d 100644 --- a/README.md +++ b/README.md @@ -199,10 +199,9 @@ blocks, can also be sent with Slack API methods: ##### Updating a message -Following up on a message after it's posted, such as using the -[`chat.update`][chat.update] method to share updates for a build status, can be -done by chaining multiple steps together using outputs from past steps as inputs -to current ones: +Updating a message after it's posted can be done with the +[`chat.update`][chat.update] method and chaining multiple steps together using +outputs from past steps as inputs to current ones: ```yaml - name: Initiate the deployment launch sequence From 7e2302a627c9242bd688796e3db613495f6a130d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 17:39:40 -0700 Subject: [PATCH 128/214] docs: break lines and add words for a smoother reading i hope --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dca0e24d..94d2402d 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,8 @@ bot associated with your app to the channel for posting: ##### Posting a message with blocks -More complex message layouts, like messages made with [Block Kit][block-kit] -blocks, can also be sent with Slack API methods: +More complex message layouts, such as messages made with [Block Kit][block-kit] +blocks, can also be sent with one of the Slack API methods: ```yaml - name: Post to a Slack channel From 220320d0238b47e30d13a43e439fae2c715af6a9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:17:05 -0700 Subject: [PATCH 129/214] docs: make the action context source more clear --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94d2402d..2cd1ff6f 100644 --- a/README.md +++ b/README.md @@ -372,7 +372,7 @@ input payload with the `payload-templated` option: ``` This replaces variables templated as `${{ github.payload.repository.html_url }}` -with the values found in the Action context. +with the values found in the GitHub Action event [payload][event-payload]. ### HTTPS proxy From 9f0e1f9ead23d769eb10a42ad589fbfd1ea6ab26 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:18:17 -0700 Subject: [PATCH 130/214] docs?: word this heading as an action for faster findings --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cd1ff6f..4ccd54af 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ input payload with the `payload-templated` option: This replaces variables templated as `${{ github.payload.repository.html_url }}` with the values found in the GitHub Action event [payload][event-payload]. -### HTTPS proxy +### Proxying HTTPS requests If you need to use a proxy to connect with Slack, you can use the `HTTPS_PROXY` or `https_proxy` environment variable. In this example we use the Slack App From a3e6c6e2f6669f48f470758b5baac01ce00c094a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:21:32 -0700 Subject: [PATCH 131/214] docs: save examples of a technique for the examples section --- README.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4ccd54af..bdee145d 100644 --- a/README.md +++ b/README.md @@ -128,11 +128,6 @@ in JSON or YAML format: A bot token or user token or [token of some other kind][tokens] can be used to call one of [the Slack API methods][methods] with this technique. -This includes the [`chat.postMessage`][chat.postMessage] method for posting -messages or [uploading a file][files.upload] with the convenience of the -[`@slack/web-api`][slack-web-api] implementation for the -[`files.uploadV2`][files.uploadV2] method. - #### Setup The exact Slack API [method][methods] used will require setting various sets of @@ -266,9 +261,9 @@ the `thread_ts` attribute of the **parent** message in the `payload`: ##### Uploading a file -Calling web API methods with [`@slack/web-api`][slack-web-api] makes uploading -files just another API call, but with all of the advantages of -[`files.uploadV2`][files.uploadV2]: +Calling [a Slack API method][methods] with [`@slack/web-api`][slack-web-api] +makes [uploading a file][files.upload] just another API call with all of the +convenience of the [`files.uploadV2`][files.uploadV2] method: ```yaml - name: Share a file to that channel From a7aa1bb3f6e86d82ebe3ce288781724b0b56fa77 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:24:22 -0700 Subject: [PATCH 132/214] docs(fix): include the token argument in the files.uploadV2 example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bdee145d..a1cbfc64 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,7 @@ convenience of the [`files.uploadV2`][files.uploadV2] method: uses: slackapi/slack-github-action@v2-development with: method: files.uploadV2 + token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} initial_comment: "the results are in!" From 6c27de4e288bccca967f0e138919edc3ba0e316a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:28:38 -0700 Subject: [PATCH 133/214] docs: use a standard case and size when writing words --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a1cbfc64..a57f5387 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ The exact Slack API [method][methods] used will require setting various sets of Methods that require an app configuration token should gather this token from the [app configuration token][config-tokens] settings instead of from a specific -app since this token is associated with the **workspace**. +app since this token is associated with the workspace. #### Usage @@ -236,7 +236,7 @@ outputs from past steps as inputs to current ones: ##### Replying to a message Posting threaded replies to a message from a past job can be done by including -the `thread_ts` attribute of the **parent** message in the `payload`: +the `thread_ts` attribute of the parent message in the `payload`: ```yaml - name: Initiate a deployment @@ -295,7 +295,7 @@ A similar approach to [Technique 1](#technique-1-slack-workflow-builder) is taken to create apps and setup the workflow, but webhooks are gathered from a different source: -- [Create a Slack App][apps] for your workspace (alternatively use an existing +- [Create a Slack app][apps] for your workspace (alternatively use an existing app you have already created and installed). - Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & Permissions**. From d5739e80a8970e5b5b7947aa171949f49be6a214 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:31:08 -0700 Subject: [PATCH 134/214] docs: post a single message with an incoming webhook and slack app --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a57f5387..8e2499c2 100644 --- a/README.md +++ b/README.md @@ -280,8 +280,8 @@ convenience of the [`files.uploadV2`][files.uploadV2] method: ### Technique 3: Slack incoming webhook -This technique uses GitHub Actions to post messages to a channel or direct -message using [incoming webhooks][incoming-webhook] from a Slack app. +This technique uses GitHub Actions to post a message to a channel or direct +message using [incoming webhooks][incoming-webhook] with a Slack app. Incoming Webhooks conform to the same rules and functionality as any of Slack's other messaging APIs. You can make your posted messages as simple as a single From 7c707501891368c5f02c21a939e3463522aa919f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:39:03 -0700 Subject: [PATCH 135/214] docs: add a reference to message formatting for incoming webhooks --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8e2499c2..d01428b3 100644 --- a/README.md +++ b/README.md @@ -283,11 +283,10 @@ convenience of the [`files.uploadV2`][files.uploadV2] method: This technique uses GitHub Actions to post a message to a channel or direct message using [incoming webhooks][incoming-webhook] with a Slack app. -Incoming Webhooks conform to the same rules and functionality as any of Slack's -other messaging APIs. You can make your posted messages as simple as a single -line of text, or make them really useful with -[interactive components][interactivity]. To make the message more expressive and -useful use [Block Kit][block-kit] to build and test visual components. +Incoming webhooks follow the same [formatting][formatting] patterns as other +Slack messaging APIs. Posted messages can be as short as a single line of text, +include additional interactivity with [interactive components][interactivity], +or be formatted with [Block Kit][block-kit] to build visual components. #### Setup @@ -409,6 +408,7 @@ All contributions are encouraged! Check out the [examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows [files.upload]: https://api.slack.com/messaging/files#upload [files.uploadV2]: https://tools.slack.dev/node-slack-sdk/web-api/#upload-a-file +[formatting]: https://api.slack.com/reference/surfaces/formatting [github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables [incoming-webhook]: https://api.slack.com/messaging/webhooks [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook From ce8964fe9b6740d855549d07084bb86b5fcb2744 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:41:28 -0700 Subject: [PATCH 136/214] docs: enumerate the steps in setup for technique three --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index d01428b3..276f898f 100644 --- a/README.md +++ b/README.md @@ -294,15 +294,15 @@ A similar approach to [Technique 1](#technique-1-slack-workflow-builder) is taken to create apps and setup the workflow, but webhooks are gathered from a different source: -- [Create a Slack app][apps] for your workspace (alternatively use an existing - app you have already created and installed). -- Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & - Permissions**. -- Install the app to your workspace (you will select a channel to notify). -- Activate and create a new webhook under **Incoming Webhooks**. -- Copy the Webhook URL from the Webhook you just generated - [add it as a secret in your repo settings][repo-secret] named - `SLACK_WEBHOOK_URL`. +1. [Create a Slack app][apps] for your workspace (alternatively use an existing + app you have already created and installed). +2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & + Permissions**. +3. Install the app to your workspace (you will select a channel to notify). +4. Activate and create a new webhook under **Incoming Webhooks**. +5. Copy the Webhook URL from the Webhook you just generated + [add it as a secret in your repo settings][repo-secret] named + `SLACK_WEBHOOK_URL`. #### Usage From 0a258a45944e9fa23a0765dfdbbb235045944ca3 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:42:59 -0700 Subject: [PATCH 137/214] docs: use the same wording in steps for creating or existing apps --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 276f898f..a2ce625c 100644 --- a/README.md +++ b/README.md @@ -294,8 +294,7 @@ A similar approach to [Technique 1](#technique-1-slack-workflow-builder) is taken to create apps and setup the workflow, but webhooks are gathered from a different source: -1. [Create a Slack app][apps] for your workspace (alternatively use an existing - app you have already created and installed). +1. [Create a Slack app][apps] for your workspace or use an existing app. 2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & Permissions**. 3. Install the app to your workspace (you will select a channel to notify). From ea1a6caf585517cf41c2a45d7f27426b3d95a9a1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:44:07 -0700 Subject: [PATCH 138/214] docs: write the webpage to gather an incoming webhook as app settings --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2ce625c..a60b60a9 100644 --- a/README.md +++ b/README.md @@ -295,8 +295,8 @@ taken to create apps and setup the workflow, but webhooks are gathered from a different source: 1. [Create a Slack app][apps] for your workspace or use an existing app. -2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & - Permissions**. +2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth + & Permissions** page on app settings. 3. Install the app to your workspace (you will select a channel to notify). 4. Activate and create a new webhook under **Incoming Webhooks**. 5. Copy the Webhook URL from the Webhook you just generated From 1360cfcf14353a60832fd68edd4558b9940a197d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:45:28 -0700 Subject: [PATCH 139/214] docs: write the selection of a channel as a partial step --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a60b60a9..3756939b 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ different source: 1. [Create a Slack app][apps] for your workspace or use an existing app. 2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & Permissions** page on app settings. -3. Install the app to your workspace (you will select a channel to notify). +3. Install the app to your workspace and select a channel to notify. 4. Activate and create a new webhook under **Incoming Webhooks**. 5. Copy the Webhook URL from the Webhook you just generated [add it as a secret in your repo settings][repo-secret] named From 982e7863e9bc099c231398e85b8ad0fe5591918f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:45:58 -0700 Subject: [PATCH 140/214] docs: wrap the app settings reference page with reference words --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3756939b..263dab4f 100644 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ different source: 2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & Permissions** page on app settings. 3. Install the app to your workspace and select a channel to notify. -4. Activate and create a new webhook under **Incoming Webhooks**. +4. Activate and create a new webhook from the **Incoming Webhooks** page. 5. Copy the Webhook URL from the Webhook you just generated [add it as a secret in your repo settings][repo-secret] named `SLACK_WEBHOOK_URL`. From c87943e088c23afee7b39cec54fc1787e84cd118 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:50:48 -0700 Subject: [PATCH 141/214] docs: write the install app as the page to install app --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 263dab4f..60be92b9 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,8 @@ different source: 1. [Create a Slack app][apps] for your workspace or use an existing app. 2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth & Permissions** page on app settings. -3. Install the app to your workspace and select a channel to notify. +3. Install the app to your workspace and select a channel to notify from the + **Install App** page. 4. Activate and create a new webhook from the **Incoming Webhooks** page. 5. Copy the Webhook URL from the Webhook you just generated [add it as a secret in your repo settings][repo-secret] named From 08e6a83a53e93aebe5b47808f6eef0e29ba91721 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:52:22 -0700 Subject: [PATCH 142/214] docs: steps 4and5and6 --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 60be92b9..17a75906 100644 --- a/README.md +++ b/README.md @@ -299,10 +299,11 @@ different source: & Permissions** page on app settings. 3. Install the app to your workspace and select a channel to notify from the **Install App** page. -4. Activate and create a new webhook from the **Incoming Webhooks** page. -5. Copy the Webhook URL from the Webhook you just generated - [add it as a secret in your repo settings][repo-secret] named - `SLACK_WEBHOOK_URL`. +4. Create additional webhooks from the **Incoming Webhooks** page. +5. Add the generated incoming webhook URL as [a repository secret][repo-secret] + named something like `SLACK_WEBHOOK_URL` +6. [Add this Action as a step][job-step] to your GitHub workflow and set the + input payload to send. #### Usage From 751cd029fafbb3bf05fc8d316a6684f931dfa38c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:53:34 -0700 Subject: [PATCH 143/214] docs: shorten one sentance and finish another --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 17a75906..3a1bf431 100644 --- a/README.md +++ b/README.md @@ -290,9 +290,7 @@ or be formatted with [Block Kit][block-kit] to build visual components. #### Setup -A similar approach to [Technique 1](#technique-1-slack-workflow-builder) is -taken to create apps and setup the workflow, but webhooks are gathered from a -different source: +Gather a Slack incoming webhook URL: 1. [Create a Slack app][apps] for your workspace or use an existing app. 2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth @@ -301,7 +299,7 @@ different source: **Install App** page. 4. Create additional webhooks from the **Incoming Webhooks** page. 5. Add the generated incoming webhook URL as [a repository secret][repo-secret] - named something like `SLACK_WEBHOOK_URL` + called `SLACK_WEBHOOK_URL` or something similar. 6. [Add this Action as a step][job-step] to your GitHub workflow and set the input payload to send. From c023102ee6e33cc492ded607dc8bb5b7fc13b722 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:55:03 -0700 Subject: [PATCH 144/214] docs: make the workflow known as a github one in technique three --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a1bf431..cc67f7a4 100644 --- a/README.md +++ b/README.md @@ -305,8 +305,8 @@ Gather a Slack incoming webhook URL: #### Usage -Add the collected webhook from above to a workflow and configure the job using -[`mrkdwn`][mrkdwn] formatting values for a message or [Block Kit][block-kit] +Add the collected webhook from above to a GitHub workflow and configure the step +using [`mrkdwn`][mrkdwn] formatting values for a message or [Block Kit][block-kit] blocks: ```yaml From 8b252ad4ce54ca1850a676cdfec4520c559a687a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:56:13 -0700 Subject: [PATCH 145/214] docs(fix): post a message as the name of the example here --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc67f7a4..3afa7a61 100644 --- a/README.md +++ b/README.md @@ -310,7 +310,7 @@ using [`mrkdwn`][mrkdwn] formatting values for a message or [Block Kit][block-ki blocks: ```yaml -- name: Send custom JSON data to Slack workflow +- name: Post a message in a channel uses: slackapi/slack-github-action@v2-development with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} From cb368c9721f12def8fede3e33d8798f76f2bbabe Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:57:03 -0700 Subject: [PATCH 146/214] docs: remove additoinal words that broke a line --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3afa7a61..d72fac99 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,7 @@ Gather a Slack incoming webhook URL: **Install App** page. 4. Create additional webhooks from the **Incoming Webhooks** page. 5. Add the generated incoming webhook URL as [a repository secret][repo-secret] - called `SLACK_WEBHOOK_URL` or something similar. + called `SLACK_WEBHOOK_URL`. 6. [Add this Action as a step][job-step] to your GitHub workflow and set the input payload to send. From a269eaf911b0cd2592ec8520436d48522c772196 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 25 Oct 2024 23:58:17 -0700 Subject: [PATCH 147/214] docs: shorten the tagline in a shorter line attempt? --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d72fac99..5b96d738 100644 --- a/README.md +++ b/README.md @@ -280,8 +280,8 @@ convenience of the [`files.uploadV2`][files.uploadV2] method: ### Technique 3: Slack incoming webhook -This technique uses GitHub Actions to post a message to a channel or direct -message using [incoming webhooks][incoming-webhook] with a Slack app. +This technique uses this Action to post a message to a channel or direct +message with [incoming webhooks][incoming-webhook] and a Slack app. Incoming webhooks follow the same [formatting][formatting] patterns as other Slack messaging APIs. Posted messages can be as short as a single line of text, From 457c62efc0928f6a5b4aad78e3b72c4b42e0baf3 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Sat, 26 Oct 2024 00:28:32 -0700 Subject: [PATCH 148/214] docs: include a section on inputs for the errors option --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 5b96d738..44f3347c 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,28 @@ blocks: Not all of the above settings serve every customization of a workflow, so these options might be useful. +### Exiting with errors + +Invalid API requests or unexpected webhook payloads cause a failing response +that can be used to fail the GitHub Actions step with the `errors` option. + +The `errors` option defaults to `false` so failed requests do not cause the +step to fail. This result can still be gathered from the `ok` output. + +```yaml +- name: Send GitHub Action data to a Slack workflow + uses: slackapi/slack-github-action@v2-development + with: + errors: true + method: chat.reverse + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + text: "palindrome" +``` + +Invalid inputs to the Action, such as not including a payload, will always cause +the GitHub step to fail. + ### Flattening nested payloads Variables and data provided in the payload might contain nested fields that need From 3b0cbbe3356f8e8b8125206cacb9939f2ef1225c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Sun, 27 Oct 2024 17:08:22 -0700 Subject: [PATCH 149/214] docs: write notes on strategies for retrying failed requests --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 44f3347c..b9268368 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,32 @@ technique, but configuring a proxy works the same way for all of them: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - message: "This message was sent through a proxy" + text: "This message was sent through a proxy" +``` + +### Retrying failed requests + +Sometimes outgoing requests fail due to [rate limits][rate-limits] or similar +[HTTP responses][retry-after] and can be retried later. + +The `retries` option can be configured to the needs of your workflow with one +of these values: + +- `0`: No retries, just hope that things go alright. +- `5`: Five retries in five minutes. **Default**. +- `10`: Ten retries in about thirty minutes. +- `RAPID`: A burst of retries to keep things running fast. + +```yaml +- name: Attempt a burst of requests + uses: slackapi/slack-github-action@v2-development + with: + method: chat.postMessage + retries: RAPID + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ secrets.SLACK_CHANNEL_ID }} + text: "status: all things are going good" ``` ## License From 3da2f359822d6df727bb518513aa7a7e5734edae Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Sun, 27 Oct 2024 17:13:37 -0700 Subject: [PATCH 150/214] docs: include the added links in a commit following above --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b9268368..af2f08d1 100644 --- a/README.md +++ b/README.md @@ -463,7 +463,9 @@ All contributions are encouraged! Check out the [methods]: https://api.slack.com/methods [mrkdwn]: https://api.slack.com/reference/surfaces/formatting [plans]: https://slack.com/pricing +[rate-limits]: https://api.slack.com/apis/rate-limits [repo-secret]: https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository +[retry-after]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After [scopes]: https://api.slack.com/scopes [slack-web-api]: https://tools.slack.dev/node-slack-sdk/web-api [tokens]: https://api.slack.com/concepts/token-types From f365ef8329c76a8df3179c9bbad5324e61c9d958 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 11:42:17 -0700 Subject: [PATCH 151/214] fix: reword an error to make missing technique the error part --- src/send.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/send.js b/src/send.js index 9f2c852b..456899dd 100644 --- a/src/send.js +++ b/src/send.js @@ -31,6 +31,6 @@ async function post(config) { case !!config.inputs.webhook: return await new Webhook().post(config); default: - throw new SlackError(config.core, "No method found to post content"); + throw new SlackError(config.core, "No technique given to post content"); } } From 30136d63f2925f28674eace33ab3fd098482e741 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 11:42:38 -0700 Subject: [PATCH 152/214] feat: set the 'ok' and 'response' output for webhook requests --- src/webhook.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webhook.js b/src/webhook.js index 69ac74a0..6bbb39db 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -27,7 +27,9 @@ export default class Webhook { config.content.values, options, ); - config.core.debug(JSON.stringify(response?.data)); + config.core.setOutput("ok", response.status === 200); + config.core.setOutput("response", JSON.stringify(response)); + config.core.debug(JSON.stringify(response)); } /** From dc37cb6ed67f73405f619884993d450287d0ccd6 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 11:42:58 -0700 Subject: [PATCH 153/214] style: capitalize the word unix in action outputs --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index a8473382..5962b94a 100644 --- a/action.yml +++ b/action.yml @@ -38,7 +38,7 @@ inputs: required: false outputs: time: - description: "The unix epoch time that the job completed" + description: "The Unix epoch time that the job completed" ok: description: "If the requested completed without errors" channel_id: From f18133b1e4aa43c80133e934c7593fa5b455661f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 11:54:47 -0700 Subject: [PATCH 154/214] test: confirm outputs match expected responses in unit --- test/send.spec.js | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/send.spec.js b/test/send.spec.js index 5bc708b6..f8cc77d0 100644 --- a/test/send.spec.js +++ b/test/send.spec.js @@ -23,9 +23,19 @@ describe("send", () => { .returns("https://hooks.slack.com"); mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); + mocks.axios.post.returns( + Promise.resolve({ status: 200, data: { ok: true } }), + ); await send(mocks.core); - assert.equal(mocks.core.setOutput.getCall(0).firstArg, "time"); - assert.isAtLeast(mocks.core.setOutput.getCall(0).lastArg, 0); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify({ status: 200, data: { ok: true } }), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.isAtLeast(mocks.core.setOutput.getCall(2).lastArg, 0); }); it("token", async () => { @@ -51,9 +61,17 @@ describe("send", () => { .returns("https://hooks.slack.com"); mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); mocks.core.getInput.withArgs("payload").returns('"greetings": "hello"'); + mocks.axios.post.returns(Promise.resolve({ status: 200, data: "ok" })); await send(mocks.core); - assert.equal(mocks.core.setOutput.getCall(0).firstArg, "time"); - assert.isAtLeast(mocks.core.setOutput.getCall(0).lastArg, 0); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify({ status: 200, data: "ok" }), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.isAtLeast(mocks.core.setOutput.getCall(2).lastArg, 0); }); }); }); From 7b7851794fb1a378e5ddc23aa261a6d7f8144c7d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 11:59:47 -0700 Subject: [PATCH 155/214] fix: return the response data from a webhook request --- src/webhook.js | 4 ++-- test/send.spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webhook.js b/src/webhook.js index 6bbb39db..ed1f2015 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -28,8 +28,8 @@ export default class Webhook { options, ); config.core.setOutput("ok", response.status === 200); - config.core.setOutput("response", JSON.stringify(response)); - config.core.debug(JSON.stringify(response)); + config.core.setOutput("response", JSON.stringify(response.data)); + config.core.debug(JSON.stringify(response.data)); } /** diff --git a/test/send.spec.js b/test/send.spec.js index f8cc77d0..db38b04e 100644 --- a/test/send.spec.js +++ b/test/send.spec.js @@ -32,7 +32,7 @@ describe("send", () => { assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); assert.equal( mocks.core.setOutput.getCall(1).lastArg, - JSON.stringify({ status: 200, data: { ok: true } }), + JSON.stringify({ ok: true }), ); assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); assert.isAtLeast(mocks.core.setOutput.getCall(2).lastArg, 0); @@ -68,7 +68,7 @@ describe("send", () => { assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); assert.equal( mocks.core.setOutput.getCall(1).lastArg, - JSON.stringify({ status: 200, data: "ok" }), + JSON.stringify("ok"), ); assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); assert.isAtLeast(mocks.core.setOutput.getCall(2).lastArg, 0); From f7fe23aeeb86487d569176401bb5f1bc02be2118 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 12:21:44 -0700 Subject: [PATCH 156/214] test: reorder the test techniques to match readme and responses --- test/send.spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/send.spec.js b/test/send.spec.js index db38b04e..a675d001 100644 --- a/test/send.spec.js +++ b/test/send.spec.js @@ -17,12 +17,12 @@ describe("send", () => { }); describe("techniques", async () => { - it("incoming webhook", async () => { + it("webhook trigger", async () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); - mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); + mocks.core.getInput.withArgs("payload").returns('"greetings": "hello"'); mocks.axios.post.returns( Promise.resolve({ status: 200, data: { ok: true } }), ); @@ -55,12 +55,12 @@ describe("send", () => { assert.isAtLeast(mocks.core.setOutput.getCall(2).lastArg, 0); }); - it("webhook trigger", async () => { + it("incoming webhook", async () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); - mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); - mocks.core.getInput.withArgs("payload").returns('"greetings": "hello"'); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("payload").returns('"text": "hello"'); mocks.axios.post.returns(Promise.resolve({ status: 200, data: "ok" })); await send(mocks.core); assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); From 7d142f296e6fce6e73d091f95d999eb2bd9ce052 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 12:30:07 -0700 Subject: [PATCH 157/214] ci: setup the specific version of node used with this project --- .github/workflows/develop.yml | 6 ++++++ .github/workflows/test.yml | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 5f72a05a..94d214ff 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -11,6 +11,12 @@ jobs: steps: - name: Checkout action uses: actions/checkout@v4 + - name: Setup the Node runtime for this project + uses: actions/setup-node@v4 + with: + cache: npm + cache-dependency-path: package-lock.json + node-version-file: .nvmrc - name: Install dependencies run: npm install - name: Package the build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34d029d9..867e4e0c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,6 +17,13 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} + - name: "build: setup the node runtime" + uses: actions/setup-node@v4 + with: + cache: npm + cache-dependency-path: package-lock.json + node-version-file: .nvmrc + - name: "build: install the required dependencies" run: npm ci From c97a706f581f5fb323dfc8920b327b4367baed87 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 12:54:10 -0700 Subject: [PATCH 158/214] ci: include the include dir in the slack health score --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 867e4e0c..831b0c29 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -224,3 +224,4 @@ jobs: codecov_token: ${{ secrets.CODECOV_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} extension: js + include: src From ac67ee68b83856f17f8de11b6f3dcfc34c8844c7 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 13:13:04 -0700 Subject: [PATCH 159/214] docs: prefer the proxy option within option while referencing env later --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index af2f08d1..5cb438d7 100644 --- a/README.md +++ b/README.md @@ -392,9 +392,9 @@ with the values found in the GitHub Action event [payload][event-payload]. ### Proxying HTTPS requests -If you need to use a proxy to connect with Slack, you can use the `HTTPS_PROXY` -or `https_proxy` environment variable. In this example we use the Slack App -technique, but configuring a proxy works the same way for all of them: +If you need to use a proxy to connect to Slack, you can use the `proxy` option. +In this example we use the technique that calls a Slack API method, but +configuring a proxy is the same for all techniques: ```yaml - name: Post to a Slack channel via a proxy @@ -408,6 +408,9 @@ technique, but configuring a proxy works the same way for all of them: text: "This message was sent through a proxy" ``` +The `proxy` option can also be provided with the `HTTPS_PROXY` or `https_proxy` +[environment variable][github-environment] from within the GitHub Actions step. + ### Retrying failed requests Sometimes outgoing requests fail due to [rate limits][rate-limits] or similar @@ -455,6 +458,7 @@ All contributions are encouraged! Check out the [files.upload]: https://api.slack.com/messaging/files#upload [files.uploadV2]: https://tools.slack.dev/node-slack-sdk/web-api/#upload-a-file [formatting]: https://api.slack.com/reference/surfaces/formatting +[github-environment]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables [github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables [incoming-webhook]: https://api.slack.com/messaging/webhooks [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook From ac915fa7972ad50c20dc5d18629c3995ef8f3c64 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 13:14:35 -0700 Subject: [PATCH 160/214] docs(style): format markdown according to the deno formatter --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5cb438d7..5c06f947 100644 --- a/README.md +++ b/README.md @@ -280,8 +280,8 @@ convenience of the [`files.uploadV2`][files.uploadV2] method: ### Technique 3: Slack incoming webhook -This technique uses this Action to post a message to a channel or direct -message with [incoming webhooks][incoming-webhook] and a Slack app. +This technique uses this Action to post a message to a channel or direct message +with [incoming webhooks][incoming-webhook] and a Slack app. Incoming webhooks follow the same [formatting][formatting] patterns as other Slack messaging APIs. Posted messages can be as short as a single line of text, @@ -306,8 +306,8 @@ Gather a Slack incoming webhook URL: #### Usage Add the collected webhook from above to a GitHub workflow and configure the step -using [`mrkdwn`][mrkdwn] formatting values for a message or [Block Kit][block-kit] -blocks: +using [`mrkdwn`][mrkdwn] formatting values for a message or +[Block Kit][block-kit] blocks: ```yaml - name: Post a message in a channel @@ -334,8 +334,8 @@ options might be useful. Invalid API requests or unexpected webhook payloads cause a failing response that can be used to fail the GitHub Actions step with the `errors` option. -The `errors` option defaults to `false` so failed requests do not cause the -step to fail. This result can still be gathered from the `ok` output. +The `errors` option defaults to `false` so failed requests do not cause the step +to fail. This result can still be gathered from the `ok` output. ```yaml - name: Send GitHub Action data to a Slack workflow @@ -416,8 +416,8 @@ The `proxy` option can also be provided with the `HTTPS_PROXY` or `https_proxy` Sometimes outgoing requests fail due to [rate limits][rate-limits] or similar [HTTP responses][retry-after] and can be retried later. -The `retries` option can be configured to the needs of your workflow with one -of these values: +The `retries` option can be configured to the needs of your workflow with one of +these values: - `0`: No retries, just hope that things go alright. - `5`: Five retries in five minutes. **Default**. From cf580e62308188693dc355e7ab7e4e79005d97b9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 28 Oct 2024 20:53:47 -0700 Subject: [PATCH 161/214] ci(style): change incoming->method test type to avoid confusion --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 831b0c29..90cab375 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -177,7 +177,7 @@ jobs: timestamp: ${{ steps.blocks.outputs.ts }} name: "tada" - - name: "integration(incoming): confirm the thread ended" + - name: "integration(method): confirm the thread ended" run: test -n "${{ steps.done.outputs.time }}" - name: "integration(incoming): post a message via incoming webhook" From 1712817e07fbc0b7aadece04b94e315a9f0ff8d8 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 29 Oct 2024 11:59:41 -0700 Subject: [PATCH 162/214] build: set the container architecture to match the github runners in ci tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 25ae5edb..36ded775 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "ncc build src/index.js --license licenses.txt --source-map", "check": "tsc --noemit --module es2022 --project ./jsconfig.json", - "dev": "act public --eventpath .github/resources/.actions/event.json --secret-file .github/resources/.env --platform ubuntu-latest=node:20-buster", + "dev": "act public --eventpath .github/resources/.actions/event.json --secret-file .github/resources/.env --platform ubuntu-latest=node:20-buster --container-architecture linux/amd64", "lint:fix": "biome check --write", "lint": "biome check", "test": "c8 mocha test/*.spec.js" From adf1dc21f8f74f1f81678eb25e43ef551e6f1e49 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 29 Oct 2024 12:11:05 -0700 Subject: [PATCH 163/214] fix: set the webapi response if the request fails without exiting action --- src/client.js | 57 +++++++++++++-------- src/send.js | 4 +- test/client.spec.js | 121 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 153 insertions(+), 29 deletions(-) diff --git a/src/client.js b/src/client.js index b0fafe26..e5fce2bb 100644 --- a/src/client.js +++ b/src/client.js @@ -52,26 +52,43 @@ export default class Client { setName: (_name) => {}, }, }); - /** - * @type {webapi.WebAPICallResult & MessageResult} - */ - const response = await client.apiCall( - config.inputs.method, - config.content.values, - ); - config.core.setOutput("ok", response.ok); - config.core.setOutput("response", JSON.stringify(response)); - if (!response.ok) { - throw new Error(response.error); - } - if (response.channel) { - config.core.setOutput("channel_id", response.channel); - } - if (response.message?.thread_ts) { - config.core.setOutput("thread_ts", response.message.thread_ts); - } - if (response.ts) { - config.core.setOutput("ts", response.ts); + try { + /** + * @type {webapi.WebAPICallResult & MessageResult=} + */ + const response = await client.apiCall( + config.inputs.method, + config.content.values, + ); + config.core.setOutput("ok", response.ok); + config.core.setOutput("response", JSON.stringify(response)); + if (response.channel) { + config.core.setOutput("channel_id", response.channel); + } + if (response.message?.thread_ts) { + config.core.setOutput("thread_ts", response.message.thread_ts); + } + if (response.ts) { + config.core.setOutput("ts", response.ts); + } + } catch (/** @type {any} */ err) { + const slackErr = /** @type {webapi.WebAPICallError} */ (err); + config.core.setOutput("ok", false); + switch (slackErr.code) { + case webapi.ErrorCode.RequestError: + config.core.setOutput("response", JSON.stringify(slackErr.original)); + break; + case webapi.ErrorCode.HTTPError: + config.core.setOutput("response", JSON.stringify(slackErr)); + break; + case webapi.ErrorCode.PlatformError: + config.core.setOutput("response", JSON.stringify(slackErr.data)); + break; + case webapi.ErrorCode.RateLimitedError: + config.core.setOutput("response", JSON.stringify(slackErr)); + break; + } + throw new Error(err); } } diff --git a/src/send.js b/src/send.js index 456899dd..7b85ed0a 100644 --- a/src/send.js +++ b/src/send.js @@ -16,7 +16,9 @@ export default async function send(core) { config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); } catch (error) { config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); - throw new SlackError(core, error, config.inputs.errors); + if (config.inputs.errors) { + throw new SlackError(core, error); + } } } diff --git a/test/client.spec.js b/test/client.spec.js index ec802724..bbddd796 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -1,5 +1,6 @@ import core from "@actions/core"; import webapi from "@slack/web-api"; +import errors from "@slack/web-api/dist/errors.js"; import { assert } from "chai"; import Client from "../src/client.js"; import Config from "../src/config.js"; @@ -141,10 +142,87 @@ describe("client", () => { }); describe("failure", () => { + it("errors when the request to the api cannot be sent correct", async () => { + /** + * @type {webapi.WebAPICallError} + */ + const response = { + code: "slack_webapi_request_error", + data: { + error: "unexpected_request_failure", + message: "Something bad happened!", + }, + }; + try { + mocks.core.getInput.reset(); + mocks.core.getBooleanInput.withArgs("errors").returns(true); + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`); + mocks.api.rejects(errors.requestErrorWithOriginal(response, true)); + await send(mocks.core); + assert.fail("Expected an error but none was found"); + } catch (error) { + assert.isTrue(mocks.core.setFailed.called); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.deepEqual( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 3); + } + }); + + it("errors when the http portion of the request fails to send", async () => { + /** + * @type {import("axios").AxiosResponse} + */ + const response = { + code: "slack_webapi_http_error", + headers: { + authorization: "none", + }, + data: { + ok: false, + error: "unknown_http_method", + }, + }; + try { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`); + mocks.api.rejects(errors.httpErrorFromResponse(response)); + await send(mocks.core); + assert.fail("Expected an error but none was found"); + } catch (error) { + assert.isFalse(mocks.core.setFailed.called); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + response.body = response.data; + response.data = undefined; + assert.deepEqual( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 3); + } + }); + it("errors when the payload arguments are invalid for the api", async () => { + /** + * @type {webapi.WebAPICallError} + */ const response = { - ok: false, - error: "missing_channel", + code: "slack_webapi_platform_error", + data: { + ok: false, + error: "missing_channel", + }, }; try { mocks.core.getInput.reset(); @@ -152,7 +230,7 @@ describe("client", () => { mocks.core.getInput.withArgs("method").returns("chat.postMessage"); mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`); - mocks.api.resolves(response); + mocks.api.rejects(errors.platformErrorFromResult(response)); await send(mocks.core); assert.fail("Expected an error but none was found"); } catch (error) { @@ -171,16 +249,43 @@ describe("client", () => { it("returns the api error and details without a exit failing", async () => { const response = { - ok: false, - error: "missing_channel", + code: "slack_webapi_platform_error", + data: { + ok: false, + error: "missing_channel", + }, }; try { - mocks.core.getInput.reset(); - mocks.core.getBooleanInput.withArgs("errors").returns(false); mocks.core.getInput.withArgs("method").returns("chat.postMessage"); mocks.core.getInput.withArgs("token").returns("xoxb-example"); mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`); - mocks.api.resolves(response); + mocks.api.rejects(errors.platformErrorFromResult(response)); + await send(mocks.core); + assert.fail("Expected an error but none was found"); + } catch (error) { + assert.isFalse(mocks.core.setFailed.called); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.deepEqual( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 3); + } + }); + + it("errors if rate limit responses are returned after retries", async () => { + const response = { + code: "slack_webapi_rate_limited_error", + retryAfter: 12, + }; + try { + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`); + mocks.api.rejects(errors.rateLimitedErrorWithDelay(12)); await send(mocks.core); assert.fail("Expected an error but none was found"); } catch (error) { From 85eeec7faaee4323ed36796bdf947bf0d7176f2d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 30 Oct 2024 20:31:17 -0700 Subject: [PATCH 164/214] fix: configure webhook retries before making the request --- src/webhook.js | 15 ++++++++------- test/webhook.spec.js | 7 +++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/webhook.js b/src/webhook.js index ed1f2015..1f017b36 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -1,4 +1,4 @@ -import { exponentialDelay, linearDelay } from "axios-retry"; +import axiosRetry, { exponentialDelay, linearDelay } from "axios-retry"; import { HttpsProxyAgent } from "https-proxy-agent"; import Config from "./config.js"; import SlackError from "./errors.js"; @@ -16,16 +16,17 @@ export default class Webhook { throw new SlackError(config.core, "No webhook was provided to post to"); } /** - * @type {import("axios").AxiosRequestConfig & import("axios-retry").IAxiosRetryConfig} + * @type {import("axios-retry").IAxiosRetryConfig} + * @see {@link https://www.npmjs.com/package/axios-retry} */ - const options = { - ...this.retries(config.inputs.retries), - ...this.proxies(config), - }; + const retries = this.retries(config.inputs.retries); + axiosRetry(config.axios, retries); const response = await config.axios.post( config.inputs.webhook, config.content.values, - options, + { + ...this.proxies(config), + }, ); config.core.setOutput("ok", response.status === 200); config.core.setOutput("response", JSON.stringify(response.data)); diff --git a/test/webhook.spec.js b/test/webhook.spec.js index 336550f2..7410a127 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -24,10 +24,9 @@ describe("webhook", () => { const [url, payload, options] = mocks.axios.post.getCall(0).args; assert.equal(url, "https://hooks.slack.com"); assert.deepEqual(payload, { message: "hello" }); - assert.equal( - /** @type {import("axios-retry").IAxiosRetryConfig} */ (options) - .retries, - 5, + assert.deepEqual( + /** @type {import("axios").AxiosRequestConfig} */(options), + {}, ); } catch (err) { console.error(err); From 713e85f34f84f4f542d61420b6a26255e794b31b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 30 Oct 2024 20:32:06 -0700 Subject: [PATCH 165/214] fix: retry failed webhook requests based on the response code --- src/webhook.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/webhook.js b/src/webhook.js index 1f017b36..3abcddba 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -87,16 +87,29 @@ export default class Webhook { case "0": return { retries: 0 }; case "5": - return { retries: 5, retryDelay: linearDelay(60 * 1000) }; // 5 minutes + return { + retryCondition: axiosRetry.isRetryableError, + retries: 5, + retryDelay: linearDelay(60 * 1000), // 5 minutes + }; case "10": return { + retryCondition: axiosRetry.isRetryableError, retries: 10, retryDelay: (count, err) => exponentialDelay(count, err, 2 * 1000), // 34.12 minutes }; case "RAPID": - return { retries: 12, retryDelay: linearDelay(1 * 1000) }; // 12 seconds + return { + retryCondition: axiosRetry.isRetryableError, + retries: 12, + retryDelay: linearDelay(1 * 1000), // 12 seconds + }; default: - return { retries: 5, retryDelay: linearDelay(60 * 1000) }; // 5 minutes + return { + retryCondition: axiosRetry.isRetryableError, + retries: 5, + retryDelay: linearDelay(60 * 1000), // 5 minutes + }; } } } From 6741210265edc88e2a81dc0bd3b951ecf3713970 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 30 Oct 2024 20:53:36 -0700 Subject: [PATCH 166/214] test: confirm both webhook techniques return successful outputs --- test/webhook.spec.js | 47 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/test/webhook.spec.js b/test/webhook.spec.js index 7410a127..f25f9645 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -11,22 +11,55 @@ describe("webhook", () => { }); describe("success", () => { - it("sends the parsed payload to the provided webhook", async () => { + it("sends the parsed payload to the provided webhook trigger", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); + mocks.core.getInput.withArgs("payload").returns("drinks: coffee"); + mocks.axios.post.returns( + Promise.resolve({ status: 200, data: { ok: true } }), + ); + try { + await send(mocks.core); + assert.equal(mocks.axios.post.getCalls().length, 1); + const [url, payload, options] = mocks.axios.post.getCall(0).args; + assert.equal(url, "https://hooks.slack.com"); + assert.deepEqual(payload, { drinks: "coffee" }); + assert.deepEqual(options, {}); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify({ ok: true }), + ); + } catch (err) { + console.error(err); + assert.fail("Failed to send the webhook"); + } + }); + + it("sends the parsed payload to the provided incoming webhook", async () => { mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); - mocks.core.getInput.withArgs("payload").returns('"message":"hello"'); - mocks.axios.post.returns(Promise.resolve("LGTM")); + mocks.core.getInput.withArgs("payload").returns("text: greetings"); + mocks.axios.post.returns(Promise.resolve({ status: 200, data: "ok" })); try { await send(mocks.core); assert.equal(mocks.axios.post.getCalls().length, 1); const [url, payload, options] = mocks.axios.post.getCall(0).args; assert.equal(url, "https://hooks.slack.com"); - assert.deepEqual(payload, { message: "hello" }); - assert.deepEqual( - /** @type {import("axios").AxiosRequestConfig} */(options), - {}, + assert.deepEqual(payload, { text: "greetings" }); + assert.deepEqual(options, {}); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify("ok"), ); } catch (err) { console.error(err); From 18980a38d29e39b7dca5b445b85576107d089302 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Wed, 30 Oct 2024 21:51:19 -0700 Subject: [PATCH 167/214] fix: catch failed axios responses from failed webhook requests --- src/webhook.js | 24 +++++++++++------- test/webhook.spec.js | 59 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/webhook.js b/src/webhook.js index 3abcddba..1136c09a 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -21,16 +21,22 @@ export default class Webhook { */ const retries = this.retries(config.inputs.retries); axiosRetry(config.axios, retries); - const response = await config.axios.post( - config.inputs.webhook, - config.content.values, - { + await config.axios + .post(config.inputs.webhook, config.content.values, { ...this.proxies(config), - }, - ); - config.core.setOutput("ok", response.status === 200); - config.core.setOutput("response", JSON.stringify(response.data)); - config.core.debug(JSON.stringify(response.data)); + }) + .then((response) => { + config.core.setOutput("ok", response.status === 200); + config.core.setOutput("response", JSON.stringify(response.data)); + config.core.debug(JSON.stringify(response.data)); + }) + .catch((error) => { + const response = error.toJSON(); + config.core.setOutput("ok", response.status === 200); + config.core.setOutput("response", JSON.stringify(response.message)); + config.core.debug(response); + throw new SlackError(config.core, response.message); + }); } /** diff --git a/test/webhook.spec.js b/test/webhook.spec.js index f25f9645..f5b1924c 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -1,4 +1,5 @@ import core from "@actions/core"; +import { AxiosError } from "axios"; import { assert } from "chai"; import Config from "../src/config.js"; import send from "../src/send.js"; @@ -87,6 +88,64 @@ describe("webhook", () => { ); } }); + + it("returns the failures from a webhook trigger", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("webhook-trigger"); + mocks.core.getInput.withArgs("payload").returns("drinks: coffee"); + const response = new AxiosError( + "Request failed with status code 400", + "ERR_BAD_REQUEST", + {}, + {}, + { status: 400 }, + ); + mocks.axios.post.resolves(Promise.reject(response)); + await send(mocks.core); + assert.equal(mocks.axios.post.getCalls().length, 1); + const [url, payload, options] = mocks.axios.post.getCall(0).args; + assert.equal(url, "https://hooks.slack.com"); + assert.deepEqual(payload, { drinks: "coffee" }); + assert.deepEqual(options, {}); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify("Request failed with status code 400"), + ); + }); + + it("returns the failures from an incoming webhook", async () => { + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("payload").returns("textt: oops"); + const response = new AxiosError( + "Request failed with status code 400", + "ERR_BAD_REQUEST", + {}, + {}, + { status: 400 }, + ); + mocks.axios.post.resolves(Promise.reject(response)); + await send(mocks.core); + assert.equal(mocks.axios.post.getCalls().length, 1); + const [url, payload, options] = mocks.axios.post.getCall(0).args; + assert.equal(url, "https://hooks.slack.com"); + assert.deepEqual(payload, { textt: "oops" }); + assert.deepEqual(options, {}); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify("Request failed with status code 400"), + ); + }); }); describe("proxies", () => { From 72702e76fce7b5c4318ae921a271242e549205f7 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 15:01:57 -0700 Subject: [PATCH 168/214] docs: include reference to threading replies to messages --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c06f947..651008a5 100644 --- a/README.md +++ b/README.md @@ -235,8 +235,9 @@ outputs from past steps as inputs to current ones: ##### Replying to a message -Posting threaded replies to a message from a past job can be done by including -the `thread_ts` attribute of the parent message in the `payload`: +Posting [threaded replies to a message][messaging-threads] from a past job can +be done by including the `thread_ts` attribute of the parent message in the +`payload`: ```yaml - name: Initiate a deployment @@ -464,6 +465,7 @@ All contributions are encouraged! Check out the [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook [interactivity]: https://api.slack.com/messaging/interactivity [job-step]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idsteps +[messaging-threads]: https://api.slack.com/messaging/sending#threading [methods]: https://api.slack.com/methods [mrkdwn]: https://api.slack.com/reference/surfaces/formatting [plans]: https://slack.com/pricing From 1c1e442798852731c2dc209ce4a94f8222cd8834 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 18:17:44 -0700 Subject: [PATCH 169/214] docs: share the possible expected outputs for techniques --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 651008a5..d4e628d7 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,24 @@ these values: text: "status: all things are going good" ``` +## Expected outputs + +Each technique above [outputs values][github-outputs] that can be used as inputs +in following steps of a GitHub workflow. + +The following outputs are returned with each of the techniques: + +- `time`: `number`. The Unix [epoch time][epoch] that the job completed. +- `ok`: `boolean`. If the request completed with success. +- `response`: `string`. The [response][response] from the request as stringified + JSON. + +While these outputs are returned with certain Slack API methods: + +- `channel_id`: `string`. The channel ID from the response. +- `ts`: `string`. The timestamp of the Slack event. +- `thread_ts`: `string`. The timestamp of a Slack message. + ## License This project is licensed under the [MIT license](LICENSE). @@ -453,6 +471,7 @@ All contributions are encouraged! Check out the [chat:write]: https://api.slack.com/scopes/chat:write [config-tokens]: https://api.slack.com/reference/manifests#config-tokens [contributing]: .github/contributing.md +[epoch]: https://en.wikipedia.org/wiki/Unix_time [event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6 [event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads [examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows @@ -460,6 +479,7 @@ All contributions are encouraged! Check out the [files.uploadV2]: https://tools.slack.dev/node-slack-sdk/web-api/#upload-a-file [formatting]: https://api.slack.com/reference/surfaces/formatting [github-environment]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables +[github-outputs]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs [github-variables]: https://docs.github.com/en/actions/learn-github-actions/variables [incoming-webhook]: https://api.slack.com/messaging/webhooks [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook @@ -471,6 +491,7 @@ All contributions are encouraged! Check out the [plans]: https://slack.com/pricing [rate-limits]: https://api.slack.com/apis/rate-limits [repo-secret]: https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository +[response]: https://api.slack.com/web#responses [retry-after]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After [scopes]: https://api.slack.com/scopes [slack-web-api]: https://tools.slack.dev/node-slack-sdk/web-api From d4e83d677700a7bf02ec230864a1874258dae18e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 19:04:38 -0700 Subject: [PATCH 170/214] fix: return the channel id from methods that return it as channel.id --- src/client.js | 9 ++++++--- test/client.spec.js | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/client.js b/src/client.js index e5fce2bb..49aa799c 100644 --- a/src/client.js +++ b/src/client.js @@ -54,7 +54,7 @@ export default class Client { }); try { /** - * @type {webapi.WebAPICallResult & MessageResult=} + * @type {webapi.WebAPICallResult & import("@slack/web-api").ChatPostMessageResponse & import("@slack/web-api").ConversationsCreateResponse} */ const response = await client.apiCall( config.inputs.method, @@ -62,8 +62,11 @@ export default class Client { ); config.core.setOutput("ok", response.ok); config.core.setOutput("response", JSON.stringify(response)); - if (response.channel) { - config.core.setOutput("channel_id", response.channel); + if (response.channel?.id ?? response.channel) { + config.core.setOutput( + "channel_id", + response.channel?.id ?? response.channel, + ); } if (response.message?.thread_ts) { config.core.setOutput("thread_ts", response.message.thread_ts); diff --git a/test/client.spec.js b/test/client.spec.js index bbddd796..90e744e6 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -107,6 +107,44 @@ describe("client", () => { } }); + it("calls 'conversations.create' with the given token and content", async () => { + try { + const args = { + name: "pull-request-review-010101", + }; + const response = { + ok: true, + channel: { + id: "C0101010101", + name: "pull-request-review-010101", + is_channel: true, + created: 1730425428, + }, + }; + mocks.core.getInput.withArgs("method").returns("chat.postMessage"); + mocks.core.getInput.withArgs("token").returns("xoxb-example"); + mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args)); + mocks.api.resolves(response); + await send(mocks.core); + assert.deepEqual(mocks.api.getCall(0).firstArg, "chat.postMessage"); + assert.deepEqual(mocks.api.getCall(0).lastArg, args); + assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); + assert.equal(mocks.core.setOutput.getCall(0).lastArg, true); + assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); + assert.equal( + mocks.core.setOutput.getCall(1).lastArg, + JSON.stringify(response), + ); + assert.equal(mocks.core.setOutput.getCall(2).firstArg, "channel_id"); + assert.equal(mocks.core.setOutput.getCall(2).lastArg, "C0101010101"); + assert.equal(mocks.core.setOutput.getCall(3).firstArg, "time"); + assert.equal(mocks.core.setOutput.getCalls().length, 4); + } catch (error) { + console.error(error); + assert.fail("Unexpected error when calling the method"); + } + }); + it("calls 'files.uploadV2' with the provided token and content", async () => { try { const args = { From 47ecafe38605c29fbb198fb09f6ac47578c8cda1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 19:16:54 -0700 Subject: [PATCH 171/214] docs: include an example workflow that uses outputs as inputs --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index d4e628d7..a51ac892 100644 --- a/README.md +++ b/README.md @@ -455,6 +455,32 @@ While these outputs are returned with certain Slack API methods: - `ts`: `string`. The timestamp of the Slack event. - `thread_ts`: `string`. The timestamp of a Slack message. +### Example responses + +The following snippet shows how multiple steps can be chained together to create +a Slack channel before posting a message: + +```yaml +- name: Create a new Slack channel for recent changes + id: conversation + uses: ./ + with: + method: conversations.create + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + name: pull-request-review-${{ github.sha }} + +- name: Send the pull request link into the Slack channel + if: ${{ steps.conversation.outputs.ok }} + uses: ./ + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_TOKEN }} + payload: | + channel: ${{ steps.conversation.outputs.channel_id }} + text: "A PR was created : ${{ github.event.pull_request.html_url }}" +``` + ## License This project is licensed under the [MIT license](LICENSE). From 1ebcdfbb8aa460e5e0ed55442b7a833beaf99947 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 19:20:16 -0700 Subject: [PATCH 172/214] docs(style): include a joining word between dates and time --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a51ac892..7c8057de 100644 --- a/README.md +++ b/README.md @@ -478,7 +478,7 @@ a Slack channel before posting a message: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ steps.conversation.outputs.channel_id }} - text: "A PR was created : ${{ github.event.pull_request.html_url }}" + text: "A PR was created : ${{ github.event.pull_request.html_url }}" ``` ## License From c0cfedbf47b61713a7ac21b91777c4182a5ed02e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 19:23:21 -0700 Subject: [PATCH 173/214] ci: error if the api method or webhook fail to send data --- .github/workflows/develop.yml | 9 +++++++++ .github/workflows/test.yml | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 94d214ff..a8907c94 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -25,6 +25,7 @@ jobs: id: wfb uses: ./ with: + errors: true webhook: ${{ secrets.SLACK_WEBHOOK_TRIGGER }} webhook-type: webhook-trigger payload: | @@ -37,6 +38,7 @@ jobs: id: api uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -46,6 +48,7 @@ jobs: id: slack uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -61,6 +64,7 @@ jobs: id: stats uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -73,6 +77,7 @@ jobs: id: finished uses: ./ with: + errors: true method: chat.update token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -88,6 +93,7 @@ jobs: - name: Include ending statistics uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -97,6 +103,7 @@ jobs: - name: Celebrate wins uses: ./ with: + errors: true method: reactions.add token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -107,6 +114,7 @@ jobs: id: file uses: ./ with: + errors: true method: files.uploadV2 token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -117,6 +125,7 @@ jobs: - name: Write one final webhook uses: ./ with: + errors: true webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} webhook-type: incoming-webhook payload: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 90cab375..aa57ada3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,6 +66,7 @@ jobs: id: wfb uses: ./ with: + errors: true webhook: ${{ secrets.SLACK_WEBHOOK_TRIGGER }} webhook-type: webhook-trigger payload: | @@ -82,6 +83,7 @@ jobs: id: message uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -95,6 +97,7 @@ jobs: id: blocks uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -114,6 +117,7 @@ jobs: id: timer uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -131,6 +135,7 @@ jobs: id: finished uses: ./ with: + errors: true method: chat.update token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -148,6 +153,7 @@ jobs: id: done uses: ./ with: + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -159,6 +165,7 @@ jobs: id: file uses: ./ with: + errors: true method: files.uploadV2 token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -170,6 +177,7 @@ jobs: - name: "integration(method): react to the completed update message" uses: ./ with: + errors: true method: reactions.add token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | @@ -184,6 +192,7 @@ jobs: id: incoming uses: ./ with: + errors: true webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} webhook-type: incoming-webhook payload: | @@ -207,6 +216,7 @@ jobs: id: payload_file uses: ./ with: + errors: true payload-file-path: ./.github/resources/.slack/incoming-webhook.json payload-templated: true webhook: ${{ secrets.SLACK_INCOMING_WEBHOOK }} From 0dd9fb1d422fc674162b9217c67b96bd95ea6d4b Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 19:31:29 -0700 Subject: [PATCH 174/214] docs(fix): use the action branch instead of a local build setup --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c8057de..76e94ea3 100644 --- a/README.md +++ b/README.md @@ -463,7 +463,7 @@ a Slack channel before posting a message: ```yaml - name: Create a new Slack channel for recent changes id: conversation - uses: ./ + uses: slackapi/slack-github-action@v2-development with: method: conversations.create token: ${{ secrets.SLACK_BOT_TOKEN }} @@ -472,7 +472,7 @@ a Slack channel before posting a message: - name: Send the pull request link into the Slack channel if: ${{ steps.conversation.outputs.ok }} - uses: ./ + uses: slackapi/slack-github-action@v2-development with: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} From 53b44cac03e5330043746ad1758db6d58274caa0 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 19:38:51 -0700 Subject: [PATCH 175/214] fix: remove custom typings and depend on api results instead --- src/client.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/client.js b/src/client.js index 49aa799c..3bff0bc1 100644 --- a/src/client.js +++ b/src/client.js @@ -11,21 +11,6 @@ import SlackError from "./errors.js"; * @see {@link https://api.slack.com/methods/} */ export default class Client { - /** - * Known response values from messages that are included in outputs. - * @typedef MessageResultDetails - * @prop {string} [thread_ts] - timestamp of the top threaded message. - */ - - /** - * Possible response values related to messages from the API. - * @typedef MessageResult - Possible message values from API methods. - * @prop {MessageResultDetails} [message] - additional message details. - * @prop {string} [ts] - timestamp of the message. - * @prop {string} [channel] - ID of the channel. - * @see {@link https://api.slack.com/methods/chat.postMessage#examples} - */ - /** * Perform the API call configured with the input payload. * @param {Config} config From a2f063c40e4122d91cf955952d32fed02ee8ffc1 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 31 Oct 2024 19:43:04 -0700 Subject: [PATCH 176/214] docs(style): remove spacing between two steps to be more consistent --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 76e94ea3..a1a799fe 100644 --- a/README.md +++ b/README.md @@ -469,7 +469,6 @@ a Slack channel before posting a message: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | name: pull-request-review-${{ github.sha }} - - name: Send the pull request link into the Slack channel if: ${{ steps.conversation.outputs.ok }} uses: slackapi/slack-github-action@v2-development From 93bb73d09a4b860bd076ee219be9d3560addca7e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 1 Nov 2024 16:16:01 -0700 Subject: [PATCH 177/214] ci(fix): tag and release this action with all included files built in dist --- .github/workflows/publish.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 39c9aaf6..11a53e69 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,8 +14,9 @@ jobs: ref: ${{ github.event.release.tag_name }} - name: Install deps and build run: npm ci && npm run build - - uses: JasonEtco/build-and-tag-action@v2 + - uses: teunmooij/github-versioned-release@v1.2.1 + with: + exclude: LICENSE + template: javascript-action env: GITHUB_TOKEN: ${{ github.token }} - with: - tag_name: ${{ github.event.release.tag_name }} From 464750e5be71e74a2598d25bc5ab88edaf8cb95c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 1 Nov 2024 16:25:50 -0700 Subject: [PATCH 178/214] ci(fix): set the node version to the version saved in project --- .github/workflows/publish.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 11a53e69..f3300458 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,13 +8,26 @@ jobs: build: runs-on: ubuntu-latest steps: - - name: Checkout + - name: Checkout the current code uses: actions/checkout@v4 with: ref: ${{ github.event.release.tag_name }} - - name: Install deps and build - run: npm ci && npm run build - - uses: teunmooij/github-versioned-release@v1.2.1 + + - name: Configure the runtime node + uses: actions/setup-node@v4 + with: + cache: npm + cache-dependency-path: package-lock.json + node-version-file: .nvmrc + + - name: Install project dependencies + run: npm ci + + - name: Build a production release + run: npm run build + + - name: Distribute the latest tagged release + uses: teunmooij/github-versioned-release@v1.2.1 with: exclude: LICENSE template: javascript-action From 1ac83ed80a711a073b859dc994ff588ca68959bd Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Fri, 1 Nov 2024 16:30:03 -0700 Subject: [PATCH 179/214] ci: remove the excluded repo license from tagged builds for more legal clarity --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3300458..212e1aad 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,6 +6,7 @@ on: jobs: build: + name: Build and tag a new version runs-on: ubuntu-latest steps: - name: Checkout the current code @@ -29,7 +30,6 @@ jobs: - name: Distribute the latest tagged release uses: teunmooij/github-versioned-release@v1.2.1 with: - exclude: LICENSE template: javascript-action env: GITHUB_TOKEN: ${{ github.token }} From 595105d5faa63c8214092f561d96b9ed28836198 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 4 Nov 2024 10:32:50 -0800 Subject: [PATCH 180/214] docs: guide the great documentation reader down a clear path Co-authored-by: Fil Maj --- README.md | 10 +++++----- src/client.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a1a799fe..02c403b7 100644 --- a/README.md +++ b/README.md @@ -125,19 +125,19 @@ in JSON or YAML format: ### Technique 2: Slack API method -A bot token or user token or [token of some other kind][tokens] can be used to +A bot token or user token or [token of some other kind][tokens] must be used to call one of [the Slack API methods][methods] with this technique. #### Setup -The exact Slack API [method][methods] used will require setting various sets of +Different Slack API [methods][methods] require different [scopes][scopes], but setup should be similar for all methods: 1. [Create a Slack app][apps] for your workspace or use an existing app. -2. Select a [method][methods] to call and add the required **scopes** to your +2. Depending on the [method][methods] you wish to call, add the required **scopes** to your app under the **OAuth & Permissions** page on app settings. 3. Install the app to your workspace using the **Install App** page. -4. Find the [token][tokens] minted for the Slack API method being called from +4. Once your app is installed to a workspace, a new [token][tokens] with your app's specified scopes will be minted for that workspace. It is worth noting that tokens are only valid for a single workspace! Find the token on the **OAuth & Permissions** page. 5. Add the token as [a repository secret][repo-secret] called `SLACK_BOT_TOKEN` or something similar and memorable. @@ -444,7 +444,7 @@ in following steps of a GitHub workflow. The following outputs are returned with each of the techniques: -- `time`: `number`. The Unix [epoch time][epoch] that the job completed. +- `time`: `number`. The Unix [epoch time][epoch] that the step completed. - `ok`: `boolean`. If the request completed with success. - `response`: `string`. The [response][response] from the request as stringified JSON. diff --git a/src/client.js b/src/client.js index 3bff0bc1..ca764cb8 100644 --- a/src/client.js +++ b/src/client.js @@ -7,7 +7,7 @@ import SlackError from "./errors.js"; * The Client class creates a WebClient from @slack/web-api for use when calling * various Slack API methods. * - * @see {@link https://slack.dev/node-slack-sdk/web-api/} + * @see {@link https://tools.slack.dev/node-slack-sdk/web-api/} * @see {@link https://api.slack.com/methods/} */ export default class Client { From 5ae8b010cbd83442a9dde4b2fc077ceffd17a61f Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Mon, 4 Nov 2024 10:36:11 -0800 Subject: [PATCH 181/214] docs(fix): make output attribute descriptions read in a written way Co-authored-by: Fil Maj --- action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 5962b94a..a53d64f6 100644 --- a/action.yml +++ b/action.yml @@ -38,9 +38,9 @@ inputs: required: false outputs: time: - description: "The Unix epoch time that the job completed" + description: "The Unix epoch time that the step completed" ok: - description: "If the requested completed without errors" + description: "If the request completed without errors" channel_id: description: "The channel ID of a message posted using a token" response: From 457c43b215c8581722977a6e4aa4b2be34c2b3b3 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 11:15:57 -0800 Subject: [PATCH 182/214] docs(style): format the recent suggestions with a deno formatter --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 02c403b7..dbcf69c8 100644 --- a/README.md +++ b/README.md @@ -130,15 +130,18 @@ call one of [the Slack API methods][methods] with this technique. #### Setup -Different Slack API [methods][methods] require different -[scopes][scopes], but setup should be similar for all methods: +Different Slack API [methods][methods] require different [scopes][scopes], but +setup should be similar for all methods: 1. [Create a Slack app][apps] for your workspace or use an existing app. -2. Depending on the [method][methods] you wish to call, add the required **scopes** to your - app under the **OAuth & Permissions** page on app settings. +2. Depending on the [method][methods] you wish to call, add the required + **scopes** to your app under the **OAuth & Permissions** page on app + settings. 3. Install the app to your workspace using the **Install App** page. -4. Once your app is installed to a workspace, a new [token][tokens] with your app's specified scopes will be minted for that workspace. It is worth noting that tokens are only valid for a single workspace! Find the token on - the **OAuth & Permissions** page. +4. Once your app is installed to a workspace, a new [token][tokens] with your + app's specified scopes will be minted for that workspace. It is worth noting + that tokens are only valid for a single workspace! Find the token on the + **OAuth & Permissions** page. 5. Add the token as [a repository secret][repo-secret] called `SLACK_BOT_TOKEN` or something similar and memorable. 6. [Add this Action as a step][job-step] to your GitHub workflow and set the From 30943b5b491b7afbef1fa99d3c79f54a7def2e55 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 11:26:29 -0800 Subject: [PATCH 183/214] docs: include a direct link to creating an app and app settings --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dbcf69c8..74b6bd0f 100644 --- a/README.md +++ b/README.md @@ -133,10 +133,10 @@ call one of [the Slack API methods][methods] with this technique. Different Slack API [methods][methods] require different [scopes][scopes], but setup should be similar for all methods: -1. [Create a Slack app][apps] for your workspace or use an existing app. -2. Depending on the [method][methods] you wish to call, add the required - **scopes** to your app under the **OAuth & Permissions** page on app - settings. +1. [Create a Slack app][apps-new] for your workspace or use an existing app. +2. Depending on the Slack API [method][methods] you wish to call, add the + required **scopes** to your app under the **OAuth & Permissions** page on + [app settings][apps]. 3. Install the app to your workspace using the **Install App** page. 4. Once your app is installed to a workspace, a new [token][tokens] with your app's specified scopes will be minted for that workspace. It is worth noting @@ -296,9 +296,9 @@ or be formatted with [Block Kit][block-kit] to build visual components. Gather a Slack incoming webhook URL: -1. [Create a Slack app][apps] for your workspace or use an existing app. +1. [Create a Slack app][apps-new] for your workspace or use an existing app. 2. Add the [`incoming-webhook`][incoming-webhook-scope] bot scope under **OAuth - & Permissions** page on app settings. + & Permissions** page on [app settings][apps]. 3. Install the app to your workspace and select a channel to notify from the **Install App** page. 4. Create additional webhooks from the **Incoming Webhooks** page. @@ -493,6 +493,7 @@ All contributions are encouraged! Check out the [contributor's guide][contributing] to learn more. [apps]: https://api.slack.com/apps +[apps-new]: https://api.slack.com/apps/new [block-kit]: https://api.slack.com/surfaces/messages#complex_layouts [chat.postMessage]: https://api.slack.com/methods/chat.postMessage [chat.update]: https://api.slack.com/methods/chat.update From 10260d319e20684ab90cb7c9e7f6a36358b6e3b9 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 11:43:45 -0800 Subject: [PATCH 184/214] docs: remove confusing words around providing a payload to send --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 74b6bd0f..acdccb49 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,8 @@ Start in Slack to create a Slack workflow: 1. [Create a Slack workflow][wfb-create] that starts from a webhook. 2. Copy the webhook URL and [add it as a repository secret][repo-secret] called `SLACK_WEBHOOK_URL`. -3. [Add this Action as a step][job-step] to your GitHub workflow and set the - input payload to send. +3. [Add this Action as a step][job-step] to your GitHub workflow and provide an + input payload to send to the webhook. 4. Configure your Slack workflow to use the payload variables sent from the GitHub Action. You can then update the steps of the Slack workflow to use these values in creative and clever ways. @@ -144,8 +144,8 @@ setup should be similar for all methods: **OAuth & Permissions** page. 5. Add the token as [a repository secret][repo-secret] called `SLACK_BOT_TOKEN` or something similar and memorable. -6. [Add this Action as a step][job-step] to your GitHub workflow and set the - input payload to send. +6. [Add this Action as a step][job-step] to your GitHub workflow and provide an + input payload to send as a message. Methods that require an app configuration token should gather this token from the [app configuration token][config-tokens] settings instead of from a specific From 4e9cf3217b5ffa4c3380f57b3e097a468f5de19f Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 11:47:31 -0800 Subject: [PATCH 185/214] docs(fix): add words to clarify to the steps that maek sense --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index acdccb49..5d1cf0d2 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ setup should be similar for all methods: 5. Add the token as [a repository secret][repo-secret] called `SLACK_BOT_TOKEN` or something similar and memorable. 6. [Add this Action as a step][job-step] to your GitHub workflow and provide an - input payload to send as a message. + input payload to send to the method. Methods that require an app configuration token should gather this token from the [app configuration token][config-tokens] settings instead of from a specific @@ -304,8 +304,8 @@ Gather a Slack incoming webhook URL: 4. Create additional webhooks from the **Incoming Webhooks** page. 5. Add the generated incoming webhook URL as [a repository secret][repo-secret] called `SLACK_WEBHOOK_URL`. -6. [Add this Action as a step][job-step] to your GitHub workflow and set the - input payload to send. +6. [Add this Action as a step][job-step] to your GitHub workflow and set an + input payload to send as a message. #### Usage From 7df120cc11e12d0e2284d2326ed7b02e56f829dd Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 11:49:00 -0800 Subject: [PATCH 186/214] docs(style): replace 'set' w 'provide' to make developer action required --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d1cf0d2..1c68df14 100644 --- a/README.md +++ b/README.md @@ -304,7 +304,7 @@ Gather a Slack incoming webhook URL: 4. Create additional webhooks from the **Incoming Webhooks** page. 5. Add the generated incoming webhook URL as [a repository secret][repo-secret] called `SLACK_WEBHOOK_URL`. -6. [Add this Action as a step][job-step] to your GitHub workflow and set an +6. [Add this Action as a step][job-step] to your GitHub workflow and provide an input payload to send as a message. #### Usage From f1dcedb26b5c76755e9da16c32b59484dcf2726d Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 12:27:08 -0800 Subject: [PATCH 187/214] docs: refer to packaged implementations for flattening and retries --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 1c68df14..67ebb9b0 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,9 @@ delimiter and will also make values stringified: webhook-type: webhook-trigger ``` +Reference to the flattening implementation is available for exploration from +within the [`flat`][flat] package. + ### Parsing templated variables Additional variables provided in the Github event [context][event-context] and @@ -440,6 +443,10 @@ these values: text: "status: all things are going good" ``` +Behind the scenes, [automatic retries][retries] are handled with the +[`@slack/web-api`][slack-web-api] package for Slack API methods, and +[`axios-retry`][axios-retry] when sending with a webhook. + ## Expected outputs Each technique above [outputs values][github-outputs] that can be used as inputs @@ -494,6 +501,7 @@ All contributions are encouraged! Check out the [apps]: https://api.slack.com/apps [apps-new]: https://api.slack.com/apps/new +[axios-retry]: https://www.npmjs.com/package/axios-retry [block-kit]: https://api.slack.com/surfaces/messages#complex_layouts [chat.postMessage]: https://api.slack.com/methods/chat.postMessage [chat.update]: https://api.slack.com/methods/chat.update @@ -506,6 +514,7 @@ All contributions are encouraged! Check out the [examples]: https://github.com/slackapi/slack-github-action/tree/main/example-workflows [files.upload]: https://api.slack.com/messaging/files#upload [files.uploadV2]: https://tools.slack.dev/node-slack-sdk/web-api/#upload-a-file +[flat]: https://www.npmjs.com/package/flat [formatting]: https://api.slack.com/reference/surfaces/formatting [github-environment]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables [github-outputs]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs @@ -521,6 +530,7 @@ All contributions are encouraged! Check out the [rate-limits]: https://api.slack.com/apis/rate-limits [repo-secret]: https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository [response]: https://api.slack.com/web#responses +[retries]: https://tools.slack.dev/node-slack-sdk/web-api/#automatic-retries [retry-after]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After [scopes]: https://api.slack.com/scopes [slack-web-api]: https://tools.slack.dev/node-slack-sdk/web-api From cb6bb43ab5e14da94480e30590b56d5b424253e4 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 12:59:07 -0800 Subject: [PATCH 188/214] docs: reference related information for sometimes response arguments --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 67ebb9b0..6baa3f7d 100644 --- a/README.md +++ b/README.md @@ -461,9 +461,9 @@ The following outputs are returned with each of the techniques: While these outputs are returned with certain Slack API methods: -- `channel_id`: `string`. The channel ID from the response. -- `ts`: `string`. The timestamp of the Slack event. -- `thread_ts`: `string`. The timestamp of a Slack message. +- `channel_id`: `string`. The [channel ID][conversation] included in the response. +- `ts`: `string`. The [timestamp][messaging-timestamp] of the Slack event or message. +- `thread_ts`: `string`. The [timestamp][messaging-timestamp] of a parent Slack message with [threaded replies][messaging-parents]. ### Example responses @@ -508,6 +508,7 @@ All contributions are encouraged! Check out the [chat:write]: https://api.slack.com/scopes/chat:write [config-tokens]: https://api.slack.com/reference/manifests#config-tokens [contributing]: .github/contributing.md +[conversation]: https://api.slack.com/types/conversation [epoch]: https://en.wikipedia.org/wiki/Unix_time [event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6 [event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads @@ -523,7 +524,9 @@ All contributions are encouraged! Check out the [incoming-webhook-scope]: https://api.slack.com/scopes/incoming-webhook [interactivity]: https://api.slack.com/messaging/interactivity [job-step]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idsteps +[messaging-parents]: https://api.slack.com/messaging/retrieving#finding_threads [messaging-threads]: https://api.slack.com/messaging/sending#threading +[messaging-timestamp]: https://api.slack.com/messaging/retrieving#individual_messages [methods]: https://api.slack.com/methods [mrkdwn]: https://api.slack.com/reference/surfaces/formatting [plans]: https://slack.com/pricing From 92ec96e23832867c0c2272e79d85a7c3b59d0edd Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 13:33:22 -0800 Subject: [PATCH 189/214] docs: make the description of action io read as corrected sentances --- action.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/action.yml b/action.yml index a53d64f6..35660108 100644 --- a/action.yml +++ b/action.yml @@ -3,52 +3,52 @@ description: "Post messages and make other API calls or start workflows with web inputs: errors: default: "false" - description: "If the job exits with an error on errors or continues" + description: "If the step exits with an error on errors or continues." required: false method: - description: "The Slack API method to call" + description: "The Slack API method to call." required: false payload: - description: "Attributes that create the content of the request" + description: "Attributes that create the content of the request." required: false payload-delimiter: - description: "Field seperators for nested attributes in the payload" + description: "Field seperator for nested attributes in the input payload." required: false payload-file-path: - description: "Path to a file containing the valid JSON payload" + description: "Path to a file containing a valid input payload." required: false payload-templated: default: "false" - description: "If templated variables in the input payloads should be replaced" + description: "If templated variables in input payloads should be replaced." required: false proxy: - description: "An optional alternate proxied route to for HTTPS connections" + description: "An optional proxied route for HTTPS connections." required: false retries: default: "5" - description: "The method to use when performing retried requests" + description: "The strategy to use when performing retried requests." token: - description: "The authentication value used with the Slack API" + description: "The authentication value used with the Slack API." required: false webhook: - description: "A location for posting request payloads" + description: "A location for posting request payloads." required: false webhook-type: - description: "Posting with either an incoming webhook or webhook trigger" + description: "Option to use either an incoming webhook or webhook trigger." required: false outputs: time: - description: "The Unix epoch time that the step completed" + description: "The Unix epoch time that the step completed." ok: - description: "If the request completed without errors" + description: "If the request completed without errors." channel_id: - description: "The channel ID of a message posted using a token" + description: "The channel ID returned with some of the Slack API methods." response: - description: "A stringified version of the Slack API response" + description: "A JSON stringified version of the Slack API response." thread_ts: - description: "The threaded timestamp on a message posted using a token" + description: "The timestamp of a parent Slack message with threaded replies." ts: - description: "The timestamp on a message posted using a token" + description: "The timestamp of a Slack message or event in the response." runs: using: "node20" main: "dist/index.js" From 500969e6267dd6d902d3ce139d67c122bb8a4da6 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 13:44:19 -0800 Subject: [PATCH 190/214] docs: update the action author and description to match techniques --- action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 35660108..3eb664d0 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,6 @@ name: "Slack: Send to Slack" -description: "Post messages and make other API calls or start workflows with webhooks" +author: "slackapi" +description: "Send data to Slack to start a Slack workflow in Workflow Builder, call a Slack API method, or post a message into a channel" inputs: errors: default: "false" From db55f1cd75fa7a1f1f459921127fd8522850bbe4 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 13:52:37 -0800 Subject: [PATCH 191/214] docs: make the input options for payloads either json or yaml --- action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 3eb664d0..437d9557 100644 --- a/action.yml +++ b/action.yml @@ -10,13 +10,13 @@ inputs: description: "The Slack API method to call." required: false payload: - description: "Attributes that create the content of the request." + description: "Attributes that create the content of the request using JSON or YAML." required: false payload-delimiter: description: "Field seperator for nested attributes in the input payload." required: false payload-file-path: - description: "Path to a file containing a valid input payload." + description: "Path to a file containing a valid input payload made of JSON or YAML." required: false payload-templated: default: "false" From bb5ce26ae1d51fd224023aaff35cfaf6c05289bf Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 13:54:50 -0800 Subject: [PATCH 192/214] docs: arrange the outputs according to required then alphabets --- action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/action.yml b/action.yml index 437d9557..db07c14e 100644 --- a/action.yml +++ b/action.yml @@ -38,14 +38,14 @@ inputs: description: "Option to use either an incoming webhook or webhook trigger." required: false outputs: + ok: + description: "If the request completed without returning errors." + response: + description: "A JSON stringified version of the Slack API response." time: description: "The Unix epoch time that the step completed." - ok: - description: "If the request completed without errors." channel_id: description: "The channel ID returned with some of the Slack API methods." - response: - description: "A JSON stringified version of the Slack API response." thread_ts: description: "The timestamp of a parent Slack message with threaded replies." ts: From dae7ca0e48ce390f4e8b0e22b8d40b831aad269a Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 4 Nov 2024 14:16:57 -0800 Subject: [PATCH 193/214] docs: format the readme with the deno formatter again --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6baa3f7d..3fb90404 100644 --- a/README.md +++ b/README.md @@ -461,9 +461,12 @@ The following outputs are returned with each of the techniques: While these outputs are returned with certain Slack API methods: -- `channel_id`: `string`. The [channel ID][conversation] included in the response. -- `ts`: `string`. The [timestamp][messaging-timestamp] of the Slack event or message. -- `thread_ts`: `string`. The [timestamp][messaging-timestamp] of a parent Slack message with [threaded replies][messaging-parents]. +- `channel_id`: `string`. The [channel ID][conversation] included in the + response. +- `ts`: `string`. The [timestamp][messaging-timestamp] of the Slack event or + message. +- `thread_ts`: `string`. The [timestamp][messaging-timestamp] of a parent Slack + message with [threaded replies][messaging-parents]. ### Example responses From 5c670dab404c4eef5fb5955a30e3e5fc1d8fb6ff Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 10:30:58 -0800 Subject: [PATCH 194/214] ci: use the installed linter from packages intead of actions --- .github/workflows/test.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4e248b44..e73b565d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,13 +27,8 @@ jobs: - name: "build: install the required dependencies" run: npm ci - - name: "build: setup the linter for formatting checks" - uses: biomejs/setup-biome@v2 - with: - version: latest - - name: "unit(test): perform lints and formatting checks" - run: biome ci . + run: npm run lint - name: "unit(test): perform check of typings" run: npm run check From 208a39c722487cbde278f87c77baa9835445feb0 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 10:33:35 -0800 Subject: [PATCH 195/214] chore(deps): bump the version of biome to the latest for ci --- package-lock.json | 149 +++++++++++++++++++++++++++++++++++++++++----- package.json | 2 +- 2 files changed, 135 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73c35057..10c5eb1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "markup-js": "^1.5.21" }, "devDependencies": { - "@biomejs/biome": "1.8.3", + "@biomejs/biome": "^1.9.4", "@types/chai": "^4.3.19", "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", @@ -94,9 +94,9 @@ "license": "MIT" }, "node_modules/@biomejs/biome": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.8.3.tgz", - "integrity": "sha512-/uUV3MV+vyAczO+vKrPdOW0Iaet7UnJMU4bNMinggGJTAnBPjCoLEYcyYtYHNnUNYlv4xZMH6hVIQCAozq8d5w==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", "dev": true, "hasInstallScript": true, "license": "MIT OR Apache-2.0", @@ -111,20 +111,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "1.8.3", - "@biomejs/cli-darwin-x64": "1.8.3", - "@biomejs/cli-linux-arm64": "1.8.3", - "@biomejs/cli-linux-arm64-musl": "1.8.3", - "@biomejs/cli-linux-x64": "1.8.3", - "@biomejs/cli-linux-x64-musl": "1.8.3", - "@biomejs/cli-win32-arm64": "1.8.3", - "@biomejs/cli-win32-x64": "1.8.3" + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.8.3.tgz", - "integrity": "sha512-9DYOjclFpKrH/m1Oz75SSExR8VKvNSSsLnVIqdnKexj6NwmiMlKk94Wa1kZEdv6MCOHGHgyyoV57Cw8WzL5n3A==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", "cpu": [ "arm64" ], @@ -138,6 +138,125 @@ "node": ">=14.21.3" } }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", diff --git a/package.json b/package.json index 7bdd101e..a36d3430 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "markup-js": "^1.5.21" }, "devDependencies": { - "@biomejs/biome": "1.8.3", + "@biomejs/biome": "^1.9.4", "@types/chai": "^4.3.19", "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", From 49f7227e73b3c1e637c4493344ec7c6e17ac6a25 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 11:08:12 -0800 Subject: [PATCH 196/214] refactor: remove chained promises while awaiting async blocks --- src/webhook.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/webhook.js b/src/webhook.js index 1136c09a..cb98a3c6 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -21,22 +21,24 @@ export default class Webhook { */ const retries = this.retries(config.inputs.retries); axiosRetry(config.axios, retries); - await config.axios - .post(config.inputs.webhook, config.content.values, { - ...this.proxies(config), - }) - .then((response) => { - config.core.setOutput("ok", response.status === 200); - config.core.setOutput("response", JSON.stringify(response.data)); - config.core.debug(JSON.stringify(response.data)); - }) - .catch((error) => { - const response = error.toJSON(); - config.core.setOutput("ok", response.status === 200); - config.core.setOutput("response", JSON.stringify(response.message)); - config.core.debug(response); - throw new SlackError(config.core, response.message); - }); + try { + const response = await config.axios.post( + config.inputs.webhook, + config.content.values, + { + ...this.proxies(config), + }, + ); + config.core.setOutput("ok", response.status === 200); + config.core.setOutput("response", JSON.stringify(response.data)); + config.core.debug(JSON.stringify(response.data)); + } catch (/** @type {any} */ err) { + const response = err.toJSON(); + config.core.setOutput("ok", response.status === 200); + config.core.setOutput("response", JSON.stringify(response.message)); + config.core.debug(response); + throw new SlackError(config.core, response.message); + } } /** From 12d40ae343f6b9a94965fa95d756252d772f2d04 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 12:13:53 -0800 Subject: [PATCH 197/214] build: configure typescript compiler options to use node 20 mappings https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping#node-20 --- jsconfig.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jsconfig.json b/jsconfig.json index 64512e1b..05eec262 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -4,12 +4,16 @@ "baseUrl": ".", "checkJs": true, "esModuleInterop": true, - "module": "es2022", + "lib": [ + "ES2023" + ], + "module": "node16", "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true + "target": "ES2022" }, "exclude": [ "node_modules" From aaed7f68ce1a5816593689dbb44a46e1e87e9a32 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 12:14:37 -0800 Subject: [PATCH 198/214] chore(deps): bump @types/node to the latest version of node 20 --- package-lock.json | 17 +++++++++-------- package.json | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10c5eb1d..c927aee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@types/js-yaml": "^4.0.9", "@types/markup-js": "^1.5.0", "@types/mocha": "^10.0.7", - "@types/node": "^20.14.10", + "@types/node": "^20.17.6", "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", "c8": "^10.1.2", @@ -671,12 +671,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", - "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", + "version": "20.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", + "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/retry": { @@ -2343,9 +2343,10 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" }, "node_modules/universal-user-agent": { "version": "6.0.1", diff --git a/package.json b/package.json index a36d3430..ebafb2fa 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@types/js-yaml": "^4.0.9", "@types/markup-js": "^1.5.0", "@types/mocha": "^10.0.7", - "@types/node": "^20.14.10", + "@types/node": "^20.17.6", "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", "c8": "^10.1.2", From b4b438fd871cab817217b6e73ad0fffaae84aae3 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 20:15:09 -0800 Subject: [PATCH 199/214] test: assert that a test fails for the correct reason --- test/content.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/content.spec.js b/test/content.spec.js index cbda9e68..ea6f63d0 100644 --- a/test/content.spec.js +++ b/test/content.spec.js @@ -306,7 +306,7 @@ describe("content", () => { mocks.core.getInput.withArgs("payload-file-path").returns("unknown.md"); try { await send(mocks.core); - assert.fail("Failed to throw for nonexistent files"); + assert.fail("Failed to throw for an unknown extension"); } catch (err) { assert.include( mocks.core.setFailed.lastCall.firstArg.toString(), From a8d832ced4cea669f3cd47c80b7ccf83f449a40e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 23:19:27 -0800 Subject: [PATCH 200/214] ci: wrap timestamp values into strings to avoid float strangeness --- .github/workflows/develop.yml | 4 ++-- .github/workflows/test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index a8907c94..ad36f682 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -70,7 +70,7 @@ jobs: payload: | channel: ${{ steps.slack.outputs.channel_id }} text: "Started at `${{ steps.slack.outputs.time }}`" - thread_ts: ${{ steps.slack.outputs.ts }} + thread_ts: "${{ steps.slack.outputs.ts }}" - name: Countdown run: sleep 3 - name: Launch time is now @@ -99,7 +99,7 @@ jobs: payload: | channel: ${{ steps.slack.outputs.channel_id }} text: "Finished at `${{ steps.finished.outputs.time }}`" - thread_ts: ${{ steps.stats.outputs.thread_ts }} + thread_ts: "${{ steps.stats.outputs.thread_ts }}" - name: Celebrate wins uses: ./ with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e73b565d..ff50c22a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -135,7 +135,7 @@ jobs: token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | channel: ${{ secrets.SLACK_CHANNEL_ID }} - ts: ${{ steps.blocks.outputs.ts }} + ts: "${{ steps.blocks.outputs.ts }}" text: ":gear: Event processed!" attachments: - color: "28a745" From cef366e077143b5650061b2300dc447d7a29b1da Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Mon, 11 Nov 2024 23:38:17 -0800 Subject: [PATCH 201/214] feat: raise detailed slackerrors without repeating outputs - core.setfailed is called just once, if errors=true, before exiting - webhook init errors before attempting to post are raised uncaught --- src/client.js | 12 +++---- src/content.js | 22 ++++++------ src/errors.js | 19 ++++++---- src/index.js | 1 + src/send.js | 3 +- src/webhook.js | 30 ++++++++-------- test/client.spec.js | 34 ++++++++++-------- test/config.spec.js | 57 +++++++++++++++++++----------- test/content.spec.js | 81 ++++++++++++++++++++++++++++-------------- test/webhook.spec.js | 84 +++++++++++++++++++++++++++++++------------- 10 files changed, 215 insertions(+), 128 deletions(-) diff --git a/src/client.js b/src/client.js index ca764cb8..b77aa6a4 100644 --- a/src/client.js +++ b/src/client.js @@ -76,7 +76,7 @@ export default class Client { config.core.setOutput("response", JSON.stringify(slackErr)); break; } - throw new Error(err); + throw new SlackError(config.core, err); } } @@ -95,13 +95,11 @@ export default class Client { return { httpsAgent: new HttpsProxyAgent(proxy), }; - } catch (err) { - config.core.warning( - "Failed to configure the HTTPS proxy agent so using default configurations.", - ); - console.error(err); + } catch (/** @type {any} */ err) { + throw new SlackError(config.core, "Failed to configure the HTTPS proxy", { + cause: err, + }); } - return undefined; } /** diff --git a/src/content.js b/src/content.js index 89e87c58..d040800c 100644 --- a/src/content.js +++ b/src/content.js @@ -58,7 +58,7 @@ export default class Content { if (!config.inputs.payload) { throw new SlackError( config.core, - "Invalid input! No payload content found", + "Invalid input! No payload content was provided", ); } try { @@ -89,14 +89,13 @@ export default class Content { return JSON.parse(wrap); } return JSON.parse(trimmed); - } catch (error) { - if (error instanceof Error) { - config.core.error(error); - } - console.error(error); + } catch (/** @type {any} */ error) { throw new SlackError( config.core, "Invalid input! Failed to parse contents of the provided payload", + { + cause: error, + }, ); } } @@ -134,16 +133,15 @@ export default class Content { } throw new SlackError( config.core, - `Failed to parse file extension ${config.inputs.payloadFilePath}`, + `Invalid input! Failed to parse file extension ${config.inputs.payloadFilePath}`, ); - } catch (error) { - if (error instanceof Error) { - config.core.error(error); - } - console.error(error); + } catch (/** @type {any} */ error) { throw new SlackError( config.core, "Invalid input! Failed to parse contents of the provided payload file", + { + cause: error, + }, ); } } diff --git a/src/errors.js b/src/errors.js index b3e78e26..b2d4ce0c 100644 --- a/src/errors.js +++ b/src/errors.js @@ -5,19 +5,24 @@ import core from "@actions/core"; */ export default class SlackError extends Error { /** - * @param {core} core - GitHub Actions core utilities. + * @typedef Options + * @property {Error} [cause] - thrown exception of this error. + */ + + /** + * @param {core} _core - GitHub Actions core utilities. * @param {any} error - The error message to throw. - * @param {boolean} fails - if the exit should be forced. + * @param {Options} options - configurations of erroring. */ - constructor(core, error, fails = true) { + constructor(_core, error, options = {}) { if (error instanceof Error) { - super(error.message); + super(error.message, { cause: options.cause }); } else { - super(error); + super(error, { cause: options.cause }); } this.name = "SlackError"; - if (fails) { - core.setFailed(error); + if (error.stack) { + this.stack = error.stack; } } } diff --git a/src/index.js b/src/index.js index f8b9ad0c..fcdf2277 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ try { } catch (error) { if (error instanceof Error) { core.error(error.message); + core.debug(`${error.name} cause: ${error.cause}`); core.debug(`${error.name} stack: ${error.stack}`); } else { core.error(`${error}`); diff --git a/src/send.js b/src/send.js index 7b85ed0a..e54e84a1 100644 --- a/src/send.js +++ b/src/send.js @@ -14,9 +14,10 @@ export default async function send(core) { try { await post(config); config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); - } catch (error) { + } catch (/** @type {any} */ error) { config.core.setOutput("time", Math.floor(new Date().valueOf() / 1000)); if (config.inputs.errors) { + core.setFailed(error); throw new SlackError(core, error); } } diff --git a/src/webhook.js b/src/webhook.js index cb98a3c6..6c4f0c14 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -49,16 +49,13 @@ export default class Webhook { */ proxies(config) { const { webhook, proxy } = config.inputs; + if (!webhook) { + throw new SlackError(config.core, "No webhook was provided to proxy to"); + } + if (!proxy) { + return undefined; + } try { - if (!webhook) { - throw new SlackError( - config.core, - "No webhook was provided to proxy to", - ); - } - if (!proxy) { - return undefined; - } if (new URL(webhook).protocol !== "https:") { config.core.debug( "The webhook destination is not HTTPS so skipping the HTTPS proxy", @@ -75,14 +72,17 @@ export default class Webhook { httpsAgent: new HttpsProxyAgent(proxy), proxy: false, }; + default: + throw new SlackError( + config.core, + `Unsupported URL protocol: ${proxy}`, + ); } - } catch (err) { - config.core.warning( - "Failed to configure the HTTPS proxy agent so using default configurations.", - ); - console.error(err); + } catch (/** @type {any} */ err) { + throw new SlackError(config.core, "Failed to configure the HTTPS proxy", { + cause: err, + }); } - return undefined; } /** diff --git a/test/client.spec.js b/test/client.spec.js index 90e744e6..6ce9aee4 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -4,6 +4,7 @@ import errors from "@slack/web-api/dist/errors.js"; import { assert } from "chai"; import Client from "../src/client.js"; import Config from "../src/config.js"; +import SlackError from "../src/errors.js"; import send from "../src/send.js"; import { mocks } from "./index.spec.js"; @@ -26,11 +27,12 @@ describe("client", () => { try { await new Client().post(config); assert.fail("Failed to throw for missing input"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "No API method was provided for use", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "No API method was provided for use"); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -48,11 +50,12 @@ describe("client", () => { try { await new Client().post(config); assert.fail("Failed to throw for missing input"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "No token was provided to post with", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "No token was provided to post with"); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); }); @@ -364,11 +367,12 @@ describe("client", () => { const client = new Client(); client.proxies(config); assert.fail("An invalid proxy URL was not thrown as error!"); - } catch { - assert.include( - mocks.core.warning.lastCall.firstArg, - "Failed to configure the HTTPS proxy agent so using default configurations.", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "Failed to configure the HTTPS proxy"); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); }); diff --git a/test/config.spec.js b/test/config.spec.js index 1d2dc77e..556af5b9 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -1,5 +1,6 @@ import { assert } from "chai"; import Config from "../src/config.js"; +import SlackError from "../src/errors.js"; import send from "../src/send.js"; import { mocks } from "./index.spec.js"; @@ -41,14 +42,18 @@ describe("config", () => { new Config(mocks.core); assert.fail("Failed to error when invalid inputs are provided"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Invalid input! Either the token or webhook is required - not both.", - ); - assert.isTrue(mocks.core.setSecret.withArgs("xoxb-example").called); - assert.isTrue( - mocks.core.setSecret.withArgs("https://example.com").called, - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! Either the token or webhook is required - not both.", + ); + assert.isTrue(mocks.core.setSecret.withArgs("xoxb-example").called); + assert.isTrue( + mocks.core.setSecret.withArgs("https://example.com").called, + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -57,10 +62,14 @@ describe("config", () => { new Config(mocks.core); assert.fail("Failed to error when invalid inputs are provided"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Missing input! Either a token or webhook is required to take action.", - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Missing input! Either a token or webhook is required to take action.", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -70,10 +79,14 @@ describe("config", () => { new Config(mocks.core); assert.fail("Failed to error when invalid inputs are provided"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Missing input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Missing input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -84,10 +97,14 @@ describe("config", () => { new Config(mocks.core); assert.fail("Failed to error when invalid inputs are provided"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Invalid input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! The webhook type must be 'incoming-webhook' or 'webhook-trigger'.", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); }); diff --git a/test/content.spec.js b/test/content.spec.js index ea6f63d0..be279d44 100644 --- a/test/content.spec.js +++ b/test/content.spec.js @@ -3,6 +3,7 @@ import core from "@actions/core"; import { assert } from "chai"; import Config from "../src/config.js"; import Content from "../src/content.js"; +import SlackError from "../src/errors.js"; import send from "../src/send.js"; import { mocks } from "./index.spec.js"; @@ -41,11 +42,15 @@ describe("content", () => { try { await send(mocks.core); assert.fail("Failed to throw for invalid input"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Invalid input! Just the payload or payload file path is required.", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! Just the payload or payload file path is required.", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); }); @@ -163,10 +168,14 @@ describe("content", () => { new Content().getContentPayload(config); assert.fail("Failed to throw for missing payload content"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Invalid input! No payload content found", - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! No payload content was provided", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -175,11 +184,15 @@ describe("content", () => { try { await send(mocks.core); assert.fail("Failed to throw for invalid JSON"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg.toString(), - "Invalid input! Failed to parse contents of the provided payload", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! Failed to parse contents of the provided payload", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); }); @@ -282,10 +295,14 @@ describe("content", () => { new Content().getContentPayloadFilePath(config); assert.fail("Failed to throw for the wrong payload type"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "Invalid input! No payload found for content", - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! No payload found for content", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -295,10 +312,14 @@ describe("content", () => { await send(mocks.core); assert.fail("Failed to throw for nonexistent files"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg.toString(), - "Invalid input! Failed to parse contents of the provided payload file", - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! Failed to parse contents of the provided payload file", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -308,10 +329,18 @@ describe("content", () => { await send(mocks.core); assert.fail("Failed to throw for an unknown extension"); } catch (err) { - assert.include( - mocks.core.setFailed.lastCall.firstArg.toString(), - "Invalid input! Failed to parse contents of the provided payload file", - ); + if (err instanceof SlackError) { + assert.include( + err.message, + "Invalid input! Failed to parse contents of the provided payload file", + ); + assert.include( + err.cause.message, + "Invalid input! Failed to parse file extension unknown.md", + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); }); diff --git a/test/webhook.spec.js b/test/webhook.spec.js index f5b1924c..2994490d 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -2,6 +2,7 @@ import core from "@actions/core"; import { AxiosError } from "axios"; import { assert } from "chai"; import Config from "../src/config.js"; +import SlackError from "../src/errors.js"; import send from "../src/send.js"; import Webhook from "../src/webhook.js"; import { mocks } from "./index.spec.js"; @@ -81,11 +82,12 @@ describe("webhook", () => { try { await new Webhook().post(config); assert.fail("Failed to throw for missing input"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "No webhook was provided to post to", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "No webhook was provided to post to"); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -103,7 +105,15 @@ describe("webhook", () => { { status: 400 }, ); mocks.axios.post.resolves(Promise.reject(response)); - await send(mocks.core); + try { + await send(mocks.core); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "Request failed with status code 400"); + } else { + assert.fail("Failed to throw a SlackError", err); + } + } assert.equal(mocks.axios.post.getCalls().length, 1); const [url, payload, options] = mocks.axios.post.getCall(0).args; assert.equal(url, "https://hooks.slack.com"); @@ -112,10 +122,6 @@ describe("webhook", () => { assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); - assert.equal( - mocks.core.setOutput.getCall(1).lastArg, - JSON.stringify("Request failed with status code 400"), - ); }); it("returns the failures from an incoming webhook", async () => { @@ -132,7 +138,15 @@ describe("webhook", () => { { status: 400 }, ); mocks.axios.post.resolves(Promise.reject(response)); - await send(mocks.core); + try { + await send(mocks.core); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "Request failed with status code 400"); + } else { + assert.fail("Failed to throw a SlackError", err); + } + } assert.equal(mocks.axios.post.getCalls().length, 1); const [url, payload, options] = mocks.axios.post.getCall(0).args; assert.equal(url, "https://hooks.slack.com"); @@ -141,10 +155,6 @@ describe("webhook", () => { assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok"); assert.equal(mocks.core.setOutput.getCall(0).lastArg, false); assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response"); - assert.equal( - mocks.core.setOutput.getCall(1).lastArg, - JSON.stringify("Request failed with status code 400"), - ); }); }); @@ -160,11 +170,12 @@ describe("webhook", () => { try { new Webhook().proxies(config); assert.fail("Failed to throw for missing input"); - } catch { - assert.include( - mocks.core.setFailed.lastCall.firstArg, - "No webhook was provided to proxy to", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "No webhook was provided to proxy to"); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); @@ -218,11 +229,34 @@ describe("webhook", () => { const webhook = new Webhook(); webhook.proxies(config); assert.fail("An invalid proxy URL was not thrown as error!"); - } catch { - assert.include( - mocks.core.warning.lastCall.firstArg, - "Failed to configure the HTTPS proxy agent so using default configurations.", - ); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "Failed to configure the HTTPS proxy"); + } else { + assert.fail("Failed to throw a SlackError", err); + } + } + }); + + it("fails to configure proxies with an unknown url protocol", async () => { + const proxy = "ssh://"; + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + mocks.core.getInput.withArgs("proxy").returns(proxy); + try { + const config = new Config(mocks.core); + const webhook = new Webhook(); + webhook.proxies(config); + assert.fail("An unknown URL protocol was not thrown as error!"); + } catch (err) { + if (err instanceof SlackError) { + assert.include(err.message, "Failed to configure the HTTPS proxy"); + assert.include(err.cause.message, "Unsupported URL protocol"); + } else { + assert.fail("Failed to throw a SlackError", err); + } } }); }); From 9df8033a69909b6efbd33c4c0f6fac16b2478966 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 07:52:43 -0800 Subject: [PATCH 202/214] temp: break ci an an experiment in erroring outputs --- .github/workflows/test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ff50c22a..b9ff1557 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,11 +78,11 @@ jobs: id: message uses: ./ with: - errors: true + errors: false method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - channel: ${{ secrets.SLACK_CHANNEL_ID }} + chanel: ${{ secrets.SLACK_CHANNEL_ID }} text: ":checkered_flag: Action happens at " - name: "integration(method): confirm a message was posted" @@ -96,6 +96,7 @@ jobs: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | + ..{ channel: ${{ secrets.SLACK_CHANNEL_ID }} text: ":eyes: Event received..." attachments: From 06eb48b85a2e28f5bcc1013d2b81b9dc6c2bc134 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 07:56:15 -0800 Subject: [PATCH 203/214] temp: remove the ci step that checks prior success --- .github/workflows/test.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b9ff1557..cd85d0d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,9 +85,6 @@ jobs: chanel: ${{ secrets.SLACK_CHANNEL_ID }} text: ":checkered_flag: Action happens at " - - name: "integration(method): confirm a message was posted" - run: test -n "${{ steps.message.outputs.ts }}" - - name: "integration(method): post a message with blocks" id: blocks uses: ./ From 4f678e0219be124734ed6eaad400268b36f39d28 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:05:06 -0800 Subject: [PATCH 204/214] revert: testing done to break ci and show error messages --- .github/workflows/test.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd85d0d4..ff50c22a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,13 +78,16 @@ jobs: id: message uses: ./ with: - errors: false + errors: true method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - chanel: ${{ secrets.SLACK_CHANNEL_ID }} + channel: ${{ secrets.SLACK_CHANNEL_ID }} text: ":checkered_flag: Action happens at " + - name: "integration(method): confirm a message was posted" + run: test -n "${{ steps.message.outputs.ts }}" + - name: "integration(method): post a message with blocks" id: blocks uses: ./ @@ -93,7 +96,6 @@ jobs: method: chat.postMessage token: ${{ secrets.SLACK_BOT_TOKEN }} payload: | - ..{ channel: ${{ secrets.SLACK_CHANNEL_ID }} text: ":eyes: Event received..." attachments: From e66be35a689e412b214ff302698c645a76ba7591 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:14:02 -0800 Subject: [PATCH 205/214] fix: accept lowercased retries with surrounding space --- src/client.js | 2 +- src/config.js | 2 +- src/webhook.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.js b/src/client.js index b77aa6a4..bb59d313 100644 --- a/src/client.js +++ b/src/client.js @@ -108,7 +108,7 @@ export default class Client { * @returns {import("@slack/web-api").RetryOptions} */ retries(option) { - switch (option) { + switch (option?.trim().toUpperCase()) { case "0": return { retries: 0 }; case "5": diff --git a/src/config.js b/src/config.js index 4894da9f..1b018e9b 100644 --- a/src/config.js +++ b/src/config.js @@ -120,7 +120,7 @@ export default class Config { * Confirm the configurations are correct enough to continue. */ validate() { - switch (this.inputs.retries) { + switch (this.inputs.retries.trim().toUpperCase()) { case this.Retries.ZERO: case this.Retries.FIVE: case this.Retries.TEN: diff --git a/src/webhook.js b/src/webhook.js index 6c4f0c14..0d6df7d4 100644 --- a/src/webhook.js +++ b/src/webhook.js @@ -91,7 +91,7 @@ export default class Webhook { * @returns {import("axios-retry").IAxiosRetryConfig} */ retries(option) { - switch (option) { + switch (option?.trim().toUpperCase()) { case "0": return { retries: 0 }; case "5": From 4826bb16d4ad3c2340ac49a873b896960856d9cb Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:31:12 -0800 Subject: [PATCH 206/214] fix: return an error if an invalid retries option is uesd --- src/config.js | 3 ++- test/config.spec.js | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/config.js b/src/config.js index 1b018e9b..43581aa7 100644 --- a/src/config.js +++ b/src/config.js @@ -127,7 +127,8 @@ export default class Config { case this.Retries.RAPID: break; default: - core.warning( + throw new SlackError( + core, `Invalid input! An unknown "retries" value was used: ${this.inputs.retries}`, ); } diff --git a/test/config.spec.js b/test/config.spec.js index 556af5b9..294b00c2 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -135,19 +135,25 @@ describe("config", () => { }); describe("validate", () => { - it("warns if an invalid retries option is provided", async () => { + it("errors if an invalid retries option is provided", async () => { mocks.axios.post.returns(Promise.resolve("LGTM")); mocks.core.getInput.withArgs("retries").returns("FOREVER"); mocks.core.getInput .withArgs("webhook") .returns("https://hooks.slack.com"); mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); - await send(mocks.core); - assert.isTrue( - mocks.core.warning.calledWith( - 'Invalid input! An unknown "retries" value was used: FOREVER', - ), - ); + try { + await send(mocks.core); + } catch (err) { + if (err instanceof SlackError) { + assert.include( + err.message, + 'Invalid input! An unknown "retries" value was used: FOREVER', + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } + } }); }); }); From 5f00b17fc562389f8c0b71a438155667583c955e Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:34:04 -0800 Subject: [PATCH 207/214] test: confirm that lowercased and spaced rapid retries work --- test/client.spec.js | 12 +++++++++++- test/config.spec.js | 21 +++++++++++++++++++++ test/webhook.spec.js | 16 +++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/test/client.spec.js b/test/client.spec.js index 6ce9aee4..1561f89c 100644 --- a/test/client.spec.js +++ b/test/client.spec.js @@ -419,7 +419,17 @@ describe("client", () => { ); }); - it('attempts a "rapid" burst of "12" retries in seconds', async () => { + it('attempts a "rapid " burst of "12" retries in seconds', async () => { + const webhook = new Client(); + const result = webhook.retries("rapid "); + assert.equal( + result.retries, + webapi.retryPolicies.rapidRetryPolicy.retries, + ); + assert.equal(result.factor, webapi.retryPolicies.rapidRetryPolicy.factor); + }); + + it('attempts a "RAPID" burst of "12" retries in seconds', async () => { const webhook = new Client(); const result = webhook.retries("RAPID"); assert.equal( diff --git a/test/config.spec.js b/test/config.spec.js index 294b00c2..633d015f 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -135,6 +135,27 @@ describe("config", () => { }); describe("validate", () => { + it('allow the "retries" option with lowercased space', async () => { + mocks.axios.post.returns(Promise.resolve("LGTM")); + mocks.core.getInput.withArgs("retries").returns(" rapid "); + mocks.core.getInput + .withArgs("webhook") + .returns("https://hooks.slack.com"); + mocks.core.getInput.withArgs("webhook-type").returns("incoming-webhook"); + try { + await send(mocks.core); + } catch (err) { + if (err instanceof SlackError) { + assert.include( + err.message, + 'Invalid input! An unknown "retries" value was used: FOREVER', + ); + } else { + assert.fail("Failed to throw a SlackError", err); + } + } + }); + it("errors if an invalid retries option is provided", async () => { mocks.axios.post.returns(Promise.resolve("LGTM")); mocks.core.getInput.withArgs("retries").returns("FOREVER"); diff --git a/test/webhook.spec.js b/test/webhook.spec.js index 2994490d..f94cf097 100644 --- a/test/webhook.spec.js +++ b/test/webhook.spec.js @@ -307,7 +307,21 @@ describe("webhook", () => { ); }); - it('attempts a "rapid" burst of "12" retries in seconds', async () => { + it('attempts a " rapid" burst of "12" retries in seconds', async () => { + const webhook = new Webhook(); + const result = webhook.retries(" rapid"); + assert.equal(result.retries, 12); + if (!result.retryDelay) { + assert.fail("No retry delay found!"); + } + assert.equal( + result.retryDelay(12, mocks.errors.axios.network_failed), + 12000, + "12th retry after 12 seconds", + ); + }); + + it('attempts a "RAPID" burst of "12" retries in seconds', async () => { const webhook = new Webhook(); const result = webhook.retries("RAPID"); assert.equal(result.retries, 12); From 6a4513b7716cae6458343b3324c3d2d0ae7c2929 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:44:30 -0800 Subject: [PATCH 208/214] build: set the mit license in the packagelock for @actions/github --- package-lock.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package-lock.json b/package-lock.json index c927aee8..47a3ed0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz", "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", + "license": "MIT", "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", From 7d08fdbde02b1b31f7e2b3d7819b9e008a90f276 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:46:36 -0800 Subject: [PATCH 209/214] chore(deps): bump chai and related types to the latest version --- package-lock.json | 26 ++++++++++++++++++-------- package.json | 4 ++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47a3ed0b..574eba6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.4", - "@types/chai": "^4.3.19", + "@types/chai": "^5.0.1", "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", "@types/markup-js": "^1.5.0", @@ -30,7 +30,7 @@ "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", "c8": "^10.1.2", - "chai": "^5.1.1", + "chai": "^5.1.2", "mocha": "^10.8.2", "mocha-suppress-logs": "^0.5.1", "sinon": "^19.0.2", @@ -630,9 +630,19 @@ } }, "node_modules/@types/chai": { - "version": "4.3.19", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.19.tgz", - "integrity": "sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.0.1.tgz", + "integrity": "sha512-5T8ajsg3M/FOncpLYW7sdOcD6yf4+722sze/tc4KQV0P8Z2rAr3SAuHCIkYmYpt8VbcQlnz8SxlOlPQYefe4cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, @@ -1043,9 +1053,9 @@ } }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ebafb2fa..25653e83 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.9.4", - "@types/chai": "^4.3.19", + "@types/chai": "^5.0.1", "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", "@types/markup-js": "^1.5.0", @@ -54,7 +54,7 @@ "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", "c8": "^10.1.2", - "chai": "^5.1.1", + "chai": "^5.1.2", "mocha": "^10.8.2", "mocha-suppress-logs": "^0.5.1", "sinon": "^19.0.2", From 9032d4640559f68b51573ce7352e5bc608ae28b6 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:48:00 -0800 Subject: [PATCH 210/214] chore(deps): bump the types of mocha to the latest version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 574eba6d..3f5870d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", "@types/markup-js": "^1.5.0", - "@types/mocha": "^10.0.7", + "@types/mocha": "^10.0.9", "@types/node": "^20.17.6", "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", @@ -675,9 +675,9 @@ "license": "MIT" }, "node_modules/@types/mocha": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", - "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==", + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", + "integrity": "sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 25653e83..6dc7182b 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types/flat": "^5.0.5", "@types/js-yaml": "^4.0.9", "@types/markup-js": "^1.5.0", - "@types/mocha": "^10.0.7", + "@types/mocha": "^10.0.9", "@types/node": "^20.17.6", "@types/sinon": "^17.0.3", "@vercel/ncc": "^0.38.1", From cba3f4443b55eeefc4d22fc3a60e49e4f7b3e007 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:50:37 -0800 Subject: [PATCH 211/214] chore(deps): bump the ncc packaging tool to the latest verison --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f5870d1..c488bd68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@types/mocha": "^10.0.9", "@types/node": "^20.17.6", "@types/sinon": "^17.0.3", - "@vercel/ncc": "^0.38.1", + "@vercel/ncc": "^0.38.2", "c8": "^10.1.2", "chai": "^5.1.2", "mocha": "^10.8.2", @@ -714,10 +714,11 @@ "license": "MIT" }, "node_modules/@vercel/ncc": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.1.tgz", - "integrity": "sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==", + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.2.tgz", + "integrity": "sha512-3yel3jaxUg9pHBv4+KeC9qlbdZPug+UMtUOlhvpDYCMSgcNSrS2Hv1LoqMsOV7hf2lYscx+BESfJOIla1WsmMQ==", "dev": true, + "license": "MIT", "bin": { "ncc": "dist/ncc/cli.js" } diff --git a/package.json b/package.json index 6dc7182b..e4cab33d 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@types/mocha": "^10.0.9", "@types/node": "^20.17.6", "@types/sinon": "^17.0.3", - "@vercel/ncc": "^0.38.1", + "@vercel/ncc": "^0.38.2", "c8": "^10.1.2", "chai": "^5.1.2", "mocha": "^10.8.2", From 7f34d31b19bcef86ff7b6eb02317887fb8927848 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:51:49 -0800 Subject: [PATCH 212/214] chore(deps): bump typescript to the latest released version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c488bd68..90d82296 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "mocha": "^10.8.2", "mocha-suppress-logs": "^0.5.1", "sinon": "^19.0.2", - "typescript": "^5.5.3" + "typescript": "^5.6.3" }, "engines": { "node": ">=20.0.0", @@ -2329,9 +2329,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index e4cab33d..c28eec67 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,6 @@ "mocha": "^10.8.2", "mocha-suppress-logs": "^0.5.1", "sinon": "^19.0.2", - "typescript": "^5.5.3" + "typescript": "^5.6.3" } } From 3982e204d2ae590e908dd1e279e63933da566c8c Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Tue, 12 Nov 2024 08:54:04 -0800 Subject: [PATCH 213/214] chore(deps): bump and fix changes needed for the latest flat version --- package-lock.json | 22 ++++++++++++++++++---- package.json | 2 +- src/content.js | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90d82296..fddd179c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@slack/web-api": "^7.7.0", "axios": "^1.7.7", "axios-retry": "^4.5.0", - "flat": "^5.0.2", + "flat": "^6.0.1", "https-proxy-agent": "^7.0.5", "js-yaml": "^4.1.0", "markup-js": "^1.5.21" @@ -1329,11 +1329,15 @@ } }, "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-6.0.1.tgz", + "integrity": "sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==", + "license": "BSD-3-Clause", "bin": { "flat": "cli.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/follow-redirects": { @@ -2497,6 +2501,16 @@ "node": ">=10" } }, + "node_modules/yargs-unparser/node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index c28eec67..17246824 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@slack/web-api": "^7.7.0", "axios": "^1.7.7", "axios-retry": "^4.5.0", - "flat": "^5.0.2", + "flat": "^6.0.1", "https-proxy-agent": "^7.0.5", "js-yaml": "^4.1.0", "markup-js": "^1.5.21" diff --git a/src/content.js b/src/content.js index d040800c..6594a32d 100644 --- a/src/content.js +++ b/src/content.js @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import github from "@actions/github"; -import flatten from "flat"; +import { flatten } from "flat"; import yaml from "js-yaml"; import markup from "markup-js"; import Config from "./config.js"; From 7b2b0f9d8759d71981508eb1f9f31af77c62ea14 Mon Sep 17 00:00:00 2001 From: "@zimeg" Date: Thu, 14 Nov 2024 12:45:58 -0800 Subject: [PATCH 214/214] ci: revert the test runs for changes to the ci file within prs --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ff50c22a..1d7ae2af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,6 @@ name: Tests on: - pull_request: + pull_request_target: push: branches: - main