diff --git a/.eslintrc.js b/.eslintrc.js index c0c37e3..24bfade 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,9 +19,28 @@ module.exports = { '**/.prettier*', '**/.version*', '**/*.md', - '**/*.js' + '**/*.js', + '**/*.js.map', + '**/*.d.ts' + ], + overrides: [ + { + files: ["*"], + "rules": { + "prefer-rest-params": "off" + } + } ], rules: { - "prettier/prettier": "error" + "prettier/prettier": "error", + "@typescript-eslint/ban-types": [ + "error", + { + "types": { + "Function": false + }, + "extendDefaults": true + } + ] }, }; diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 7ade64b..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,69 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: - - '*' - - '!main' - schedule: - - cron: '36 15 * * 5' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/payment-paytr.yml b/.github/workflows/payment-paytr.yml deleted file mode 100644 index 7882e02..0000000 --- a/.github/workflows/payment-paytr.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Payment-paytr tests pipeline -on: [push] - -jobs: - unit-tests: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./packages/medusa/payment-paytr - - steps: - - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.9.1 - with: - access_token: ${{ github.token }} - - - name: Checkout - uses: actions/checkout@v2.3.5 - with: - fetch-depth: 0 - - - name: Setup Node.js environment - uses: actions/setup-node@v2.4.1 - with: - node-version: "14" - cache: "npm" - - - name: create test env file - run: | - touch .env.test - echo MERCHANT_ID=${{ secrets.PAYTR_MERCHANT_ID }} >> .env.test - echo MERCHANT_KEY=${{ secrets.PAYTR_MERCHANT_KEY }} >> .env.test - echo MERCHANT_SALT=${{ secrets.PAYTR_MERCHANT_SALT }} >> .env.test - echo TOKEN_ENDPOINT=${{ secrets.PAYTR_TOKEN_ENDPOINT }} >> .env.test - echo REFUND_ENDPOINT=${{ secrets.PAYTR_REFUND_ENDPOINT }} >> .env.test - env: - MERCHANT_ID: ${{secrets.PAYTR_MERCHANT_ID}} - MERCHANT_KEY: ${{secrets.PAYTR_MERCHANT_KEY}} - MERCHANT_SALT: ${{secrets.PAYTR_MERCHANT_SALT}} - TOKEN_ENDPOINT: ${{secrets.PAYTR_TOKEN_ENDPOINT}} - REFUND_ENDPOINT: ${{secrets.PAYTR_REFUND_ENDPOINT}} - - - name: 'npm install' - run: npm install - - - name: 'run unit tests' - run: npm run test diff --git a/README.md b/README.md index e86d93f..7c644cd 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ Issues Licence Contributing -CodeQL security analysis status

@@ -48,9 +47,9 @@ make it easier for you to find them :rocket: # Plugins -| Name | Target | Badges | -|---------------------------|---------------|----------------------------------------------| -| `payment-paytr` | `medusa` | | +| Name | Target | Badges | +|---------------------------------------------------------------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`medusa-plugin-sentry (Link)`](https://github.com/adrien2p/medusa-plugins/tree/main/packages/medusa-plugin-sentry) | `medusa` | Downloads per month NPM Version | [![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/cloudy.png)](#discussions) @@ -71,26 +70,4 @@ If you found the package helpful consider supporting me with a coffee # Contribute -Contributions are welcome! You can look at the contribution [guidelines](./CONTRIBUTING.md) - -## Test a package locally - -Go to the plugin package directory and Build your local plugin - -```bash -npm run build -npm pack -``` - -Then copy the tgz file newly creating by the `pack` command into your project. - -## Target project - -In your target project past the tgz file previously copied. -then update your `package.json` - -```bash -"my-package": "file:my-package-1.0.0.tgz" -``` - -now run `npm i` and run your project \ No newline at end of file +Contributions are welcome! You can look at the contribution [guidelines](./CONTRIBUTING.md) \ No newline at end of file diff --git a/package.json b/package.json index e552835..fe996af 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ ] }, "scripts": { - "build": "run-p clean run-s build:api /node_modules/.bin/lerna/tsc -b ./packages/tsconfig.api-base.json", "bootstrap": "./node_modules/.bin/lerna/lerna bootstrap", "clean": "./node_modules/.bin/lerna/lerna run --parallel clean", "release": "./node_modules/.bin/lerna/lerna publish", @@ -46,6 +45,6 @@ "npm-run-all": "^4.1.5", "prettier": "^2.5.0", "tslint": "~6.1.0", - "typescript": "^3.7.5" + "typescript": "^4.5.5" } } diff --git a/packages/medusa-plugin-sentry/.gitignore b/packages/medusa-plugin-sentry/.gitignore index c712284..d398854 100644 --- a/packages/medusa-plugin-sentry/.gitignore +++ b/packages/medusa-plugin-sentry/.gitignore @@ -1,7 +1,11 @@ .idea -/lib -/dist -api/ + +/api +/handlers +/utils +/types +/services + node_modules .DS_store **/.DS_Store @@ -9,8 +13,8 @@ node_modules dist coverage -/tsconfig.tsbuildinfo -/package-lock.json -/yarn.json +tsconfig.tsbuildinfo +package-lock.json +yarn.json .env.* \ No newline at end of file diff --git a/packages/medusa-plugin-sentry/README.md b/packages/medusa-plugin-sentry/README.md new file mode 100644 index 0000000..3caf592 --- /dev/null +++ b/packages/medusa-plugin-sentry/README.md @@ -0,0 +1,219 @@ +

