diff --git a/.circleci/config.yml b/.circleci/config.yml index a5ca486edf..8dd22fa444 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,13 +3,13 @@ orbs: node: circleci/node@3.0.0 executors: - node: - parameters: - image: - type: string - default: "12.13" - docker: - - image: circleci/node:<< parameters.image >> + node: + parameters: + image: + type: string + default: "14.18" + docker: + - image: circleci/node:<< parameters.image >> aliases: install_node_modules: &install_node_modules @@ -18,7 +18,7 @@ aliases: command: yarn --frozen-lockfile attach_to_bootstrap: &attach_to_bootstrap attach_workspace: - at: ./ + at: ./ jobs: bootstrap: diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..1faf09169d --- /dev/null +++ b/.eslintignore @@ -0,0 +1,101 @@ +# FILES TODO + +/packages/medusa/src/services/cart.js +/packages/medusa/src/services/claim-item.js +/packages/medusa/src/services/customer.js +/packages/medusa/src/services/draft-order.js +/packages/medusa/src/services/event-bus.js +/packages/medusa/src/services/fulfillment-provider.js +/packages/medusa/src/services/idempotency-key.js +/packages/medusa/src/services/inventory.js +/packages/medusa/src/services/line-item.js +/packages/medusa/src/services/middleware.js +/packages/medusa/src/services/note.js +/packages/medusa/src/services/notification.js +/packages/medusa/src/services/oauth.js +/packages/medusa/src/services/payment-provider.js +/packages/medusa/src/services/product-collection.js +/packages/medusa/src/services/product-variant.js +/packages/medusa/src/services/product.js +/packages/medusa/src/services/query-builder.js +/packages/medusa/src/services/return-reason.js +/packages/medusa/src/services/return.js +/packages/medusa/src/services/shipping-option.js +/packages/medusa/src/services/shipping-profile.js +/packages/medusa/src/services/store.js +/packages/medusa/src/services/swap.js +/packages/medusa/src/services/system-payment-provider.js +/packages/medusa/src/services/totals.js + +/packages/medusa/src/subscribers/notification.js +/packages/medusa/src/subscribers/order.js + +/packages/medusa/src/loaders/api.js +/packages/medusa/src/loaders/database.js +/packages/medusa/src/loaders/defaults.js +/packages/medusa/src/loaders/express.js +/packages/medusa/src/loaders/index.js +/packages/medusa/src/loaders/logger.js +/packages/medusa/src/loaders/models.js +/packages/medusa/src/loaders/passport.js +/packages/medusa/src/loaders/plugins.js +/packages/medusa/src/loaders/redis.js +/packages/medusa/src/loaders/repositories.js +/packages/medusa/src/loaders/services.js +/packages/medusa/src/loaders/subscribers.js + +# END OF FILES TODO + +/packages/medusa/src/api +/packages/medusa/src/models +/packages/medusa/src/repositories +/packages/medusa/src/commands +/packages/medusa/src/helpers +/packages/medusa/src/migrations +/packages/medusa/src/utils + +/integration-tests +/docs +/docs-util +/scripts +/www +/packages/babel-preset-medusa-package +/packages/create-medusa-app +/packages/medusa-cli +/packages/medusa-core-utils +/packages/medusa-dev-cli +/packages/medusa-file-s3 +/packages/medusa-file-spaces +/packages/medusa-fulfillment-manual +/packages/medusa-fulfillment-webshipper +/packages/medusa-interfaces +/packages/medusa-payment-adyen +/packages/medusa-payment-klarna +/packages/medusa-payment-manual +/packages/medusa-payment-paypal +/packages/medusa-payment-stripe +/packages/medusa-plugin-add-ons +/packages/medusa-plugin-brightpearl +/packages/medusa-plugin-contentful +/packages/medusa-plugin-discount-generator +/packages/medusa-plugin-economic +/packages/medusa-plugin-ip-lookup +/packages/medusa-plugin-mailchimp +/packages/medusa-plugin-permissions +/packages/medusa-plugin-restock-notification +/packages/medusa-plugin-segment +/packages/medusa-plugin-sendgrid +/packages/medusa-plugin-slack-notification +/packages/medusa-plugin-twilio-sms +/packages/medusa-plugin-wishlist +/packages/medusa-telemetry +/packages/medusa-test-utils + +packages/**/scripts +packages/*/*.js +**/dist/* + +**/__mocks__/* +**/__tests__/* + +jest.config.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..c40421efa9 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,33 @@ +module.exports = { + parser: `@babel/eslint-parser`, + parserOptions: { + requireConfigFile: false, + ecmaFeatures: { + experimentalDecorators: true, + }, + }, + plugins: [`eslint-plugin-prettier`], + extends: [`eslint:recommended`, `google`, `eslint-config-prettier`], + rules: { + "prettier/prettier": `error`, + curly: [2, `all`], + "new-cap": `off`, + "require-jsdoc": `off`, + semi: `off`, + "no-unused-expressions": `off`, + camelcase: `off`, + }, + env: { + es6: true, + node: true, + jest: true, + }, + overrides: [ + { + files: [`*.ts`], + parser: `@typescript-eslint/parser`, + plugins: [`@typescript-eslint/eslint-plugin`], + extends: [`plugin:@typescript-eslint/recommended`], + }, + ], +} diff --git a/.gitignore b/.gitignore index 99c023dd24..b4a5fb20f6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules .DS_Store lerna-debug.log +.eslintcache + diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..80160b183b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +node_modules/.bin/lint-staged || node scripts/on-lint-error.js diff --git a/README.md b/README.md index ac5f3b2616..017545d1f5 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,17 @@

Medusa

+ +

+ Website | + Roadmap | + Blog | + LinkedIn | + Twitter | + Documentation | + Notion +

+

Medusa is an open-source headless commerce engine that enables developers to create amazing digital commerce experiences.

@@ -30,26 +41,26 @@ Medusa is an open-source headless commerce engine that enables developers to cre ## 🚀 Quickstart 1. **Install Medusa CLI** - ```bash - npm install -g @medusajs/medusa-cli - ``` + ```bash + npm install -g @medusajs/medusa-cli + ``` 2. **Create a new Medusa project** - ``` - medusa new my-medusa-store --seed - ``` + ``` + medusa new my-medusa-store --seed + ``` 3. **Start your Medusa engine** - ```bash - medusa develop - ``` - + ```bash + medusa develop + ``` 4. **Use the API** - ```bash - curl localhost:9000/store/products | python -m json.tool - ``` + ```bash + curl localhost:9000/store/products | python -m json.tool + ``` After these four steps and only a couple of minutes, you now have a complete commerce engine running locally. You may now explore [the documentation](https://docs.medusa-commerce.com/api) to learn how to interact with the Medusa API. You may also add [plugins](https://github.com/medusajs/medusa/tree/master/packages) to your Medusa store by specifying them in your `medusa-config.js` file. ## 🛒 Setting up a storefront for your Medusa project + Medusa is a headless commerce engine which means that it can be used for any type of digital commerce experience - you may use it as the backend for an app, a voice application, social commerce experiences or a traditional e-commerce website, you may even want to integrate Medusa into your own software to enable commerce functionality. All of these are use cases that Medusa supports - to learn more read the documentation or reach out. To provide a quick way to get you started with a storefront install one of our traditional e-commerce starters: @@ -67,7 +78,9 @@ To provide a quick way to get you started with a storefront install one of our t With your starter and your Medusa store running you can open http://localhost:8000 (for Gatsby) or http://localhost:3000 (for Nextjs) in your browser and view the products in your store, build a cart, add shipping details and pay and complete an order. ## ⭐️ Features + Medusa comes with a set of building blocks that allow you to create amazing digital commerce experiences, below is a list of some of the features that Medusa come with out of the box: + - **Headless**: Medusa is a highly customizable commerce API which means that you may use any presentation layer such as a website, app, chatbots, etc. - **Regions** allow you to specify currencies, payment providers, shipping providers, tax rates and more for one or more countries for truly international sales. - **Orders** come with all the functionality necessary to perform powerful customer service operations with ease. @@ -78,13 +91,14 @@ Medusa comes with a set of building blocks that allow you to create amazing digi - **Returns** allow customers to send back products and can be configured to function in a 100% automated flow through accounting and payment plugins. - **Fulfillment API** makes it easy to integrate with any fulfillment provider by creating fulfillment plugins, check the `/packages` directory for a full list of plugins. - **Payments API** makes it easy to integrate with any payment provider by creating payment plugins, we already support Stripe, Paypal and Klarna. -- **Notification API** allow integrations with email providers, chatbots, Slack channels, etc. -- **Customer Login** to give customers a way of managing their data, viewing their orders and saving payment details. +- **Notification API** allow integrations with email providers, chatbots, Slack channels, etc. +- **Customer Login** to give customers a way of managing their data, viewing their orders and saving payment details. - **Shipping Options & Profiles** enable powerful rules for free shipping limits, multiple fulfillment methods and more. - **Medusa's Plugin Architecture** makes it intuitive and easy to manage your integrations, switch providers and grow with ease. - **Customization** is supported for those special use cases that all the other e-commerce platforms can't accommodate. ## Database support + In production Medusa requires Postgres and Redis, but SQLite is supported for development and testing purposes. If you plan on using Medusa for a project it is recommended that you install Postgres and Redis on your dev machine. - [Install PostgreSQL](https://www.postgresql.org/download/) @@ -105,21 +119,3 @@ The Medusa repository is a mono-repository managed using Lerna. Lerna allows us Licensed under the [MIT License](https://github.com/medusajs/medusa/blob/master/LICENSE) ## Thank you! - -

- - Website - - | - - Notion Home - - | - - Twitter - - | - - Docs - -