+ Downloads per month + NPM Version + Contributors + Awesome medusajs + Documentation + Twitter + Discord + Npm download + Activity + Issues + Licence + Contributing +

+ +# Medusa Sentry plugin + +## Getting started + +First of all, you need to install the plugin as follow +`yarn add @medusa-plugins/medusa-plugni-sentry` + +Then, go to your `medusa-config.js` file and in the plugins collection property add the following at the beginning to be registered first +```javascript +{ + resolve: `@medusa-plugins/medusa-plugin-sentry`, + options: { + dsn: "__YOUR_DSN__", + apiToken: "__YOUR_API_TOKEN__", + integrations: (router, Sentry, Tracing) => { + return [ + new Sentry.Integrations.Http({ tracing: true }), + new Tracing.Integrations.Express({ router }), + ]; + }, + tracesSampleRate: 1.0, + shouldHandleError: (code) => code >= 400, + webHookOptions: { + path: "/sentry/webhook", + secret: "__YOUR_SECRET__", + emitOnIssue: true, + emitOnError: false, + emitOnComment: true, + emitOnEventOrMetricAlert: true, + emitOnInstallOrDeleted: false, + } + }, +}, +``` + +> The `webHookOptions.path` is always attached on the `/admin` domain. Which means that if you specify something like `/sentry` the result path will be `/admin/sentry` + +## Configuration + +You can see above some configuration for the plugin. To be able to know all the options available +you can have a look at +- [NodeOptions](https://github.com/getsentry/sentry-javascript/blob/7304215d875decf0bf555cab82aa90fc1341b27e/packages/node/src/types.ts#L30) + +And here are the plugin configuration types +```typescript +export type SentryWebHookOptions = { + path: string; + secret: string; + emitOnIssue?: boolean | ((req) => Promise); + emitOnError?: boolean | ((req) => Promise); + emitOnComment?: boolean | ((req) => Promise); + emitOnEventOrMetricAlert?: boolean | ((req) => Promise); + emitOnInstallOrDeleted?: boolean | ((req) => Promise); +} + +export type SentryOptions = Omit & { + integrations: Integration[] | ((router: Router, sentry: typeof Sentry, tracing: typeof Tracing) => Integration[]); + shouldHandleError: (code: number) => boolean; + requestHandlerOptions?: RequestHandlerOptions; + enableRequestHandler?: boolean; + enableTracing?: boolean; + webHookOptions?: SentryWebHookOptions, +}; +``` + +## Web hooks + +> Learn more about sentry integration [here](https://docs.sentry.io/product/integrations/integration-platform/) + +With this plugin, you can register the path and options to the web hook you want to make available for sentry +using the `webHookOptions` from the [config](#getting-started). + +To activate the web hook you have to provide the appropriate configurations. + +Once sentry send an event to the web hook, each type of resource will emit +his own event that you can subscribe to using the medusa [subscribers](https://docs.medusajs.com/advanced/backend/subscribers/overview/). + +Here is the list of the event that can be emitted + +```typescript +export enum SentryWebHookEvent { + SENTRY_RECEIVED_ISSUE = 'SentryReceivedIssue', + SENTRY_RECEIVED_ERROR = 'SentryReceivedError', + SENTRY_RECEIVED_COMMENT = 'SentryReceivedComment', + SENTRY_RECEIVED_EVENT_OR_METRIC_ALERT = 'SentryReceivedEventOrMetricAlert', + SENTRY_RECEIVED_INSTALL_OR_DELETED = 'SentryReceivedInstallOrDeleted', +} +``` + +It is also possible to specify a function for each of the `emitOn*` options which take the request as the parameter. From that method you can +resolve any of your services and call it to handle the event. In that case, the even bus will not fire the corresponding event. + +## API + +The sentry plugins will provide you some authenticated end points if you want to get some data about your transactions and events related to the transactions. + +### /admin/sentry-transactions + +This end point allow you to retrieve all your transactions for a given period, here are the allowed query parameters + +```markdown +- organisation - The organisation to fetch the transactions from +- project - The project to fetch the transactions from +- statsPeriod - The period from when to fetch the transactions (default: 24h) +- perPage - The number of transaction per page +- cursor - The cursor to send to fetch the transactions for a given page +``` + +The output of that query looks like the following + +```json +{ + "data": [ + { + "transaction": "POST /admin/customers/:id", + "id": "***", + "project.name": "node-express" + }, + // ... 19 other items + ], + "meta": { + "fields": { + "transaction": "string", + "id": "string", + "project.name": "string" + }, + "units": { + "transaction": null, + "id": null, + "project.name": null + }, + "isMetricsData": false, + "tips": { + "query": null, + "columns": null + } + }, + "next_cursor": "0:20:0", + "prev_cursor": "0:0:0" +} +``` + +### /admin/sentry-transaction-events + +This end point allow you to retrieve all your transaction events for a given period, here are the allowed query parameters + +```markdown +- transaction - The transaction for which the events must be retrieved (e.g "GET /admin/users") +- organisation - The organisation to fetch the transactions from +- project - The project to fetch the transactions from +- statsPeriod - The period from when to fetch the transactions (default: 24h) +- perPage - The number of transaction per page +- cursor - The cursor to send to fetch the transactions for a given page +``` + +The output of that query looks like the following + +```json +{ + "data": [ + { + "spans.db": 46.443939, + "timestamp": "2022-10-11T14:00:11+00:00", + "id": "***", + "transaction.duration": 115, + "spans.http": null, + "project.name": "node-express" + }, + { + "spans.db": 5.561113, + "timestamp": "2022-10-11T13:30:43+00:00", + "id": "***", + "transaction.duration": 18, + "spans.http": null, + "project.name": "node-express" + } + ], + "meta": { + "fields": { + "spans.db": "duration", + "timestamp": "date", + "id": "string", + "transaction.duration": "duration", + "spans.http": "duration", + "project.name": "string" + }, + "units": { + "spans.db": "millisecond", + "timestamp": null, + "id": null, + "transaction.duration": "millisecond", + "spans.http": "millisecond", + "project.name": null + }, + "isMetricsData": false, + "tips": { + "query": null, + "columns": null + } + }, + "prev_cursor": "0:0:0", + "next_cursor": "0:100:0" +} +``` \ No newline at end of file diff --git a/packages/medusa-plugin-sentry/package.json b/packages/medusa-plugin-sentry/package.json index 5275333..a1463d0 100644 --- a/packages/medusa-plugin-sentry/package.json +++ b/packages/medusa-plugin-sentry/package.json @@ -1,25 +1,47 @@ { - "name": "@medusa-plugins/medusa-plugin-sentry", + "name": "medusa-plugin-sentry", "version": "1.0.0", - "main": "index.js", "author": "Adrien de Peretti", + "description": "Sentry plugin for medusajs", + "keywords": [ + "analytics", + "sentry", + "medusa", + "medusajs", + "e-commerce", + "error-management" + ], "license": "MIT", + "files": [ + "api", + "handlers", + "utils", + "types" + ], "scripts": { "build": "run-s clean build:tsc", "build:tsc": "tsc -b", - "clean": "rimraf lib coverage tsconfig.tsbuildinfo" + "clean": "rimraf api handlers utils types coverage tsconfig.tsbuildinfo" }, "peerDependencies": { - "@medusajs/medusa": "^1.4.1" + "@medusajs/medusa": "^1.4.1", + "medusa-interfaces": "^1.3.3", + "typeorm": "^0.2.45" }, "devDependencies": { + "@medusajs/medusa": "^1.4.1", + "medusa-interfaces": "^1.3.3", "ts-node": "^8.6.2", - "@medusajs/medusa": "^1.4.1" + "typeorm": "^0.2.45" }, "dependencies": { "@sentry/node": "^7.14.1", "@sentry/tracing": "^7.14.1", - "@types/express": "types/express", - "express": "^4.18.1" + "@types/express": "^4.17.14", + "axios": "^1.0.0", + "class-validator": "^0.13.2", + "cors": "^2.8.5", + "express": "^4.18.1", + "medusa-core-utils": "^1.1.31" } } diff --git a/packages/medusa-plugin-sentry/src/api/handlers/sentry-transaction-events.ts b/packages/medusa-plugin-sentry/src/api/handlers/sentry-transaction-events.ts new file mode 100644 index 0000000..c8dd3ed --- /dev/null +++ b/packages/medusa-plugin-sentry/src/api/handlers/sentry-transaction-events.ts @@ -0,0 +1,31 @@ +import { Request, Response } from 'express'; +import { validator } from '@medusajs/medusa/dist/utils/validator'; +import SentryService from '../../services/sentry'; +import { IsString } from 'class-validator'; +import { GetSentryTransactionsParams } from './sentry-transaction'; + +export default (token: string) => { + return async (req: Request, res: Response) => { + const { transaction, organisation, project, statsPeriod, perPage, cursor } = await validator( + GetSentryTransactionEventsParams, + req.query + ); + + const sentryService: SentryService = req.scope.resolve(SentryService.RESOLVE_KEY); + const result = await sentryService.fetchTransactionEvents({ + transaction, + organisation, + project, + statsPeriod, + perPage, + cursor, + token, + }); + res.json(result); + }; +}; + +export class GetSentryTransactionEventsParams extends GetSentryTransactionsParams { + @IsString() + transaction: string; +} diff --git a/packages/medusa-plugin-sentry/src/api/handlers/sentry-transaction.ts b/packages/medusa-plugin-sentry/src/api/handlers/sentry-transaction.ts new file mode 100644 index 0000000..6d4229a --- /dev/null +++ b/packages/medusa-plugin-sentry/src/api/handlers/sentry-transaction.ts @@ -0,0 +1,43 @@ +import { Request, Response } from 'express'; +import { validator } from '@medusajs/medusa/dist/utils/validator'; +import SentryService from '../../services/sentry'; +import { IsOptional, IsString } from 'class-validator'; + +export default (token: string) => { + return async (req: Request, res: Response) => { + const { organisation, project, statsPeriod, perPage, cursor } = await validator( + GetSentryTransactionsParams, + req.query + ); + + const sentryService: SentryService = req.scope.resolve(SentryService.RESOLVE_KEY); + const result = await sentryService.fetchSentryTransactions({ + organisation, + project, + statsPeriod, + perPage, + cursor, + token, + }); + res.json(result); + }; +}; + +export class GetSentryTransactionsParams { + @IsString() + organisation: string; + + @IsString() + project: string; + + @IsString() + statsPeriod: string; + + @IsOptional() + @IsString() + perPage?: string; + + @IsOptional() + @IsString() + cursor?: string; +} diff --git a/packages/medusa-plugin-sentry/src/api/handlers/sentry-web-hook.ts b/packages/medusa-plugin-sentry/src/api/handlers/sentry-web-hook.ts new file mode 100644 index 0000000..1164712 --- /dev/null +++ b/packages/medusa-plugin-sentry/src/api/handlers/sentry-web-hook.ts @@ -0,0 +1,66 @@ +import { Request, Response } from 'express'; +import SentryService from '../../services/sentry'; +import { verifySignature } from '../../utils'; +import { SentryWebHookOptions } from '../../types'; + +export default (webHookOptions: SentryWebHookOptions) => { + return async (req: Request, res: Response) => { + if (!verifySignature(req, webHookOptions.secret)) { + return res.sendStatus(401); + } + + res.status(200); + + // Parse the JSON body fields off of the request + const { action, data, installation, actor } = req.body; + const { uuid } = installation || {}; + + // Identify the resource triggering the webhook in Sentry + const resource = req.header('sentry-hook-resource'); + if (!action || !data || !uuid || !resource) { + return res.sendStatus(400); + } + + const sentryService: SentryService = req.scope.resolve(SentryService.RESOLVE_KEY); + + const dataToEmit = { + actor, + action, + data, + installation, + }; + + // Handle webhooks related to issues + if (webHookOptions.emitOnIssue && resource === 'issue') { + await sentryService.handleIssues(dataToEmit); + res.status(200); + } + + // Handle webhooks related to errors + if (webHookOptions.emitOnError && resource === 'error') { + await sentryService.handleErrors(dataToEmit); + res.status(200); + } + + // Handle webhooks related to comments + if (webHookOptions.emitOnComment && resource === 'comment') { + await sentryService.handleComments(dataToEmit); + + res.status(200); + } + + // Handle webhooks related to alerts + if (webHookOptions.emitOnEventOrMetricAlert && (resource === 'event_alert' || resource === 'metric_alert')) { + await sentryService.handleAlerts(dataToEmit); + res.status(200); + } + + // Handle uninstallation webhook + if (webHookOptions.emitOnInstallOrDeleted && resource === 'installation' && action === 'deleted') { + await sentryService.handleInstallation(dataToEmit); + res.status(200); + } + + res.send(); + }; +}; diff --git a/packages/medusa-plugin-sentry/src/api/index.ts b/packages/medusa-plugin-sentry/src/api/index.ts index 952a8e8..edfb36f 100644 --- a/packages/medusa-plugin-sentry/src/api/index.ts +++ b/packages/medusa-plugin-sentry/src/api/index.ts @@ -1,19 +1,16 @@ -import { Router } from 'express'; +import express, { Router } from 'express'; +import cors from 'cors'; import * as Sentry from '@sentry/node'; import * as Tracing from '@sentry/tracing'; -import { NodeOptions } from '@sentry/node/types/types'; -import { Integration } from '@sentry/types/types/integration'; -import { RequestHandlerOptions } from '@sentry/node/types/handlers'; +import authenticate from '@medusajs/medusa/dist/api/middlewares/authenticate'; +import wrapHandler from '@medusajs/medusa/dist/api/middlewares/await-middleware'; +import { getConfigFile } from 'medusa-core-utils'; -export type SentryOptions = Omit & { - integrations: Integration[] | ((router: Router, sentry: typeof Sentry, tracing: typeof Tracing) => Integration[]); - shouldHandleError: (code: number) => boolean; - requestHandlerOptions?: RequestHandlerOptions; - enableRequestHandler?: boolean; - enableTracing?: boolean; -}; - -export default function (rootDirectory, pluginOptions: SentryOptions) { +import { SentryOptions, SentryWebHookOptions } from '../types'; +import sentryTransactionsHandler from './handlers/sentry-transaction'; +import sentryTransactionEventsHandler from './handlers/sentry-transaction-events'; +import sentryWebHookHandler from './handlers/sentry-web-hook'; +export default function (rootDirectory, pluginOptions: SentryOptions): Router { const router = Router(); const { @@ -22,6 +19,7 @@ export default function (rootDirectory, pluginOptions: SentryOptions) { requestHandlerOptions = {}, enableTracing = true, enableRequestHandler = true, + webHookOptions, ...options } = pluginOptions; @@ -41,6 +39,23 @@ export default function (rootDirectory, pluginOptions: SentryOptions) { router.use(Sentry.Handlers.tracingHandler()); } + attachSentryErrorHandler(shouldHandleError); + + if (webHookOptions) { + attachSentryWebHook(router, webHookOptions); + } + + attachAdminEndPoints(router, rootDirectory, pluginOptions); + + return router; +} + +/** + * Attach the sentry error handler in the medusa core + * @param shouldHandleError + */ +function attachSentryErrorHandler(shouldHandleError) { + /* eslint-disable @typescript-eslint/no-var-requires */ const medusaErrorHandler = require('@medusajs/medusa/dist/api/middlewares/error-handler'); const originalMedusaErrorHandler = medusaErrorHandler.default; medusaErrorHandler.default = () => { @@ -59,6 +74,47 @@ export default function (rootDirectory, pluginOptions: SentryOptions) { })(err, req, res, () => void 0); }; }; +} - return router; +/** + * Attach sentry web hook + * @param router + * @param webHookOptions + */ +function attachSentryWebHook(router: Router, webHookOptions: SentryWebHookOptions): void { + router.post( + '/admin' + webHookOptions.path, + express.json(), + express.urlencoded({ extended: true }), + sentryWebHookHandler(webHookOptions) + ); +} + +/** + * Attach specific sentry end point to fetch data under the admin domain + * @param router + * @param rootDirectory + * @param pluginOptions + */ +function attachAdminEndPoints(router, rootDirectory, pluginOptions) { + const { apiToken } = pluginOptions; + const { configModule } = getConfigFile(rootDirectory, 'medusa-config') as { + configModule: { projectConfig: { admin_cors: string } }; + }; + const { projectConfig } = configModule; + + const corsOptions = { + origin: projectConfig.admin_cors.split(','), + credentials: true, + }; + + router.options('/admin/sentry-transactions', cors(corsOptions)); + router.get('/admin/sentry-transactions', authenticate(), wrapHandler(sentryTransactionsHandler(apiToken))); + + router.options('/admin/sentry-transaction-events', cors(corsOptions)); + router.get( + '/admin/sentry-transaction-events', + authenticate(), + wrapHandler(sentryTransactionEventsHandler(apiToken)) + ); } diff --git a/packages/medusa-plugin-sentry/src/services/sentry.ts b/packages/medusa-plugin-sentry/src/services/sentry.ts new file mode 100644 index 0000000..6beae31 --- /dev/null +++ b/packages/medusa-plugin-sentry/src/services/sentry.ts @@ -0,0 +1,226 @@ +import { EventBusService, TransactionBaseService } from '@medusajs/medusa'; +import axios from 'axios'; +import formatRegistrationName from '@medusajs/medusa/dist/utils/format-registration-name'; +import { EntityManager } from 'typeorm'; + +import { SentryOptions, SentryWebHookData, SentryWebHookEvent } from '../types'; +import { isFunction } from '../utils'; + +type InjectedDeps = { + manager: EntityManager; + eventBusService: EventBusService; +}; + +type SentryFetchResult = { + data: Record[]; + meta: unknown; + prev_cursor: string; + next_cursor: string; +}; + +export default class SentryService extends TransactionBaseService { + static readonly RESOLVE_KEY = formatRegistrationName(`${process.cwd()}/services/sentry.js`); + protected readonly sentryApiBaseUrl = 'https://sentry.io/api/0/organizations'; + + protected manager_: EntityManager; + protected transactionManager_: EntityManager | undefined; + + protected readonly config_: SentryOptions; + protected readonly eventBusService_: EventBusService; + + constructor({ manager, eventBusService }: InjectedDeps, config: SentryOptions) { + // @ts-ignore + super(...arguments); + + this.manager_ = manager; + this.config_ = config; + this.eventBusService_ = eventBusService; + } + + /** + * Fetch paginated transactions from an organisation project on sentry + * @param organisation The organisation on which to fetch the transactions + * @param project The project in the organisation on which to fetch the transactions + * @param statsPeriod The period from when to fetch the transactions (default: 24h) + * @param perPage The number of transaction per page + * @param token The token to use to send request to sentry + * @param cursor The cursor to send to fetch the transactions for a given page + * @return The result is composed of the data and the next cursor for the pagination purpose + */ + async fetchSentryTransactions({ + organisation, + project, + statsPeriod, + perPage, + token, + cursor, + }): Promise { + perPage = perPage ?? 100; + + const queryParams = { + field: 'transaction', + per_page: Number(perPage), + project, + query: `event.type:transaction`, + statsPeriod, + // The three values from cursor are: cursor identifier (integer, usually 0), row offset, and is_prev (1 or 0). + // e.g 0:10:0 + cursor, + }; + + return await this.fetchSentry({ + organisation, + token, + perPage, + queryParams, + }); + } + + /** + * Fetch paginated transaction events from an organisation project on sentry + * @param transaction The transaction for which to fetch the events (e.g "GET /admin/users") + * @param organisation The organisation on which to fetch the transactions + * @param project The project in the organisation on which to fetch the transactions + * @param statsPeriod The period from when to fetch the transactions (default: 24h) + * @param perPage The number of transaction per page + * @param token The token to use to send request to sentry + * @param cursor The cursor to send to fetch the transactions for a given page + * @return The result is composed of the data and the next cursor for the pagination purpose + */ + async fetchTransactionEvents({ + transaction, + organisation, + project, + statsPeriod, + perPage, + token, + cursor, + }): Promise { + perPage = perPage ?? 100; + + const queryParams = { + field: ['id', 'transaction.duration', 'timestamp', 'spans.db'], + per_page: Number(perPage), + project, + query: `event.type:transaction transaction:"${transaction}"`, + statsPeriod, + sort: '-timestamp', + // The three values from cursor are: cursor identifier (integer, usually 0), row offset, and is_prev (1 or 0). + // e.g 0:10:0 + cursor, + }; + + return await this.fetchSentry({ + organisation, + token, + perPage, + queryParams, + }); + } + + async handleIssues(data: SentryWebHookData): Promise { + await this.manager_.transaction(async (transactionManager) => { + if (isFunction(this.config_.webHookOptions.emitOnIssue)) { + return await this.config_.webHookOptions.emitOnIssue(this.__container__); + } + await this.eventBusService_ + .withTransaction(transactionManager) + .emit(SentryWebHookEvent.SENTRY_RECEIVED_ISSUE, data); + }); + } + + async handleErrors(data: SentryWebHookData): Promise { + await this.manager_.transaction(async (transactionManager) => { + if (isFunction(this.config_.webHookOptions.emitOnError)) { + return await this.config_.webHookOptions.emitOnError(this.__container__); + } + await this.eventBusService_ + .withTransaction(transactionManager) + .emit(SentryWebHookEvent.SENTRY_RECEIVED_ERROR, data); + }); + } + + async handleComments(data: SentryWebHookData): Promise { + await this.manager_.transaction(async (transactionManager) => { + if (isFunction(this.config_.webHookOptions.emitOnComment)) { + return await this.config_.webHookOptions.emitOnComment(this.__container__); + } + await this.eventBusService_ + .withTransaction(transactionManager) + .emit(SentryWebHookEvent.SENTRY_RECEIVED_COMMENT, data); + }); + } + + async handleAlerts(data: SentryWebHookData): Promise { + await this.manager_.transaction(async (transactionManager) => { + if (isFunction(this.config_.webHookOptions.emitOnEventOrMetricAlert)) { + return await this.config_.webHookOptions.emitOnEventOrMetricAlert(this.__container__); + } + await this.eventBusService_ + .withTransaction(transactionManager) + .emit(SentryWebHookEvent.SENTRY_RECEIVED_EVENT_OR_METRIC_ALERT, data); + }); + } + + async handleInstallation(data: SentryWebHookData): Promise { + await this.manager_.transaction(async (transactionManager) => { + if (isFunction(this.config_.webHookOptions.emitOnInstallOrDeleted)) { + return await this.config_.webHookOptions.emitOnInstallOrDeleted(this.__container__); + } + await this.eventBusService_ + .withTransaction(transactionManager) + .emit(SentryWebHookEvent.SENTRY_RECEIVED_INSTALL_OR_DELETED, data); + }); + } + + protected async fetchSentry({ organisation, token, queryParams, perPage }): Promise { + const url = this.sentryApiBaseUrl + `/${organisation}/events/`; + + const searchParams = new URLSearchParams(); + Object.entries(queryParams).forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((val) => { + searchParams.append(key, val); + }); + } else { + value && searchParams.append(key, value?.toString()); + } + }); + + const { + data: { data, meta }, + headers, + } = await axios.get(url, { + headers: { + Authorization: `Bearer ${token}`, + }, + params: searchParams, + }); + + const nextCursor: string = SentryService.buildNextCursor(headers['link']); + + let prevCursor: string; + if (nextCursor) { + prevCursor = SentryService.buildPrevCursor(nextCursor, perPage); + } + + return { data, meta, prev_cursor: prevCursor, next_cursor: nextCursor }; + } + + protected static buildNextCursor(link: string): string { + let result = ''; + + const nextCursorMatch = link?.match(/.*cursor="(.*)"$/); + if (nextCursorMatch && nextCursorMatch[1]) { + result = nextCursorMatch[1]?.split(',')[0]; + } + + return result; + } + + protected static buildPrevCursor(nextCursor: string, perPage: number): string { + const parts = nextCursor.split(':'); + const prevCursorItems = Math.max(0, Number(parts[1]) - perPage * 2); + return `${parts[0]}:${prevCursorItems}:${parts[2]}`; + } +} diff --git a/packages/medusa-plugin-sentry/src/types/index.ts b/packages/medusa-plugin-sentry/src/types/index.ts new file mode 100644 index 0000000..ccbc13c --- /dev/null +++ b/packages/medusa-plugin-sentry/src/types/index.ts @@ -0,0 +1,41 @@ +import { NodeOptions } from '@sentry/node/types/types'; +import { Integration } from '@sentry/types/types/integration'; +import { Router } from 'express'; +import * as Sentry from '@sentry/node'; +import * as Tracing from '@sentry/tracing'; +import { RequestHandlerOptions } from '@sentry/node/types/handlers'; + +export type SentryWebHookOptions = { + path: string; + secret: string; + emitOnIssue?: boolean | ((req) => Promise); + emitOnError?: boolean | ((req) => Promise); + emitOnComment?: boolean | ((req) => Promise); + emitOnEventOrMetricAlert?: boolean | ((req) => Promise); + emitOnInstallOrDeleted?: boolean | ((req) => Promise); +}; + +export type SentryOptions = Omit & { + integrations: Integration[] | ((router: Router, sentry: typeof Sentry, tracing: typeof Tracing) => Integration[]); + apiToken?: string; + shouldHandleError: (code: number) => boolean; + requestHandlerOptions?: RequestHandlerOptions; + enableRequestHandler?: boolean; + enableTracing?: boolean; + webHookOptions?: SentryWebHookOptions; +}; + +export enum SentryWebHookEvent { + SENTRY_RECEIVED_ISSUE = 'SentryReceivedIssue', + SENTRY_RECEIVED_ERROR = 'SentryReceivedError', + SENTRY_RECEIVED_COMMENT = 'SentryReceivedComment', + SENTRY_RECEIVED_EVENT_OR_METRIC_ALERT = 'SentryReceivedEventOrMetricAlert', + SENTRY_RECEIVED_INSTALL_OR_DELETED = 'SentryReceivedInstallOrDeleted', +} + +export type SentryWebHookData = { + actor: unknown; + action: unknown; + data: unknown; + installation: unknown; +}; diff --git a/packages/medusa-plugin-sentry/src/utils/index.ts b/packages/medusa-plugin-sentry/src/utils/index.ts new file mode 100644 index 0000000..4214883 --- /dev/null +++ b/packages/medusa-plugin-sentry/src/utils/index.ts @@ -0,0 +1,12 @@ +import crypto from 'crypto'; + +export function verifySignature(req, secret: string): boolean { + const hmac = crypto.createHmac('sha256', secret); + hmac.update(JSON.stringify(req.body), 'utf8'); + const digest = hmac.digest('hex'); + return digest === req.get('sentry-hook-signature'); +} + +export function isFunction(val: unknown): val is Function { + return val != null && typeof val === 'function'; +} diff --git a/packages/medusa-plugin-sentry/tsconfig.json b/packages/medusa-plugin-sentry/tsconfig.json index 336f115..a7fbb56 100644 --- a/packages/medusa-plugin-sentry/tsconfig.json +++ b/packages/medusa-plugin-sentry/tsconfig.json @@ -1,12 +1,17 @@ { - "extends": "../tsconfig.json", + "extends": "../../tsconfig.json", "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDecoratorMetadata": true, + "module": "commonjs", + "noEmit": false, "resolveJsonModule": true, "esModuleInterop": true, "outDir": ".", "rootDir": "src", - "baseUrl": "./", + "baseUrl": "./" }, - "include": ["src/**/*"], - "exclude": ["node_modules", "**/__tests__/*", "**/__e2e__/*"], + "include": ["src"], + "exclude": ["**/node_modules", "**/__tests__/*", "**/__e2e__/*"], } \ No newline at end of file diff --git a/packages/tsconfig.json b/packages/tsconfig.json deleted file mode 100644 index b559349..0000000 --- a/packages/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDecoratorMetadata": true, - "module": "commonjs", - "noEmit": false, - "composite": true - }, - "files": [], - "references": [{ "path": "./medusa-plugin-sentry" }] -} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index bf4b486..9628e20 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "target": "es2017", "sourceMap": true, "skipLibCheck": true, - "esModuleInterop": true + "esModuleInterop": true, + "incremental": false }, - "exclude": ["**/node_modules"] + "exclude": ["node_modules"] } diff --git a/yarn.lock b/yarn.lock index b5b58a6..8d04730 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1453,17 +1453,49 @@ resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== +"@sqltools/formatter@^1.2.2": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.3.tgz#1185726610acc37317ddab11c3c7f9066966bd20" + integrity sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@types/express@types/express": - version "4.14.0" - resolved "https://codeload.github.com/types/express/tar.gz/7670cf1cbce96b3159e3ff04a253926b34111220" +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== dependencies: - "@types/serve-static" "github:types/npm-serve-static#c1f96843c8b96a37f6c534cb1dadb48f5329e4e0" - path-to-regexp "github:pillarjs/path-to-regexp#ec285ed3500aed455df59e3b8b07f473412918a4" + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^4.17.18": + version "4.17.31" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz#a1139efeab4e7323834bb0226e62ac019f474b2f" + integrity sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.14": + version "4.17.14" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.14.tgz#143ea0557249bc1b3b54f15db4c81c3d4eb3569c" + integrity sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" "@types/ioredis@^4.28.10": version "4.28.10" @@ -1482,6 +1514,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.186.tgz#862e5514dd7bd66ada6c70ee5fce844b06c8ee97" integrity sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw== +"@types/mime@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -1507,9 +1544,28 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/serve-static@github:types/npm-serve-static#c1f96843c8b96a37f6c534cb1dadb48f5329e4e0": - version "1.11.1" - resolved "https://codeload.github.com/types/npm-serve-static/tar.gz/c1f96843c8b96a37f6c534cb1dadb48f5329e4e0" +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/serve-static@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +"@types/zen-observable@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" + integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== "@typescript-eslint/eslint-plugin@^5.4.0": version "5.39.0" @@ -1709,6 +1765,11 @@ ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -1717,6 +1778,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" + integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== + append-field@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" @@ -1879,6 +1945,15 @@ axios@^0.21.1: dependencies: follow-redirects "^1.14.0" +axios@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.0.0.tgz#16ded6096c1d37650db9f6a8d48a2f7c1bb58622" + integrity sha512-SsHsGFN1qNPFT5QhSoSD37SHDfGyLSW5AESmyLk2JeCMHv5g0I9g0Hz/zQHx2KNe0jGXh2q2hAm7OdkXm360CA== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -2002,6 +2077,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -2195,7 +2278,7 @@ class-transformer@^0.5.1: resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== -class-validator@^0.13.1: +class-validator@^0.13.1, class-validator@^0.13.2: version "0.13.2" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.2.tgz#64b031e9f3f81a1e1dcd04a5d604734608b24143" integrity sha512-yBUcQy07FPlGzUjoLuUfIOXzgynnQPPruyK1Ge2B74k9ROwnle1E+NxLWnUv5OLU8hA/qL5leAE9XnXq3byaBw== @@ -2227,6 +2310,18 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + cli-progress@^3.4.0: version "3.11.2" resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.11.2.tgz#f8c89bd157e74f3f2c43bcfb3505670b4d48fc77" @@ -2294,6 +2389,15 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2386,7 +2490,7 @@ columnify@^1.5.4: strip-ansi "^6.0.1" wcwidth "^1.0.0" -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -3389,7 +3493,7 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.14.0: +follow-redirects@^1.14.0, follow-redirects@^1.15.0: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== @@ -3406,6 +3510,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -3792,6 +3905,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -3882,7 +4000,7 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3938,7 +4056,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4378,7 +4496,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: +js-yaml@^4.0.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -4838,6 +4956,11 @@ medusa-core-utils@^1.1.31: joi "^17.3.0" joi-objectid "^3.0.1" +medusa-interfaces@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-1.3.3.tgz#85745ea768fcbb093d3d5d3dee987fcdf996cb45" + integrity sha512-e2ZkKtBppBVZYu09y479b5JgkNhzzDLuOHPxVXN2CVK9vUCYrzfEPMXd6B21CTchrhD07MCtR17l4OElRvPtSw== + medusa-telemetry@0.0.13: version "0.0.13" resolved "https://registry.yarnpkg.com/medusa-telemetry/-/medusa-telemetry-0.0.13.tgz#48f40051d6c7dadc8eed2898a5b5f43adaab1d3c" @@ -5138,6 +5261,15 @@ mute-stream@0.0.8, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -5456,7 +5588,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -5776,6 +5908,23 @@ parse-url@^6.0.0: parse-path "^4.0.0" protocols "^1.4.0" +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -5867,12 +6016,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -"path-to-regexp@github:pillarjs/path-to-regexp#ec285ed3500aed455df59e3b8b07f473412918a4": - version "1.5.3" - resolved "https://codeload.github.com/pillarjs/path-to-regexp/tar.gz/ec285ed3500aed455df59e3b8b07f473412918a4" - dependencies: - isarray "0.0.1" - path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -6109,6 +6252,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.28: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -6578,6 +6726,11 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + scrypt-kdf@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/scrypt-kdf/-/scrypt-kdf-2.0.1.tgz#3355224c52d398331b2cbf2b70a7be26b52c53e6" @@ -6639,6 +6792,14 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sha.js@^2.4.11: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shallow-clone@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" @@ -7101,6 +7262,20 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -7298,10 +7473,33 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^3.7.5: - version "3.9.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" - integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== +typeorm@^0.2.45: + version "0.2.45" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.2.45.tgz#e5bbb3af822dc4646bad96cfa48cd22fa4687cea" + integrity sha512-c0rCO8VMJ3ER7JQ73xfk0zDnVv0WDjpsP6Q1m6CVKul7DB9iVdWLRjPzc8v2eaeBuomsbZ2+gTaYr8k1gm3bYA== + dependencies: + "@sqltools/formatter" "^1.2.2" + app-root-path "^3.0.0" + buffer "^6.0.3" + chalk "^4.1.0" + cli-highlight "^2.1.11" + debug "^4.3.1" + dotenv "^8.2.0" + glob "^7.1.6" + js-yaml "^4.0.0" + mkdirp "^1.0.4" + reflect-metadata "^0.1.13" + sha.js "^2.4.11" + tslib "^2.1.0" + uuid "^8.3.2" + xml2js "^0.4.23" + yargs "^17.0.1" + zen-observable-ts "^1.0.0" + +typescript@^4.5.5: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== uglify-js@^3.1.4: version "3.17.2" @@ -7675,6 +7873,19 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +xml2js@^0.4.23: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -7723,6 +7934,11 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^21.0.0: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@^15.3.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -7740,7 +7956,7 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.2.0: +yargs@^16.0.0, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -7753,6 +7969,19 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" +yargs@^17.0.1: + version "17.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c" + integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g== + dependencies: + 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.0.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" @@ -7762,3 +7991,16 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zen-observable-ts@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83" + integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA== + dependencies: + "@types/zen-observable" "0.8.3" + zen-observable "0.8.15" + +zen-observable@0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" + integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==