diff --git a/docs/content/how-to/deploying-admin-on-netlify.md b/docs/content/how-to/deploying-admin-on-netlify.md new file mode 100644 index 0000000000..22cd735a5d --- /dev/null +++ b/docs/content/how-to/deploying-admin-on-netlify.md @@ -0,0 +1,69 @@ +--- +title: "Deploying Admin on Netlify" +--- + +# Deploying Admin on Netlify + +This is a guide for deploying Medusa Admin on Netlify. Netlify is a platform that offers hosting and backend services for applications and static websites. + +> At this point, you should have a running instance of Medusa Admin. If not, check out [these steps](https://github.com/medusajs/admin#-setting-up-admin) or use `npx create-medusa-app` to set up your application in a matter of minutes. For the latter, see [this guide](https://docs.medusa-commerce.com/how-to/create-medusa-app) for a small walkthrough. + +### 1. Install the Netlify CLI + +Install Netlify CLI on your machine using npm: + +```shell= +npm install netlify-cli -g +``` + +### 2. Login to your Netlify account + +Connect to your Netlify account from your terminal: + +```shell= +netlify login +``` + +Follow the instructions in your terminal. + +### 3. Netlify setup + +In order to deploy on Netlify, you need to create a new site, link the admin repository to the site and configure environment variables. + +The Netlify CLI is used to achieve this. + +#### Create a new site + +```shell= +netlify init +``` + +Follow the instructions in your terminal to authorize and connect your Git repository. + +The default build and deploy settings fit the needs of a Gatsby application, so leave these as is. + +#### Add an environment variable + +```shell= +netlify env:set GATSBY_MEDUSA_BACKEND_URL "https://your-medusa-server.com" +``` + +The above environment variable should point to your Medusa server. + +### 4. Push and deploy + +Finally to deploy the admin, commit and push your changes to the repository connected in step 3. + +```shell= +git add . +git commit -m "Deploy Medusa Admin on Netlify" +git push origin main +``` + +Within a couple of minutes, your Medusa Admin is live and running on Netlify. + +> If you experience CORS issues in your new setup, you might need to add your admin url as part of the ADMIN_CORS environment variable in your server setup. + +### What's next? + +If you haven't deployed your Medusa server to use with your new admin, check out our guide [Deploying on Heroku](https://docs.medusa-commerce.com/how-to/deploying-on-heroku). diff --git a/docs/content/how-to/deploying-on-heroku.md b/docs/content/how-to/deploying-on-heroku.md new file mode 100644 index 0000000000..dd77abad9e --- /dev/null +++ b/docs/content/how-to/deploying-on-heroku.md @@ -0,0 +1,200 @@ +--- +title: "Deploying on Heroku" +--- + +# Deploying on Heroku + +This is a guide for deploying a Medusa project on Heroku. Heroku is at PaaS that allows you to easily deploy your applications in the cloud. + +> We assume, that you are currently running a local instance of Medusa. If not, check out our [Quickstart](https://docs.medusa-commerce.com/quickstart/quick-start) or use `npx create-medusa-app` to set up your application in a matter of minutes. For the latter, see [this guide](https://docs.medusa-commerce.com/how-to/create-medusa-app) for a small walkthrough. + +### 1. Install the Heroku CLI + +Install Heroku on your machine: + +**Ubuntu** + +```shell= +sudo snap install --classic heroku +``` + +**MacOS** + +```shell= +brew tap heroku/brew && brew install heroku +``` + +**Windows** + +Download the appropriate installer for your Windows installation: + +[64-bit installer](https://cli-assets.heroku.com/heroku-x64.exe) +[32-bit installer](https://cli-assets.heroku.com/heroku-x86.exe) + +### 2. Login to Heroku from your CLI + +Connect to your Heroku account from your terminal: + +```shell= +heroku login +``` + +> Follow the instructions on your terminal + +### 3. Create an app on Heroku + +In your **Medusa project directory** run the following commands to create an app on Heroku and add it as a remote origin. + +```shell= +heroku create medusa-test-app +heroku git:remote -a medusa-test-app +``` + +### 4. Install Postgresql and Redis on Heroku + +Medusa requires a Postgres database and a Redis instance to work. These are added through the Heroku CLI using the following commands. + +> In this below example, we initialize the resources on free plans. This is not a valid configuration for a production environment. + +#### Postgresql + +Add a Postgres addon to your Heroku app + +```shell= +heroku addons:create heroku-postgresql:hobby-dev +``` + +You can find more informations, plans and pricing about Heroku Postgres [here](https://elements.heroku.com/addons/heroku-postgresql). + +#### Redis To Go + +Add a Redis instance to your Heroku app + +> The addon `redistogo:nano` is free, but Heroku requires you to add a payment method to proceed. + +```shell= +heroku addons:create redistogo:nano +``` + +You can find more informations, plans and pricing about Redis To Go [here](https://elements.heroku.com/addons/redistogo). + +### 5. Configure environment variables on Heroku + +Medusa requires a set of environment variables. From you project repository run the following commands:. + +```shell= +heroku config:set NODE_ENV=production +heroku config:set JWT_SECRET=your-super-secret +heroku config:set COOKIE_SECRET=your-super-secret-pt2 +heroku config:set NPM_CONFIG_PRODUCTION=false +``` + +> Make sure to use actual secrets in a production environment. +> Additionally, we need to set the buildpack to Node.js + +```shell= +heroku buildpacks:set heroku/nodejs +``` + +#### Configure the Redis URL + +The library we use for connecting to Redis, does not allow usernames in the connection string. Therefore, we need to perform the following commands to remove it. +Get the current Redis URL: + +```shell= +heroku config:get REDISTOGO_URL +``` + +You should get something like: + +```shell= +redis://redistogo:some_password_123@some.redistogo.com:9660/ +``` + +Remove the username from the Redis URL: + +```shell= +redis://r̶e̶d̶i̶s̶t̶o̶g̶o̶:some_password_123@sole.redistogo.com:9660/ +``` + +Set the new environment variable `REDIS_URL` + +```shell= +heroku config:set REDIS_URL=redis://:some_password_123@sole.redistogo.com:9660/ +``` + +### 6. Configure Medusa + +Before jumping into the deployment, we need to configure Medusa. + +#### `medusa-config.js` + +Update `module.exports` to include the following: + +```javascript= +module.exports = { + projectConfig: { + redis_url: REDIS_URL, + database_url: DATABASE_URL, + database_type: "postgres", + store_cors: STORE_CORS, + admin_cors: ADMIN_CORS, + database_extra: + process.env.NODE_ENV !== "development" + ? { ssl: { rejectUnauthorized: false } } + : {}, + }, + plugins, +}; +``` + +#### `package.json` + +Update `scripts` to include the following: + +```json= +... +"scripts": { + "serve": "medusa start", + "start": "medusa develop", + "heroku-postbuild": "medusa migrations run", + "prepare": "npm run build", + "build": "babel src -d dist --extensions \".ts,.js\"" +}, +... +``` + +### 6. Launch you Medusa app + +Finally, we need to commit and push our changes to Heroku: + +```shell= +git add . +git commit -m "Deploy Medusa App on Heroku" +git push heroku HEAD:master +``` + +### 7. Inspect your build logs + +You can explore your Heroku app build logs using the following command in your project directory. + +```shell= +heroku logs -n 500000 --remote heroku --tail +``` + +### 8. Create a user (optional) + +As an optional extra step, we can create a user for you to use when your admin system is up and running. + +```shell= +heroku run -a medusa-test-app -- medusa user -e "some-user@test.com" -p "SuperSecret1234" +``` + +### What's next? + +You now have a production ready application running on Heroku. This can be scaled and configured to fit your business needs. + +Furthermore, you can deploy a Medusa Admin for your application, such that you can start managing your store from an interface. + +- [Deploy Admin on Netlify](https://docs.medusa-commerce.com/how-to/deploying-admin-on-netlify) +- Deploy Admin on Gatsby Cloud (Coming soon) diff --git a/docs/content/how-to/deploying-on-qovery.md b/docs/content/how-to/deploying-on-qovery.md new file mode 100644 index 0000000000..ac343b35dd --- /dev/null +++ b/docs/content/how-to/deploying-on-qovery.md @@ -0,0 +1,122 @@ +# Deploying on Qovery + +This is a guide for deploying a Medusa project to Qovery. Qovery is a Continuous Deployment Platform, that provides you with the developer experience of Heroku on top of your cloud provider (e.g. AWS, DigitalOcean). + +> We assume, that you are currently running a local instance of Medusa. If not, check out our [Quickstart](https://docs.medusa-commerce.com/quickstart/quick-start) or use `npx create-medusa-app` to set up your application in a matter of minutes. For the latter, see [this guide](https://docs.medusa-commerce.com/how-to/create-medusa-app) for a small walkthrough. + +### 1. Qovery Console + +Create an account on [Qovery](https://www.qovery.com/) on their free community plan and jump into the console. + +### 2. Setup + +Create a project and an environment. + +### 3. Add your Medusa app + +Add a new app to your Qovery environment and connect the Git repository that holds your Medusa project. In your application settings, set the port to 9000 unless something else is specified in your setup. + +> If you used our `npx` starter, your repository will most likely hold all components; storefront, admin and backend. Ensure that **Root application path** in Qovery is pointing to your Medusa project (`/backend`). + +### 4. Add a database + +Navigate to your environment overview and add the databases required by Medusa. + +- Add Postgres database version 10, 11 or 12 +- Add Redis database version 5 or 6 + +### 5. Configure Medusa + +Our Medusa project needs a bit of configuration to fit the needs of Qovery. + +#### Update `medusa-config.js` + +First, add the Postgres and Redis database url to your `medusa-config.js`. In Qovery, click on your Medusa app in the environment overview. Navigate to environment variables in the sidebar on the left. Among the secret variables you should find your database urls. They should look something like this: + +```javascript= +QOVERY_REDIS_123456789_DATABASE_URL +QOVERY_POSTGRESQL_123456789_DATABASE_URL +``` + +Add these to your `medusa-config.js`. + +```javascript= +const DATABASE_URL = process.env.QOVERY_POSTGRESQL_123456789_DATABASE_URL +const REDIS_URL= process.env.QOVERY_REDIS_123456789_DATABASE_URL +``` + +Furthermore, update `module.exports` to include the following: + +```javascript= +module.exports = { + projectConfig: { + redis_url: REDIS_URL, + database_url: DATABASE_URL, + database_type: "postgres", + store_cors: STORE_CORS, + admin_cors: ADMIN_CORS, + database_extra: { } + }, + plugins, +}; +``` + +> **IMPORTANT**: We are using the Qovery community plan, that does not allow SSL connections for the database, so this is disabled. +> +> In a production environment, you would need the following in the config: +> `database_extra: { ssl: { rejectUnauthorized: false } }` + +#### Add some extra variables + +We need to add a couple of more environment variables in Qovery. Add the following variables in your Console with an application scope: + +```javascript= +JTW_SECRET=something_secret_jwt +COOKIE_SECRET=something_secret_cookie +``` + +> Make sure to use actual secrets in a production environment. + +#### Update `package.json` + +Update `scripts` to the following: + +```json= +"scripts": { + "serve": "medusa start", + "start": "medusa migrations run && medusa start", + "prepare": "npm run build", + "build": "babel src -d dist --extensions \".ts,.js\"" + }, +``` + +### 6. Deploy Medusa + +Finally, deploy your Redis and Postgres followed by your Medusa application. + +#### Deploy databases + +In your environment overview in Qovery, deploy your databases one after the other. Only when these are deployed, proceed to next step. + +#### Push changes to your repository + +To initialise your first build Qovery, simply commit and push your changes. + +```shell= +git add . +git commit -m "chore: Qovery setup" +git push origin main +``` + +### 6. Try it out! + +In Qovery, click on your Medusa app in the environment overview. In the top right you are able to open up your application. Navigate to `/health` to ensure, that the app is running. + +### What's next? + +You now have an application running on Qovery. This can be scaled and configured to fit your business needs. As mentioned, we used the community plan, so this should be upgraded when moving to production. + +Furthermore, you can deploy Medusa Admin for your application, such that you can start managing your store from an interface. + +- [Deploy Admin on Netlify](https://docs.medusa-commerce.com/how-to/deploying-admin-on-netlify) +- Deploy Admin on Gatsby Cloud (Coming soon) diff --git a/docs/content/how-to/making-your-store-more-powerful-with-contentful.md b/docs/content/how-to/making-your-store-more-powerful-with-contentful.md new file mode 100644 index 0000000000..df29cc86df --- /dev/null +++ b/docs/content/how-to/making-your-store-more-powerful-with-contentful.md @@ -0,0 +1,310 @@ +--- +title: Making your store more powerful with Contentful +--- + +# Making your store more powerful with Contentful + +In [part 1](https://docs.medusa-commerce.com/how-to/headless-ecommerce-store-with-gatsby-contentful-medusa/) of this series you have set up [Medusa](https://medusa-commerce.com) with Contentful as your CMS system and added a Gatsby storefront. In this part you will get a further introduction to Contentful and learn how [`medusa-plugin-contentful`](https://github.com/medusajs/medusa/tree/master/packages/medusa-plugin-contentful) can be leveraged to make your store more powerful. Apart from a front page, product pages and a checkout flow, most ecommerce stores also need miscalleneous pages like About and Contact pages. In this guide you will add a Rich Text content module to your Contentful space so that you can make this pages cool. You will also see how the content modules can be used to give your product pages more life. + +What you will do in this guide: + +- Add a rich text content module +- Add rich text to your `/about` page +- Add a "Related Products" section to your product page + +Topics covered: + +- Contentful Migrations +- Product enrichment + +## Creating a rich text content module + +In this guide you will make use of [Contentful Migrations](https://github.com/contentful/contentful-migration) to keep a versioned controlled record of how your Content evolves over time. The Contentful app allows you to create content models straight from their dashboard, however, when using the migrations tool you will be able to 1) quickly replicate your Contentful space and 2) incorporate migrations as part of a CI/CD pipeline. [You can read more about how to use CMS as Code here](https://www.contentful.com/help/cms-as-code/). + +To prepare your migration create a new file at `contentful-migrations/rich-text.js` and add the following code: + +```javascript +// contentful-migrations/rich-text.js + +module.exports = function (migration, context) { + const richText = migration + .createContentType("richText") + .name("Rich Text") + .displayField("title") + + richText.createField("title").name("Title (Internal)").type("Symbol") + richText.createField("body").name("Body").type("RichText") +} +``` + +This small snippet will create a content model in your Contentful space with two fields: a title which will be used to name entries in a meaningful manner (i.e. it won't be displayed to customers) and a body which contains the rich text to display. To apply your migration run: + +```shell +yarn migrate:contentful --file contentful-migrations/rich-text.js +``` + +If you go to your Contentful space and click Content Model you will see that the Rich Text model has been added to your space: +![](https://i.imgur.com/sCMjr4B.png) + +The validation rules in the Page model only allow Hero and Tile Sections to be added to the Content Modules fields so you will need another migration to make it possible for pages to make use of the new Rich Text modules. Create a new migration at `contentful-migrations/update-page-module-validation.js` and add the following: + +```javascript +// contentful-migrations/update-page-module-validation.js + +module.exports = function (migration, context) { + const page = migration.editContentType("page") + + page.editField("contentModules").items({ + type: "Link", + linkType: "Entry", + validations: [ + { + linkContentType: ["hero", "tileSection", "richText"], + }, + ], + }) +} +``` + +After migrating your space you are ready create your new contact page: + +```shell +yarn migrate:contentful --file contentful-migrations/update-page-module-validation.js +``` + +## Adding Rich Text to About + +To use your new Rich Text module **Content > Page > About**, and click **Add Content > Page**. You will now make use of the new Rich Text module to add some more details about your store. You can write your own text or use the text provided below if you just want to copy/paste. + +> ### About Medusa +> +> Medusa is an open-source headless commerce engine for fast-growing businesses. Getting started with Medusa is very easy and you will be able to start selling online with a basic setup in no time, however, the real power of Medusa starts showing up when you add custom functionality and extend your core to fit your needs. +> +> The core Medusa package and all the official Medusa plugins ship as individual NPM packages that you install into a Node project. You store and plugins are configured in your medusa-config.js file making it very easy to manage your store as your business grows. Custom functionality doesn't have to come from plugins, you can also add project-level functionality by simply adding files in your `src/` folder. Medusa will automatically register your custom functionalities in the bootstrap phase. + +![](https://i.imgur.com/hqiaoFq.png) + +When you have added your text you can click **Publish changes** (make sure the About page is published too). + +## Updating the storefront to support the Rich Text module + +> If you want to jump straight to the final frontend code visit [medusajs/medusa-contentful-storefront@part-2](https://github.com/medusajs/medusa-contentful-storefront/tree/part-2). + +To display your newly created Rich Text module open up the storefront code and create a new file at `src/components/rich-text/rich-text.jsx`. + +```jsx +// src/components/rich-text/rich-text.jsx + +import React from "react" +import { renderRichText } from "gatsby-source-contentful/rich-text" + +import * as styles from "../../styles/rich-text.module.css" + +const RichText = ({ data }) => { + return ( +
+
+ {data.body ? renderRichText(data.body) : ""} +
+
+ ) +} + +export default RichText +``` + +The `renderRichText` function is imported from the `gatsby-source-contentful` plugin to easily transform the text you entered in the Rich Text module to html. To make the Rich Text component render nicely add a style file as well at `src/styles/rich-text.module.css`. + +```css +/* src/styles/rich-text.module.css */ + +.container { + display: flex; + padding-top: 100px; + padding-bottom: 100px; +} + +.content { + margin: auto; + max-width: 870px; +} +``` + +If you restart your storefront server now you will not be able to see your new Rich Text module just yet. The last step to making that happen will be to let the Page component know to render the new Rich Text component when it encounters Rich Text in the Page's Content Modules. In your editor open up the file `src/pages/{ContentfulPage.slug}.js` and add the following: + +At the top of the file import your `RichText` component: + +```javascript +... +import RichText from "../components/rich-text/rich-text" +... +``` + +Now in the `contentModules.map` function return the `RichText` component whenever a `ContentfulRichText` module is encountered. Add a case to the switch statement: + +```javascript + case "ContentfulRichText": + return +``` + +Finally you will need to fetch the Rich Text data from Gatsby's data layer by modifying the GraphQL code at the bottom of the file after the line with `contentModules {` add: + +```graphql + ... on ContentfulRichText { + id + body { + raw + } + internal { + type + } + } +``` + +Restart your local Gatsby server and visit `http://localhost:8000/about`, you will now see the your newly added Rich Text module. + +![](https://i.imgur.com/8Teuxin.png) + +## Enriching your Product pages + +You have now seen how the Page model in Contentful can be extended to include a new content module in a reusable and modular manner. The same idea can be extended to your Product pages allowing you to create completely bespoke universes around your products. You will use the same techniques as above to create a Related Products section below the "Medusa Shirt" product. + +### Migrating Products + +First, add a new field to the Product content model. Using migrations you can create a file `contentful-migrations/product-add-modules.js`: + +```javascript +// contentful-migrations/product-add-modules.js + +module.exports = function (migration, context) { + const product = migration.editContentType("product") + + product + .createField("contentModules") + .name("Content Modules") + .type("Array") + .items({ + type: "Link", + linkType: "Entry", + validations: [ + { + linkContentType: ["hero", "tileSection", "richText"], + }, + ], + }) +} +``` + +Run the migration: + +``` +yarn migrate:contentful --file contentful-migrations/product-add-modules.js +``` + +### Adding "Related Products" Tile Section + +After the migration you can now add Content Modules to Products, to enrich the Product pages with relevant content. In this guide you will add a Tile Section that holds "Related Products", but the functionality could be further extended to showcase look book images, inspirational content or more detailed product descriptions. + +In Contentful go to **Content > Product > Medusa Shirt** scroll all the way to the bottom, where you should be able to find the new _Content Modules_ field: + +![](https://i.imgur.com/jUUpW9I.png) + +Click **Add content > Tile Section** which will open a new Tile Section. For the Title write "Related Products", and for Tiles click **Add content > Add existing content > Medusa Waterbottle > Insert 1 entry**. + +![](https://i.imgur.com/N7alMGz.png) + +Click **Publish** and make sure that the Medusa Shirt product is published too. + +Your data is now ready to be used in the storefront, but you still need to make a couple of changes to the storefront code to be able to view the new content. + +## Adding Content Modules to Product pages + +Just like you did for the Page component, you will have to fetch the Content Modules from Gatsby's GraphQL data layer. + +In the file `src/pages/products/{ContentfulProduct.handle}.js` add the following in the GraphQL query at the bottom of the file (e.g. after the variants query): + +```graphql + # src/pages/products/{ContentfulProduct.handle}.js + + contentModules { + ... on ContentfulTileSection { + id + title + tiles { + ... on ContentfulProduct { + id + title + handle + thumbnail { + gatsbyImageData + } + internal { + type + } + } + ... on ContentfulTile { + id + title + cta + image { + gatsbyImageData + } + link { + linkTo + reference { + slug + } + } + internal { + type + } + } + } + internal { + type + } + } + } +``` + +This snippet will query the Content Modules defined for the product and will allow you to use the data in your components. + +Next open up the `src/views/products.jsx` file and add the following snippets. + +Import the `TileSection` component: + +```javascript +import TileSection from "../components/tile-section/tile-section" +``` + +Add the Content Modules in the JSX just before the final closing `div`: + +```jsx +// src/views/products.jsx + +
+ {product.contentModules?.map((cm) => { + switch (cm.internal.type) { + case "ContentfulTileSection": + return + default: + return null + } + })} +
+``` + +Restart the Gatsby server and visit http://localhost:8000/product/medusa-shirt you should now see the new "Related Products" Tile Section below the Product page controls. + +![](https://i.imgur.com/AQHKA6j.png) + +## Summary + +In this guide you created a new content model for Rich Text input in Contentful using [contentful-migration](https://github.com/contentful/contentful-migration). You further extended the storefront to render the new Rich Text plugin. The concepts in this guide are meant to demonstrate how Contentful can be used to make your store more powerful in a modular and scalable way. The content modules covered in this guide could be further extended to add other custom modules, for example, you could add a Newsletter Signup, module that when encountered in the code renders a newsletter form. + +## What's next + +In the next part of this guide you will learn how to implement further commerce functionalities to your site such as adding support for discount codes, region based shopping and more. (Coming soon) + +- [Deploying Medusa on Heroku](https://docs.medusa-commerce.com/how-to/deploying-on-heroku) +- [Deploying Medusa Admin on Netlify](https://docs.medusa-commerce.com/how-to/deploying-admin-on-netlify) diff --git a/docs/content/how-to/uploading-images-to-s3.md b/docs/content/how-to/uploading-images-to-s3.md new file mode 100644 index 0000000000..bc71a22736 --- /dev/null +++ b/docs/content/how-to/uploading-images-to-s3.md @@ -0,0 +1,85 @@ +# Uploading images to S3 + +In order to work with images in Medusa, you need a file service plugin responsible for hosting. Following this guide will allow you to upload images to AWS S3. + +### Before you start + +At this point, you should have an instance of our store engine running. If not, we have a [full guide](https://docs.medusa-commerce.com/tutorial/set-up-your-development-environment) for setting up your local environment. + +### Set up up AWS + +#### Create an S3 bucket + +In the AWS console navigate to S3 and create a bucket for your images. Make sure to uncheck "Block _all_ public access". + +Additionally, you need to add a policy to your bucket, that will allow public access to objects that are uploaded. Navigate to the permissions tab of your bucket and add the following policy: + +```shell= +{ + "Id": "Policy1397632521960", + "Statement": [ + { + "Sid": "Stmt1397633323327", + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*", + "Principal": { + "AWS": [ + "*" + ] + } + } + ] +} +``` + +Be aware, that this will allow for anyone to acces your bucket. Avoid storing sensitive data. + +#### Generate access keys + +Navigate to the IAM section of your AWS console and perform the following steps: + +- Add a new user with programmatic access +- Add the existing **AmazonS3FullAccess** policy to the user +- Submit the details + +Upon successfull creation of the user, you are presented with an **Access key ID** and a **Secret access key**. Note both of them down for later use. + +### Installation + +First, install the plugin using your preferred package manager: + +``` +yarn add medusa-file-s3 +``` + +Then configure your `medusa-config.js` to include the plugin alongside the required options: + +```=javascript +{ + resolve: `medusa-file-s3`, + options: { + s3_url: "https://s3-guide-test.s3.eu-west-1.amazonaws.com", + bucket: "test", + region: "eu-west-1" + access_key_id: "YOUR-ACCESS-KEY", + secret_access_key: "YOUR-SECRET-KEY", + }, +}, +``` + +In the above options, an `s3_url` is included. The url has the following format: + +```shell= +https://[bucket].s3.[region].amazonaws.com +``` + +The two access keys in the options are the ones created in the previous section. + +> Make sure to use an environment variable for the secret key in a live environment. + +### Try it out + +Finally, run your Medusa server alongside our admin system to try out your new file service. Upon editing or creating products, you can now upload thumbnails and images, that are stored in an AWS S3 bucket. diff --git a/docs/content/how-to/uploading-images-to-spaces.md b/docs/content/how-to/uploading-images-to-spaces.md new file mode 100644 index 0000000000..c0c1dc6d85 --- /dev/null +++ b/docs/content/how-to/uploading-images-to-spaces.md @@ -0,0 +1,48 @@ +# Uploading images to Spaces + +In order to work with images in Medusa, you need a file service plugin responsible for hosting. Following this guide will allow you to upload images to DigitalOcean Spaces. + +### Before you start + +At this point, you should have an instance of our store engine running. If not, we have a [full guide](https://docs.medusa-commerce.com/tutorial/set-up-your-development-environment) for setting up your local environment. + +### Set up up DigitalOcean + +#### Create a Space + +Create an account on DigitalOcean and navigate to Spaces. Create a new Space with the default settings. + +#### Generate access keys + +Navigate to API in the left sidebar. Generate a new Spaces access key. This should provide you with an access key id and a secret key. Note them both down. + +### Installation + +First, install the plugin using your preferred package manager: + +``` +yarn add medusa-file-spaces +``` + +Then configure your `medusa-config.js` to include the plugin alongside the required options: + +```=javascript +{ + resolve: `medusa-file-spaces`, + options: { + spaces_url: "https://test.fra1.digitaloceanspaces.com", + bucket: "test", + endpoint: "fra1.digitaloceanspaces.com", + access_key_id: "YOUR-ACCESS-KEY", + secret_access_key: "YOUR-SECRET-KEY", + }, +}, +``` + +In the above options, a `spaces_url` is included. This can be found in your Space overview. The `bucket` should point to the name you gave your Space. The `endpoint` identifies the region in which you created the Space. And finally the two keys are the ones created in the previous section. + +> Make sure to use an environment variable for the secret key in a live environment. + +### Try it out! + +Finally, run your Medusa server alongside our admin system to try out your new file service. Upon editing or creating products, you can now upload thumbnails and images, that are stored in DigitalOcean Spaces. diff --git a/integration-tests/api/.babelrc.js b/integration-tests/api/.babelrc.js index 39c0fa3e45..bde709c495 100644 --- a/integration-tests/api/.babelrc.js +++ b/integration-tests/api/.babelrc.js @@ -1,13 +1,13 @@ -let ignore = [`**/dist`]; +let ignore = [`**/dist`] // Jest needs to compile this code, but generally we don't want this copied // to output folders if (process.env.NODE_ENV !== `test`) { - ignore.push(`**/__tests__`); + ignore.push(`**/__tests__`) } module.exports = { sourceMaps: true, presets: ["babel-preset-medusa-package"], ignore, -}; +} diff --git a/integration-tests/api/__tests__/admin/__snapshots__/product.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/product.js.snap index 8fc7342918..0e103ae418 100644 --- a/integration-tests/api/__tests__/admin/__snapshots__/product.js.snap +++ b/integration-tests/api/__tests__/admin/__snapshots__/product.js.snap @@ -380,5 +380,198 @@ Array [ "weight": null, "width": null, }, + Object { + "collection": Any, + "collection_id": "test-collection1", + "created_at": Any, + "deleted_at": null, + "description": "test-product-description", + "discountable": true, + "handle": "test-product_filtering_3", + "height": null, + "hs_code": null, + "id": StringMatching /\\^test-\\*/, + "images": Array [], + "is_giftcard": false, + "length": null, + "material": null, + "metadata": null, + "mid_code": null, + "options": Any, + "origin_country": null, + "profile_id": StringMatching /\\^sp_\\*/, + "status": "draft", + "subtitle": null, + "tags": Any, + "thumbnail": null, + "title": "Test product filtering 3", + "type": Any, + "type_id": "test-type", + "updated_at": Any, + "variants": Any, + "weight": null, + "width": null, + }, + Object { + "collection": Any, + "collection_id": "test-collection1", + "created_at": Any, + "deleted_at": null, + "description": "test-product-description", + "discountable": true, + "handle": "test-product_filtering_1", + "height": null, + "hs_code": null, + "id": StringMatching /\\^test-\\*/, + "images": Array [], + "is_giftcard": false, + "length": null, + "material": null, + "metadata": null, + "mid_code": null, + "options": Any, + "origin_country": null, + "profile_id": StringMatching /\\^sp_\\*/, + "status": "proposed", + "subtitle": null, + "tags": Any, + "thumbnail": null, + "title": "Test product filtering 1", + "type": Any, + "type_id": "test-type", + "updated_at": Any, + "variants": Any, + "weight": null, + "width": null, + }, + Object { + "collection": Any, + "collection_id": "test-collection2", + "created_at": Any, + "deleted_at": null, + "description": "test-product-description", + "discountable": true, + "handle": "test-product_filtering_2", + "height": null, + "hs_code": null, + "id": StringMatching /\\^test-\\*/, + "images": Array [], + "is_giftcard": false, + "length": null, + "material": null, + "metadata": null, + "mid_code": null, + "options": Any, + "origin_country": null, + "profile_id": StringMatching /\\^sp_\\*/, + "status": "published", + "subtitle": null, + "tags": Any, + "thumbnail": null, + "title": "Test product filtering 2", + "type": Any, + "type_id": "test-type", + "updated_at": Any, + "variants": Any, + "weight": null, + "width": null, + }, +] +`; + +exports[`/admin/products GET /admin/products returns a list of products with giftcard in list 1`] = ` +Array [ + Object { + "collection": null, + "collection_id": null, + "created_at": Any, + "deleted_at": null, + "description": "test-giftcard-description", + "discountable": false, + "handle": "test-giftcard", + "height": null, + "hs_code": null, + "id": StringMatching /\\^prod_\\*/, + "images": Array [], + "is_giftcard": true, + "length": null, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^opt_\\*/, + "metadata": null, + "product_id": StringMatching /\\^prod_\\*/, + "title": "Denominations", + "updated_at": Any, + }, + ], + "origin_country": null, + "profile_id": StringMatching /\\^sp_\\*/, + "status": "draft", + "subtitle": null, + "tags": Array [], + "thumbnail": null, + "title": "Test Giftcard", + "type": null, + "type_id": null, + "updated_at": Any, + "variants": Array [ + Object { + "allow_backorder": false, + "barcode": null, + "created_at": Any, + "deleted_at": null, + "ean": null, + "height": null, + "hs_code": null, + "id": StringMatching /\\^variant_\\*/, + "inventory_quantity": 0, + "length": null, + "manage_inventory": true, + "material": null, + "metadata": null, + "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": StringMatching /\\^opt_\\*/, + "metadata": null, + "option_id": StringMatching /\\^opt_\\*/, + "updated_at": Any, + "value": "100", + "variant_id": StringMatching /\\^variant_\\*/, + }, + ], + "origin_country": null, + "prices": Array [ + Object { + "amount": 100, + "created_at": Any, + "currency_code": "usd", + "deleted_at": null, + "id": Any, + "region_id": null, + "sale_amount": null, + "updated_at": Any, + "variant_id": StringMatching /\\^variant_\\*/, + }, + ], + "product_id": StringMatching /\\^prod_\\*/, + "sku": null, + "title": "Test variant", + "upc": null, + "updated_at": Any, + "weight": null, + "width": null, + }, + ], + "weight": null, + "width": null, + }, ] `; diff --git a/integration-tests/api/__tests__/admin/product.js b/integration-tests/api/__tests__/admin/product.js index 1e398b65c5..c40f92a12b 100644 --- a/integration-tests/api/__tests__/admin/product.js +++ b/integration-tests/api/__tests__/admin/product.js @@ -46,7 +46,35 @@ describe("/admin/products", () => { const api = useApi() const res = await api - .get("/admin/products?status%5B%5D=null", { + .get("/admin/products", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(res.status).toEqual(200) + expect(res.data.products).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "test-product", + status: "draft", + }), + expect.objectContaining({ + id: "test-product1", + status: "draft", + }), + ]) + ) + }) + + it("returns a list of all products when no query is provided", async () => { + const api = useApi() + + const res = await api + .get("/admin/products?q=", { headers: { Authorization: "Bearer test_token", }, @@ -89,7 +117,7 @@ describe("/admin/products", () => { }) const response = await api - .get("/admin/products?status%5B%5D=proposed", { + .get("/admin/products?status[]=proposed", { headers: { Authorization: "Bearer test_token", }, @@ -109,6 +137,258 @@ describe("/admin/products", () => { ) }) + it("returns a list of products where status is proposed or published", async () => { + const api = useApi() + + const notExpected = [ + expect.objectContaining({ status: "draft" }), + expect.objectContaining({ status: "rejected" }), + ] + + const response = await api + .get("/admin/products?status[]=published,proposed", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.status).toEqual(200) + expect(response.data.products).toEqual([ + expect.objectContaining({ + id: "test-product_filtering_1", + status: "proposed", + }), + expect.objectContaining({ + id: "test-product_filtering_2", + status: "published", + }), + ]) + + for (const notExpect of notExpected) { + expect(response.data.products).toEqual( + expect.not.arrayContaining([notExpect]) + ) + } + }) + + it("returns a list of products in collection", async () => { + const api = useApi() + + const notExpected = [ + expect.objectContaining({ collection_id: "test-collection" }), + expect.objectContaining({ collection_id: "test-collection2" }), + ] + + const response = await api + .get("/admin/products?collection_id[]=test-collection1", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.status).toEqual(200) + expect(response.data.products).toEqual([ + expect.objectContaining({ + id: "test-product_filtering_1", + collection_id: "test-collection1", + }), + expect.objectContaining({ + id: "test-product_filtering_3", + collection_id: "test-collection1", + }), + ]) + + for (const notExpect of notExpected) { + expect(response.data.products).toEqual( + expect.not.arrayContaining([notExpect]) + ) + } + }) + + it("returns a list of products with tags", async () => { + const api = useApi() + + const notExpected = [ + expect.objectContaining({ id: "tag1" }), + expect.objectContaining({ id: "tag2" }), + expect.objectContaining({ id: "tag4" }), + ] + + const response = await api + .get("/admin/products?tags[]=tag3", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.status).toEqual(200) + expect(response.data.products).toEqual([ + expect.objectContaining({ + id: "test-product_filtering_1", + tags: [expect.objectContaining({ id: "tag3" })], + }), + expect.objectContaining({ + id: "test-product_filtering_2", + tags: [expect.objectContaining({ id: "tag3" })], + }), + ]) + for (const product of response.data.products) { + for (const notExpect of notExpected) { + expect(product.tags).toEqual(expect.not.arrayContaining([notExpect])) + } + } + }) + + it("returns a list of products with tags in a collection", async () => { + const api = useApi() + + const notExpectedTags = [ + expect.objectContaining({ id: "tag1" }), + expect.objectContaining({ id: "tag2" }), + expect.objectContaining({ id: "tag3" }), + ] + + const notExpectedCollections = [ + expect.objectContaining({ collection_id: "test-collection" }), + expect.objectContaining({ collection_id: "test-collection2" }), + ] + + const response = await api + .get("/admin/products?collection_id[]=test-collection1&tags[]=tag4", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.status).toEqual(200) + expect(response.data.products).toEqual([ + expect.objectContaining({ + id: "test-product_filtering_3", + collection_id: "test-collection1", + tags: [expect.objectContaining({ id: "tag4" })], + }), + ]) + + for (const notExpect of notExpectedCollections) { + expect(response.data.products).toEqual( + expect.not.arrayContaining([notExpect]) + ) + } + + for (const product of response.data.products) { + for (const notExpect of notExpectedTags) { + expect(product.tags).toEqual(expect.not.arrayContaining([notExpect])) + } + } + }) + + it("returns a list of products with giftcard in list", async () => { + const api = useApi() + + const payload = { + title: "Test Giftcard", + is_giftcard: true, + description: "test-giftcard-description", + options: [{ title: "Denominations" }], + variants: [ + { + title: "Test variant", + prices: [{ currency_code: "usd", amount: 100 }], + options: [{ value: "100" }], + }, + ], + } + + await api + .post("/admin/products", payload, { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + const response = await api + .get("/admin/products?is_giftcard=true", { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.data.products).toEqual( + expect.not.arrayContaining([ + expect.objectContaining({ is_giftcard: false }), + ]) + ) + + expect(response.status).toEqual(200) + expect(response.data.products).toMatchSnapshot([ + { + title: "Test Giftcard", + id: expect.stringMatching(/^prod_*/), + is_giftcard: true, + description: "test-giftcard-description", + profile_id: expect.stringMatching(/^sp_*/), + options: [ + { + title: "Denominations", + id: expect.stringMatching(/^opt_*/), + product_id: expect.stringMatching(/^prod_*/), + created_at: expect.any(String), + updated_at: expect.any(String), + }, + ], + + variants: [ + { + title: "Test variant", + id: expect.stringMatching(/^variant_*/), + product_id: expect.stringMatching(/^prod_*/), + created_at: expect.any(String), + updated_at: expect.any(String), + prices: [ + { + id: expect.any(String), + currency_code: "usd", + amount: 100, + variant_id: expect.stringMatching(/^variant_*/), + created_at: expect.any(String), + updated_at: expect.any(String), + }, + ], + options: [ + { + id: expect.stringMatching(/^opt_*/), + option_id: expect.stringMatching(/^opt_*/), + created_at: expect.any(String), + variant_id: expect.stringMatching(/^variant_*/), + updated_at: expect.any(String), + }, + ], + }, + ], + created_at: expect.any(String), + updated_at: expect.any(String), + }, + ]) + }) + it("returns a list of products with child entities", async () => { const api = useApi() @@ -306,6 +586,42 @@ describe("/admin/products", () => { created_at: expect.any(String), updated_at: expect.any(String), }, + { + id: expect.stringMatching(/^test-*/), + profile_id: expect.stringMatching(/^sp_*/), + created_at: expect.any(String), + type: expect.any(Object), + collection: expect.any(Object), + options: expect.any(Array), + tags: expect.any(Array), + variants: expect.any(Array), + created_at: expect.any(String), + updated_at: expect.any(String), + }, + { + id: expect.stringMatching(/^test-*/), + profile_id: expect.stringMatching(/^sp_*/), + created_at: expect.any(String), + type: expect.any(Object), + collection: expect.any(Object), + options: expect.any(Array), + tags: expect.any(Array), + variants: expect.any(Array), + created_at: expect.any(String), + updated_at: expect.any(String), + }, + { + id: expect.stringMatching(/^test-*/), + profile_id: expect.stringMatching(/^sp_*/), + created_at: expect.any(String), + type: expect.any(Object), + collection: expect.any(Object), + options: expect.any(Array), + tags: expect.any(Array), + variants: expect.any(Array), + created_at: expect.any(String), + updated_at: expect.any(String), + }, ]) }) }) @@ -592,6 +908,28 @@ describe("/admin/products", () => { ) }) + it("updates product (removes images when empty array included)", async () => { + const api = useApi() + + const payload = { + images: [], + } + + const response = await api + .post("/admin/products/test-product", payload, { + headers: { + Authorization: "Bearer test_token", + }, + }) + .catch((err) => { + console.log(err) + }) + + expect(response.status).toEqual(200) + + expect(response.data.product.images.length).toEqual(0) + }) + it("fails to update product with invalid status", async () => { const api = useApi() @@ -696,6 +1034,7 @@ describe("/admin/products", () => { ) }) }) + describe("testing for soft-deletion + uniqueness on handles, collection and variant properties", () => { beforeEach(async () => { try { diff --git a/integration-tests/api/__tests__/store/__snapshots__/product-variants.js.snap b/integration-tests/api/__tests__/store/__snapshots__/product-variants.js.snap index 073655bb2a..3aa71856e9 100644 --- a/integration-tests/api/__tests__/store/__snapshots__/product-variants.js.snap +++ b/integration-tests/api/__tests__/store/__snapshots__/product-variants.js.snap @@ -17,6 +17,18 @@ Object { "material": null, "metadata": null, "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": "test-variant-option", + "metadata": null, + "option_id": "test-option", + "updated_at": Any, + "value": "Default variant", + "variant_id": "test-variant", + }, + ], "origin_country": null, "prices": Array [ Object { @@ -61,6 +73,18 @@ Object { "material": null, "metadata": null, "mid_code": null, + "options": Array [ + Object { + "created_at": Any, + "deleted_at": null, + "id": "test-variant-option", + "metadata": null, + "option_id": "test-option", + "updated_at": Any, + "value": "Default variant", + "variant_id": "test-variant", + }, + ], "origin_country": null, "prices": Array [ Object { diff --git a/integration-tests/api/__tests__/store/cart.js b/integration-tests/api/__tests__/store/cart.js index cc1e39d956..5b54e54165 100644 --- a/integration-tests/api/__tests__/store/cart.js +++ b/integration-tests/api/__tests__/store/cart.js @@ -70,6 +70,24 @@ describe("/store/carts", () => { expect(getRes.status).toEqual(200) }) + it("fails to create a cart when no region exist", async () => { + const api = useApi() + + await dbConnection.manager.query( + `UPDATE "country" SET region_id=null WHERE iso_2 = 'us'` + ) + await dbConnection.manager.query(`DELETE from region`) + + try { + await api.post("/store/carts") + } catch (error) { + expect(error.response.status).toEqual(400) + expect(error.response.data.message).toEqual( + "A region is required to create a cart" + ) + } + }) + it("creates a cart with country", async () => { const api = useApi() @@ -141,16 +159,16 @@ describe("/store/carts", () => { expect.assertions(2) const api = useApi() - try { - await api.post("/store/carts/test-cart", { - discounts: [{ code: "CREATED" }], + let response = await api + .post("/store/carts/test-cart", { + discounts: [{ code: "SPENT" }], + }) + .catch((error) => { + expect(error.response.status).toEqual(400) + expect(error.response.data.message).toEqual( + "Discount has been used maximum allowed times" + ) }) - } catch (error) { - expect(error.response.status).toEqual(400) - expect(error.response.data.message).toEqual( - "Discount has been used maximum allowed times" - ) - } }) it("fails to apply expired discount", async () => { diff --git a/integration-tests/api/__tests__/store/draft-order.js b/integration-tests/api/__tests__/store/draft-order.js index 6821163f8d..f36bac585f 100644 --- a/integration-tests/api/__tests__/store/draft-order.js +++ b/integration-tests/api/__tests__/store/draft-order.js @@ -1,63 +1,63 @@ -const path = require("path"); +const path = require("path") -const setupServer = require("../../../helpers/setup-server"); -const { useApi } = require("../../../helpers/use-api"); -const { initDb, useDb } = require("../../../helpers/use-db"); +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") -const draftOrderSeeder = require("../../helpers/draft-order-seeder"); +const draftOrderSeeder = require("../../helpers/draft-order-seeder") -jest.setTimeout(30000); +jest.setTimeout(30000) describe("/store/carts (draft-orders)", () => { - let medusaProcess; - let dbConnection; + let medusaProcess + let dbConnection beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")); - dbConnection = await initDb({ cwd }); - medusaProcess = await setupServer({ cwd }); - }); + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd }) + medusaProcess = await setupServer({ cwd }) + }) afterAll(async () => { - const db = useDb(); - await db.shutdown(); + const db = useDb() + await db.shutdown() - medusaProcess.kill(); - }); + medusaProcess.kill() + }) describe("POST /admin/draft-order", () => { beforeEach(async () => { try { - await draftOrderSeeder(dbConnection); + await draftOrderSeeder(dbConnection) } catch (err) { - console.log(err); - throw err; + console.log(err) + throw err } - }); + }) afterEach(async () => { - const db = useDb(); - await db.teardown(); - }); + const db = useDb() + await db.teardown() + }) it("completes a cart for a draft order thereby creating an order for the draft order", async () => { - const api = useApi(); + const api = useApi() const response = await api .post("/store/carts/test-cart/complete-cart", {}) .catch((err) => { - console.log(err); - }); + console.log(err) + }) - expect(response.status).toEqual(200); + expect(response.status).toEqual(200) const createdOrder = await api .get(`/store/orders/${response.data.data.id}`, {}) .catch((err) => { - console.log(err); - }); + console.log(err) + }) - expect(createdOrder.data.order.cart_id).toEqual("test-cart"); - }); - }); -}); + expect(createdOrder.data.order.cart_id).toEqual("test-cart") + }) + }) +}) diff --git a/integration-tests/api/__tests__/store/gift-cards.js b/integration-tests/api/__tests__/store/gift-cards.js index 7b69120fa2..83b72f86ff 100644 --- a/integration-tests/api/__tests__/store/gift-cards.js +++ b/integration-tests/api/__tests__/store/gift-cards.js @@ -1,63 +1,63 @@ -const path = require("path"); -const { Region, GiftCard } = require("@medusajs/medusa"); +const path = require("path") +const { Region, GiftCard } = require("@medusajs/medusa") -const setupServer = require("../../../helpers/setup-server"); -const { useApi } = require("../../../helpers/use-api"); -const { initDb, useDb } = require("../../../helpers/use-db"); +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") -jest.setTimeout(30000); +jest.setTimeout(30000) describe("/store/gift-cards", () => { - let medusaProcess; - let dbConnection; + let medusaProcess + let dbConnection beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")); - dbConnection = await initDb({ cwd }); - medusaProcess = await setupServer({ cwd }); - }); + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd }) + medusaProcess = await setupServer({ cwd }) + }) afterAll(async () => { - const db = useDb(); - await db.shutdown(); - medusaProcess.kill(); - }); + const db = useDb() + await db.shutdown() + medusaProcess.kill() + }) describe("GET /store/gift-cards/:code", () => { beforeEach(async () => { - const manager = dbConnection.manager; + const manager = dbConnection.manager await manager.insert(Region, { id: "region", name: "Test Region", currency_code: "usd", tax_rate: 0, - }); + }) await manager.insert(GiftCard, { id: "gift_test", code: "GC_TEST", value: 200, balance: 120, region_id: "region", - }); - }); + }) + }) afterEach(async () => { - const db = useDb(); - await db.teardown(); - }); + const db = useDb() + await db.teardown() + }) it("retrieves a gift card", async () => { - const api = useApi(); + const api = useApi() - const response = await api.get("/store/gift-cards/GC_TEST"); - expect(response.status).toEqual(200); + const response = await api.get("/store/gift-cards/GC_TEST") + expect(response.status).toEqual(200) expect(response.data.gift_card).toEqual({ id: "gift_test", code: "GC_TEST", value: 200, balance: 120, region: expect.any(Object), - }); - }); - }); -}); + }) + }) + }) +}) diff --git a/integration-tests/api/__tests__/store/product-variants.js b/integration-tests/api/__tests__/store/product-variants.js index e7e7ab1960..a517fc7b0c 100644 --- a/integration-tests/api/__tests__/store/product-variants.js +++ b/integration-tests/api/__tests__/store/product-variants.js @@ -79,6 +79,9 @@ describe("/store/variants", () => { }, ], product: expect.any(Object), + options: [ + { created_at: expect.any(String), updated_at: expect.any(String) }, + ], }, ], }) @@ -127,6 +130,9 @@ describe("/store/variants", () => { }, ], product: expect.any(Object), + options: [ + { created_at: expect.any(String), updated_at: expect.any(String) }, + ], }, }) }) diff --git a/integration-tests/api/__tests__/store/return-reason.js b/integration-tests/api/__tests__/store/return-reason.js index 8aa0c4c9d4..9cc8138daf 100644 --- a/integration-tests/api/__tests__/store/return-reason.js +++ b/integration-tests/api/__tests__/store/return-reason.js @@ -1,94 +1,96 @@ -const path = require("path"); +const path = require("path") -const { ReturnReason } = require("@medusajs/medusa"); +const { ReturnReason } = require("@medusajs/medusa") -const setupServer = require("../../../helpers/setup-server"); -const { useApi } = require("../../../helpers/use-api"); -const { initDb, useDb } = require("../../../helpers/use-db"); +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") -jest.setTimeout(30000); +jest.setTimeout(30000) describe("/store/return-reasons", () => { - let medusaProcess; - let dbConnection; + let medusaProcess + let dbConnection beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")); - dbConnection = await initDb({ cwd }); - medusaProcess = await setupServer({ cwd }); - }); + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd }) + medusaProcess = await setupServer({ cwd }) + }) afterAll(async () => { - const db = useDb(); - await db.shutdown(); - medusaProcess.kill(); - }); + const db = useDb() + await db.shutdown() + medusaProcess.kill() + }) describe("GET /store/return-reasons", () => { - let rrId; - let rrId_1; - let rrId_2; + let rrId + let rrId_1 + let rrId_2 beforeEach(async () => { try { const created = dbConnection.manager.create(ReturnReason, { value: "wrong_size", label: "Wrong size", - }); + }) - const result = await dbConnection.manager.save(created); - rrId = result.id; + const result = await dbConnection.manager.save(created) + rrId = result.id const created_child = dbConnection.manager.create(ReturnReason, { value: "too_big", label: "Too Big", - parent_return_reason_id: rrId - }); + parent_return_reason_id: rrId, + }) - const result_child = await dbConnection.manager.save(created_child); - rrId_1 = result_child.id; + const result_child = await dbConnection.manager.save(created_child) + rrId_1 = result_child.id const created_2 = dbConnection.manager.create(ReturnReason, { value: "too_big_1", label: "Too Big 1", - }); + }) - const result_2 = await dbConnection.manager.save(created_2); - rrId_2 = result_2.id; + const result_2 = await dbConnection.manager.save(created_2) + rrId_2 = result_2.id } catch (err) { - console.log(err); - throw err; + console.log(err) + throw err } - }); + }) afterEach(async () => { - const db = useDb(); - await db.teardown(); - }); + const db = useDb() + await db.teardown() + }) it("list return reasons", async () => { - const api = useApi(); + const api = useApi() const response = await api.get("/store/return-reasons").catch((err) => { - console.log(err); - }); + console.log(err) + }) - expect(response.status).toEqual(200); + expect(response.status).toEqual(200) expect(response.data.return_reasons).toEqual([ expect.objectContaining({ id: rrId, value: "wrong_size", - return_reason_children:[expect.objectContaining({ - id: rrId_1, - value: "too_big", - }),] + return_reason_children: [ + expect.objectContaining({ + id: rrId_1, + value: "too_big", + }), + ], }), expect.objectContaining({ id: rrId_2, value: "too_big_1", }), - ]); - }); - }); -}); + ]) + }) + }) +}) diff --git a/integration-tests/api/__tests__/store/returns.js b/integration-tests/api/__tests__/store/returns.js index 3d5151dedb..d01a90047e 100644 --- a/integration-tests/api/__tests__/store/returns.js +++ b/integration-tests/api/__tests__/store/returns.js @@ -1,4 +1,4 @@ -const path = require("path"); +const path = require("path") const { Region, ReturnReason, @@ -12,56 +12,56 @@ const { LineItem, Discount, DiscountRule, -} = require("@medusajs/medusa"); +} = require("@medusajs/medusa") -const setupServer = require("../../../helpers/setup-server"); -const { useApi } = require("../../../helpers/use-api"); -const { initDb, useDb } = require("../../../helpers/use-db"); +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") -jest.setTimeout(30000); +jest.setTimeout(30000) describe("/store/carts", () => { - let medusaProcess; - let dbConnection; + let medusaProcess + let dbConnection beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")); - dbConnection = await initDb({ cwd }); - medusaProcess = await setupServer({ cwd }); - }); + const cwd = path.resolve(path.join(__dirname, "..", "..")) + dbConnection = await initDb({ cwd }) + medusaProcess = await setupServer({ cwd }) + }) afterAll(async () => { - const db = useDb(); - await db.shutdown(); - medusaProcess.kill(); - }); + const db = useDb() + await db.shutdown() + medusaProcess.kill() + }) describe("POST /store/returns", () => { - let rrId; - let rrId_child; - let rrResult; + let rrId + let rrId_child + let rrResult beforeEach(async () => { - const manager = dbConnection.manager; + const manager = dbConnection.manager await manager.query( `ALTER SEQUENCE order_display_id_seq RESTART WITH 111` - ); + ) const defaultProfile = await manager.findOne(ShippingProfile, { type: "default", - }); - + }) + await manager.insert(Region, { id: "region", name: "Test Region", currency_code: "usd", tax_rate: 0, - }); - + }) + await manager.insert(Customer, { id: "cus_1234", email: "test@email.com", - }); + }) await manager.insert(Order, { id: "order_test", @@ -71,7 +71,7 @@ describe("/store/carts", () => { region_id: "region", tax_rate: 0, currency_code: "usd", - }); + }) await manager.insert(DiscountRule, { id: "discount_rule_id", @@ -79,7 +79,7 @@ describe("/store/carts", () => { value: 10, allocation: "total", type: "percentage", - }); + }) const d = manager.create(Discount, { id: "test-discount", @@ -87,9 +87,9 @@ describe("/store/carts", () => { is_dynamic: false, is_disabled: false, rule_id: "discount_rule_id", - }); + }) - await manager.save(d); + await manager.save(d) const ord = manager.create(Order, { id: "order_with_discount", @@ -99,18 +99,18 @@ describe("/store/carts", () => { region_id: "region", tax_rate: 0, currency_code: "usd", - }); + }) - ord.discounts = [d]; + ord.discounts = [d] - await manager.save(ord); + await manager.save(ord) await manager.insert(Product, { id: "test-product", title: "test product", profile_id: defaultProfile.id, options: [{ id: "test-option", title: "Size" }], - }); + }) await manager.insert(ProductVariant, { id: "test-variant", @@ -123,7 +123,7 @@ describe("/store/carts", () => { value: "Size", }, ], - }); + }) await manager.insert(LineItem, { id: "test-item", @@ -135,7 +135,7 @@ describe("/store/carts", () => { unit_price: 8000, quantity: 1, variant_id: "test-variant", - }); + }) await manager.insert(ShippingOption, { id: "test-option", @@ -147,35 +147,35 @@ describe("/store/carts", () => { price_type: "flat_rate", amount: 1000, is_return: true, - }); + }) const created = dbConnection.manager.create(ReturnReason, { value: "wrong_size", label: "Wrong Size", - }); - const result = await dbConnection.manager.save(created); + }) + const result = await dbConnection.manager.save(created) rrResult = result - rrId = result.id; + rrId = result.id const created_1 = dbConnection.manager.create(ReturnReason, { value: "too_big", label: "Too Big", parent_return_reason_id: rrId, - }); + }) - const result_1 = await dbConnection.manager.save(created_1); + const result_1 = await dbConnection.manager.save(created_1) - rrId_child = result_1.id; - }); + rrId_child = result_1.id + }) afterEach(async () => { - const db = useDb(); - await db.teardown(); - }); + const db = useDb() + await db.teardown() + }) it("creates a return", async () => { - const api = useApi(); + const api = useApi() const response = await api .post("/store/returns", { @@ -188,16 +188,16 @@ describe("/store/carts", () => { ], }) .catch((err) => { - return err.response; - }); - expect(response.status).toEqual(200); + return err.response + }) + expect(response.status).toEqual(200) - expect(response.data.return.refund_amount).toEqual(8000); - }); + expect(response.data.return.refund_amount).toEqual(8000) + }) it("failes to create a return with a reason category", async () => { - const api = useApi(); - + const api = useApi() + const response = await api .post("/store/returns", { order_id: "order_test", @@ -211,17 +211,18 @@ describe("/store/carts", () => { ], }) .catch((err) => { - return err.response; - }); + return err.response + }) - expect(response.status).toEqual(400); - expect(response.data.message).toEqual('Cannot apply return reason category') - - }); + expect(response.status).toEqual(400) + expect(response.data.message).toEqual( + "Cannot apply return reason category" + ) + }) it("creates a return with reasons", async () => { - const api = useApi(); - + const api = useApi() + const response = await api .post("/store/returns", { order_id: "order_test", @@ -236,28 +237,28 @@ describe("/store/carts", () => { }) .catch((err) => { console.log(err.response) - return err.response; - }); - expect(response.status).toEqual(200); + return err.response + }) + expect(response.status).toEqual(200) expect(response.data.return.items).toEqual([ expect.objectContaining({ reason_id: rrId_child, note: "TOO small", }), - ]); - }); + ]) + }) it("creates a return with discount and non-discountable item", async () => { - const api = useApi(); + const api = useApi() await dbConnection.manager.query( `UPDATE line_item set allow_discounts=false where id='test-item'` - ); + ) await dbConnection.manager.query( `UPDATE line_item set order_id='order_with_discount' where id='test-item'` - ); + ) const response = await api .post("/store/returns", { @@ -270,15 +271,15 @@ describe("/store/carts", () => { ], }) .catch((err) => { - return err.response; - }); + return err.response + }) - expect(response.status).toEqual(200); - expect(response.data.return.refund_amount).toEqual(8000); - }); + expect(response.status).toEqual(200) + expect(response.data.return.refund_amount).toEqual(8000) + }) it("creates a return with shipping method", async () => { - const api = useApi(); + const api = useApi() const response = await api .post("/store/returns", { @@ -294,12 +295,11 @@ describe("/store/carts", () => { ], }) .catch((err) => { - return err.response; - }); - expect(response.status).toEqual(200); + return err.response + }) + expect(response.status).toEqual(200) - expect(response.data.return.refund_amount).toEqual(7000); - }); - - }); -}); + expect(response.data.return.refund_amount).toEqual(7000) + }) + }) +}) diff --git a/integration-tests/api/helpers/call-helpers.js b/integration-tests/api/helpers/call-helpers.js index 9ecc592811..9a9233d42d 100644 --- a/integration-tests/api/helpers/call-helpers.js +++ b/integration-tests/api/helpers/call-helpers.js @@ -1,33 +1,33 @@ -const { useApi } = require("../../helpers/use-api"); +const { useApi } = require("../../helpers/use-api") const header = { headers: { authorization: "Bearer test_token", }, -}; +} const resolveCall = async (path, payload, header) => { - const api = useApi(); - let res; + const api = useApi() + let res try { - const resp = await api.post(path, payload, header); - res = resp.status; + const resp = await api.post(path, payload, header) + res = resp.status } catch (expectedException) { try { - res = expectedException.response.status; + res = expectedException.response.status } catch (_) { - console.error(expectedException); + console.error(expectedException) } } - return res; -}; + return res +} const determineFail = (actual, expected, path) => { if (expected !== actual) { - console.log(`failed at path : ${path}`); + console.log(`failed at path : ${path}`) } - expect(actual).toEqual(expected); -}; + expect(actual).toEqual(expected) +} /** * Allows you to wrap a Call function so that you may reuse some input values. @@ -36,8 +36,8 @@ const determineFail = (actual, expected, path) => { * @returns */ module.exports.partial = function (fun, input = {}) { - return async (remaining) => await fun({ ...remaining, ...input }); -}; + return async (remaining) => await fun({ ...remaining, ...input }) +} /** * Allows you to assert a specific code result from a POST call. @@ -51,9 +51,9 @@ module.exports.expectPostCallToReturn = async function ( payload: {}, } ) { - const res = await resolveCall(input.path, input.payload, header); - determineFail(res, input.code, input.path); -}; + const res = await resolveCall(input.path, input.payload, header) + determineFail(res, input.code, input.path) +} /** * Allows you to assert a specific code result from multiple POST @@ -76,10 +76,10 @@ module.exports.expectAllPostCallsToReturn = async function ( input.pathf(i), input.payloadf ? input.payloadf(i) : {}, header - ); - determineFail(res, input.code, input.pathf(i)); + ) + determineFail(res, input.code, input.pathf(i)) } -}; +} /** * Allows you to retrieve a specific object the response @@ -92,9 +92,9 @@ module.exports.expectAllPostCallsToReturn = async function ( * to the get parameter provided. */ module.exports.callGet = async function ({ path, get }) { - const api = useApi(); - const res = await api.get(path, header); + const api = useApi() + const res = await api.get(path, header) - determineFail(res.status, 200, path); - return res?.data[get]; -}; + determineFail(res.status, 200, path) + return res?.data[get] +} diff --git a/integration-tests/api/helpers/cart-seeder.js b/integration-tests/api/helpers/cart-seeder.js index f7e499368a..d4b054873f 100644 --- a/integration-tests/api/helpers/cart-seeder.js +++ b/integration-tests/api/helpers/cart-seeder.js @@ -108,6 +108,28 @@ module.exports = async (connection, data = {}) => { tenPercent.rule = tenPercentRule await manager.save(tenPercent) + const dUsageLimit = await manager.create(Discount, { + id: "test-discount-usage-limit", + code: "SPENT", + is_dynamic: false, + is_disabled: false, + usage_limit: 10, + usage_count: 10, + }) + + const drUsage = await manager.create(DiscountRule, { + id: "test-discount-rule-usage-limit", + description: "Created", + type: "fixed", + value: 10000, + allocation: "total", + }) + + dUsageLimit.rule = drUsage + dUsageLimit.regions = [r] + + await manager.save(dUsageLimit) + const d = await manager.create(Discount, { id: "test-discount", code: "CREATED", diff --git a/integration-tests/api/helpers/claim-seeder.js b/integration-tests/api/helpers/claim-seeder.js index 7c34d987e8..ae19e09e68 100644 --- a/integration-tests/api/helpers/claim-seeder.js +++ b/integration-tests/api/helpers/claim-seeder.js @@ -4,10 +4,10 @@ const { LineItem, Fulfillment, Return, -} = require("@medusajs/medusa"); +} = require("@medusajs/medusa") module.exports = async (connection, data = {}) => { - const manager = connection.manager; + const manager = connection.manager let orderWithClaim = manager.create(Order, { id: "order-with-claim", @@ -40,9 +40,9 @@ module.exports = async (connection, data = {}) => { ], items: [], ...data, - }); + }) - await manager.save(orderWithClaim); + await manager.save(orderWithClaim) const li = manager.create(LineItem, { id: "test-item-co-2", @@ -54,9 +54,9 @@ module.exports = async (connection, data = {}) => { quantity: 1, variant_id: "test-variant", order_id: orderWithClaim.id, - }); + }) - await manager.save(li); + await manager.save(li) const li2 = manager.create(LineItem, { id: "test-item-co-3", @@ -68,9 +68,9 @@ module.exports = async (connection, data = {}) => { quantity: 4, variant_id: "test-variant", order_id: orderWithClaim.id, - }); + }) - await manager.save(li2); + await manager.save(li2) const claimWithFulfillment = manager.create(ClaimOrder, { id: "claim-w-f", @@ -79,23 +79,23 @@ module.exports = async (connection, data = {}) => { fulfillment_status: "not_fulfilled", order_id: "order-with-claim", ...data, - }); + }) const ful1 = manager.create(Fulfillment, { id: "fulfillment-co-1", data: {}, provider_id: "test-ful", - }); + }) const ful2 = manager.create(Fulfillment, { id: "fulfillment-co-2", data: {}, provider_id: "test-ful", - }); + }) - claimWithFulfillment.fulfillments = [ful1, ful2]; + claimWithFulfillment.fulfillments = [ful1, ful2] - await manager.save(claimWithFulfillment); + await manager.save(claimWithFulfillment) const claimWithReturn = manager.create(ClaimOrder, { id: "claim-w-r", @@ -104,9 +104,9 @@ module.exports = async (connection, data = {}) => { fulfillment_status: "not_fulfilled", order_id: "order-with-claim", ...data, - }); + }) - await manager.save(claimWithReturn); + await manager.save(claimWithReturn) await manager.insert(Return, { id: "return-id-2", @@ -114,5 +114,5 @@ module.exports = async (connection, data = {}) => { status: "requested", refund_amount: 0, data: {}, - }); -}; + }) +} diff --git a/integration-tests/api/helpers/draft-order-seeder.js b/integration-tests/api/helpers/draft-order-seeder.js index 5adfc9bf30..19b63b0d7c 100644 --- a/integration-tests/api/helpers/draft-order-seeder.js +++ b/integration-tests/api/helpers/draft-order-seeder.js @@ -14,35 +14,35 @@ const { Discount, DiscountRule, Payment, -} = require("@medusajs/medusa"); +} = require("@medusajs/medusa") module.exports = async (connection, data = {}) => { - const manager = connection.manager; + const manager = connection.manager const defaultProfile = await manager.findOne(ShippingProfile, { type: "default", - }); + }) await manager.insert(Product, { id: "test-product", title: "test product", profile_id: defaultProfile.id, options: [{ id: "test-option", title: "Size" }], - }); + }) await manager.insert(Address, { id: "oli-shipping", first_name: "oli", last_name: "test", country_code: "us", - }); + }) await manager.insert(Product, { id: "test-product-2", title: "test product 2", profile_id: defaultProfile.id, options: [{ id: "test-option-color", title: "Color" }], - }); + }) await manager.insert(ProductVariant, { id: "test-variant", @@ -55,7 +55,7 @@ module.exports = async (connection, data = {}) => { value: "Size", }, ], - }); + }) await manager.insert(ProductVariant, { id: "test-variant-2", @@ -68,22 +68,22 @@ module.exports = async (connection, data = {}) => { value: "Color", }, ], - }); + }) const ma = manager.create(MoneyAmount, { variant_id: "test-variant", currency_code: "usd", amount: 8000, - }); - await manager.save(ma); + }) + await manager.save(ma) const ma2 = manager.create(MoneyAmount, { variant_id: "test-variant-2", currency_code: "usd", amount: 10000, - }); + }) - await manager.save(ma2); + await manager.save(ma2) await manager.insert(Region, { id: "test-region", @@ -96,7 +96,7 @@ module.exports = async (connection, data = {}) => { is_installed: true, }, ], - }); + }) await manager.insert(Region, { id: "test-region-2", @@ -109,7 +109,7 @@ module.exports = async (connection, data = {}) => { is_installed: true, }, ], - }); + }) await manager.insert(DiscountRule, { id: "discount_rule_id", @@ -117,7 +117,7 @@ module.exports = async (connection, data = {}) => { value: 10, allocation: "total", type: "percentage", - }); + }) const d = manager.create(Discount, { id: "test-discount", @@ -125,7 +125,7 @@ module.exports = async (connection, data = {}) => { is_dynamic: false, is_disabled: false, rule_id: "discount_rule_id", - }); + }) d.regions = [ { @@ -134,27 +134,27 @@ module.exports = async (connection, data = {}) => { currency_code: "usd", tax_rate: 0, }, - ]; + ] - await manager.save(d); + await manager.save(d) await manager.query( `UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'` - ); + ) await manager.query( `UPDATE "country" SET region_id='test-region-2' WHERE iso_2 = 'de'` - ); + ) await manager.insert(Customer, { id: "oli-test", email: "oli@test.dk", - }); + }) await manager.insert(Customer, { id: "lebron-james", email: "lebron@james.com", - }); + }) await manager.insert(ShippingOption, { id: "test-option", @@ -165,7 +165,7 @@ module.exports = async (connection, data = {}) => { price_type: "flat_rate", amount: 1000, data: {}, - }); + }) await manager.insert(ShippingOption, { id: "test-option-req", @@ -176,14 +176,14 @@ module.exports = async (connection, data = {}) => { price_type: "flat_rate", amount: 1000, data: {}, - }); + }) await manager.insert(ShippingOptionRequirement, { id: "option-req", shipping_option_id: "test-option-req", type: "min_subtotal", amount: 10, - }); + }) const c = manager.create(Cart, { id: "test-cart", @@ -207,7 +207,7 @@ module.exports = async (connection, data = {}) => { ], type: "draft_order", metadata: { draft_order_id: "test-draft-order" }, - }); + }) const pay = manager.create(Payment, { id: "test-payment", @@ -216,13 +216,13 @@ module.exports = async (connection, data = {}) => { amount_refunded: 0, provider_id: "test-pay", data: {}, - }); + }) - await manager.save(pay); + await manager.save(pay) - c.payment = pay; + c.payment = pay - await manager.save(c); + await manager.save(c) await manager.insert(PaymentSession, { id: "test-session", @@ -231,7 +231,7 @@ module.exports = async (connection, data = {}) => { is_selected: true, data: {}, status: "authorized", - }); + }) const draftOrder = manager.create(DraftOrder, { id: "test-draft-order", @@ -255,7 +255,7 @@ module.exports = async (connection, data = {}) => { region_id: "test-region", discounts: [], ...data, - }); + }) - await manager.save(draftOrder); -}; + await manager.save(draftOrder) +} diff --git a/integration-tests/api/helpers/order-seeder.js b/integration-tests/api/helpers/order-seeder.js index ac3bde962c..dcd270a139 100644 --- a/integration-tests/api/helpers/order-seeder.js +++ b/integration-tests/api/helpers/order-seeder.js @@ -12,21 +12,21 @@ const { Payment, Order, Swap, -} = require("@medusajs/medusa"); +} = require("@medusajs/medusa") module.exports = async (connection, data = {}) => { - const manager = connection.manager; + const manager = connection.manager const defaultProfile = await manager.findOne(ShippingProfile, { type: "default", - }); + }) await manager.insert(Product, { id: "test-product", title: "test product", profile_id: defaultProfile.id, options: [{ id: "test-option", title: "Size" }], - }); + }) await manager.insert(ProductVariant, { id: "test-variant", @@ -39,7 +39,7 @@ module.exports = async (connection, data = {}) => { value: "Size", }, ], - }); + }) await manager.insert(ProductVariant, { id: "test-variant-2", @@ -52,37 +52,37 @@ module.exports = async (connection, data = {}) => { value: "Large", }, ], - }); + }) const ma2 = manager.create(MoneyAmount, { variant_id: "test-variant-2", currency_code: "usd", amount: 8000, - }); - await manager.save(ma2); + }) + await manager.save(ma2) const ma = manager.create(MoneyAmount, { variant_id: "test-variant", currency_code: "usd", amount: 8000, - }); - await manager.save(ma); + }) + await manager.save(ma) await manager.insert(Region, { id: "test-region", name: "Test Region", currency_code: "usd", tax_rate: 0, - }); + }) await manager.query( `UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'` - ); + ) await manager.insert(Customer, { id: "test-customer", email: "test@email.com", - }); + }) await manager.insert(ShippingOption, { id: "test-option", @@ -93,7 +93,7 @@ module.exports = async (connection, data = {}) => { price_type: "flat_rate", amount: 1000, data: {}, - }); + }) await manager.insert(ShippingOption, { id: "test-return-option", @@ -105,7 +105,7 @@ module.exports = async (connection, data = {}) => { price_type: "flat_rate", amount: 1000, is_return: true, - }); + }) const order = manager.create(Order, { id: "test-order", @@ -157,9 +157,9 @@ module.exports = async (connection, data = {}) => { ], items: [], ...data, - }); + }) - await manager.save(order); + await manager.save(order) const li = manager.create(LineItem, { id: "test-item", @@ -172,9 +172,9 @@ module.exports = async (connection, data = {}) => { quantity: 1, variant_id: "test-variant", order_id: "test-order", - }); + }) - await manager.save(li); + await manager.save(li) await manager.insert(ShippingMethod, { id: "test-method", @@ -183,7 +183,7 @@ module.exports = async (connection, data = {}) => { order_id: "test-order", price: 1000, data: {}, - }); + }) const orderTemplate = () => { return { @@ -195,8 +195,8 @@ module.exports = async (connection, data = {}) => { currency_code: "usd", tax_rate: 0, ...data, - }; - }; + } + } const paymentTemplate = () => { return { @@ -204,8 +204,8 @@ module.exports = async (connection, data = {}) => { currency_code: "usd", provider_id: "test-pay", data: {}, - }; - }; + } + } const orderWithClaim = manager.create(Order, { id: "test-order-w-c", @@ -219,59 +219,59 @@ module.exports = async (connection, data = {}) => { }, ], ...orderTemplate(), - }); + }) - await manager.save(orderWithClaim); + await manager.save(orderWithClaim) await manager.insert(Payment, { order_id: "test-order-w-c", id: "o-pay1", ...paymentTemplate(), - }); + }) const orderWithSwap = manager.create(Order, { id: "test-order-w-s", ...orderTemplate(), - }); + }) - await manager.save(orderWithSwap); + await manager.save(orderWithSwap) await manager.insert(Payment, { order_id: "test-order-w-s", id: "o-pay2", ...paymentTemplate(), - }); + }) const swap1 = manager.create(Swap, { id: "swap-1", order_id: "test-order-w-s", fulfillment_status: "not_fulfilled", payment_status: "not_paid", - }); + }) const pay1 = manager.create(Payment, { id: "pay1", ...paymentTemplate(), - }); + }) - swap1.payment = pay1; + swap1.payment = pay1 const swap2 = manager.create(Swap, { id: "swap-2", order_id: "test-order-w-s", fulfillment_status: "not_fulfilled", payment_status: "not_paid", - }); + }) const pay2 = manager.create(Payment, { id: "pay2", ...paymentTemplate(), - }); + }) - swap2.payment = pay2; + swap2.payment = pay2 - await manager.save(swap1); - await manager.save(swap2); + await manager.save(swap1) + await manager.save(swap2) const orderWithFulfillment = manager.create(Order, { id: "test-order-w-f", @@ -288,15 +288,15 @@ module.exports = async (connection, data = {}) => { }, ], ...orderTemplate(), - }); + }) - await manager.save(orderWithFulfillment); + await manager.save(orderWithFulfillment) await manager.insert(Payment, { order_id: "test-order-w-f", id: "o-pay3", ...paymentTemplate(), - }); + }) const orderWithReturn = manager.create(Order, { id: "test-order-w-r", @@ -311,13 +311,13 @@ module.exports = async (connection, data = {}) => { }, ], ...orderTemplate(), - }); + }) - await manager.save(orderWithReturn); + await manager.save(orderWithReturn) await manager.insert(Payment, { order_id: "test-order-w-r", id: "o-pay4", ...paymentTemplate(), - }); -}; + }) +} diff --git a/integration-tests/api/helpers/product-seeder.js b/integration-tests/api/helpers/product-seeder.js index 0f93673c18..8cea9fadb6 100644 --- a/integration-tests/api/helpers/product-seeder.js +++ b/integration-tests/api/helpers/product-seeder.js @@ -25,6 +25,22 @@ module.exports = async (connection, data = {}) => { await manager.save(coll) + const coll1 = manager.create(ProductCollection, { + id: "test-collection1", + handle: "test-collection1", + title: "Test collection 1", + }) + + await manager.save(coll1) + + const coll2 = manager.create(ProductCollection, { + id: "test-collection2", + handle: "test-collection2", + title: "Test collection 2", + }) + + await manager.save(coll2) + const tag = manager.create(ProductTag, { id: "tag1", value: "123", @@ -32,6 +48,20 @@ module.exports = async (connection, data = {}) => { await manager.save(tag) + const tag3 = manager.create(ProductTag, { + id: "tag3", + value: "123", + }) + + await manager.save(tag3) + + const tag4 = manager.create(ProductTag, { + id: "tag4", + value: "123", + }) + + await manager.save(tag4) + const type = manager.create(ProductType, { id: "test-type", value: "test-type", @@ -199,4 +229,46 @@ module.exports = async (connection, data = {}) => { }) await manager.save(variant5) + + const product1 = manager.create(Product, { + id: "test-product_filtering_1", + handle: "test-product_filtering_1", + title: "Test product filtering 1", + profile_id: defaultProfile.id, + description: "test-product-description", + type: { id: "test-type", value: "test-type" }, + collection_id: "test-collection1", + status: "proposed", + tags: [{ id: "tag3", value: "123" }], + }) + + await manager.save(product1) + + const product2 = manager.create(Product, { + id: "test-product_filtering_2", + handle: "test-product_filtering_2", + title: "Test product filtering 2", + profile_id: defaultProfile.id, + description: "test-product-description", + type: { id: "test-type", value: "test-type" }, + collection_id: "test-collection2", + status: "published", + tags: [{ id: "tag3", value: "123" }], + }) + + await manager.save(product2) + + const product3 = manager.create(Product, { + id: "test-product_filtering_3", + handle: "test-product_filtering_3", + title: "Test product filtering 3", + profile_id: defaultProfile.id, + description: "test-product-description", + type: { id: "test-type", value: "test-type" }, + collection_id: "test-collection1", + status: "draft", + tags: [{ id: "tag4", value: "1234" }], + }) + + await manager.save(product3) } diff --git a/integration-tests/api/jest.config.js b/integration-tests/api/jest.config.js index af00a0c2f4..caebf0e68a 100644 --- a/integration-tests/api/jest.config.js +++ b/integration-tests/api/jest.config.js @@ -13,4 +13,4 @@ module.exports = { ], transform: { "^.+\\.[jt]s$": `../../jest-transformer.js` }, setupFilesAfterEnv: ["../setup.js"], -}; +} diff --git a/integration-tests/api/package.json b/integration-tests/api/package.json index 7c694041db..b921c2217a 100644 --- a/integration-tests/api/package.json +++ b/integration-tests/api/package.json @@ -8,15 +8,15 @@ "build": "babel src -d dist --extensions \".ts,.js\"" }, "dependencies": { - "@medusajs/medusa": "1.1.41-dev-1634206968632", - "medusa-interfaces": "1.1.23-dev-1634206968632", + "@medusajs/medusa": "1.1.41-dev-1634220658423", + "medusa-interfaces": "1.1.23-dev-1634220658423", "typeorm": "^0.2.31" }, "devDependencies": { "@babel/cli": "^7.12.10", "@babel/core": "^7.12.10", "@babel/node": "^7.12.10", - "babel-preset-medusa-package": "1.1.15-dev-1634206968632", + "babel-preset-medusa-package": "1.1.15-dev-1634220658423", "jest": "^26.6.3" } } diff --git a/integration-tests/api/src/services/test-ful.js b/integration-tests/api/src/services/test-ful.js index b73427707c..f7726108ac 100644 --- a/integration-tests/api/src/services/test-ful.js +++ b/integration-tests/api/src/services/test-ful.js @@ -1,10 +1,10 @@ -import { FulfillmentService } from "medusa-interfaces"; +import { FulfillmentService } from "medusa-interfaces" class TestFulService extends FulfillmentService { - static identifier = "test-ful"; + static identifier = "test-ful" constructor() { - super(); + super() } getFulfillmentOptions() { @@ -12,42 +12,42 @@ class TestFulService extends FulfillmentService { { id: "manual-fulfillment", }, - ]; + ] } validateFulfillmentData(data, cart) { - return data; + return data } validateOption(data) { - return true; + return true } canCalculate() { - return false; + return false } calculatePrice() { - throw Error("Manual Fulfillment service cannot calculatePrice"); + throw Error("Manual Fulfillment service cannot calculatePrice") } createOrder() { // No data is being sent anywhere - return Promise.resolve({}); + return Promise.resolve({}) } createReturn() { - return Promise.resolve({}); + return Promise.resolve({}) } createFulfillment() { // No data is being sent anywhere - return Promise.resolve({}); + return Promise.resolve({}) } cancelFulfillment() { - return Promise.resolve({}); + return Promise.resolve({}) } } -export default TestFulService; +export default TestFulService diff --git a/integration-tests/api/src/services/test-not.js b/integration-tests/api/src/services/test-not.js index 852bed1b72..39c5ba665a 100644 --- a/integration-tests/api/src/services/test-not.js +++ b/integration-tests/api/src/services/test-not.js @@ -1,19 +1,19 @@ -import { NotificationService } from "medusa-interfaces"; +import { NotificationService } from "medusa-interfaces" class TestNotiService extends NotificationService { - static identifier = "test-not"; + static identifier = "test-not" constructor() { - super(); + super() } async sendNotification() { - return Promise.resolve(); + return Promise.resolve() } async resendNotification() { - return Promise.resolve(); + return Promise.resolve() } } -export default TestNotiService; +export default TestNotiService diff --git a/integration-tests/api/src/services/test-pay.js b/integration-tests/api/src/services/test-pay.js index 22725965ab..7536c45a45 100644 --- a/integration-tests/api/src/services/test-pay.js +++ b/integration-tests/api/src/services/test-pay.js @@ -1,59 +1,59 @@ -import { PaymentService } from "medusa-interfaces"; +import { PaymentService } from "medusa-interfaces" class TestPayService extends PaymentService { - static identifier = "test-pay"; + static identifier = "test-pay" constructor() { - super(); + super() } async getStatus(paymentData) { - return "authorized"; + return "authorized" } async retrieveSavedMethods(customer) { - return Promise.resolve([]); + return Promise.resolve([]) } async createPayment() { - return {}; + return {} } async retrievePayment(data) { - return {}; + return {} } async getPaymentData(sessionData) { - return {}; + return {} } async authorizePayment(sessionData, context = {}) { - return { data: {}, status: "authorized" }; + return { data: {}, status: "authorized" } } async updatePaymentData(sessionData, update) { - return {}; + return {} } async updatePayment(sessionData, cart) { - return {}; + return {} } async deletePayment(payment) { - return {}; + return {} } async capturePayment(payment) { - return {}; + return {} } async refundPayment(payment, amountToRefund) { - return {}; + return {} } async cancelPayment(payment) { - return {}; + return {} } } -export default TestPayService; +export default TestPayService diff --git a/integration-tests/api/yarn.lock b/integration-tests/api/yarn.lock index 2fd0b45a7d..069f5d490e 100644 --- a/integration-tests/api/yarn.lock +++ b/integration-tests/api/yarn.lock @@ -1223,10 +1223,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@medusajs/medusa-cli@1.1.18-dev-1634206968632": - version "1.1.18-dev-1634206968632" - resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.18-dev-1634206968632.tgz#26c12ed689f9d0485c14eccdd377b67bff505f90" - integrity sha512-oQwdVu2M2v5JRC+gwNXoL/fyGkH6nRaEBDG1rJk0YAmMawMxOIAAd/t1tMK16P69xK4eIUb4fkwX7BpC7kWKiA== +"@medusajs/medusa-cli@1.1.18-dev-1634220658423": + version "1.1.18-dev-1634220658423" + resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.18-dev-1634220658423.tgz#e72313f7749bf0a2c3b8930716f9b84ad09fc939" + integrity sha512-EGNKYTXX4JNHrsP8HAuK7XBQ4irL9jLTB5Fr1jS+XH2gx5xNTj1zJpyahNROI8wWIBEu1MmFNdKh4hJm1RfGMQ== dependencies: "@babel/polyfill" "^7.8.7" "@babel/runtime" "^7.9.6" @@ -1244,8 +1244,8 @@ is-valid-path "^0.1.1" joi-objectid "^3.0.1" meant "^1.0.1" - medusa-core-utils "1.1.22-dev-1634206968632" - medusa-telemetry "0.0.5-dev-1634206968632" + medusa-core-utils "1.1.22-dev-1634220658423" + medusa-telemetry "0.0.5-dev-1634220658423" netrc-parser "^3.1.6" open "^8.0.6" ora "^5.4.1" @@ -1259,13 +1259,13 @@ winston "^3.3.3" yargs "^15.3.1" -"@medusajs/medusa@1.1.41-dev-1634206968632": - version "1.1.41-dev-1634206968632" - resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.41-dev-1634206968632.tgz#f4fb24416aab594401d4a8de91f2bcd63f4078d9" - integrity sha512-3nbKX3vcB1sDCkt9FUs611ZxBrGGWBGArEXwtwHpJ/PScesPE1Gl9y+q8lfnu8NInYD3hw1Tpp4JMaOrfpifgw== +"@medusajs/medusa@1.1.41-dev-1634220658423": + version "1.1.41-dev-1634220658423" + resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.41-dev-1634220658423.tgz#b3b251d0d97734b7c3806253bd49d5b62eef8324" + integrity sha512-oqM2vaSoc/k5nID3k32sslIkl1yXwCf4GTGrlywZ/x8BgmYFfTMgPJ8Lr3WrAuh7hwsXrOR2Rj9Bq22cFeKZZQ== dependencies: "@hapi/joi" "^16.1.8" - "@medusajs/medusa-cli" "1.1.18-dev-1634206968632" + "@medusajs/medusa-cli" "1.1.18-dev-1634220658423" "@types/lodash" "^4.14.168" awilix "^4.2.3" body-parser "^1.19.0" @@ -1287,8 +1287,8 @@ joi "^17.3.0" joi-objectid "^3.0.1" jsonwebtoken "^8.5.1" - medusa-core-utils "1.1.22-dev-1634206968632" - medusa-test-utils "1.1.25-dev-1634206968632" + medusa-core-utils "1.1.22-dev-1634220658423" + medusa-test-utils "1.1.25-dev-1634220658423" morgan "^1.9.1" multer "^1.4.2" passport "^0.4.0" @@ -1933,10 +1933,10 @@ babel-preset-jest@^26.6.2: babel-plugin-jest-hoist "^26.6.2" babel-preset-current-node-syntax "^1.0.0" -babel-preset-medusa-package@1.1.15-dev-1634206968632: - version "1.1.15-dev-1634206968632" - resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.15-dev-1634206968632.tgz#752d6b645d0d09ad53d955bc823572b8d05ecda6" - integrity sha512-AvhdeUwC2uUnMFN9qA4oPDyZ+P0IO+rmiG/Udl7mssh43o3thDATllZSNbRBIwr4jy9M67oj+dbtrO6o8vJoBQ== +babel-preset-medusa-package@1.1.15-dev-1634220658423: + version "1.1.15-dev-1634220658423" + resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.15-dev-1634220658423.tgz#c8c1251e93d0f1e012acc720a3d60d112ddbbbb1" + integrity sha512-1CcmDubw9VByN/flg7NQ+rHu0Vt4RjBycgeXE7s7A9IcMYh1CC7HoHTFYPFpisUE6vU/axiNmbq+43bEbVCdyg== dependencies: "@babel/plugin-proposal-class-properties" "^7.12.1" "@babel/plugin-proposal-decorators" "^7.12.1" @@ -5110,25 +5110,25 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -medusa-core-utils@1.1.22-dev-1634206968632: - version "1.1.22-dev-1634206968632" - resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.22-dev-1634206968632.tgz#7eb0e6f95d8bd494aec706aa081ffbba8825e4ba" - integrity sha512-htkx9927dyEWdfOXw2xj58O7GPtPaTY5p9JO/Sja6LuN9QaotMPVrOkqy7drHiGtdn3IzjIMq4i7iub2DgJy0w== +medusa-core-utils@1.1.22-dev-1634220658423: + version "1.1.22-dev-1634220658423" + resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.22-dev-1634220658423.tgz#44457d84f8817c1680426573f43c6c0019be40f3" + integrity sha512-fvHEUaHc41li90neDjqUvq5oCvuVbgYhEv2PbtwbpNBIalFnnhB8iFReFEunZZeLq12catBZmAXEW3QkaI2ltA== dependencies: joi "^17.3.0" joi-objectid "^3.0.1" -medusa-interfaces@1.1.23-dev-1634206968632: - version "1.1.23-dev-1634206968632" - resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.23-dev-1634206968632.tgz#1b9de0742daf85ddeacb11424857f5d6cc75e8f0" - integrity sha512-8KsXxuMF+jFczyGa088DLVHzfejwD0X74g/AuXhCTkHbc52cbCZr9o7GMok7hfOCz7wp2kKUDBACL0y6PzxHrQ== +medusa-interfaces@1.1.23-dev-1634220658423: + version "1.1.23-dev-1634220658423" + resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.23-dev-1634220658423.tgz#5e498e92f26999d0dc3850404fb3a25c34644f16" + integrity sha512-N0qAn5Hwv+w5flejeH4zF5EI8d9X7CjwXuYBt37MTwmdsh4XZU/dhnt49S3iLwJUGvw9oinzII6N15e5LljxWQ== dependencies: - medusa-core-utils "1.1.22-dev-1634206968632" + medusa-core-utils "1.1.22-dev-1634220658423" -medusa-telemetry@0.0.5-dev-1634206968632: - version "0.0.5-dev-1634206968632" - resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.5-dev-1634206968632.tgz#a9a1161aacfe0aaa476ba3ce3c15cb199c201cef" - integrity sha512-z3a5mXplM+XxkY3vJ8t159odgmkdMUHAWBZEM9PGaltkCh9Ew3RWkNXZeC60nW7tc85sfn1/nRxWpLgIywbruQ== +medusa-telemetry@0.0.5-dev-1634220658423: + version "0.0.5-dev-1634220658423" + resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.5-dev-1634220658423.tgz#ebebe8b53601412a3d6501adba710098948abcad" + integrity sha512-qr6LubLeXSIARlGc+M8vFxMKI/rAORDJxmTNYv9Ykrg22yPd+1bIy1sTNxtxz2Q9Q90uwJcTeOO8oY/uYUCM8g== dependencies: axios "^0.21.1" axios-retry "^3.1.9" @@ -5140,13 +5140,13 @@ medusa-telemetry@0.0.5-dev-1634206968632: remove-trailing-slash "^0.1.1" uuid "^8.3.2" -medusa-test-utils@1.1.25-dev-1634206968632: - version "1.1.25-dev-1634206968632" - resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.25-dev-1634206968632.tgz#7306ff294c23bc2f229ae3112e8fc5b5ea362500" - integrity sha512-P+aa/Z7qywZt7z+6vj/owZhX8bHJCPsn1/Ak/9cGM0oJM/QGPzrQ00auikE13XyD95/53fPMh5G5mB8cJ+QbNg== +medusa-test-utils@1.1.25-dev-1634220658423: + version "1.1.25-dev-1634220658423" + resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.25-dev-1634220658423.tgz#1ce959fa2456d6e1a2653ac596d371b3dc05936b" + integrity sha512-irOZiSM9EeJdjUawUCi9IDjg+RlsQkfwbO9SHvABwmlXGA8BZ6hS2qUWuSK0tg0+oEKIojf+9VSG7dCkcTue2Q== dependencies: "@babel/plugin-transform-classes" "^7.9.5" - medusa-core-utils "1.1.22-dev-1634206968632" + medusa-core-utils "1.1.22-dev-1634220658423" randomatic "^3.1.1" merge-descriptors@1.0.1: diff --git a/integration-tests/helpers/test-server.js b/integration-tests/helpers/test-server.js index a06bc12ab8..7feb62d855 100644 --- a/integration-tests/helpers/test-server.js +++ b/integration-tests/helpers/test-server.js @@ -6,8 +6,10 @@ const importFrom = require("import-from") const initialize = async () => { const app = express() - const loaders = importFrom(process.cwd(), "@medusajs/medusa/dist/loaders") - .default + const loaders = importFrom( + process.cwd(), + "@medusajs/medusa/dist/loaders" + ).default const { dbConnection } = await loaders({ directory: path.resolve(process.cwd()), diff --git a/integration-tests/jest.config.js b/integration-tests/jest.config.js index bf175fbbeb..83f803a68e 100644 --- a/integration-tests/jest.config.js +++ b/integration-tests/jest.config.js @@ -1,8 +1,8 @@ -const glob = require(`glob`); +const glob = require(`glob`) const pkgs = glob .sync(`${__dirname}/*/`) - .map((p) => p.replace(__dirname, `/integration-tests`)); + .map((p) => p.replace(__dirname, `/integration-tests`)) module.exports = { testEnvironment: `node`, @@ -19,4 +19,4 @@ module.exports = { ], transform: { "^.+\\.[jt]s$": `/jest-transformer.js` }, setupFilesAfterEnv: ["/integration-tests/setup.js"], -}; +} diff --git a/jest-transformer.js b/jest-transformer.js index a5f750c9d6..f6d0fb7336 100644 --- a/jest-transformer.js +++ b/jest-transformer.js @@ -1,4 +1,4 @@ -const babelPreset = require(`babel-preset-medusa-package`)(); +const babelPreset = require(`babel-preset-medusa-package`)() module.exports = require(`babel-jest`).createTransformer({ ...babelPreset, -}); +}) diff --git a/package.json b/package.json index b30f2a4d29..358c4a0cb9 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "private": true, "devDependencies": { "@babel/core": "^7.12.7", + "@babel/eslint-parser": "^7.15.8", "@babel/node": "^7.12.6", "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-transform-classes": "^7.10.4", @@ -11,16 +12,24 @@ "@babel/preset-env": "^7.11.5", "@babel/register": "^7.11.5", "@babel/runtime": "^7.11.2", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", "axios": "^0.21.1", "axios-mock-adapter": "^1.19.0", "babel-jest": "^26.6.3", "babel-preset-medusa-package": "^1.0.0", "cross-env": "^7.0.2", + "eslint": "^8.0.0", + "eslint-config-google": "^0.14.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", "express": "^4.17.1", "get-port": "^5.1.1", + "husky": "^7.0.2", "import-from": "^3.0.0", "jest": "^26.6.3", "lerna": "^3.22.1", + "lint-staged": "^11.2.3", "microbundle": "^0.13.3", "mongoose": "^5.10.15", "pg-god": "^1.0.11", @@ -28,9 +37,16 @@ "resolve-cwd": "^3.0.0", "typeorm": "^0.2.31" }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": "eslint --ext .js,.jsx,.ts,.tsx --fix", + "*.{md,yaml,yml}": "prettier --write" + }, "scripts": { + "hooks:install": "husky install", + "hooks:uninstall": "husky uninstall", "publish:next": "lerna publish --canary --preid next --dist-tag next", "bootstrap": "lerna bootstrap", + "lint": "eslint --ext .js,.jsx,.ts,.tsx .", "jest": "jest", "test": "jest", "prettier": "prettier", diff --git a/packages/medusa-cli/.eslintrc b/packages/medusa-cli/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-cli/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-cli/.prettierrc b/packages/medusa-cli/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-cli/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-core-utils/.eslintrc b/packages/medusa-core-utils/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-core-utils/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-core-utils/.prettierrc b/packages/medusa-core-utils/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-core-utils/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-core-utils/src/validator.js b/packages/medusa-core-utils/src/validator.js index 43fe68f5cd..243b19d20f 100644 --- a/packages/medusa-core-utils/src/validator.js +++ b/packages/medusa-core-utils/src/validator.js @@ -96,4 +96,33 @@ Joi.orderFilter = () => { }) } +Joi.productFilter = () => { + return Joi.object().keys({ + id: Joi.string(), + q: Joi.string().allow(null, ""), + status: Joi.array() + .items(Joi.string().valid("proposed", "draft", "published", "rejected")) + .single(), + collection_id: Joi.array() + .items(Joi.string()) + .single(), + tags: Joi.array() + .items(Joi.string()) + .single(), + title: Joi.string(), + description: Joi.string(), + handle: Joi.string(), + is_giftcard: Joi.string(), + type: Joi.string(), + offset: Joi.string(), + limit: Joi.string(), + expand: Joi.string(), + fields: Joi.string(), + order: Joi.string().optional(), + created_at: Joi.dateFilter(), + updated_at: Joi.dateFilter(), + deleted_at: Joi.dateFilter(), + }) +} + export default Joi diff --git a/packages/medusa-file-s3/.eslintrc b/packages/medusa-file-s3/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-file-s3/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-file-s3/.prettierrc b/packages/medusa-file-s3/.prettierrc deleted file mode 100644 index 48e90e8d40..0000000000 --- a/packages/medusa-file-s3/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} diff --git a/packages/medusa-file-s3/README.md b/packages/medusa-file-s3/README.md new file mode 100644 index 0000000000..e5ea97a571 --- /dev/null +++ b/packages/medusa-file-s3/README.md @@ -0,0 +1,15 @@ +# medusa-file-s3 + +Upload files to an AWS S3 bucket. + +## Options + +``` + s3_url: [url of your s3 bucket], + access_key_id: [access-key], + secret_access_key: [secret-access-key], + bucket: [name of your bucket], + region: [region of your bucket], +``` + +Follow [this guide](https://docs.medusa-commerce.com/how-to/uploading-images-to-s3) to configure the plugin. diff --git a/packages/medusa-file-spaces/.eslintrc b/packages/medusa-file-spaces/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-file-spaces/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-file-spaces/.prettierrc b/packages/medusa-file-spaces/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-file-spaces/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-file-spaces/README.md b/packages/medusa-file-spaces/README.md new file mode 100644 index 0000000000..f756dc1cde --- /dev/null +++ b/packages/medusa-file-spaces/README.md @@ -0,0 +1,15 @@ +# medusa-file-spaces + +Upload files to a DigitalOcean Space. + +## Options + +``` + spaces_url: [url of your DigitalOcean space], + access_key_id: [access-key], + secret_access_key: [secret-access-key], + bucket: [name of your bucket], + endpoint: [endpoint of you DigitalOcean space], +``` + +Follow [this guide](https://docs.medusa-commerce.com/how-to/uploading-images-to-spaces) to configure the plugin. diff --git a/packages/medusa-fulfillment-manual/.eslintrc b/packages/medusa-fulfillment-manual/.eslintrc deleted file mode 100644 index 797b6ef5bf..0000000000 --- a/packages/medusa-fulfillment-manual/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} \ No newline at end of file diff --git a/packages/medusa-fulfillment-manual/.prettierrc b/packages/medusa-fulfillment-manual/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-fulfillment-manual/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-fulfillment-webshipper/.eslintrc b/packages/medusa-fulfillment-webshipper/.eslintrc deleted file mode 100644 index 797b6ef5bf..0000000000 --- a/packages/medusa-fulfillment-webshipper/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} \ No newline at end of file diff --git a/packages/medusa-fulfillment-webshipper/.prettierrc b/packages/medusa-fulfillment-webshipper/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-fulfillment-webshipper/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js b/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js index 2cf02de38e..150a6dfe8b 100644 --- a/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js +++ b/packages/medusa-fulfillment-webshipper/src/services/webshipper-fulfillment.js @@ -106,7 +106,13 @@ class WebshipperFulfillmentService extends FulfillmentService { const fromOrder = await this.orderService_.retrieve(orderId, { select: ["total"], - relations: ["discounts", "shipping_address", "returns"], + relations: [ + "discounts", + "discounts.rule", + "discounts.rule.valid_for", + "shipping_address", + "returns", + ], }) const methodData = returnOrder.shipping_method.data @@ -286,10 +292,11 @@ class WebshipperFulfillmentService extends FulfillmentService { )) && this.invoiceGenerator_.createCertificateOfOrigin ) { - const base64Coo = await this.invoiceGenerator_.createCertificateOfOrigin( - fromOrder, - fulfillmentItems - ) + const base64Coo = + await this.invoiceGenerator_.createCertificateOfOrigin( + fromOrder, + fulfillmentItems + ) certificateOfOrigin = await this.client_.documents .create({ @@ -419,9 +426,8 @@ class WebshipperFulfillmentService extends FulfillmentService { url: l.url, tracking_number: l.number, })) - const [orderId, fulfillmentIndex] = wsOrder.data.attributes.ext_ref.split( - "." - ) + const [orderId, fulfillmentIndex] = + wsOrder.data.attributes.ext_ref.split(".") if (orderId.charAt(0).toLowerCase() === "s") { if (fulfillmentIndex.startsWith("ful")) { diff --git a/packages/medusa-interfaces/.eslintrc b/packages/medusa-interfaces/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-interfaces/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-interfaces/.prettierrc b/packages/medusa-interfaces/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-interfaces/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-payment-adyen/.eslintrc b/packages/medusa-payment-adyen/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-payment-adyen/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-payment-adyen/.prettierrc b/packages/medusa-payment-adyen/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-payment-adyen/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-payment-klarna/.eslintrc b/packages/medusa-payment-klarna/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-payment-klarna/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-payment-klarna/.prettierrc b/packages/medusa-payment-klarna/.prettierrc deleted file mode 100644 index 48e90e8d40..0000000000 --- a/packages/medusa-payment-klarna/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} diff --git a/packages/medusa-payment-manual/.eslintrc b/packages/medusa-payment-manual/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-payment-manual/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-payment-manual/.prettierrc b/packages/medusa-payment-manual/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-payment-manual/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-payment-paypal/.eslintrc b/packages/medusa-payment-paypal/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-payment-paypal/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-payment-paypal/.prettierrc b/packages/medusa-payment-paypal/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-payment-paypal/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-payment-stripe/.eslintrc b/packages/medusa-payment-stripe/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-payment-stripe/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-payment-stripe/.prettierrc b/packages/medusa-payment-stripe/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-payment-stripe/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-payment-stripe/src/services/stripe-bancontact.js b/packages/medusa-payment-stripe/src/services/stripe-bancontact.js new file mode 100644 index 0000000000..3b731e74d4 --- /dev/null +++ b/packages/medusa-payment-stripe/src/services/stripe-bancontact.js @@ -0,0 +1,241 @@ +import _ from "lodash" +import Stripe from "stripe" +import { PaymentService } from "medusa-interfaces" + +class BancontactProviderService extends PaymentService { + static identifier = "stripe-bancontact" + + constructor( + { stripeProviderService, customerService, totalsService, regionService }, + options + ) { + super() + + /** + * Required Stripe options: + * { + * api_key: "stripe_secret_key", REQUIRED + * webhook_secret: "stripe_webhook_secret", REQUIRED + * // Use this flag to capture payment immediately (default is false) + * capture: true + * } + */ + this.options_ = options + + /** @private @const {Stripe} */ + this.stripe_ = Stripe(options.api_key) + + /** @private @const {CustomerService} */ + this.stripeProviderService_ = stripeProviderService + + /** @private @const {CustomerService} */ + this.customerService_ = customerService + + /** @private @const {RegionService} */ + this.regionService_ = regionService + + /** @private @const {TotalsService} */ + this.totalsService_ = totalsService + } + + /** + * Fetches Stripe payment intent. Check its status and returns the + * corresponding Medusa status. + * @param {object} paymentData - payment method data from cart + * @returns {string} the status of the payment intent + */ + async getStatus(paymentData) { + return await this.stripeProviderService_.getStatus(paymentData) + } + + /** + * Fetches a customers saved payment methods if registered in Stripe. + * @param {object} customer - customer to fetch saved cards for + * @returns {Promise>} saved payments methods + */ + async retrieveSavedMethods(customer) { + return Promise.resolve([]) + } + + /** + * Fetches a Stripe customer + * @param {string} customerId - Stripe customer id + * @returns {Promise} Stripe customer + */ + async retrieveCustomer(customerId) { + return await this.stripeProviderService_.retrieveCustomer(customerId) + } + + /** + * Creates a Stripe customer using a Medusa customer. + * @param {object} customer - Customer data from Medusa + * @returns {Promise} Stripe customer + */ + async createCustomer(customer) { + return await this.stripeProviderService_.createCustomer(customer) + } + + /** + * Creates a Stripe payment intent. + * If customer is not registered in Stripe, we do so. + * @param {object} cart - cart to create a payment for + * @returns {object} Stripe payment intent + */ + async createPayment(cart) { + const { customer_id, region_id, email } = cart + const region = await this.regionService_.retrieve(region_id) + const { currency_code } = region + + const amount = await this.totalsService_.getTotal(cart) + + const intentRequest = { + amount: Math.round(amount), + currency: currency_code, + payment_method_types: ["bancontact"], + capture_method: "automatic", + metadata: { cart_id: `${cart.id}` }, + } + + if (customer_id) { + const customer = await this.customerService_.retrieve(customer_id) + + if (customer.metadata?.stripe_id) { + intentRequest.customer = customer.metadata.stripe_id + } else { + const stripeCustomer = await this.createCustomer({ + email, + id: customer_id, + }) + + intentRequest.customer = stripeCustomer.id + } + } else { + const stripeCustomer = await this.createCustomer({ + email, + }) + + intentRequest.customer = stripeCustomer.id + } + + const paymentIntent = await this.stripe_.paymentIntents.create( + intentRequest + ) + + return paymentIntent + } + + /** + * Retrieves Stripe payment intent. + * @param {object} data - the data of the payment to retrieve + * @returns {Promise} Stripe payment intent + */ + async retrievePayment(data) { + return await this.stripeProviderService_.retrievePayment(data) + } + + /** + * Gets a Stripe payment intent and returns it. + * @param {object} sessionData - the data of the payment to retrieve + * @returns {Promise} Stripe payment intent + */ + async getPaymentData(sessionData) { + return await this.stripeProviderService_.getPaymentData(sessionData) + } + + /** + * Authorizes Stripe payment intent by simply returning + * the status for the payment intent in use. + * @param {object} sessionData - payment session data + * @param {object} context - properties relevant to current context + * @returns {Promise<{ status: string, data: object }>} result with data and status + */ + async authorizePayment(sessionData, context = {}) { + return await this.stripeProviderService_.authorizePayment( + sessionData, + context + ) + } + + async updatePaymentData(sessionData, update) { + return await this.stripeProviderService_.updatePaymentData( + sessionData, + update + ) + } + + /** + * Updates Stripe payment intent. + * @param {object} sessionData - payment session data. + * @param {object} update - objec to update intent with + * @returns {object} Stripe payment intent + */ + async updatePayment(sessionData, cart) { + try { + const stripeId = cart.customer?.metadata?.stripe_id || undefined + + if (stripeId !== sessionData.customer) { + return this.createPayment(cart) + } else { + if (cart.total && sessionData.amount === Math.round(cart.total)) { + return sessionData + } + + return this.stripe_.paymentIntents.update(sessionData.id, { + amount: Math.round(cart.total), + }) + } + } catch (error) { + throw error + } + } + + async deletePayment(payment) { + return await this.stripeProviderService_.deletePayment(payment) + } + + /** + * Updates customer of Stripe payment intent. + * @param {string} paymentIntentId - id of payment intent to update + * @param {string} customerId - id of new Stripe customer + * @returns {object} Stripe payment intent + */ + async updatePaymentIntentCustomer(paymentIntentId, customerId) { + return await this.stripeProviderService_.updatePaymentIntentCustomer( + paymentIntentId, + customerId + ) + } + + /** + * Captures payment for Stripe payment intent. + * @param {object} paymentData - payment method data from cart + * @returns {object} Stripe payment intent + */ + async capturePayment(payment) { + return await this.stripeProviderService_.capturePayment(payment) + } + + /** + * Refunds payment for Stripe payment intent. + * @param {object} paymentData - payment method data from cart + * @param {number} amountToRefund - amount to refund + * @returns {string} refunded payment intent + */ + async refundPayment(payment, amountToRefund) { + return await this.stripeProviderService_.refundPayment( + payment, + amountToRefund + ) + } + + /** + * Cancels payment for Stripe payment intent. + * @param {object} paymentData - payment method data from cart + * @returns {object} canceled payment intent + */ + async cancelPayment(payment) { + return await this.stripeProviderService_.cancelPayment(payment) + } +} + +export default BancontactProviderService diff --git a/packages/medusa-plugin-add-ons/.eslintrc b/packages/medusa-plugin-add-ons/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-add-ons/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-add-ons/.prettierrc b/packages/medusa-plugin-add-ons/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-add-ons/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-brightpearl/.eslintrc b/packages/medusa-plugin-brightpearl/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-brightpearl/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-brightpearl/.prettierrc b/packages/medusa-plugin-brightpearl/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-brightpearl/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-contentful/.eslintrc b/packages/medusa-plugin-contentful/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-contentful/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-contentful/.prettierrc b/packages/medusa-plugin-contentful/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-contentful/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-discount-generator/.eslintrc b/packages/medusa-plugin-discount-generator/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-discount-generator/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-discount-generator/.prettierrc b/packages/medusa-plugin-discount-generator/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-discount-generator/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-economic/.eslintrc b/packages/medusa-plugin-economic/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-economic/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-economic/.prettierrc b/packages/medusa-plugin-economic/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-economic/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-ip-lookup/.prettierrc b/packages/medusa-plugin-ip-lookup/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-ip-lookup/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-mailchimp/.eslintrc b/packages/medusa-plugin-mailchimp/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-mailchimp/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-mailchimp/.prettierrc b/packages/medusa-plugin-mailchimp/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-mailchimp/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-permissions/.prettierrc b/packages/medusa-plugin-permissions/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-permissions/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-restock-notification/.eslintrc b/packages/medusa-plugin-restock-notification/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-restock-notification/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-restock-notification/.prettierrc b/packages/medusa-plugin-restock-notification/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-restock-notification/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js b/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js index 328873b17b..caa4b924bc 100644 --- a/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js +++ b/packages/medusa-plugin-restock-notification/src/services/__tests__/restock-notification.js @@ -135,6 +135,57 @@ describe("RestockNotificationService", () => { }) describe("triggerRestock", () => { + afterEach(() => { + jest.useRealTimers() + }) + + it("trigger delay default to 0", async () => { + const restockNotiService = new RestockNotificationService({ + manager: MockManager, + productVariantService: ProductVariantService, + restockNotificationModel: RestockNotificationModel, + eventBusService: EventBusService, + }) + + restockNotiService.restockExecute_ = jest.fn() + + jest.clearAllMocks() + jest.useFakeTimers() + + restockNotiService.triggerRestock("variant_test") + + jest.runAllTimers() + + expect(setTimeout).toHaveBeenCalledTimes(1) + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 0) + }) + + it("trigger delay 10", async () => { + const restockNotiService = new RestockNotificationService( + { + manager: MockManager, + productVariantService: ProductVariantService, + restockNotificationModel: RestockNotificationModel, + eventBusService: EventBusService, + }, + { trigger_delay: 10 } + ) + + restockNotiService.restockExecute_ = jest.fn() + + jest.clearAllMocks() + jest.useFakeTimers() + + restockNotiService.triggerRestock("variant_test") + + jest.runAllTimers() + + expect(setTimeout).toHaveBeenCalledTimes(1) + expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 10) + }) + }) + + describe("restockExecute_", () => { const restockNotiService = new RestockNotificationService({ manager: MockManager, productVariantService: ProductVariantService, @@ -145,20 +196,20 @@ describe("RestockNotificationService", () => { it("non-existing noti does nothing", async () => { jest.clearAllMocks() - await expect(restockNotiService.triggerRestock("variant_test")).resolves + await expect(restockNotiService.restockExecute_("variant_test")).resolves }) it("existing noti but out of stock does nothing", async () => { jest.clearAllMocks() - await expect(restockNotiService.triggerRestock("variant_outofstock")) + await expect(restockNotiService.restockExecute_("variant_outofstock")) .resolves }) it("existing noti emits and deletes", async () => { jest.clearAllMocks() - await restockNotiService.triggerRestock("variant_1234") + await restockNotiService.restockExecute_("variant_1234") expect(EventBusService.emit).toHaveBeenCalledTimes(1) expect(EventBusService.emit).toHaveBeenCalledWith( @@ -187,7 +238,7 @@ describe("RestockNotificationService", () => { { inventory_required: 5 } ) - await service.triggerRestock("variant_1234") + await service.restockExecute_("variant_1234") expect(EventBusService.emit).toHaveBeenCalledTimes(1) expect(EventBusService.emit).toHaveBeenCalledWith( @@ -214,7 +265,7 @@ describe("RestockNotificationService", () => { { inventory_required: 5 } ) - await service.triggerRestock("variant_low_inventory") + await service.restockExecute_("variant_low_inventory") expect(EventBusService.emit).toHaveBeenCalledTimes(0) expect(RestockNotificationModel.delete).toHaveBeenCalledTimes(0) diff --git a/packages/medusa-plugin-restock-notification/src/services/restock-notification.js b/packages/medusa-plugin-restock-notification/src/services/restock-notification.js index d91c576785..b0de97286a 100644 --- a/packages/medusa-plugin-restock-notification/src/services/restock-notification.js +++ b/packages/medusa-plugin-restock-notification/src/services/restock-notification.js @@ -108,10 +108,15 @@ class RestockNotificationService extends BaseService { * and emits a restocked event to the event bus. After successful emission the * restock notification is deleted. * @param {string} variantId - the variant id to trigger restock for - * @return {Promise} The resulting restock notification + * @return The resulting restock notification */ - async triggerRestock(variantId) { - return this.atomicPhase_(async (manager) => { + triggerRestock(variantId) { + const delay = this.options_?.trigger_delay ?? 0 + setTimeout(() => this.restockExecute_(variantId), delay) + } + + async restockExecute_(variantId) { + return await this.atomicPhase_(async (manager) => { const restockRepo = manager.getRepository(this.restockNotificationModel_) const existing = await this.retrieve(variantId) diff --git a/packages/medusa-plugin-segment/.eslintrc b/packages/medusa-plugin-segment/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-segment/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-segment/.prettierrc b/packages/medusa-plugin-segment/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-segment/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-segment/src/subscribers/order.js b/packages/medusa-plugin-segment/src/subscribers/order.js index 33f1d2f291..b048245d5f 100644 --- a/packages/medusa-plugin-segment/src/subscribers/order.js +++ b/packages/medusa-plugin-segment/src/subscribers/order.js @@ -44,6 +44,8 @@ class OrderSubscriber { "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "payments", "fulfillments", @@ -146,6 +148,8 @@ class OrderSubscriber { "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "payments", "fulfillments", @@ -253,6 +257,8 @@ class OrderSubscriber { "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "payments", "fulfillments", diff --git a/packages/medusa-plugin-sendgrid/.eslintrc b/packages/medusa-plugin-sendgrid/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-sendgrid/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-sendgrid/.prettierrc b/packages/medusa-plugin-sendgrid/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-sendgrid/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js index a577a67696..d951685bd9 100644 --- a/packages/medusa-plugin-sendgrid/src/services/sendgrid.js +++ b/packages/medusa-plugin-sendgrid/src/services/sendgrid.js @@ -320,6 +320,8 @@ class SendGridService extends NotificationService { "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "shipping_methods.shipping_option", "payments", @@ -363,6 +365,8 @@ class SendGridService extends NotificationService { "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "shipping_methods.shipping_option", "payments", @@ -496,6 +500,8 @@ class SendGridService extends NotificationService { relations: [ "items", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_address", "returns", "swaps", @@ -601,6 +607,8 @@ class SendGridService extends NotificationService { relations: [ "items", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_address", "swaps", "swaps.additional_items", @@ -686,7 +694,14 @@ class SendGridService extends NotificationService { }) const order = await this.orderService_.retrieve(swap.order_id, { - relations: ["items", "discounts", "swaps", "swaps.additional_items"], + relations: [ + "items", + "discounts", + "discounts.rule", + "discounts.rule.valid_for", + "swaps", + "swaps.additional_items", + ], }) let merged = [...order.items] diff --git a/packages/medusa-plugin-slack-notification/.eslintrc b/packages/medusa-plugin-slack-notification/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-slack-notification/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-slack-notification/.prettierrc b/packages/medusa-plugin-slack-notification/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-slack-notification/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-slack-notification/src/services/slack.js b/packages/medusa-plugin-slack-notification/src/services/slack.js index 5821191e95..0770f1c9db 100644 --- a/packages/medusa-plugin-slack-notification/src/services/slack.js +++ b/packages/medusa-plugin-slack-notification/src/services/slack.js @@ -40,6 +40,8 @@ class SlackService extends BaseService { "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "payments", "fulfillments", diff --git a/packages/medusa-plugin-twilio-sms/.eslintrc b/packages/medusa-plugin-twilio-sms/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-twilio-sms/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-twilio-sms/.prettierrc b/packages/medusa-plugin-twilio-sms/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-twilio-sms/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-plugin-wishlist/.eslintrc b/packages/medusa-plugin-wishlist/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-plugin-wishlist/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-plugin-wishlist/.prettierrc b/packages/medusa-plugin-wishlist/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-plugin-wishlist/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-telemetry/.eslintrc b/packages/medusa-telemetry/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-telemetry/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa-telemetry/.prettierrc b/packages/medusa-telemetry/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa-telemetry/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa-test-utils/.eslintrc b/packages/medusa-test-utils/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa-test-utils/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa/.eslintrc b/packages/medusa/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/packages/medusa/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/packages/medusa/.prettierrc b/packages/medusa/.prettierrc deleted file mode 100644 index 70175ce150..0000000000 --- a/packages/medusa/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} \ No newline at end of file diff --git a/packages/medusa/src/api/routes/admin/auth/delete-session.js b/packages/medusa/src/api/routes/admin/auth/delete-session.js new file mode 100644 index 0000000000..7316311fea --- /dev/null +++ b/packages/medusa/src/api/routes/admin/auth/delete-session.js @@ -0,0 +1,17 @@ +import _ from "lodash" + +/** + * @oas [get] /auth + * operationId: "DeleteAuth" + * summary: "Delete Session" + * description: "Deletes the current session for the logged in user." + * tags: + * - Auth + * responses: + * "200": + * description: OK + */ +export default async (req, res) => { + req.session.destroy() + res.status(200).end() +} diff --git a/packages/medusa/src/api/routes/admin/auth/index.js b/packages/medusa/src/api/routes/admin/auth/index.js index 3a8a9f896a..b74d357a8f 100644 --- a/packages/medusa/src/api/routes/admin/auth/index.js +++ b/packages/medusa/src/api/routes/admin/auth/index.js @@ -13,5 +13,11 @@ export default app => { ) route.post("/", middlewares.wrap(require("./create-session").default)) + route.delete( + "/", + middlewares.authenticate(), + middlewares.wrap(require("./delete-session").default) + ) + return app } diff --git a/packages/medusa/src/api/routes/admin/discounts/create-discount.js b/packages/medusa/src/api/routes/admin/discounts/create-discount.js index e7b9ea364e..66b51c64e3 100644 --- a/packages/medusa/src/api/routes/admin/discounts/create-discount.js +++ b/packages/medusa/src/api/routes/admin/discounts/create-discount.js @@ -1,4 +1,5 @@ import { MedusaError, Validator } from "medusa-core-utils" +import { defaultRelations } from "." /** * @oas [post] /discounts @@ -96,11 +97,10 @@ export default async (req, res) => { const discountService = req.scope.resolve("discountService") const created = await discountService.create(value) - const discount = await discountService.retrieve(created.id, [ - "rule", - "rule.valid_for", - "regions", - ]) + const discount = await discountService.retrieve( + created.id, + defaultRelations + ) res.status(200).json({ discount }) } catch (err) { diff --git a/packages/medusa/src/api/routes/admin/draft-orders/index.js b/packages/medusa/src/api/routes/admin/draft-orders/index.js index d5a08f41ce..e68f244c21 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/index.js +++ b/packages/medusa/src/api/routes/admin/draft-orders/index.js @@ -57,6 +57,7 @@ export const defaultCartRelations = [ "payment_sessions", "shipping_methods.shipping_option", "discounts", + "discounts.rule", ] export const defaultCartFields = [ diff --git a/packages/medusa/src/api/routes/admin/draft-orders/register-payment.js b/packages/medusa/src/api/routes/admin/draft-orders/register-payment.js index e38bc93d54..b9d854453f 100644 --- a/packages/medusa/src/api/routes/admin/draft-orders/register-payment.js +++ b/packages/medusa/src/api/routes/admin/draft-orders/register-payment.js @@ -43,7 +43,14 @@ export default async (req, res) => { .withTransaction(manager) .retrieve(draftOrder.cart_id, { select: ["total"], - relations: ["discounts", "shipping_methods", "region", "items"], + relations: [ + "discounts", + "discounts.rule", + "discounts.rule.valid_for", + "shipping_methods", + "region", + "items", + ], }) await paymentProviderService diff --git a/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js b/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js index 4a00dbbd89..bc54bdc90b 100644 --- a/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js +++ b/packages/medusa/src/api/routes/admin/orders/__tests__/get-order.js @@ -7,6 +7,8 @@ const defaultRelations = [ "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "payments", "fulfillments", diff --git a/packages/medusa/src/api/routes/admin/orders/create-claim.js b/packages/medusa/src/api/routes/admin/orders/create-claim.js index ade142b418..77c8a37004 100644 --- a/packages/medusa/src/api/routes/admin/orders/create-claim.js +++ b/packages/medusa/src/api/routes/admin/orders/create-claim.js @@ -202,7 +202,12 @@ export default async (req, res) => { const order = await orderService .withTransaction(manager) .retrieve(id, { - relations: ["items", "discounts"], + relations: [ + "items", + "cart", + "cart.discounts", + "cart.discounts.rule", + ], }) await claimService.withTransaction(manager).create({ diff --git a/packages/medusa/src/api/routes/admin/orders/index.js b/packages/medusa/src/api/routes/admin/orders/index.js index 0a6f64ddfe..a9e4af97b6 100644 --- a/packages/medusa/src/api/routes/admin/orders/index.js +++ b/packages/medusa/src/api/routes/admin/orders/index.js @@ -221,6 +221,8 @@ export const defaultRelations = [ "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "payments", "fulfillments", @@ -316,6 +318,8 @@ export const allowedRelations = [ "billing_address", "shipping_address", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods", "payments", "fulfillments", diff --git a/packages/medusa/src/api/routes/admin/products/index.js b/packages/medusa/src/api/routes/admin/products/index.js index 8125eebd52..e32e13d168 100644 --- a/packages/medusa/src/api/routes/admin/products/index.js +++ b/packages/medusa/src/api/routes/admin/products/index.js @@ -46,7 +46,11 @@ export default app => { ) route.get("/:id", middlewares.wrap(require("./get-product").default)) - route.get("/", middlewares.wrap(require("./list-products").default)) + route.get( + "/", + middlewares.normalizeQuery(), + middlewares.wrap(require("./list-products").default) + ) return app } @@ -121,3 +125,18 @@ export const allowedRelations = [ "type", "collection", ] + +export const filterableFields = [ + "id", + "status", + "collection_id", + "tags", + "title", + "description", + "handle", + "is_giftcard", + "type", + "created_at", + "updated_at", + "deleted_at", +] diff --git a/packages/medusa/src/api/routes/admin/products/list-products.js b/packages/medusa/src/api/routes/admin/products/list-products.js index bd4873a82d..ea309a43ab 100644 --- a/packages/medusa/src/api/routes/admin/products/list-products.js +++ b/packages/medusa/src/api/routes/admin/products/list-products.js @@ -1,6 +1,6 @@ import _ from "lodash" import { MedusaError, Validator } from "medusa-core-utils" -import { defaultFields, defaultRelations } from "./" +import { defaultFields, defaultRelations, filterableFields } from "./" /** * @oas [get] /products @@ -31,6 +31,17 @@ import { defaultFields, defaultRelations } from "./" * $ref: "#/components/schemas/product" */ export default async (req, res) => { + const schema = Validator.productFilter() + + const { value, error } = schema.validate(req.query) + + if (error) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + JSON.stringify(error.details) + ) + } + try { const productService = req.scope.resolve("productService") @@ -53,21 +64,16 @@ export default async (req, res) => { expandFields = req.query.expand.split(",") } - if ("is_giftcard" in req.query) { - selector.is_giftcard = req.query.is_giftcard === "true" + for (const k of filterableFields) { + if (k in value) { + selector[k] = value[k] + } } - if ("status" in req.query) { - const schema = Validator.array() - .items( - Validator.string().valid("proposed", "draft", "published", "rejected") - ) - .single() - - const { value, error } = schema.validate(req.query.status) - - if (value && !error) { - selector.status = value + if (selector.status?.indexOf("null") > -1) { + selector.status.splice(selector.status.indexOf("null"), 1) + if (selector.status.length === 0) { + delete selector.status } } diff --git a/packages/medusa/src/api/routes/store/carts/create-cart.js b/packages/medusa/src/api/routes/store/carts/create-cart.js index 88fe6dc6ca..eb3ac34cec 100644 --- a/packages/medusa/src/api/routes/store/carts/create-cart.js +++ b/packages/medusa/src/api/routes/store/carts/create-cart.js @@ -82,6 +82,14 @@ export default async (req, res) => { if (!value.region_id) { const regionService = req.scope.resolve("regionService") const regions = await regionService.withTransaction(manager).list({}) + + if (!regions?.length) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `A region is required to create a cart` + ) + } + regionId = regions[0].id } diff --git a/packages/medusa/src/api/routes/store/carts/index.js b/packages/medusa/src/api/routes/store/carts/index.js index 42363bd12c..6c702af07b 100644 --- a/packages/medusa/src/api/routes/store/carts/index.js +++ b/packages/medusa/src/api/routes/store/carts/index.js @@ -116,4 +116,6 @@ export const defaultRelations = [ "payment_sessions", "shipping_methods.shipping_option", "discounts", + "discounts.rule", + "discounts.rule.valid_for", ] diff --git a/packages/medusa/src/api/routes/store/orders/index.js b/packages/medusa/src/api/routes/store/orders/index.js index 8aaa346bca..e7aefc2571 100644 --- a/packages/medusa/src/api/routes/store/orders/index.js +++ b/packages/medusa/src/api/routes/store/orders/index.js @@ -36,6 +36,8 @@ export const defaultRelations = [ "items.variant.product", "shipping_methods", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "customer", "payments", "region", @@ -74,6 +76,8 @@ export const allowedRelations = [ "items.variant.product", "shipping_methods", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "customer", "payments", "region", diff --git a/packages/medusa/src/api/routes/store/variants/__tests__/get-variant.js b/packages/medusa/src/api/routes/store/variants/__tests__/get-variant.js index 890ab5e078..eb16ea70d1 100644 --- a/packages/medusa/src/api/routes/store/variants/__tests__/get-variant.js +++ b/packages/medusa/src/api/routes/store/variants/__tests__/get-variant.js @@ -16,7 +16,7 @@ describe("Get variant by id", () => { it("calls get variant from variantSerice", () => { expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1) expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", { - relations: ["prices"], + relations: ["prices", "options"], }) }) diff --git a/packages/medusa/src/api/routes/store/variants/index.js b/packages/medusa/src/api/routes/store/variants/index.js index bc26ec1f55..b4ed48b30d 100644 --- a/packages/medusa/src/api/routes/store/variants/index.js +++ b/packages/medusa/src/api/routes/store/variants/index.js @@ -12,4 +12,4 @@ export default app => { return app } -export const defaultRelations = ["prices"] +export const defaultRelations = ["prices", "options"] diff --git a/packages/medusa/src/migrations/1632828114899-delete_date_on_shipping_option_requirements.ts b/packages/medusa/src/migrations/1632828114899-delete_date_on_shipping_option_requirements.ts new file mode 100644 index 0000000000..12011f31a9 --- /dev/null +++ b/packages/medusa/src/migrations/1632828114899-delete_date_on_shipping_option_requirements.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class deleteDateOnShippingOptionRequirements1632828114899 + implements MigrationInterface { + name = "deleteDateOnShippingOptionRequirements1632828114899" + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "shipping_option_requirement" ADD "deleted_at" TIMESTAMP WITH TIME ZONE` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "shipping_option_requirement" DROP COLUMN "deleted_at"` + ) + } +} diff --git a/packages/medusa/src/models/discount.ts b/packages/medusa/src/models/discount.ts index a4ef833b3c..64ac6c29c5 100644 --- a/packages/medusa/src/models/discount.ts +++ b/packages/medusa/src/models/discount.ts @@ -35,7 +35,7 @@ export class Discount { @Column({ nullable: true }) rule_id: string - @ManyToOne(() => DiscountRule, { cascade: true, eager: true }) + @ManyToOne(() => DiscountRule, { cascade: true }) @JoinColumn({ name: "rule_id" }) rule: DiscountRule diff --git a/packages/medusa/src/models/shipping-option-requirement.ts b/packages/medusa/src/models/shipping-option-requirement.ts index b847687d02..547ddf58cd 100644 --- a/packages/medusa/src/models/shipping-option-requirement.ts +++ b/packages/medusa/src/models/shipping-option-requirement.ts @@ -16,7 +16,7 @@ import { JoinTable, } from "typeorm" import { ulid } from "ulid" -import { DbAwareColumn } from "../utils/db-aware-column" +import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column" import { ShippingOption } from "./shipping-option" @@ -44,6 +44,9 @@ export class ShippingOptionRequirement { @Column({ type: "int" }) amount: number + @DeleteDateColumn({ type: resolveDbType("timestamptz") }) + deleted_at: Date + @BeforeInsert() private beforeInsert() { if (this.id) return diff --git a/packages/medusa/src/repositories/product.ts b/packages/medusa/src/repositories/product.ts index c88f090fb3..f686ef80d2 100644 --- a/packages/medusa/src/repositories/product.ts +++ b/packages/medusa/src/repositories/product.ts @@ -15,7 +15,27 @@ export class ProductRepository extends Repository { if (Array.isArray(idsOrOptionsWithoutRelations)) { entities = await this.findByIds(idsOrOptionsWithoutRelations) } else { - entities = await this.find(idsOrOptionsWithoutRelations) + // Since tags are in a one-to-many realtion they cant be included in a + // regular query, to solve this add the join on tags seperately if + // the query exists + const tags = idsOrOptionsWithoutRelations.where.tags + delete idsOrOptionsWithoutRelations.where.tags + let qb = this.createQueryBuilder("product") + .select(["product.id"]) + .where(idsOrOptionsWithoutRelations.where) + .skip(idsOrOptionsWithoutRelations.skip) + .take(idsOrOptionsWithoutRelations.take) + + if (tags) { + qb = qb + .leftJoinAndSelect("product.tags", "tags") + .andWhere( + `tags.id IN (:...ids)`, { ids: tags._value} + ) + } + + entities = await qb + .getMany() } const entitiesIds = entities.map(({ id }) => id) diff --git a/packages/medusa/src/services/__tests__/cart.js b/packages/medusa/src/services/__tests__/cart.js index be537e6ab1..3b3ded3f73 100644 --- a/packages/medusa/src/services/__tests__/cart.js +++ b/packages/medusa/src/services/__tests__/cart.js @@ -647,12 +647,13 @@ describe("CartService", () => { "shipping_address", "billing_address", "gift_cards", - "discounts", "customer", "region", "payment_sessions", "region.countries", + "discounts", "discounts.rule", + "discounts.rule.valid_for", "discounts.regions", ], { diff --git a/packages/medusa/src/services/__tests__/discount.js b/packages/medusa/src/services/__tests__/discount.js index 5802063386..acc61e58cf 100644 --- a/packages/medusa/src/services/__tests__/discount.js +++ b/packages/medusa/src/services/__tests__/discount.js @@ -13,6 +13,9 @@ describe("DiscountService", () => { id: IdMap.getId("france"), } }, + withTransaction: function() { + return this + }, } const discountService = new DiscountService({ diff --git a/packages/medusa/src/services/__tests__/order.js b/packages/medusa/src/services/__tests__/order.js index 76db532917..72555220f9 100644 --- a/packages/medusa/src/services/__tests__/order.js +++ b/packages/medusa/src/services/__tests__/order.js @@ -231,6 +231,8 @@ describe("OrderService", () => { "payment", "items", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "gift_cards", "shipping_methods", ], diff --git a/packages/medusa/src/services/__tests__/shipping-option.js b/packages/medusa/src/services/__tests__/shipping-option.js index e72c4d74b3..8001065535 100644 --- a/packages/medusa/src/services/__tests__/shipping-option.js +++ b/packages/medusa/src/services/__tests__/shipping-option.js @@ -296,24 +296,19 @@ describe("ShippingOptionService", () => { }) describe("removeRequirement", () => { - const shippingOptionRepository = MockRepository({ - findOne: q => { - switch (q.where.id) { - default: - return Promise.resolve({ - requirements: [ - { - id: IdMap.getId("requirement_id"), - }, - ], - }) - } + const shippingOptionRequirementRepository = MockRepository({ + softRemove: q => { + return Promise.resolve() }, + findOne: i => + i.where.id === IdMap.getId("requirement_id") + ? { id: IdMap.getId("requirement_id") } + : null, }) const optionService = new ShippingOptionService({ manager: MockManager, - shippingOptionRepository, + shippingOptionRequirementRepository, }) beforeEach(() => { @@ -321,22 +316,19 @@ describe("ShippingOptionService", () => { }) it("remove requirement successfully", async () => { - await optionService.removeRequirement( - IdMap.getId("validId"), - IdMap.getId("requirement_id") - ) + await optionService.removeRequirement(IdMap.getId("requirement_id")) - expect(shippingOptionRepository.save).toBeCalledTimes(1) - expect(shippingOptionRepository.save).toBeCalledWith({ requirements: [] }) + expect(shippingOptionRequirementRepository.findOne).toBeCalledTimes(1) + expect(shippingOptionRequirementRepository.findOne).toBeCalledWith({ + where: { id: IdMap.getId("requirement_id") }, + }) + expect(shippingOptionRequirementRepository.softRemove).toBeCalledTimes(1) }) it("is idempotent", async () => { await optionService.removeRequirement(IdMap.getId("validId"), "something") - expect(shippingOptionRepository.save).toBeCalledTimes(1) - expect(shippingOptionRepository.save).toBeCalledWith({ - requirements: [{ id: IdMap.getId("requirement_id") }], - }) + expect(shippingOptionRequirementRepository.softRemove).toBeCalledTimes(1) }) }) diff --git a/packages/medusa/src/services/__tests__/swap.js b/packages/medusa/src/services/__tests__/swap.js index 7bbde84fb0..5801075e8f 100644 --- a/packages/medusa/src/services/__tests__/swap.js +++ b/packages/medusa/src/services/__tests__/swap.js @@ -209,6 +209,7 @@ describe("SwapService", () => { "order.swaps", "order.swaps.additional_items", "order.discounts", + "order.discounts.rule", "additional_items", "return_order", "return_order.items", diff --git a/packages/medusa/src/services/auth.js b/packages/medusa/src/services/auth.js index 3216886c5d..364524843d 100644 --- a/packages/medusa/src/services/auth.js +++ b/packages/medusa/src/services/auth.js @@ -3,7 +3,7 @@ import { BaseService } from "medusa-interfaces" /** * Can authenticate a user based on email password combination - * @implements BaseService + * @extends BaseService */ class AuthService extends BaseService { constructor({ userService, customerService }) { @@ -47,7 +47,9 @@ class AuthService extends BaseService { success: true, user, } - } catch (error) {} + } catch (error) { + // ignore + } } try { diff --git a/packages/medusa/src/services/cart.js b/packages/medusa/src/services/cart.js index 1c5831295c..93c9ecb01b 100644 --- a/packages/medusa/src/services/cart.js +++ b/packages/medusa/src/services/cart.js @@ -166,6 +166,8 @@ class CartService extends BaseService { relationSet.add("items") relationSet.add("gift_cards") relationSet.add("discounts") + relationSet.add("discounts.rule") + relationSet.add("discounts.rule.valid_for") //relationSet.add("discounts.parent_discount") //relationSet.add("discounts.parent_discount.rule") //relationSet.add("discounts.parent_discount.regions") @@ -600,18 +602,21 @@ class CartService extends BaseService { "shipping_address", "billing_address", "gift_cards", - "discounts", "customer", "region", "payment_sessions", "region.countries", + "discounts", "discounts.rule", + "discounts.rule.valid_for", "discounts.regions", ], }) if ("region_id" in update) { - await this.setRegion_(cart, update.region_id, update.country_code) + const countryCode = + update.country_code || update.shipping_address?.country_code + await this.setRegion_(cart, update.region_id, countryCode) } if ("customer_id" in update) { @@ -871,6 +876,7 @@ class CartService extends BaseService { async applyDiscount(cart, discountCode) { const discount = await this.discountService_.retrieveByCode(discountCode, [ "rule", + "rule.valid_for", "regions", ]) @@ -968,7 +974,13 @@ class CartService extends BaseService { async removeDiscount(cartId, discountCode) { return this.atomicPhase_(async manager => { const cart = await this.retrieve(cartId, { - relations: ["discounts", "payment_sessions", "shipping_methods"], + relations: [ + "discounts", + "discounts.rule", + "discounts.rule.valid_for", + "payment_sessions", + "shipping_methods", + ], }) if (cart.discounts.some(({ rule }) => rule.type === "free_shipping")) { @@ -1165,6 +1177,8 @@ class CartService extends BaseService { relations: [ "items", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "gift_cards", "billing_address", "shipping_address", @@ -1317,6 +1331,8 @@ class CartService extends BaseService { relations: [ "shipping_methods", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "shipping_methods.shipping_option", "items", "items.variant", @@ -1369,7 +1385,12 @@ class CartService extends BaseService { } const result = await this.retrieve(cartId, { - relations: ["discounts", "shipping_methods"], + relations: [ + "discounts", + "discounts.rule", + "discounts.rule.valid_for", + "shipping_methods", + ], }) // if cart has freeshipping, adjust price @@ -1546,7 +1567,13 @@ class CartService extends BaseService { async delete(cartId) { return this.atomicPhase_(async manager => { const cart = await this.retrieve(cartId, { - relations: ["items", "discounts", "payment_sessions"], + relations: [ + "items", + "discounts", + "discounts.rule", + "discounts.rule.valid_for", + "payment_sessions", + ], }) if (cart.completed_at) { diff --git a/packages/medusa/src/services/claim.js b/packages/medusa/src/services/claim.js index 9d60a28626..8e73dbf83e 100644 --- a/packages/medusa/src/services/claim.js +++ b/packages/medusa/src/services/claim.js @@ -1,7 +1,5 @@ -import _ from "lodash" -import { Validator, MedusaError } from "medusa-core-utils" +import { MedusaError } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" -import { Brackets } from "typeorm" class ClaimService extends BaseService { static Events = { @@ -102,7 +100,7 @@ class ClaimService extends BaseService { } update(id, data) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const claimRepo = manager.getCustomRepository(this.claimRepository_) const claim = await this.retrieve(id, { relations: ["shipping_methods"] }) @@ -113,13 +111,7 @@ class ClaimService extends BaseService { ) } - const { - claim_items, - shipping_methods, - metadata, - fulfillment_status, - no_notification, - } = data + const { claim_items, shipping_methods, metadata, no_notification } = data if (metadata) { claim.metadata = this.setMetadata_(claim, metadata) @@ -183,9 +175,11 @@ class ClaimService extends BaseService { * Creates a Claim on an Order. Claims consists of items that are claimed and * optionally items to be sent as replacement for the claimed items. The * shipping address that the new items will be shipped to + * @param {Object} data - the object containing all data required to create a claim + * @return {Object} created claim */ create(data) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const claimRepo = manager.getCustomRepository(this.claimRepository_) const { @@ -257,8 +251,8 @@ class ClaimService extends BaseService { let toRefund = refund_amount if (type === "refund" && typeof refund_amount === "undefined") { - const lines = claim_items.map(ci => { - const orderItem = order.items.find(oi => oi.id === ci.item_id) + const lines = claim_items.map((ci) => { + const orderItem = order.items.find((oi) => oi.id === ci.item_id) return { ...orderItem, quantity: ci.quantity, @@ -274,7 +268,7 @@ class ClaimService extends BaseService { } const newItems = await Promise.all( - additional_items.map(i => + additional_items.map((i) => this.lineItemService_ .withTransaction(manager) .generate(i.variant_id, order.region_id, i.quantity) @@ -332,7 +326,7 @@ class ClaimService extends BaseService { await this.returnService_.withTransaction(manager).create({ order_id: order.id, claim_order_id: result.id, - items: claim_items.map(ci => ({ + items: claim_items.map((ci) => ({ item_id: ci.item_id, quantity: ci.quantity, metadata: ci.metadata, @@ -362,7 +356,7 @@ class ClaimService extends BaseService { ) { const { metadata, no_notification } = config - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const claim = await this.retrieve(id, { relations: [ "additional_items", @@ -429,7 +423,7 @@ class ClaimService extends BaseService { is_claim: true, no_notification: evaluatedNoNotification, }, - claim.additional_items.map(i => ({ + claim.additional_items.map((i) => ({ item_id: i.id, quantity: i.quantity, })), @@ -445,7 +439,7 @@ class ClaimService extends BaseService { for (const item of claim.additional_items) { const fulfillmentItem = successfullyFulfilled.find( - f => item.id === f.item_id + (f) => item.id === f.item_id ) if (fulfillmentItem) { @@ -485,7 +479,7 @@ class ClaimService extends BaseService { } async cancelFulfillment(fulfillmentId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const canceled = await this.fulfillmentService_ .withTransaction(manager) .cancelFulfillment(fulfillmentId) @@ -508,7 +502,7 @@ class ClaimService extends BaseService { } async processRefund(id) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const claim = await this.retrieve(id, { relations: ["order", "order.payments"], }) @@ -560,7 +554,7 @@ class ClaimService extends BaseService { ) { const { metadata, no_notification } = config - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const claim = await this.retrieve(id, { relations: ["additional_items"], }) @@ -584,7 +578,7 @@ class ClaimService extends BaseService { claim.fulfillment_status = "shipped" for (const i of claim.additional_items) { - const shipped = shipment.items.find(si => si.item_id === i.id) + const shipped = shipment.items.find((si) => si.item_id === i.id) if (shipped) { const shippedQty = (i.shipped_quantity || 0) + shipped.quantity await this.lineItemService_.withTransaction(manager).update(i.id, { @@ -617,7 +611,7 @@ class ClaimService extends BaseService { } async cancel(id) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const claim = await this.retrieve(id, { relations: ["return_order", "fulfillments", "order", "order.refunds"], }) @@ -665,6 +659,7 @@ class ClaimService extends BaseService { /** * @param {Object} selector - the query object for find + * @param {Object} config - the config object containing query settings * @return {Promise} the result of the find operation */ async list( @@ -678,7 +673,8 @@ class ClaimService extends BaseService { /** * Gets an order by id. - * @param {string} orderId - id of order to retrieve + * @param {string} claimId - id of order to retrieve + * @param {Object} config - the config object containing query settings * @return {Promise} the order document */ async retrieve(claimId, config = {}) { @@ -717,7 +713,7 @@ class ClaimService extends BaseService { const keyPath = `metadata.${key}` return this.orderModel_ .updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } }) - .catch(err => { + .catch((err) => { throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) }) } diff --git a/packages/medusa/src/services/custom-shipping-option.js b/packages/medusa/src/services/custom-shipping-option.js index df4c6e5d05..01e7290746 100644 --- a/packages/medusa/src/services/custom-shipping-option.js +++ b/packages/medusa/src/services/custom-shipping-option.js @@ -1,6 +1,5 @@ import { MedusaError } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" -import _ from "lodash" class CustomShippingOptionService extends BaseService { constructor({ manager, customShippingOptionRepository }) { @@ -36,7 +35,7 @@ class CustomShippingOptionService extends BaseService { * Retrieves a specific shipping option. * @param {string} id - the id of the custom shipping option to retrieve. * @param {*} config - any options needed to query for the result. - * @returns {Promise} which resolves to the requested custom shipping option. + * @return {Promise} which resolves to the requested custom shipping option. */ async retrieve(id, config = {}) { const customShippingOptionRepo = this.manager_.getCustomRepository( @@ -84,14 +83,14 @@ class CustomShippingOptionService extends BaseService { * Creates a custom shipping option associated with a given author * @param {object} data - the custom shipping option to create * @param {*} config - any configurations if needed, including meta data - * @returns {Promise} resolves to the creation result + * @return {Promise} resolves to the creation result */ async create(data, config = { metadata: {} }) { const { metadata } = config const { cart_id, shipping_option_id, price } = data - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const customShippingOptionRepo = manager.getCustomRepository( this.customShippingOptionRepository_ ) diff --git a/packages/medusa/src/services/discount.js b/packages/medusa/src/services/discount.js index ef987e617d..bb796208ce 100644 --- a/packages/medusa/src/services/discount.js +++ b/packages/medusa/src/services/discount.js @@ -1,14 +1,11 @@ -import _ from "lodash" -import randomize from "randomatic" import { BaseService } from "medusa-interfaces" import { Validator, MedusaError } from "medusa-core-utils" -import { MedusaErrorCodes } from "medusa-core-utils/dist/errors" import { parse, toSeconds } from "iso8601-duration" import { Brackets, ILike } from "typeorm" /** * Provides layer to manipulate discounts. - * @implements BaseService + * @implements {BaseService} */ class DiscountService extends BaseService { constructor({ @@ -17,7 +14,6 @@ class DiscountService extends BaseService { discountRuleRepository, giftCardRepository, totalsService, - productVariantService, productService, regionService, eventBusService, @@ -80,21 +76,13 @@ class DiscountService extends BaseService { id: Validator.string().optional(), description: Validator.string().optional(), type: Validator.string().required(), - value: Validator.number() - .min(0) - .required(), + value: Validator.number().min(0).required(), allocation: Validator.string().required(), valid_for: Validator.array().optional(), created_at: Validator.date().optional(), - updated_at: Validator.date() - .allow(null) - .optional(), - deleted_at: Validator.date() - .allow(null) - .optional(), - metadata: Validator.object() - .allow(null) - .optional(), + updated_at: Validator.date().allow(null).optional(), + deleted_at: Validator.date().allow(null).optional(), + metadata: Validator.object().allow(null).optional(), }) const { value, error } = schema.validate(discountRule) @@ -117,6 +105,7 @@ class DiscountService extends BaseService { /** * @param {Object} selector - the query object for find + * @param {Object} config - the config object containing query settings * @return {Promise} the result of the find operation */ async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) { @@ -130,6 +119,7 @@ class DiscountService extends BaseService { /** * @param {Object} selector - the query object for find + * @param {Object} config - the config object containing query settings * @return {Promise} the result of the find operation */ async listAndCount( @@ -153,11 +143,11 @@ class DiscountService extends BaseService { delete where.code - query.where = qb => { + query.where = (qb) => { qb.where(where) qb.andWhere( - new Brackets(qb => { + new Brackets((qb) => { qb.where({ code: ILike(`%${q}%`) }) }) ) @@ -176,16 +166,20 @@ class DiscountService extends BaseService { * @return {Promise} the result of the create operation */ async create(discount) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRepo = manager.getCustomRepository(this.discountRepository_) const ruleRepo = manager.getCustomRepository(this.discountRuleRepository_) + if (discount.rule?.valid_for) { + discount.rule.valid_for = discount.rule.valid_for.map((id) => ({ id })) + } + const validatedRule = this.validateDiscountRule_(discount.rule) if (discount.regions) { discount.regions = await Promise.all( - discount.regions.map(regionId => - this.regionService_.retrieve(regionId) + discount.regions.map((regionId) => + this.regionService_.withTransaction(manager).retrieve(regionId) ) ) } @@ -205,6 +199,7 @@ class DiscountService extends BaseService { /** * Gets a discount by id. * @param {string} discountId - id of discount to retrieve + * @param {Object} config - the config object containing query settings * @return {Promise} the discount */ async retrieve(discountId, config = {}) { @@ -229,6 +224,7 @@ class DiscountService extends BaseService { /** * Gets a discount by discount code. * @param {string} discountCode - discount code of discount to retrieve + * @param {array} relations - list of relations * @return {Promise} the discount document */ async retrieveByCode(discountCode, relations = []) { @@ -265,7 +261,7 @@ class DiscountService extends BaseService { * @return {Promise} the result of the update operation */ async update(discountId, update) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRepo = manager.getCustomRepository(this.discountRepository_) const discount = await this.retrieve(discountId) @@ -283,7 +279,7 @@ class DiscountService extends BaseService { if (regions) { discount.regions = await Promise.all( - regions.map(regionId => this.regionService_.retrieve(regionId)) + regions.map((regionId) => this.regionService_.retrieve(regionId)) ) } @@ -293,6 +289,11 @@ class DiscountService extends BaseService { if (rule) { discount.rule = this.validateDiscountRule_(rule) + if (rule.valid_for) { + discount.rule.valid_for = discount.rule.valid_for.map((id) => ({ + id, + })) + } } for (const [key, value] of Object.entries(rest)) { @@ -307,11 +308,11 @@ class DiscountService extends BaseService { /** * Creates a dynamic code for a discount id. * @param {string} discountId - the id of the discount to create a code for - * @param {string} code - the code to identify the discount by + * @param {Object} data - the object containing a code to identify the discount by * @return {Promise} the newly created dynamic code */ async createDynamicCode(discountId, data) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRepo = manager.getCustomRepository(this.discountRepository_) const discount = await this.retrieve(discountId) @@ -360,13 +361,15 @@ class DiscountService extends BaseService { * @return {Promise} the newly created dynamic code */ async deleteDynamicCode(discountId, code) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRepo = manager.getCustomRepository(this.discountRepository_) const discount = await discountRepo.findOne({ where: { parent_discount_id: discountId, code }, }) - if (!discount) return Promise.resolve() + if (!discount) { + return Promise.resolve() + } await discountRepo.softRemove(discount) @@ -381,7 +384,7 @@ class DiscountService extends BaseService { * @return {Promise} the result of the update operation */ async addValidProduct(discountId, productId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRuleRepo = manager.getCustomRepository( this.discountRuleRepository_ ) @@ -392,7 +395,7 @@ class DiscountService extends BaseService { const { rule } = discount - const exists = rule.valid_for.find(p => p.id === productId) + const exists = rule.valid_for.find((p) => p.id === productId) // If product is already present, we return early if (exists) { return rule @@ -414,7 +417,7 @@ class DiscountService extends BaseService { * @return {Promise} the result of the update operation */ async removeValidProduct(discountId, productId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRuleRepo = manager.getCustomRepository( this.discountRuleRepository_ ) @@ -425,13 +428,13 @@ class DiscountService extends BaseService { const { rule } = discount - const exists = rule.valid_for.find(p => p.id === productId) + const exists = rule.valid_for.find((p) => p.id === productId) // If product is not present, we return early if (!exists) { return rule } - rule.valid_for = rule.valid_for.filter(p => p.id !== productId) + rule.valid_for = rule.valid_for.filter((p) => p.id !== productId) const updated = await discountRuleRepo.save(rule) return updated @@ -445,14 +448,14 @@ class DiscountService extends BaseService { * @return {Promise} the result of the update operation */ async addRegion(discountId, regionId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRepo = manager.getCustomRepository(this.discountRepository_) const discount = await this.retrieve(discountId, { relations: ["regions"], }) - const exists = discount.regions.find(r => r.id === regionId) + const exists = discount.regions.find((r) => r.id === regionId) // If region is already present, we return early if (exists) { return discount @@ -474,20 +477,20 @@ class DiscountService extends BaseService { * @return {Promise} the result of the update operation */ async removeRegion(discountId, regionId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRepo = manager.getCustomRepository(this.discountRepository_) const discount = await this.retrieve(discountId, { relations: ["regions"], }) - const exists = discount.regions.find(r => r.id === regionId) + const exists = discount.regions.find((r) => r.id === regionId) // If region is not present, we return early if (!exists) { return discount } - discount.regions = discount.regions.filter(r => r.id !== regionId) + discount.regions = discount.regions.filter((r) => r.id !== regionId) const updated = await discountRepo.save(discount) return updated @@ -500,12 +503,14 @@ class DiscountService extends BaseService { * @return {Promise} the result of the delete operation */ async delete(discountId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const discountRepo = manager.getCustomRepository(this.discountRepository_) const discount = await discountRepo.findOne({ where: { id: discountId } }) - if (!discount) return Promise.resolve() + if (!discount) { + return Promise.resolve() + } await discountRepo.softRemove(discount) @@ -515,7 +520,7 @@ class DiscountService extends BaseService { /** * Decorates a discount. - * @param {Discount} discount - the discount to decorate. + * @param {string} discountId - id of discount to decorate * @param {string[]} fields - the fields to include. * @param {string[]} expandFields - fields to expand. * @return {Discount} return the decorated discount. diff --git a/packages/medusa/src/services/draft-order.js b/packages/medusa/src/services/draft-order.js index fe2ceb51aa..96d089b7bf 100644 --- a/packages/medusa/src/services/draft-order.js +++ b/packages/medusa/src/services/draft-order.js @@ -250,10 +250,10 @@ class DraftOrderService extends BaseService { } const { - items, shipping_methods, discounts, no_notification_order, + items, ...rest } = data diff --git a/packages/medusa/src/services/fulfillment.js b/packages/medusa/src/services/fulfillment.js index ed0b4e4f80..b2cd6b71bc 100644 --- a/packages/medusa/src/services/fulfillment.js +++ b/packages/medusa/src/services/fulfillment.js @@ -1,10 +1,9 @@ -import _ from "lodash" import { BaseService } from "medusa-interfaces" import { MedusaError } from "medusa-core-utils" /** * Handles Fulfillments - * @implements BaseService + * @extends BaseService */ class FulfillmentService extends BaseService { constructor({ @@ -61,7 +60,7 @@ class FulfillmentService extends BaseService { } partitionItems_(shippingMethods, items) { - let partitioned = [] + const partitioned = [] // partition order items to their dedicated shipping method for (const method of shippingMethods) { const temp = { shipping_method: method } @@ -95,12 +94,12 @@ class FulfillmentService extends BaseService { async getFulfillmentItems_(order, items, transformer) { const toReturn = await Promise.all( items.map(async ({ item_id, quantity }) => { - const item = order.items.find(i => i.id === item_id) + const item = order.items.find((i) => i.id === item_id) return transformer(item, quantity) }) ) - return toReturn.filter(i => !!i) + return toReturn.filter((i) => !!i) } /** @@ -137,6 +136,7 @@ class FulfillmentService extends BaseService { /** * Retrieves a fulfillment by its id. * @param {string} id - the id of the fulfillment to retrieve + * @param {object} config - optional values to include with fulfillmentRepository query * @return {Fulfillment} the fulfillment */ async retrieve(id, config = {}) { @@ -165,11 +165,11 @@ class FulfillmentService extends BaseService { * those partitions. * @param {Order} order - order to create fulfillment for * @param {{ item_id: string, quantity: number}[]} itemsToFulfill - the items in the order to fulfill - * @param {object} metadata - potential metadata to add + * @param {object} custom - potential custom values to add * @return {Fulfillment[]} the created fulfillments */ async createFulfillment(order, itemsToFulfill, custom = {}) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const fulfillmentRepository = manager.getCustomRepository( this.fulfillmentRepository_ ) @@ -190,18 +190,19 @@ class FulfillmentService extends BaseService { const ful = fulfillmentRepository.create({ ...custom, provider_id: shipping_method.shipping_option.provider_id, - items: items.map(i => ({ item_id: i.id, quantity: i.quantity })), + items: items.map((i) => ({ item_id: i.id, quantity: i.quantity })), data: {}, }) - let result = await fulfillmentRepository.save(ful) + const result = await fulfillmentRepository.save(ful) - result.data = await this.fulfillmentProviderService_.createFulfillment( - shipping_method, - items, - { ...order }, - { ...result } - ) + result.data = + await this.fulfillmentProviderService_.createFulfillment( + shipping_method, + items, + { ...order }, + { ...result } + ) return fulfillmentRepository.save(result) }) @@ -220,7 +221,7 @@ class FulfillmentService extends BaseService { * */ cancelFulfillment(fulfillmentOrId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { let id = fulfillmentOrId if (typeof fulfillmentOrId === "object") { id = fulfillmentOrId.id @@ -260,9 +261,9 @@ class FulfillmentService extends BaseService { /** * Creates a shipment by marking a fulfillment as shipped. Adds - * tracking numbers and potentially more metadata. + * tracking links and potentially more metadata. * @param {Order} fulfillmentId - the fulfillment to ship - * @param {TrackingLink[]} trackingNumbers - tracking numbers for the shipment + * @param {TrackingLink[]} trackingLinks - tracking links for the shipment * @param {object} config - potential configuration settings, such as no_notification and metadata * @return {Fulfillment} the shipped fulfillment */ @@ -276,7 +277,7 @@ class FulfillmentService extends BaseService { ) { const { metadata, no_notification } = config - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const fulfillmentRepository = manager.getCustomRepository( this.fulfillmentRepository_ ) @@ -298,7 +299,7 @@ class FulfillmentService extends BaseService { const now = new Date() fulfillment.shipped_at = now - fulfillment.tracking_links = trackingLinks.map(tl => + fulfillment.tracking_links = trackingLinks.map((tl) => trackingLinkRepo.create(tl) ) diff --git a/packages/medusa/src/services/gift-card.js b/packages/medusa/src/services/gift-card.js index 3762ce36f5..b96372d223 100644 --- a/packages/medusa/src/services/gift-card.js +++ b/packages/medusa/src/services/gift-card.js @@ -5,7 +5,7 @@ import { Brackets } from "typeorm" /** * Provides layer to manipulate gift cards. - * @implements BaseService + * @extends BaseService */ class GiftCardService extends BaseService { static Events = { @@ -101,7 +101,7 @@ class GiftCardService extends BaseService { .select(["gift_card.id"]) .where(where) .andWhere( - new Brackets(qb => { + new Brackets((qb) => { return qb .where(`gift_card.code ILIKE :q`, { q: `%${q}%` }) .orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` }) @@ -111,14 +111,14 @@ class GiftCardService extends BaseService { return giftCardRepo.findWithRelations( rels, - raw.map(i => i.id) + raw.map((i) => i.id) ) } return giftCardRepo.findWithRelations(rels, query) } async createTransaction(data) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const gctRepo = manager.getCustomRepository(this.giftCardTransactionRepo_) const created = gctRepo.create(data) const saved = await gctRepo.save(created) @@ -132,7 +132,7 @@ class GiftCardService extends BaseService { * @return {Promise} the result of the create operation */ async create(giftCard) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_) if (!giftCard.region_id) { @@ -169,6 +169,7 @@ class GiftCardService extends BaseService { /** * Gets a gift card by id. * @param {string} giftCardId - id of gift card to retrieve + * @param {object} config - optional values to include with gift card query * @return {Promise} the gift card */ async retrieve(giftCardId, config = {}) { @@ -244,7 +245,7 @@ class GiftCardService extends BaseService { * @return {Promise} the result of the update operation */ async update(giftCardId, update) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_) const giftCard = await this.retrieve(giftCardId) @@ -285,12 +286,14 @@ class GiftCardService extends BaseService { * @return {Promise} the result of the delete operation */ async delete(giftCardId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_) const giftCard = await giftCardRepo.findOne({ where: { id: giftCardId } }) - if (!giftCard) return Promise.resolve() + if (!giftCard) { + return Promise.resolve() + } await giftCardRepo.softRemove(giftCard) diff --git a/packages/medusa/src/services/order.js b/packages/medusa/src/services/order.js index 64684ac6d6..07e1a96901 100644 --- a/packages/medusa/src/services/order.js +++ b/packages/medusa/src/services/order.js @@ -178,6 +178,7 @@ class OrderService extends BaseService { /** * @param {Object} selector - the query object for find + * @param {Object} config - the config to be used for find * @return {Promise} the result of the find operation */ async list( @@ -187,9 +188,8 @@ class OrderService extends BaseService { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) const query = this.buildQuery_(selector, config) - const { select, relations, totalsToSelect } = this.transformQueryForTotals_( - config - ) + const { select, relations, totalsToSelect } = + this.transformQueryForTotals_(config) if (select && select.length) { query.select = select @@ -201,7 +201,7 @@ class OrderService extends BaseService { const raw = await orderRepo.find(query) - return raw.map(r => this.decorateTotals_(r, totalsToSelect)) + return raw.map((r) => this.decorateTotals_(r, totalsToSelect)) } async listAndCount( @@ -231,11 +231,11 @@ class OrderService extends BaseService { }, } - query.where = qb => { + query.where = (qb) => { qb.where(where) qb.andWhere( - new Brackets(qb => { + new Brackets((qb) => { qb.where(`shipping_address.first_name ILIKE :qfn`, { qfn: `%${q}%`, }) @@ -246,20 +246,19 @@ class OrderService extends BaseService { } } - const { select, relations, totalsToSelect } = this.transformQueryForTotals_( - config - ) + const { select, relations, totalsToSelect } = + this.transformQueryForTotals_(config) if (select && select.length) { query.select = select } - let rels = relations + const rels = relations delete query.relations const raw = await orderRepo.findWithRelations(rels, query) const count = await orderRepo.count(query) - const orders = raw.map(r => this.decorateTotals_(r, totalsToSelect)) + const orders = raw.map((r) => this.decorateTotals_(r, totalsToSelect)) return [orders, count] } @@ -289,13 +288,15 @@ class OrderService extends BaseService { "swaps.additional_items.refundable", ] - const totalsToSelect = select.filter(v => totalFields.includes(v)) + const totalsToSelect = select.filter((v) => totalFields.includes(v)) if (totalsToSelect.length > 0) { const relationSet = new Set(relations) relationSet.add("items") relationSet.add("swaps") relationSet.add("swaps.additional_items") relationSet.add("discounts") + relationSet.add("discounts.rule") + relationSet.add("discounts.rule.valid_for") relationSet.add("gift_cards") relationSet.add("gift_card_transactions") relationSet.add("refunds") @@ -303,7 +304,7 @@ class OrderService extends BaseService { relationSet.add("region") relations = [...relationSet] - select = select.filter(v => !totalFields.includes(v)) + select = select.filter((v) => !totalFields.includes(v)) } return { @@ -316,15 +317,15 @@ class OrderService extends BaseService { /** * Gets an order by id. * @param {string} orderId - id of order to retrieve + * @param {Object} config - config of order to retrieve * @return {Promise} the order document */ async retrieve(orderId, config = {}) { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) const validatedId = this.validateId_(orderId) - const { select, relations, totalsToSelect } = this.transformQueryForTotals_( - config - ) + const { select, relations, totalsToSelect } = + this.transformQueryForTotals_(config) const query = { where: { id: validatedId }, @@ -355,14 +356,14 @@ class OrderService extends BaseService { /** * Gets an order by cart id. * @param {string} cartId - cart id to find order + * @param {Object} config - the config to be used to find order * @return {Promise} the order document */ async retrieveByCartId(cartId, config = {}) { const orderRepo = this.manager_.getCustomRepository(this.orderRepository_) - const { select, relations, totalsToSelect } = this.transformQueryForTotals_( - config - ) + const { select, relations, totalsToSelect } = + this.transformQueryForTotals_(config) const query = { where: { cart_id: cartId }, @@ -395,7 +396,7 @@ class OrderService extends BaseService { * @return {Promise} the order document */ async existsByCartId(cartId) { - const order = await this.retrieveByCartId(cartId).catch(_ => undefined) + const order = await this.retrieveByCartId(cartId).catch((_) => undefined) if (!order) { return false } @@ -407,7 +408,7 @@ class OrderService extends BaseService { * @return {Promise} the result of the find operation */ async completeOrder(orderId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId) if (order.status === "canceled") { @@ -426,7 +427,7 @@ class OrderService extends BaseService { } ) - await completeOrderJob.finished().catch(error => { + await completeOrderJob.finished().catch((error) => { throw error }) @@ -443,7 +444,7 @@ class OrderService extends BaseService { * @return {Promise} resolves to the creation result. */ async createFromCart(cartId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const cart = await this.cartService_ .withTransaction(manager) .retrieve(cartId, { @@ -453,6 +454,8 @@ class OrderService extends BaseService { "payment", "items", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "gift_cards", "shipping_methods", ], @@ -615,6 +618,7 @@ class OrderService extends BaseService { * @param {string} fulfillmentId - the fulfillment that has now been shipped * @param {TrackingLink[]} trackingLinks - array of tracking numebers * associated with the shipment + * @param {Object} config - the config of the order that has been shipped * @param {Dictionary} metadata - optional metadata to add to * the fulfillment * @return {order} the resulting order following the update. @@ -630,7 +634,7 @@ class OrderService extends BaseService { ) { const { metadata, no_notification } = config - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { relations: ["items"] }) const shipment = await this.fulfillmentService_.retrieve(fulfillmentId) @@ -662,7 +666,7 @@ class OrderService extends BaseService { order.fulfillment_status = "shipped" for (const item of order.items) { - const shipped = shipmentRes.items.find(si => si.item_id === item.id) + const shipped = shipmentRes.items.find((si) => si.item_id === item.id) if (shipped) { const shippedQty = (item.shipped_quantity || 0) + shipped.quantity if (shippedQty !== item.quantity) { @@ -696,11 +700,11 @@ class OrderService extends BaseService { /** * Creates an order - * @param {object} order - the order to create + * @param {object} data - the data to create an order * @return {Promise} resolves to the creation result. */ async create(data) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const orderRepo = manager.getCustomRepository(this.orderRepository_) const order = orderRepo.create(data) const result = await orderRepo.save(order) @@ -716,7 +720,7 @@ class OrderService extends BaseService { /** * Updates the order's billing address. - * @param {string} orderId - the id of the order to update + * @param {object} order - the order to update * @param {object} address - the value to set the billing address to * @return {Promise} the result of the update operation */ @@ -751,7 +755,7 @@ class OrderService extends BaseService { /** * Updates the order's shipping address. - * @param {string} orderId - the id of the order to update + * @param {object} order - the order to update * @param {object} address - the value to set the shipping address to * @return {Promise} the result of the update operation */ @@ -783,7 +787,7 @@ class OrderService extends BaseService { } async addShippingMethod(orderId, optionId, data, config = {}) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { select: ["subtotal"], relations: [ @@ -841,7 +845,7 @@ class OrderService extends BaseService { * @return {Promise} resolves to the update result. */ async update(orderId, update) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId) if (order.status === "canceled") { @@ -870,13 +874,7 @@ class OrderService extends BaseService { ) } - const { - metadata, - items, - billing_address, - shipping_address, - ...rest - } = update + const { ...rest } = update if ("metadata" in update) { order.metadata = this.setMetadata_(order, update.metadata) @@ -928,7 +926,7 @@ class OrderService extends BaseService { * @return {Promise} result of the update operation. */ async cancel(orderId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { relations: [ "fulfillments", @@ -948,16 +946,16 @@ class OrderService extends BaseService { } const throwErrorIf = (arr, pred, type) => - arr?.filter(pred).find(_ => { + arr?.filter(pred).find((_) => { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, `All ${type} must be canceled before canceling an order` ) }) - const notCanceled = o => !o.canceled_at + const notCanceled = (o) => !o.canceled_at throwErrorIf(order.fulfillments, notCanceled, "fulfillments") - throwErrorIf(order.returns, r => r.status !== "canceled", "returns") + throwErrorIf(order.returns, (r) => r.status !== "canceled", "returns") throwErrorIf(order.swaps, notCanceled, "swaps") throwErrorIf(order.claims, notCanceled, "claims") @@ -996,7 +994,7 @@ class OrderService extends BaseService { * @return {Promise} result of the update operation. */ async capturePayment(orderId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const orderRepo = manager.getCustomRepository(this.orderRepository_) const order = await this.retrieve(orderId, { relations: ["payments"] }) @@ -1013,7 +1011,7 @@ class OrderService extends BaseService { const result = await this.paymentProviderService_ .withTransaction(manager) .capturePayment(p) - .catch(err => { + .catch((err) => { this.eventBus_ .withTransaction(manager) .emit(OrderService.Events.PAYMENT_CAPTURE_FAILED, { @@ -1035,7 +1033,7 @@ class OrderService extends BaseService { } order.payments = payments - order.payment_status = payments.every(p => p.captured_at !== null) + order.payment_status = payments.every((p) => p.captured_at !== null) ? "captured" : "requires_action" @@ -1091,6 +1089,8 @@ class OrderService extends BaseService { * we need to partition the order items, such that they can be sent * to their respective fulfillment provider. * @param {string} orderId - id of order to cancel. + * @param {Object} itemsToFulfill - items to fulfil. + * @param {Object} config - the config to cancel. * @return {Promise} result of the update operation. */ async createFulfillment( @@ -1103,7 +1103,7 @@ class OrderService extends BaseService { ) { const { metadata, no_notification } = config - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { // NOTE: we are telling the service to calculate all totals for us which // will add to what is fetched from the database. We want this to happen // so that we get all order details. These will thereafter be forwarded @@ -1119,6 +1119,8 @@ class OrderService extends BaseService { ], relations: [ "discounts", + "discounts.rule", + "discounts.rule.valid_for", "region", "fulfillments", "shipping_address", @@ -1163,7 +1165,7 @@ class OrderService extends BaseService { // Update all line items to reflect fulfillment for (const item of order.items) { const fulfillmentItem = successfullyFulfilled.find( - f => item.id === f.item_id + (f) => item.id === f.item_id ) if (fulfillmentItem) { @@ -1211,10 +1213,10 @@ class OrderService extends BaseService { /** * Cancels a fulfillment (if related to an order) * @param {string} fulfillmentId - the ID of the fulfillment to cancel - * @returns updated order + * @return {Promise} updated order */ async cancelFulfillment(fulfillmentId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const canceled = await this.fulfillmentService_ .withTransaction(manager) .cancelFulfillment(fulfillmentId) @@ -1258,12 +1260,12 @@ class OrderService extends BaseService { async getFulfillmentItems_(order, items, transformer) { const toReturn = await Promise.all( items.map(async ({ item_id, quantity }) => { - const item = order.items.find(i => i.id.equals(item_id)) + const item = order.items.find((i) => i.id.equals(item_id)) return transformer(item, quantity) }) ) - return toReturn.filter(i => !!i) + return toReturn.filter((i) => !!i) } /** @@ -1273,7 +1275,7 @@ class OrderService extends BaseService { * @return {Promise} the result of the update operation */ async archive(orderId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId) if (order.status !== ("completed" || "refunded")) { @@ -1292,6 +1294,12 @@ class OrderService extends BaseService { /** * Refunds a given amount back to the customer. + * @param {string} orderId - id of the order to refund. + * @param {float} refundAmount - the amount to refund. + * @param {string} reason - the reason to refund. + * @param {string} note - note for refund. + * @param {Object} config - the config for refund. + * @return {Promise} the result of the refund operation. */ async createRefund( orderId, @@ -1304,7 +1312,7 @@ class OrderService extends BaseService { ) { const { no_notification } = config - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { select: ["refundable_amount", "total", "refunded_total"], relations: ["payments"], @@ -1374,7 +1382,7 @@ class OrderService extends BaseService { } if (totalsFields.includes("items.refundable")) { - order.items = order.items.map(i => ({ + order.items = order.items.map((i) => ({ ...i, refundable: this.totalsService_.getLineItemRefund(order, { ...i, @@ -1389,7 +1397,7 @@ class OrderService extends BaseService { order.swaps.length ) { for (const s of order.swaps) { - s.additional_items = s.additional_items.map(i => ({ + s.additional_items = s.additional_items.map((i) => ({ ...i, refundable: this.totalsService_.getLineItemRefund(order, { ...i, @@ -1412,10 +1420,11 @@ class OrderService extends BaseService { * mismatches. * @param {string} orderId - the order to return. * @param {object} receivedReturn - the received return + * @param {float} customRefundAmount - the custom refund amount return * @return {Promise} the result of the update operation */ async registerReturnReceived(orderId, receivedReturn, customRefundAmount) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const order = await this.retrieve(orderId, { select: ["total", "refunded_total", "refundable_amount"], relations: ["items", "returns", "payments"], @@ -1435,7 +1444,7 @@ class OrderService extends BaseService { ) } - let refundAmount = customRefundAmount || receivedReturn.refund_amount + const refundAmount = customRefundAmount || receivedReturn.refund_amount const orderRepo = manager.getCustomRepository(this.orderRepository_) @@ -1504,7 +1513,7 @@ class OrderService extends BaseService { const keyPath = `metadata.${key}` return this.orderModel_ .updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } }) - .catch(err => { + .catch((err) => { throw new MedusaError(MedusaError.Types.DB_ERROR, err.message) }) } diff --git a/packages/medusa/src/services/product.js b/packages/medusa/src/services/product.js index 45b7375af4..d46a5968c5 100644 --- a/packages/medusa/src/services/product.js +++ b/packages/medusa/src/services/product.js @@ -295,7 +295,7 @@ class ProductService extends BaseService { let product = productRepo.create(rest) - if (images && images.length) { + if (images) { product.images = await this.upsertImages_(images) } @@ -380,11 +380,11 @@ class ProductService extends BaseService { ...rest } = update - if (!product.thumbnail && !update.thumbnail && images && images.length) { + if (!product.thumbnail && !update.thumbnail && images?.length) { product.thumbnail = images[0] } - if (images && images.length) { + if (images) { product.images = await this.upsertImages_(images) } diff --git a/packages/medusa/src/services/region.js b/packages/medusa/src/services/region.js index 0994214958..44d611ca0e 100644 --- a/packages/medusa/src/services/region.js +++ b/packages/medusa/src/services/region.js @@ -1,11 +1,10 @@ -import _ from "lodash" -import { Validator, MedusaError } from "medusa-core-utils" +import { MedusaError } from "medusa-core-utils" import { BaseService } from "medusa-interfaces" import { countries } from "../utils/countries" /** * Provides layer to manipulate regions. - * @implements BaseService + * @extends BaseService */ class RegionService extends BaseService { static Events = { @@ -83,11 +82,11 @@ class RegionService extends BaseService { /** * Creates a region. - * @param {Region} rawRegion - the unvalidated region + * @param {Region} regionObject - the unvalidated region * @return {Region} the newly created region */ async create(regionObject) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepository = manager.getCustomRepository( this.regionRepository_ ) @@ -118,7 +117,7 @@ class RegionService extends BaseService { } if (metadata) { - regionObject.metadata = this.setMetadata_(region, metadata) + regionObject.metadata = this.setMetadata_(regionObject, metadata) } for (const [key, value] of Object.entries(validated)) { @@ -145,7 +144,7 @@ class RegionService extends BaseService { * @return {Promise} the result of the update operation */ async update(regionId, update) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepository = manager.getCustomRepository( this.regionRepository_ ) @@ -218,17 +217,17 @@ class RegionService extends BaseService { if (region.countries) { region.countries = await Promise.all( - region.countries.map(countryCode => + region.countries.map((countryCode) => this.validateCountry_(countryCode, id) ) - ).catch(err => { + ).catch((err) => { throw err }) } if (region.payment_providers) { region.payment_providers = await Promise.all( - region.payment_providers.map(async pId => { + region.payment_providers.map(async (pId) => { const pp = await ppRepository.findOne({ where: { id: pId } }) if (!pp) { throw new MedusaError( @@ -244,7 +243,7 @@ class RegionService extends BaseService { if (region.fulfillment_providers) { region.fulfillment_providers = await Promise.all( - region.fulfillment_providers.map(async fId => { + region.fulfillment_providers.map(async (fId) => { const fp = await fpRepository.findOne({ where: { id: fId } }) if (!fp) { throw new MedusaError( @@ -283,7 +282,7 @@ class RegionService extends BaseService { .withTransaction(this.transactionManager_) .retrieve(["currencies"]) - const storeCurrencies = store.currencies.map(curr => curr.code) + const storeCurrencies = store.currencies.map((curr) => curr.code) if (!storeCurrencies.includes(currencyCode.toLowerCase())) { throw new MedusaError( @@ -305,7 +304,7 @@ class RegionService extends BaseService { ) const countryCode = code.toUpperCase() - const validCountry = countries.find(c => c.alpha2 === countryCode) + const validCountry = countries.find((c) => c.alpha2 === countryCode) if (!validCountry) { throw new MedusaError( MedusaError.Types.INVALID_DATA, @@ -339,6 +338,7 @@ class RegionService extends BaseService { /** * Retrieves a region by its id. * @param {string} regionId - the id of the region to retrieve + * @param {object} config - configuration settings * @return {Region} the region */ async retrieve(regionId, config = {}) { @@ -361,7 +361,8 @@ class RegionService extends BaseService { /** * Lists all regions based on a query - * @param {object} listOptions - query object for find + * @param {object} selector - query object for find + * @param {object} config - configuration settings * @return {Promise} result of the find operation */ async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) { @@ -377,12 +378,14 @@ class RegionService extends BaseService { * @return {Promise} the result of the delete operation */ async delete(regionId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepo = manager.getCustomRepository(this.regionRepository_) const region = await regionRepo.findOne({ where: { id: regionId } }) - if (!region) return Promise.resolve() + if (!region) { + return Promise.resolve() + } await regionRepo.softRemove(region) @@ -397,7 +400,7 @@ class RegionService extends BaseService { * @return {Promise} the result of the update operation */ async addCountry(regionId, code) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepo = manager.getCustomRepository(this.regionRepository_) const country = await this.validateCountry_(code, regionId) @@ -407,7 +410,7 @@ class RegionService extends BaseService { // Check if region already has country if ( region.countries && - region.countries.map(c => c.iso_2).includes(country.iso_2) + region.countries.map((c) => c.iso_2).includes(country.iso_2) ) { return region } @@ -434,7 +437,7 @@ class RegionService extends BaseService { * @return {Promise} the result of the update operation */ async removeCountry(regionId, code) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepo = manager.getCustomRepository(this.regionRepository_) const region = await this.retrieve(regionId, { relations: ["countries"] }) @@ -442,13 +445,13 @@ class RegionService extends BaseService { // Check if region contains country. If not, we simpy resolve if ( region.countries && - !region.countries.map(c => c.iso_2).includes(code) + !region.countries.map((c) => c.iso_2).includes(code) ) { return region } region.countries = region.countries.filter( - country => country.iso_2 !== code + (country) => country.iso_2 !== code ) const updated = await regionRepo.save(region) @@ -470,7 +473,7 @@ class RegionService extends BaseService { * @return {Promise} the result of the update operation */ async addPaymentProvider(regionId, providerId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepo = manager.getCustomRepository(this.regionRepository_) const ppRepo = manager.getCustomRepository( this.paymentProviderRepository_ @@ -517,7 +520,7 @@ class RegionService extends BaseService { * @return {Promise} the result of the update operation */ async addFulfillmentProvider(regionId, providerId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepo = manager.getCustomRepository(this.regionRepository_) const fpRepo = manager.getCustomRepository( this.fulfillmentProviderRepository_ @@ -561,7 +564,7 @@ class RegionService extends BaseService { * @return {Promise} the result of the update operation */ async removePaymentProvider(regionId, providerId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepo = manager.getCustomRepository(this.regionRepository_) const region = await this.retrieve(regionId, { @@ -595,7 +598,7 @@ class RegionService extends BaseService { * @return {Promise} the result of the update operation */ async removeFulfillmentProvider(regionId, providerId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const regionRepo = manager.getCustomRepository(this.regionRepository_) const region = await this.retrieve(regionId, { diff --git a/packages/medusa/src/services/return.js b/packages/medusa/src/services/return.js index 0092e0e5c7..06f92aff45 100644 --- a/packages/medusa/src/services/return.js +++ b/packages/medusa/src/services/return.js @@ -518,6 +518,8 @@ class ReturnService extends BaseService { "returns", "payments", "discounts", + "discounts.rule", + "discounts.rule.valid_for", "refunds", "shipping_methods", "region", diff --git a/packages/medusa/src/services/shipping-option.js b/packages/medusa/src/services/shipping-option.js index c745bfbfb1..ad2cbf8724 100644 --- a/packages/medusa/src/services/shipping-option.js +++ b/packages/medusa/src/services/shipping-option.js @@ -450,7 +450,9 @@ class ShippingOptionService extends BaseService { */ async update(optionId, update) { return this.atomicPhase_(async manager => { - const option = await this.retrieve(optionId) + const option = await this.retrieve(optionId, { + relations: ["requirements"], + }) if ("metadata" in update) { option.metadata = await this.setMetadata_(option, update.metadata) @@ -498,6 +500,20 @@ class ShippingOptionService extends BaseService { acc.push(validated) } + + if (option.requirements) { + const accReqs = acc.map(a => a.id) + const toRemove = option.requirements.filter( + r => !accReqs.includes(r.id) + ) + await Promise.all( + toRemove.map(async req => { + await this.removeRequirement(req.id) + }) + ) + } + + option.requirements = acc } if ("price_type" in update) { @@ -585,28 +601,24 @@ class ShippingOptionService extends BaseService { /** * Removes a requirement from a shipping option - * @param {string} optionId - the shipping option to remove from * @param {string} requirementId - the id of the requirement to remove * @return {Promise} the result of update */ - async removeRequirement(optionId, requirementId) { + async removeRequirement(requirementId) { return this.atomicPhase_(async manager => { - const option = await this.retrieve(optionId, { - relations: "requirements", - }) - const newReqs = option.requirements.map(r => { - if (r.id === requirementId) { - return null - } else { - return r - } - }) + try { + const reqRepo = manager.getCustomRepository(this.requirementRepository_) + const requirement = await reqRepo.findOne({ + where: { id: requirementId }, + }) - option.requirements = newReqs.filter(Boolean) + const result = await reqRepo.softRemove(requirement) - const optionRepo = manager.getCustomRepository(this.optionRepository_) - const result = await optionRepo.save(option) - return result + return result + } catch (error) { + // Delete is idempotent, but we return a promise to allow then-chaining + return Promise.resolve() + } }) } diff --git a/packages/medusa/src/services/swap.js b/packages/medusa/src/services/swap.js index 4c081474aa..4069abf11b 100644 --- a/packages/medusa/src/services/swap.js +++ b/packages/medusa/src/services/swap.js @@ -537,6 +537,7 @@ class SwapService extends BaseService { "order.swaps", "order.swaps.additional_items", "order.discounts", + "order.discounts.rule", "additional_items", "return_order", "return_order.items", diff --git a/packages/medusa/src/services/transaction.js b/packages/medusa/src/services/transaction.js index 5af02ae3b0..7f57599ec6 100644 --- a/packages/medusa/src/services/transaction.js +++ b/packages/medusa/src/services/transaction.js @@ -1,9 +1,8 @@ import { BaseService } from "medusa-interfaces" import mongoose from "mongoose" -import _ from "lodash" class TransactionService extends BaseService { - constructor({}) { + constructor() { super() } diff --git a/packages/medusa/src/services/user.js b/packages/medusa/src/services/user.js index 52a55f6e01..6a254f4dd2 100644 --- a/packages/medusa/src/services/user.js +++ b/packages/medusa/src/services/user.js @@ -6,7 +6,7 @@ import { BaseService } from "medusa-interfaces" /** * Provides layer to manipulate users. - * @implements BaseService + * @extends BaseService */ class UserService extends BaseService { static Events = { @@ -48,9 +48,7 @@ class UserService extends BaseService { * @return {string} the validated email */ validateEmail_(email) { - const schema = Validator.string() - .email() - .required() + const schema = Validator.string().email().required() const { value, error } = schema.validate(email) if (error) { throw new MedusaError( @@ -75,6 +73,7 @@ class UserService extends BaseService { * Gets a user by id. * Throws in case of DB Error and if user was not found. * @param {string} userId - the id of the user to get. + * @param {Object} config - query configs * @return {Promise} the user document. */ async retrieve(userId, config = {}) { @@ -99,6 +98,7 @@ class UserService extends BaseService { * Gets a user by api token. * Throws in case of DB Error and if user was not found. * @param {string} apiToken - the token of the user to get. + * @param {string[]} relations - relations to include with the user * @return {Promise} the user document. */ async retrieveByApiToken(apiToken, relations = []) { @@ -123,6 +123,7 @@ class UserService extends BaseService { * Gets a user by email. * Throws in case of DB Error and if user was not found. * @param {string} email - the email of the user to get. + * @param {Object} config - query config * @return {Promise} the user document. */ async retrieveByEmail(email, config = {}) { @@ -144,7 +145,7 @@ class UserService extends BaseService { /** * Hashes a password * @param {string} password - the value to hash - * @return hashed password + * @return {string} hashed password */ async hashPassword_(password) { const buf = await Scrypt.kdf(password, { logN: 1, r: 1, p: 1 }) @@ -155,10 +156,11 @@ class UserService extends BaseService { * Creates a user with username being validated. * Fails if email is not a valid format. * @param {object} user - the user to create + * @param {string} password - user's password to hash * @return {Promise} the result of create */ async create(user, password) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const userRepo = manager.getCustomRepository(this.userRepository_) const validatedEmail = this.validateEmail_(user.email) @@ -177,11 +179,12 @@ class UserService extends BaseService { /** * Updates a user. - * @param {object} user - the user to create + * @param {object} userId - id of the user to update + * @param {object} update - the values to be updated on the user * @return {Promise} the result of create */ async update(userId, update) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const userRepo = manager.getCustomRepository(this.userRepository_) const validatedId = this.validateId_(userId) @@ -222,13 +225,15 @@ class UserService extends BaseService { * @return {Promise} the result of the delete operation. */ async delete(userId) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const userRepo = manager.getCustomRepository(this.userRepository_) // Should not fail, if user does not exist, since delete is idempotent const user = await userRepo.findOne({ where: { id: userId } }) - if (!user) return Promise.resolve() + if (!user) { + return Promise.resolve() + } await userRepo.softRemove(user) @@ -242,10 +247,10 @@ class UserService extends BaseService { * password does not work. * @param {string} userId - the userId to set password for * @param {string} password - the old password to set - * @returns {Promise} the result of the update operation + * @return {Promise} the result of the update operation */ async setPassword_(userId, password) { - return this.atomicPhase_(async manager => { + return this.atomicPhase_(async (manager) => { const userRepo = manager.getCustomRepository(this.userRepository_) const user = await this.retrieve(userId) @@ -270,8 +275,8 @@ class UserService extends BaseService { * The token will be signed with the users current password hash as a secret * a long side a payload with userId and the expiry time for the token, which * is always 15 minutes. - * @param {User} user - the user to reset password for - * @returns {string} the generated JSON web token + * @param {string} userId - the id of the user to reset password for + * @return {string} the generated JSON web token */ async generateResetPasswordToken(userId) { const user = await this.retrieve(userId) diff --git a/packages/medusa/src/subscribers/order.js b/packages/medusa/src/subscribers/order.js index 01ac3c9cca..ec49737a47 100644 --- a/packages/medusa/src/subscribers/order.js +++ b/packages/medusa/src/subscribers/order.js @@ -32,7 +32,13 @@ class OrderSubscriber { handleOrderPlaced = async data => { const order = await this.orderService_.retrieve(data.id, { select: ["subtotal"], - relations: ["discounts", "items", "gift_cards"], + relations: [ + "discounts", + "discounts.rule", + "discounts.rule.valid_for", + "items", + "gift_cards", + ], }) await Promise.all( diff --git a/scripts/on-lint-error.js b/scripts/on-lint-error.js new file mode 100644 index 0000000000..8d77e79021 --- /dev/null +++ b/scripts/on-lint-error.js @@ -0,0 +1,11 @@ +console.log(` + +Oops! Medusa noticed some lint or style warnings in the code for this +commit. Your changes have been committed, but you should fix the warnings before +creating a pull request. +Use 'npm run lint' to manually re-run these checks. You can also disable these +checks: +- for a single commit: git commit --no-verify +- for all future commits: npm run hooks:uninstall + +`) diff --git a/www/docs/.eslintrc b/www/docs/.eslintrc deleted file mode 100644 index 2a889697f0..0000000000 --- a/www/docs/.eslintrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "plugins": ["prettier"], - "extends": ["prettier"], - "rules": { - "prettier/prettier": "error", - "semi": "error", - "no-unused-expressions": "true" - } -} diff --git a/www/docs/.prettierrc b/www/docs/.prettierrc deleted file mode 100644 index 48e90e8d40..0000000000 --- a/www/docs/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "endOfLine": "lf", - "semi": false, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5" -} diff --git a/www/docs/sidebars.js b/www/docs/sidebars.js index b8fadb9968..c8b65336cc 100644 --- a/www/docs/sidebars.js +++ b/www/docs/sidebars.js @@ -69,6 +69,10 @@ module.exports = { type: "doc", id: "how-to/headless-ecommerce-store-with-gatsby-contentful-medusa", }, + { + type: "doc", + id: "how-to/making-your-store-more-powerful-with-contentful", + }, ], }, { @@ -79,6 +83,14 @@ module.exports = { type: "doc", id: "how-to/create-medusa-app", }, + { + type: "doc", + id: "how-to/uploading-images-to-spaces", + }, + { + type: "doc", + id: "how-to/uploading-images-to-s3", + }, ], }, { @@ -99,5 +111,19 @@ module.exports = { }, ], }, + { + type: "category", + label: "Deploy", + items: [ + { + type: "doc", + id: "how-to/deploying-on-heroku", + }, + { + type: "doc", + id: "how-to/deploying-admin-on-netlify", + }, + ], + }, ], } diff --git a/www/reference/.prettierrc b/www/reference/.prettierrc deleted file mode 100644 index 33d2cfa3f6..0000000000 --- a/www/reference/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "arrowParens": "avoid", - "semi": false -} diff --git a/yarn.lock b/yarn.lock index 2c3b66f84c..7f5b3d76d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -275,6 +275,15 @@ semver "^6.3.0" source-map "^0.5.0" +"@babel/eslint-parser@^7.15.8": + version "7.15.8" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.15.8.tgz#8988660b59d739500b67d0585fd4daca218d9f11" + integrity sha512-fYP7QFngCvgxjUuw8O057SVH5jCXsbFFOoE77CFDcvzwBVgTOkMD/L4mIC5Ud1xf8chK/no2fRbSSn1wvNmKuQ== + dependencies: + eslint-scope "^5.1.1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.0" + "@babel/generator@^7.11.5": version "7.11.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" @@ -2985,6 +2994,21 @@ resolve-pathname "^3.0.0" tslib "^2.2.0" +"@eslint/eslintrc@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.2.tgz#6044884f7f93c4ecc2d1694c7486cce91ef8f746" + integrity sha512-x1ZXdEFsvTcnbTZgqcWUL9w2ybgZCw/qbKTPQnab+XnYA2bMQpJCh+/bBzCRfDJaJdlrrQlOk49jNtru9gL/6Q== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.0.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.yarnpkg.com/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -3071,6 +3095,20 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@humanwhocodes/config-array@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.6.0.tgz#b5621fdb3b32309d2d16575456cbc277fa8f021a" + integrity sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" + integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -4649,6 +4687,76 @@ 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.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.0.0.tgz#ecc7cc69d1e6f342beb6ea9cf9fbc02c97a212ac" + integrity sha512-T6V6fCD2U0YesOedvydTnrNtsC8E+c2QzpawIpDdlaObX0OX5dLo7tLU5c64FhTZvA1Xrdim+cXDI7NPsVx8Cg== + dependencies: + "@typescript-eslint/experimental-utils" "5.0.0" + "@typescript-eslint/scope-manager" "5.0.0" + debug "^4.3.1" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.0.0.tgz#c7d7e67443dfb9fd93a5d060fb72c9e9b5638bbc" + integrity sha512-Dnp4dFIsZcPawD6CT1p5NibNUQyGSEz80sULJZkyhyna8AEqArmfwMwJPbmKzWVo4PabqNVzHYlzmcdLQWk+pg== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "5.0.0" + "@typescript-eslint/types" "5.0.0" + "@typescript-eslint/typescript-estree" "5.0.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.0.0.tgz#50d1be2e0def82d73e863cceba74aeeac9973592" + integrity sha512-B6D5rmmQ14I1fdzs71eL3DAuvnPHTY/t7rQABrL9BLnx/H51Un8ox1xqYAchs0/V2trcoyxB1lMJLlrwrJCDgw== + dependencies: + "@typescript-eslint/scope-manager" "5.0.0" + "@typescript-eslint/types" "5.0.0" + "@typescript-eslint/typescript-estree" "5.0.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.0.0.tgz#aea0fb0e2480c1169a02e89d9005ac3f2835713f" + integrity sha512-5RFjdA/ain/MDUHYXdF173btOKncIrLuBmA9s6FJhzDrRAyVSA+70BHg0/MW6TE+UiKVyRtX91XpVS0gVNwVDQ== + dependencies: + "@typescript-eslint/types" "5.0.0" + "@typescript-eslint/visitor-keys" "5.0.0" + +"@typescript-eslint/types@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.0.0.tgz#25d93f6d269b2d25fdc51a0407eb81ccba60eb0f" + integrity sha512-dU/pKBUpehdEqYuvkojmlv0FtHuZnLXFBn16zsDmlFF3LXkOpkAQ2vrKc3BidIIve9EMH2zfTlxqw9XM0fFN5w== + +"@typescript-eslint/typescript-estree@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.0.0.tgz#bc20f413c6e572c7309dbe5fa3be027984952af3" + integrity sha512-V/6w+PPQMhinWKSn+fCiX5jwvd1vRBm7AX7SJQXEGQtwtBvjMPjaU3YTQ1ik2UF1u96X7tsB96HMnulG3eLi9Q== + dependencies: + "@typescript-eslint/types" "5.0.0" + "@typescript-eslint/visitor-keys" "5.0.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.0.0.tgz#b789f7cd105e59bee5c0983a353942a5a48f56df" + integrity sha512-yRyd2++o/IrJdyHuYMxyFyBhU762MRHQ/bAGQeTnN3pGikfh+nEmM61XTqaDH1XDp53afZ+waXrk0ZvenoZ6xw== + dependencies: + "@typescript-eslint/types" "5.0.0" + eslint-visitor-keys "^3.0.0" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -4823,6 +4931,11 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" @@ -4843,6 +4956,11 @@ acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== +acorn@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" + integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== + address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -4904,7 +5022,7 @@ ajv@^5.0.1: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.1.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -4958,6 +5076,11 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -5164,6 +5287,11 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -6106,6 +6234,13 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + 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" @@ -6126,6 +6261,14 @@ cli-progress@^3.4.0: colors "^1.1.2" string-width "^4.2.0" +cli-truncate@2.1.0, cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cli-ux@^5.4.9: version "5.6.3" resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.6.3.tgz#eecdb2e0261171f2b28f2be6b18c490291c3a287" @@ -6306,6 +6449,11 @@ colorette@^1.3.0: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== +colorette@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + colors@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -6361,6 +6509,11 @@ commander@^7.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commander@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.2.0.tgz#37fe2bde301d87d47a53adeff8b5915db1381ca8" + integrity sha512-LLKxDvHeL91/8MIyTAD5BFMNtoIwztGPMiM/7Bl8rIPmHCZXRxmSWr91h57dpOpnQ6jIUqEWdXE/uBYMfiVZDA== + comment-patterns@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/comment-patterns/-/comment-patterns-0.11.0.tgz#de7533d5da9b4241457e53c164cee87b907f0bb0" @@ -6690,6 +6843,17 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + cross-env@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" @@ -6697,7 +6861,7 @@ cross-env@^7.0.2: dependencies: cross-spawn "^7.0.1" -cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3: +cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -7024,7 +7188,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.1.1, debug@^4.3.1: +debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -7102,6 +7266,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -7303,6 +7472,13 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" @@ -7533,6 +7709,13 @@ enhanced-resolve@^5.8.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enquirer@^2.3.5, enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -7667,7 +7850,24 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-scope@5.1.1: +eslint-config-google@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a" + integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw== + +eslint-config-prettier@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + +eslint-plugin-prettier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" + integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -7675,11 +7875,96 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +eslint-scope@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978" + integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" + integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== + +eslint@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.0.0.tgz#2c2d0ac6353755667ac90c9ff4a9c1315e43fcff" + integrity sha512-03spzPzMAO4pElm44m60Nj08nYonPGQXmw6Ceai/S4QK82IgwWO1EXx1s9namKzVlbVu3Jf81hb+N+8+v21/HQ== + dependencies: + "@eslint/eslintrc" "^1.0.2" + "@humanwhocodes/config-array" "^0.6.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^6.0.0" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.0.0" + espree "^9.0.0" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.2.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090" + integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ== + dependencies: + acorn "^8.5.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.0.0" + esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -7692,7 +7977,7 @@ estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0: +estraverse@^5.1.0, estraverse@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== @@ -7794,7 +8079,7 @@ execa@^4.0.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^5.0.0: +execa@^5.0.0, execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== @@ -7943,6 +8228,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-glob@^2.2.6: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -7971,7 +8261,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -8043,6 +8333,13 @@ figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + file-loader@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" @@ -8149,6 +8446,19 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" + integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -8291,6 +8601,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -8486,6 +8801,13 @@ glob-parent@^6.0.0: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -8544,6 +8866,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.6.0, globals@^13.9.0: + version "13.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7" + integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g== + dependencies: + type-fest "^0.20.2" + globalyzer@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" @@ -9149,6 +9478,11 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" +husky@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.2.tgz#21900da0f30199acca43a46c043c4ad84ae88dff" + integrity sha512-8yKEWNX4z2YsofXAMT7KvA1g8p+GxtB1ffV8XtpAEGuXNAbCV5wdNKH+qTpw8SM9fh4aMPDR+yQuKfgnreyZlg== + hyperlinker@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" @@ -9195,12 +9529,12 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^4.0.3: +ignore@^4.0.3, ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.4: +ignore@^5.1.4, ignore@^5.1.8: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== @@ -9225,7 +9559,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.2.1, import-fresh@^3.2.2, import-fresh@^3.3.0: +import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.2.2, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -9627,6 +9961,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" @@ -10405,6 +10746,11 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -10566,6 +10912,14 @@ levenary@^1.1.1: dependencies: leven "^3.1.0" +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -10589,6 +10943,39 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= +lint-staged@^11.2.3: + version "11.2.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-11.2.3.tgz#fc3f4569cc4f46553309dfc1447b8aef69f744fb" + integrity sha512-Tfmhk8O2XFMD25EswHPv+OYhUjsijy5D7liTdxeXvhG2rsadmOLFtyj8lmlfoFFXY8oXWAIOKpoI+lJe1DB1mw== + dependencies: + cli-truncate "2.1.0" + colorette "^1.4.0" + commander "^8.2.0" + cosmiconfig "^7.0.1" + debug "^4.3.2" + enquirer "^2.3.6" + execa "^5.1.1" + listr2 "^3.12.2" + micromatch "^4.0.4" + normalize-path "^3.0.0" + please-upgrade-node "^3.2.0" + string-argv "0.3.1" + stringify-object "3.3.0" + supports-color "8.1.1" + +listr2@^3.12.2: + version "3.12.2" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.12.2.tgz#2d55cc627111603ad4768a9e87c9c7bb9b49997e" + integrity sha512-64xC2CJ/As/xgVI3wbhlPWVPx0wfTqbUAkpb7bjDi0thSWMqrf07UFhrfsGoo8YSXmF049Rp9C0cjLC8rZxK9A== + dependencies: + cli-truncate "^2.1.0" + colorette "^1.4.0" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.6.7" + through "^2.3.8" + wrap-ansi "^7.0.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -10817,6 +11204,16 @@ lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + loglevel@^1.6.8: version "1.7.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" @@ -12049,6 +12446,18 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + 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.3" + original@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" @@ -12610,6 +13019,13 @@ pkg-up@3.1.0: dependencies: find-up "^3.0.0" +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -12988,6 +13404,11 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -12998,6 +13419,13 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + prettier@^2.1.1: version "2.3.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" @@ -13048,6 +13476,11 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -13658,6 +14091,11 @@ regexp.prototype.flags@^1.2.0: call-bind "^1.0.2" define-properties "^1.1.3" +regexpp@^3.1.0, regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + regexpu-core@^4.7.0, regexpu-core@^4.7.1: version "4.7.1" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" @@ -13941,6 +14379,14 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -14076,7 +14522,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.4.0, rxjs@^6.6.3: +rxjs@^6.4.0, rxjs@^6.6.3, rxjs@^6.6.7: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -14198,6 +14644,11 @@ selfsigned@^1.10.8: dependencies: node-forge "^0.10.0" +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -14220,7 +14671,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -14467,6 +14918,24 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + sliced@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" @@ -14791,6 +15260,11 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= +string-argv@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + string-hash@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" @@ -14869,7 +15343,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -stringify-object@^3.3.0: +stringify-object@3.3.0, stringify-object@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== @@ -14957,6 +15431,11 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -14991,6 +15470,13 @@ stylehacks@^5.0.1: browserslist "^4.16.0" postcss-selector-parser "^6.0.4" +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -15017,13 +15503,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0, supports-color@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-hyperlinks@^2.0.0, supports-hyperlinks@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" @@ -15256,7 +15735,7 @@ through2@^4.0.0: dependencies: readable-stream "3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -15424,7 +15903,7 @@ tslib@2.0.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== -tslib@^1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -15439,6 +15918,13 @@ tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -15451,6 +15937,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -15875,6 +16368,11 @@ uuid@^8.3.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + v8-to-istanbul@^7.0.0: version "7.1.2" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" @@ -16272,7 +16770,7 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" -word-wrap@~1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==