diff --git a/.editorconfig b/.editorconfig index 9d08a1a828..c46bceae19 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,11 @@ indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true + +[*.{js,jsx,ts,tsx,vue}] +indent_style = space +indent_size = 2 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 150 diff --git a/.env b/.env deleted file mode 100644 index 9b31c9a66c..0000000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -COMPOSE_PROJECT_NAME=vue-storefront diff --git a/.eslintignore b/.eslintignore index 0779adf9c2..75702544c6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,7 @@ -core/build/*.js -node_modules -packages/module/*.js \ No newline at end of file +**/node_modules/**/* +**/lib/* +**/GraphQL.ts +!**/.vuepress/**/* +packages/boilerplate/composables/nuxt/plugin.js +packages/commercetools/composables/nuxt/plugin.js +packages/core/cache/nuxt/plugin.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 0debca6290..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,71 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, jest: true }, - globals: { fetchMock: true }, - parser: 'vue-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - ecmaVersion: 8, - sourceType: 'module' - }, - // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style - extends: [ - 'plugin:vue/recommended', - 'standard', - 'plugin:@typescript-eslint/recommended' - ], - plugins: ['vue', 'vue-storefront', '@typescript-eslint'], - // add your custom rules here - rules: { - '@typescript-eslint/no-var-requires': 1, - '@typescript-eslint/indent': ['error', 2], - '@typescript-eslint/camelcase': 0, - semi: 'off', - '@typescript-eslint/semi': 0, - '@typescript-eslint/member-delimiter-style': ['error', { 'multiline': { 'delimiter': 'comma', 'requireLast': false }, 'singleline': { 'delimiter': 'comma' } }], - '@typescript-eslint/no-empty-interface': 1, - '@typescript-eslint/no-use-before-define': 1, - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/class-name-casing': 1, - '@typescript-eslint/no-unused-vars': 0, - '@typescript-eslint/explicit-function-return-type': 0, - 'handle-callback-err': 1, - 'prefer-promise-reject-errors': 0, - 'import/no-duplicates': 1, - 'vue/return-in-computed-property': 1, - 'vue/no-use-v-if-with-v-for': 0, - 'vue/no-unused-components': 1, - 'vue/no-v-html': 0, - 'vue/no-template-shadow': 2, - /* max attributes-per-line and order-in-components - ** we should use this later, when eslint-plugin-vue will support auto fixing this - */ - 'vue/max-attributes-per-line': 0, - 'vue/order-in-components': 0, - 'vue/attributes-order': 0, - // less restricted v-for -> v-if rules - 'vue/no-confusing-v-for-v-if': 0, - // allow paren-less arrow functions - 'arrow-parens': 0, - 'prefer-arrow-callback': 1, - // allow async-await - 'generator-star-spacing': 0, - // allow debugger during development - 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, - 'no-restricted-imports': [2, { paths: ['lodash-es'] }], - 'vue-storefront/no-corecomponent-import': 'error', - 'vue-storefront/no-corecomponent': 'error', - 'vue-storefront/no-corepage-import': 'error', - 'vue-storefront/no-corepage': 'error' - }, - overrides: [ - { - // @todo check if this is closed https://github.com/typescript-eslint/typescript-eslint/issues/342 - // This is an issue with interfaces so we need to wait until it fixed. - files: ['core/**/*.ts'], - rules: { - 'no-undef': 1 - } - } - ] -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..486733f360 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,138 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "vue-eslint-parser", + "parserOptions": { + "parser": "@typescript-eslint/parser", + "ecmaVersion": 2017, + "sourceType": "module" + }, + "plugins": ["vue", "@typescript-eslint"], + "env": { + "browser": true, + "commonjs": true, + "node": true, + "jest": true + }, + "globals": { + "Promise": true, + "process": true, + "console": true, + "Set": true, + "Intl": true + }, + "rules": { + "eqeqeq": 2, + "no-use-before-define": [ + 2, + { + "functions": false + } + ], + "no-undef": 2, + "no-unused-vars": 2, + "brace-style": 2, + "no-mixed-spaces-and-tabs": 2, + "key-spacing": 2, + "comma-spacing": 2, + "array-bracket-spacing": 2, + "space-in-parens": 2, + "no-trailing-spaces": 2, + "comma-dangle": 2, + "comma-style": 2, + "space-infix-ops": 2, + "keyword-spacing": 2, + "space-before-blocks": 2, + "spaced-comment": 2, + "no-multiple-empty-lines": [ + 2, + { + "max": 1 + } + ], + "complexity": 2, + "max-depth": [ + 2, + { + "max": 3 + } + ], + "default-case": 0, + "dot-notation": 2, + "no-alert": 2, + "no-empty-function": 0, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-implicit-coercion": 2, + "no-multi-spaces": 2, + "no-useless-return": 2, + "no-console": 0, + "global-require": 1, + "camelcase": 0, + "computed-property-spacing": 2, + "consistent-this": 2, + "func-call-spacing": 2, + "func-names": 0, + "func-name-matching": 2, + "func-style": [ + 2, + "declaration", + { + "allowArrowFunctions": true + } + ], + "indent": [ + 2, + 2, + { + "SwitchCase": 1 + } + ], + "line-comment-position": 2, + "linebreak-style": 2, + "lines-around-comment": 2, + "max-statements-per-line": 2, + "no-lonely-if": 2, + "prefer-const": 2, + "no-mixed-operators": 2, + "no-multi-assign": 2, + "no-unneeded-ternary": 2, + "object-property-newline": [ + 2, + { + "allowAllPropertiesOnSameLine": true + } + ], + "operator-linebreak": 2, + "quote-props": [2, "as-needed"], + "quotes": [2, "single"], + "semi": 2, + "semi-spacing": 2, + "one-var": [2, "never"], + "eol-last": 2, + "newline-after-var": 0, + "no-var": 2, + "@typescript-eslint/no-empty-function": 0, + "no-case-declarations": 0, + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/no-unused-vars": 2, + "@typescript-eslint/ban-ts-ignore": 0, + "@typescript-eslint/explicit-module-boundary-types": 0 + }, + "overrides": [ + { + "files": "*.ts", + "rules": { + "no-undef": "off", + "no-unused-vars": "off" + } + } + ] +} diff --git a/.github/ISSUE_TEMPLATE/1.bug-report.yml b/.github/ISSUE_TEMPLATE/1.bug-report.yml new file mode 100644 index 0000000000..5bc7ad62eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1.bug-report.yml @@ -0,0 +1,83 @@ +name: "🐛 Bug report" +description: | + Create a report to help us improve +labels: + - bug + - triage-needed +title: '[Bug]: ' +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report, please make sure to [search for existing issues](https://github.com/vuestorefront/vue-storefront/issues) before filing a new one! + - type: textarea + attributes: + label: Describe the Bug + description: Can you provide a clear and concise description of the bug + validations: + required: true + - type: textarea + id: currentbehavior + attributes: + label: Current behavior + placeholder: Describe the current behavior pointing exactly why it's not working as intended + - type: textarea + id: expectedbehavior + attributes: + label: Expected behavior + placeholder: Describe what the desired behavior should be + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + placeholder: Please provide the steps to reproduce and if possible a minimal reproducible example of the problem + - type: input + attributes: + label: What version of Vue Storefront are you using? + description: 'For example: 2.3.4' + validations: + required: true + - type: input + attributes: + label: What version of Node.js are you using? + description: 'For example: 12.0.0' + validations: + required: true + - type: input + attributes: + label: What browser (and version) are you using? + description: 'For example: Chrome, Safari' + validations: + required: true + - type: input + attributes: + label: What operating system (and version) are you using? + description: 'For example: macOS, Windows' + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: fixthebug + attributes: + label: Able to fix / change the documentation? + description: Can you handle this change and create a Pull Request? + options: + - label: 'Yes' + required: false + - label: 'No' + required: false + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/vuestorefront/vue-storefront/blob/master/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/2.documentation-issue.yml b/.github/ISSUE_TEMPLATE/2.documentation-issue.yml new file mode 100644 index 0000000000..1b3ba7313f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2.documentation-issue.yml @@ -0,0 +1,31 @@ +name: "📚 Documentation Issue" +description: | + Report issues in our documentation +labels: + - documentation + - triage-needed +body: + - type: textarea + attributes: + label: Provide a description of requested docs changes + placeholder: Briefly describe which document needs to be corrected. + validations: + required: true + - type: checkboxes + id: fixthebug + attributes: + label: Able to fix / change the documentation? + description: Can you handle this change and create a Pull Request? + options: + - label: 'Yes' + required: false + - label: 'No' + required: false + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/vuestorefront/vue-storefront/blob/master/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/3.feature-request.yml b/.github/ISSUE_TEMPLATE/3.feature-request.yml new file mode 100644 index 0000000000..30cf58d1dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3.feature-request.yml @@ -0,0 +1,46 @@ +name: "🚀 Feature Request" +description: Sugest a new feature request or improvement on the project +title: '[Feature]: ' +labels: + - feature + - enhancement + - triage-needed + +body: + - type: markdown + attributes: + value: | + Please, provide as many information, and knowledge so the feature can be correctly designed and developed. + - type: textarea + id: suggestion + attributes: + label: How the project can be improved? + description: What is the motivation for adding / enhancing this feature? Can you describe a concrete use case for this feature or why one of current ones should be enhanced. + placeholder: Describe the motivation or the concrete use case + validations: + required: true + - type: textarea + id: acceptcriterea + attributes: + label: What are the acceptance criteria? + description: List the acceptance criteria for this task in a form of a list. + value: '- [ ]' + - type: textarea + id: additionalinfo + attributes: + label: Additional information + description: If you think that any additional information would be useful please provide them here. + - type: input + attributes: + label: What version of Vue Storefront this feature can be implemented? + description: 'For example: 2.3.4' + validations: + required: true + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/vuestorefront/vue-storefront/blob/master/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/4.question.yml b/.github/ISSUE_TEMPLATE/4.question.yml new file mode 100644 index 0000000000..a760ab0344 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4.question.yml @@ -0,0 +1,28 @@ +name: "❓ Question / Basic Issue" +description: | + Do you have a question on the implementation or a basic issue +labels: + - triage-needed +body: + - type: markdown + attributes: + value: If you are not sure how something works or want discuss something just describe your doubts. + - type: textarea + attributes: + label: What is your question / Please describe your issue + validations: + required: true + - type: input + attributes: + label: What version of Vue Storefront are you using? + description: 'For example: 2.3.4' + validations: + required: true + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/vuestorefront/vue-storefront/blob/master/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/RFC.md b/.github/ISSUE_TEMPLATE/RFC.md deleted file mode 100644 index bd6ca4670d..0000000000 --- a/.github/ISSUE_TEMPLATE/RFC.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -name: RFC -about: Request for comments -labels: RFC ---- - -# Summary -[summary]: #summary - -One paragraph explanation of the feature. - -# Motivation -[motivation]: #motivation - -Why are we doing this? What use cases does it support? What is the expected outcome? - -# Guide-level explanation -[guide-level-explanation]: #guide-level-explanation - -Explain the proposal as if it was already included into the project and you were teaching it to another Vue Storefront developer. That generally means: - -- Introducing new named concepts. -- Explaining the feature largely in terms of examples. -- Explaining how Vue Storefront developers should *think* about the feature, and how it should impact the way they use Vue Storefront. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. - -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation - -This is the technical portion of the RFC. Explain the design in sufficient detail that: - -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. - -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. - -# Drawbacks -[drawbacks]: #drawbacks - -Why should we *not* do this? - -# Rationale and alternatives -[rationale-and-alternatives]: #rationale-and-alternatives - -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? - -# Prior art -[prior-art]: #prior-art - -Discuss prior art, both the good and the bad, in relation to this proposal. -A few examples of what this can include are: - -- For feature proposals: Does this feature exist in other e-commerce platforms and what experience have their community had? -- For community proposals: Is this done by some other community and what were their experiences with it? -- For other teams: What lessons can we learn from what other communities have done here? - -This section is intended to encourage you as an author to think about the lessons from other platforms, provide readers of your RFC with a fuller picture. -If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other platforms. - - -# Unresolved questions -[unresolved-questions]: #unresolved-questions - -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? - -# Future possibilities -[future-possibilities]: #future-possibilities - -Think about what the natural extension and evolution of your proposal would -be and how it would affect the language and project as a whole in a holistic -way. Try to use this section as a tool to more fully consider all possible -interactions with the project and language in your proposal. -Also consider how the this all fits into the roadmap for the project -and of the relevant sub-team. - - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 3bd21ff784..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -labels: bug - ---- - -## Current behavior - - - - -## Expected behavior - - - - -## Steps to reproduce the issue - - - - -## Version of Vue Storefront - -- [ ] Vue Storefront -- [ ] Vue Storefront Next - - -## Can you handle fixing this bug by yourself? - -- [ ] YES -- [ ] NO - -## Which [Release Cycle](https://docs.vuestorefront.io/guide/basics/release-cycle.html) state this refers to? Info for developer. (doesn't apply to Next) -Pick one option. - -- [ ] This is a bug report for test version on https://test.storefrontcloud.io - In this case Developer should create branch from `develop` branch and create Pull Request `2. Feature / Improvement` back to `develop`. -- [ ] This is a bug report for current Release Candidate version on https://next.storefrontcloud.io - In this case Developer should create branch from `release` branch and create Pull Request `3. Stabilisation fix` back to `release`. -- [ ] This is a bug report for current Stable version on https://demo.storefrontcloud.io and should be placed in next stable version hotfix - In this case Developer should create branch from `hotfix` or `master` branch and create Pull Request `4. Hotfix` back to `hotfix`. - -## Environment details - -- Browser: -- OS: -- Node: -- Code Version: - -## Additional information - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..64ba95063c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Documentation + url: https://docs.vuestorefront.io/ + about: Find more information about the project and how to implement in our documentation. + - name: Discord Chat + url: https://discord.vuestorefront.io/ + about: Ask questions and discuss with other Vue Storefront users in real time. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 0d12ccb9da..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -labels: feature request - ---- - -## What is the motivation for adding / enhancing this feature? - - - -## What are the acceptance criteria - - -- [ ] ... - -## Version of Vue Storefront - -- [ ] Vue Storefront -- [ ] Vue Storefront Next - -## Can you complete this feature request by yourself? - -- [ ] YES -- [ ] NO - -## Additional information - - diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 8d92170361..0000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Question -about: If you are not sure how something works or want discuss something just describe your doubts. -labels: question ---- - -## Prerequisites - -Please answer the following questions for yourself before submitting an issue. -- [ ] I am running the latest version -- [ ] I checked the documentation and found no answer -- [ ] I checked to make sure that this issue has not already been filed - -## Check our forum: -https://forum.vuestorefront.io/ - -# Is there something you don't understand? What is it? Describe it. - - -# Can't find what you're looking for? - - - - -## Keep the problem description concise and include: -- [ ] A brief description of the problem, -- [ ] Where the problem is occurring, -- [ ] The length of time the problem has been occurring, -- [ ] The size of the problem. - - -## Additional information - - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9cbcd1ca31..87f637ae12 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,32 +1,57 @@ -### Related Issues - + -closes # - -### Short Description of the PR +## Description +## Related Issue + + + + + -### Screenshots of Visual Changes before/after (if There Are Any) - +## Motivation and Context + -### Pull Request Checklist - -- [ ] I have updated the Changelog ([V1](https://github.com/DivanteLtd/vue-storefront/blob/develop/CHANGELOG.md)) [v2](https://docs-next.vuestorefront.io/contributing/creating-changelog.html) and mentioned all breaking changes in the public API. -- [ ] I have documented all new public APIs and made changes to existing docs mentioning the parts I've changed so they're up to date. -- [ ] I have tested my Pull Request on production build and (to my knowledge) it works without any issues - -- [ ] I have followed [naming conventions](https://github.com/kettanaito/naming-cheatsheet) +## How Has This Been Tested? + + + - +## Screenshots: + -### Upgrade Notes and Changelog -- [x] No upgrade steps required (100% backward compatibility and no breaking changes) -- [ ] I've updated the [Upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/upgrade-notes/README.md) and [Changelog](https://github.com/vuestorefront/vue-storefront/blob/develop/CHANGELOG.md) on how to port existing Vue Storefront sites with this new feature +## Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) +## Checklist: + + +- [ ] I have read the **CONTRIBUTING** document. -### Contribution and Currently Important Rules Acceptance - +#### Changelog +- [ ] I have updated the Changelog ([V1](https://github.com/DivanteLtd/vue-storefront/blob/develop/CHANGELOG.md)) [v2](https://docs-next.vuestorefront.io/contributing/creating-changelog.html) and mentioned all breaking changes in the public API. +- [ ] I have documented all new public APIs and made changes to existing docs mentioning the parts I've changed so they're up to date. + +#### Tests +- [ ] I have written test cases for my code +- [ ] I have tested my Pull Request on production build and (to my knowledge) it works without any issues +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. + +> I tested manually my code, and it works well with both: +- [ ] Default Theme +- [ ] Capybara Theme + +#### Code standards +- [ ] My code follows the code style of this project. + +- [ ] I have followed [naming conventions](https://github.com/kettanaito/naming-cheatsheet) -- [ ] I read and followed [contribution rules](https://github.com/vuestorefront/vue-storefront/blob/master/CONTRIBUTING.md) +#### Docs +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. diff --git a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 2d3c0374f9..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: 1. First PR for Vue Storefront -about: Is it your first PR for Vue Storefront and you're not sure what to pick? No worries, we got you covered! Pick this template. - ---- - -This is my first PR for storefront or i'm not sure which other PR should i choose. - - -### Related issues - - -closes # - -### Short description - - - -### Screenshots of visual changes before/after (if there are any) - - -### Which environment this relates to -Check your case. In case of any doubts please read about [Release Cycle](https://docs.vuestorefront.io/guide/basics/release-cycle.html) - - -### Checklist - -- [ ] No upgrade steps required (100% backward compatibility and no breaking changes) -- [ ] I've updated the Upgrade notes and Changelog - -- [ ] I updated the documentation -- [ ] My code is covered with unit tests - -**IMPORTANT NOTICE** - Remember to update `CHANGELOG.md` with description of your change - -### Contribution and currently important rules acceptance - - -- [ ] I read and followed [contribution rules](https://github.com/vuestorefront/vue-storefront/blob/master/CONTRIBUTING.md) diff --git a/.github/PULL_REQUEST_TEMPLATE/feature-improvement.md b/.github/PULL_REQUEST_TEMPLATE/feature-improvement.md deleted file mode 100644 index 13f088845d..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE/feature-improvement.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: 2. Feature / Improvement -about: New feature or some improvements to current develop version (https://test.storefrontcloud.io). This is for branches created from `develop` branch and should be merged back into `develop`. - ---- - -This is a feature or improvement for current test version on https://test.storefrontcloud.io - -### Related issues - - -closes # - -### Short description and why it's useful - - - - -### Screenshots of visual changes before/after (if there are any) - - - - -### PR status check -This is a feature or improvement PR for current test version. In case of any doubts please read about [Release Cycle](https://docs.vuestorefront.io/guide/basics/release-cycle.html) - -- [ ] I've created this branch from `develop` branch and want to merge it back to `develop` - -### Upgrade Notes and Changelog - -- [x] No upgrade steps required (100% backward compatibility and no breaking changes) -- [ ] I've updated the [Upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/upgrade-notes/README.md) and [Changelog](https://github.com/vuestorefront/vue-storefront/blob/develop/CHANGELOG.md) on how to port existing VS sites with this new feature - -**IMPORTANT NOTICE** - PR needs to have all checks to be merged in (except braking changes check). diff --git a/.github/PULL_REQUEST_TEMPLATE/hotfix.md b/.github/PULL_REQUEST_TEMPLATE/hotfix.md deleted file mode 100644 index ef51c1f1df..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE/hotfix.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: 4. Hotfix -about: Bugfix for current stable version (https://demo.storefrontcloud.io). This is for branches created from `hotfix` or `master` branch and should be merged back into `hotfix`. - ---- - -This is a bugfix for current stable version on https://demo.storefrontcloud.io - -### Related issues - - -closes # - -### Short description and why it's useful - - - - -### Screenshots of visual changes before/after (if there are any) - - - - -### PR status check -This is hotfix PR, it means that it contain fix for current stable version. In case of any doubts please read about [Release Cycle](https://docs.vuestorefront.io/guide/basics/release-cycle.html) - -- [ ] I've created this branch from `master` or `hotfix` branch -- [ ] I want to merge this PR into `hotfix` branch - -### Upgrade Notes and Changelog - -- [x] No upgrade steps required (100% backward compatibility and no breaking changes) -- [ ] I've updated the [Upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/upgrade-notes/README.md) and [Changelog](https://github.com/vuestorefront/vue-storefront/blob/develop/CHANGELOG.md) on how to port existing VS sites with this new feature - -**IMPORTANT NOTICE** - PR needs to have all checks to be merged in. diff --git a/.github/PULL_REQUEST_TEMPLATE/stabilisation-fix.md b/.github/PULL_REQUEST_TEMPLATE/stabilisation-fix.md deleted file mode 100644 index 666db6de76..0000000000 --- a/.github/PULL_REQUEST_TEMPLATE/stabilisation-fix.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: 3. Stabilisation fix -about: Fix for current Release Candidate version (https://next.storefrontcloud.io). This is for branches created from `release` branch and should be merged back into `release`. - ---- - -This is a stabilisation fix for current Release Candidate version on https://next.storefrontcloud.io - -### Related issues - - -closes # - -### Short description and why it's useful - - - - -### Screenshots of visual changes before/after (if there are any) - - - - -### PR status check -This is Release Candidate stabilisation PR, it means that it contain fix for current RC version. In case of any doubts please read about [Release Cycle](https://docs.vuestorefront.io/guide/basics/release-cycle.html) - -- [ ] I've created this branch from `release` branch -- [ ] I want to merge this PR into `release` branch - -### Upgrade Notes and Changelog - -- [x] No upgrade steps required (100% backward compatibility and no breaking changes) -- [ ] I've updated the [Upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/upgrade-notes/README.md) and [Changelog](https://github.com/vuestorefront/vue-storefront/blob/develop/CHANGELOG.md) on how to port existing VS sites with this new feature - -**IMPORTANT NOTICE** - PR needs to have all checks to be merged in. diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000000..8b9116cf70 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,23 @@ +# Add 'Next' label to any change to packages files +Next: +- true + +# Add 'core' label to any change to packages/core files within the source dir EXCEPT for the docs sub-folder +core: +- any: ['packages/core/**/*', '!packages/core/docs/**/*'] + +# Add 'commercetools' label to any change to packages/commercetools files +commercetools: +- packages/commercetools/**/* + +# Add 'boilerplate' label to any change to packages/boilerplate files +boilerplate: +- packages/boilerplate/**/* + +# Add 'docs' label to any change to packages/core/docs files EXCEPT for the changelog +docs: +- any: ['packages/core/docs/**/*', '!packages/core/docs/changelog/**/*'] + +# Add 'ci' label to any change to continuous integration files inside .github folder +ci: +- .github/**/* diff --git a/.github/semantic.yml b/.github/semantic.yml new file mode 100644 index 0000000000..1f65fc4555 --- /dev/null +++ b/.github/semantic.yml @@ -0,0 +1,24 @@ +titleOnly: true +scopes: + - core + - core-theme + - docs + - commercetools + - commercetools-theme + - boilerplate + - composables + - ci +types: + - feat + - fix + - improvement + - docs + - style + - refactor + - perf + - test + - build + - ci + - chore + - revert + - rfc diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000..0ef631e003 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,66 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +# daysUntilStale: 60 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +# daysUntilClose: 7 + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +# exemptLabels: +# - backlog + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: wontfix + +# Comment to post when marking as stale. Set to `false` to disable +# markComment: > +# This issue has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +pulls: + daysUntilStale: 15 + daysUntilClose: 7 + markComment: > + This pull request has been automatically marked as stale. + It will be closed if no further activity occurs. + Thank you for your contributions. + +issues: + daysUntilStale: 30 + daysUntilClose: 7 + markComment: > + This issue has been automatically marked as stale. + It will be closed if no further activity occurs. + Thank you for your contributions. + exemptLabels: + - backlog diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000000..86cfce3815 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,78 @@ +name: Deploy Docs +on: + push: + branches: + - release/next + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v1 + - shell: bash + run: | + echo "1.0.`date +%s`" > version.txt + - name: Upload version artifact + uses: actions/upload-artifact@v2 + with: + name: version + path: version.txt + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::$(cat version.txt) + - name: Build and publish docker image + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: docs-storefrontcloud-io/v2:${{ steps.get_version.outputs.VERSION }} + registry: registry.storefrontcloud.io + username: ${{ secrets.DOCS_CLOUD_USERNAME }} + password: ${{ secrets.DOCS_CLOUD_PASSWORD }} + workdir: . + dockerfile: ./packages/core/docs/Dockerfile + buildoptions: "--compress" + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - name: Download version artifact + uses: actions/download-artifact@v2 + with: + name: version + - name: Get the version + id: get_version + run: echo ::set-output name=VERSION::$(cat version.txt) + - uses: chrnorm/deployment-action@releases/v1 + name: Create GitHub deployment + id: deployment + with: + token: "${{ github.token }}" + target_url: https://docs.europe-west1.gcp.storefrontcloud.io/v2 + environment: production + initial_status: in_progress + - name: Deploy on docs.europe-west1.gcp.storefrontcloud.io/v2 + run: | + if curl -s -H 'X-User-Id: ${{ secrets.DOCS_CLOUD_USERNAME }}' -H 'X-Api-Key: ${{ secrets.DOCS_CLOUD_PASSWORD }}' -H 'Content-Type: application/json' -X POST -d '{"code":"docs","region":"europe-west1.gcp","additionalApps":{"apps":[{"name":"docs-v2","tag":"${{ steps.get_version.outputs.VERSION }}","image":"registry.storefrontcloud.io/docs-storefrontcloud-io/v2","path":"/v2","port":"80"}]}}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then + echo "Instance updated" + else + echo "Something went wrong during the update process..." + exit 1 + fi + - name: Update deployment status (success) + if: success() + uses: chrnorm/deployment-status@releases/v1 + with: + token: "${{ github.token }}" + target_url: https://docs.europe-west1.gcp.storefrontcloud.io/v2 + state: "success" + description: Congratulations! The deploy is done. + deployment_id: ${{ steps.deployment.outputs.deployment_id }} + - name: Update deployment status (failure) + if: failure() + uses: chrnorm/deployment-status@releases/v1 + with: + token: "${{ github.token }}" + target_url: https://docs.europe-west1.gcp.storefrontcloud.io/v2 + description: Unfortunately, the instance hasn't been updated. + state: "failure" + deployment_id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/deploy-lighthouse.yml b/.github/workflows/deploy-lighthouse.yml new file mode 100644 index 0000000000..8e6b54f6c5 --- /dev/null +++ b/.github/workflows/deploy-lighthouse.yml @@ -0,0 +1,69 @@ +name: Lighthouse Test + +on: + workflow_run: + workflows: + - Deploy to Storefrontcloud + types: + - completed + branches: + - master + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: chrnorm/deployment-action@releases/v1 + name: Create GitHub deployment + id: deployment + with: + token: "${{ github.token }}" + target_url: https://demo-lighthouse.europe-west1.gcp.storefrontcloud.io + environment: production + initial_status: in_progress + - name: Deploy on demo-lighthouse.europe-west1.gcp.storefrontcloud.io + if: github.ref == 'refs/heads/next' + run: | + if curl -s -H 'X-User-Id: ${{ secrets.DOCS_CLOUD_USERNAME }}' -H 'X-Api-Key: ${{ secrets.DOCS_CLOUD_PASSWORD }}' -H 'Content-Type: application/json' -X POST -d '{ + "code":"demo-lighthouse", + "region":"europe-west1.gcp", + "frontContainerVersion":"${{ github.sha }}" + }' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then + echo "Instance updated" + else + echo "Something went wrong during the update process..." + exit 1 + fi + - name: Update deployment status (success) + if: success() + uses: chrnorm/deployment-status@releases/v1 + with: + token: "${{ github.token }}" + target_url: https://demo-lighthouse.europe-west1.gcp.storefrontcloud.io + state: "success" + description: Congratulations! The deploy is done. + deployment_id: ${{ steps.deployment.outputs.deployment_id }} + - name: Update deployment status (failure) + if: failure() + uses: chrnorm/deployment-status@releases/v1 + with: + token: "${{ github.token }}" + target_url: https://demo-lighthouse.europe-west1.gcp.storefrontcloud.io + description: Unfortunately, the instance hasn't been updated. + state: "failure" + deployment_id: ${{ steps.deployment.outputs.deployment_id }} + + lighthouse: + runs-on: ubuntu-latest + needs: deploy + steps: + - uses: actions/checkout@v2 + - name: Audit URLs using Lighthouse + uses: treosh/lighthouse-ci-action@v7 + with: + urls: | + https://demo-lighthouse.europe-west1.gcp.storefrontcloud.io + # budgetPath: ./budget.json # test performance budgets + uploadArtifacts: true # save results as an action artifacts + temporaryPublicStorage: true # upload lighth diff --git a/.github/workflows/deploy-preview-storefrontcloud.yml b/.github/workflows/deploy-preview-storefrontcloud.yml new file mode 100644 index 0000000000..da9cf9a3c9 --- /dev/null +++ b/.github/workflows/deploy-preview-storefrontcloud.yml @@ -0,0 +1,66 @@ +name: Deploy PR Preview on Storefrontcloud +on: + pull_request: + types: [opened, synchronize] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v1 + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: "12.x" + - name: Build and publish docker image + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: vsf-next-demo-storefrontcloud-io/vue-storefront:${{ github.sha }} + registry: registry.storefrontcloud.io + username: ${{ secrets.CLOUD_USERNAME }} + password: ${{ secrets.CLOUD_PASSWORD }} + dockerfile: dev/docker/Dockerfile + buildoptions: "--compress" + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - uses: chrnorm/deployment-action@releases/v1 + name: Create GitHub deployment + id: deployment + with: + token: "${{ github.token }}" + environment: preview + initial_status: in_progress + - name: Deploy on Storefrontcloud.io + id: deploy + uses: storefrontcloud/storefrontcloud-preview-deploy@0.1.0 + with: + token: "${{ github.token }}" + namespace: "vsf-next-demo" + username: ${{ secrets.CLOUD_USERNAME }} + password: ${{ secrets.CLOUD_PASSWORD }} + - name: Comment PR + if: success() + uses: storefrontcloud/storefrontcloud-comment-pr-preview-deploy@master + with: + preview_url: '${{ steps.deploy.outputs.preview_url }}' + token: "${{ github.token }}" + namespace: 'vsf-next-demo' + - name: Update deployment status (success) + if: success() + uses: chrnorm/deployment-status@releases/v1 + with: + token: "${{ github.token }}" + target_url: ${{ steps.deploy.outputs.preview_url }} + state: "success" + description: Congratulations! The deploy is done. + deployment_id: ${{ steps.deployment.outputs.deployment_id }} + - name: Update deployment status (failure) + if: failure() + uses: chrnorm/deployment-status@releases/v1 + with: + token: "${{ github.token }}" + description: Unfortunately, the instance hasn't been updated. + state: "failure" + deployment_id: ${{ steps.deployment.outputs.deployment_id }} \ No newline at end of file diff --git a/.github/workflows/deploy-storefrontcloud.yml b/.github/workflows/deploy-storefrontcloud.yml index ec33530597..9ce4ab77bc 100644 --- a/.github/workflows/deploy-storefrontcloud.yml +++ b/.github/workflows/deploy-storefrontcloud.yml @@ -3,35 +3,23 @@ on: push: branches: - master - - develop - - hotfix/v* + - main + - release/next + - test/next jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v1 - - name: Add theme master - if: github.ref != 'refs/heads/develop' - run: | - git clone --single-branch --branch master https://github.com/DivanteLtd/vsf-default.git ./src/themes/default - - name: Add theme develop - if: github.ref == 'refs/heads/develop' - run: | - git clone --single-branch --branch develop https://github.com/DivanteLtd/vsf-default.git ./src/themes/default - name: Setup node uses: actions/setup-node@v1 with: - node-version: "10.x" - - name: Create config file - run: | - echo '{"server":{"compression":false,"api":"api-search-query","useOutputCacheTagging":true,"useOutputCache":true,"dynamicConfigReload":true,"dynamicConfigInclude":["redis","elasticsearch","api","graphql","cart","products","orders","users","stock","images","mailchimp","mailer","urlModule"]},"api":{"url":"https://demo.storefrontcloud.io"},"elasticsearch":{"host":"/api/catalog"},"entities":{"attribute":{"loadByAttributeMetadata":true}},"urlModule":{"enableMapFallbackUrl":true},"redis":{"host":"redis"},"graphql":{"host":"https://demo.storefrontcloud.io/graphql"},"cart":{"create_endpoint":"https://demo.storefrontcloud.io/api/cart/create?token={{token}}","updateitem_endpoint":"https://demo.storefrontcloud.io/api/cart/update?token={{token}}&cartId={{cartId}}","deleteitem_endpoint":"https://demo.storefrontcloud.io/api/cart/delete?token={{token}}&cartId={{cartId}}","pull_endpoint":"https://demo.storefrontcloud.io/api/cart/pull?token={{token}}&cartId={{cartId}}","totals_endpoint":"https://demo.storefrontcloud.io/api/cart/totals?token={{token}}&cartId={{cartId}}","paymentmethods_endpoint":"https://demo.storefrontcloud.io/api/cart/payment-methods?token={{token}}&cartId={{cartId}}","shippingmethods_endpoint":"https://demo.storefrontcloud.io/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}","shippinginfo_endpoint":"https://demo.storefrontcloud.io/api/cart/shipping-information?token={{token}}&cartId={{cartId}}","collecttotals_endpoint":"https://demo.storefrontcloud.io/api/cart/collect-totals?token={{token}}&cartId={{cartId}}","deletecoupon_endpoint":"https://demo.storefrontcloud.io/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}","applycoupon_endpoint":"https://demo.storefrontcloud.io/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}"},"products":{"endpoint":"https://demo.storefrontcloud.io/api/product"},"orders":{"endpoint":"https://demo.storefrontcloud.io/api/order"},"users":{"endpoint":"https://demo.storefrontcloud.io/api/user","history_endpoint":"https://demo.storefrontcloud.io/api/user/order-history?token={{token}}","resetPassword_endpoint":"https://demo.storefrontcloud.io/api/user/resetPassword","changePassword_endpoint":"https://demo.storefrontcloud.io/api/user/changePassword?token={{token}}","login_endpoint":"https://demo.storefrontcloud.io/api/user/login","create_endpoint":"https://demo.storefrontcloud.io/api/user/create","me_endpoint":"https://demo.storefrontcloud.io/api/user/me?token={{token}}","refresh_endpoint":"https://demo.storefrontcloud.io/api/user/refresh"},"stock":{"endpoint":"https://demo.storefrontcloud.io/api/stock"},"images":{"baseUrl":"https://demo.vuestorefront.io/img/"},"mailchimp":{"endpoint":"https://demo.storefrontcloud.io/api/ext/mailchimp-subscribe/subscribe"},"mailer":{"endpoint":{"send":"https://demo.storefrontcloud.io/api/ext/mail-service/send-email","token":"https://demo.storefrontcloud.io/api/ext/mail-service/get-token"}}}' > config/local-cloud-demo.json - echo '{"server":{"compression":false,"api":"api-search-query","useOutputCacheTagging":true,"useOutputCache":true,"dynamicConfigReload":true,"dynamicConfigInclude":["redis","elasticsearch","api","graphql","cart","products","orders","users","stock","images","mailchimp","mailer","urlModule"]},"api":{"url":"https://demo.storefrontcloud.io"},"elasticsearch":{"host":"/api/catalog"},"entities":{"attribute":{"loadByAttributeMetadata":true}},"urlModule":{"enableMapFallbackUrl":true},"redis":{"host":"redis"},"graphql":{"host":"https://demo.storefrontcloud.io/graphql"},"cart":{"create_endpoint":"https://demo.storefrontcloud.io/api/cart/create?token={{token}}","updateitem_endpoint":"https://demo.storefrontcloud.io/api/cart/update?token={{token}}&cartId={{cartId}}","deleteitem_endpoint":"https://demo.storefrontcloud.io/api/cart/delete?token={{token}}&cartId={{cartId}}","pull_endpoint":"https://demo.storefrontcloud.io/api/cart/pull?token={{token}}&cartId={{cartId}}","totals_endpoint":"https://demo.storefrontcloud.io/api/cart/totals?token={{token}}&cartId={{cartId}}","paymentmethods_endpoint":"https://demo.storefrontcloud.io/api/cart/payment-methods?token={{token}}&cartId={{cartId}}","shippingmethods_endpoint":"https://demo.storefrontcloud.io/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}","shippinginfo_endpoint":"https://demo.storefrontcloud.io/api/cart/shipping-information?token={{token}}&cartId={{cartId}}","collecttotals_endpoint":"https://demo.storefrontcloud.io/api/cart/collect-totals?token={{token}}&cartId={{cartId}}","deletecoupon_endpoint":"https://demo.storefrontcloud.io/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}","applycoupon_endpoint":"https://demo.storefrontcloud.io/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}"},"products":{"endpoint":"https://demo.storefrontcloud.io/api/product"},"orders":{"endpoint":"https://demo.storefrontcloud.io/api/order"},"users":{"endpoint":"https://demo.storefrontcloud.io/api/user","history_endpoint":"https://demo.storefrontcloud.io/api/user/order-history?token={{token}}","resetPassword_endpoint":"https://demo.storefrontcloud.io/api/user/resetPassword","changePassword_endpoint":"https://demo.storefrontcloud.io/api/user/changePassword?token={{token}}","login_endpoint":"https://demo.storefrontcloud.io/api/user/login","create_endpoint":"https://demo.storefrontcloud.io/api/user/create","me_endpoint":"https://demo.storefrontcloud.io/api/user/me?token={{token}}","refresh_endpoint":"https://demo.storefrontcloud.io/api/user/refresh"},"stock":{"endpoint":"https://demo.storefrontcloud.io/api/stock"},"images":{"baseUrl":"https://demo.vuestorefront.io/img/"},"mailchimp":{"endpoint":"https://demo.storefrontcloud.io/api/ext/mailchimp-subscribe/subscribe"},"mailer":{"endpoint":{"send":"https://demo.storefrontcloud.io/api/ext/mail-service/send-email","token":"https://demo.storefrontcloud.io/api/ext/mail-service/get-token"}}}' > config/local-cloud-next.json - echo '{"server":{"compression":false,"api":"api-search-query","useOutputCacheTagging":true,"useOutputCache":true,"dynamicConfigReload":true,"dynamicConfigInclude":["redis","elasticsearch","api","graphql","cart","products","orders","users","stock","images","mailchimp","mailer","urlModule"]},"api":{"url":"https://test.storefrontcloud.io"},"elasticsearch":{"host":"/api/catalog"},"entities":{"attribute":{"loadByAttributeMetadata":true}},"urlModule":{"enableMapFallbackUrl":true},"redis":{"host":"redis"},"graphql":{"host":"https://test.storefrontcloud.io/graphql"},"cart":{"create_endpoint":"https://test.storefrontcloud.io/api/cart/create?token={{token}}","updateitem_endpoint":"https://test.storefrontcloud.io/api/cart/update?token={{token}}&cartId={{cartId}}","deleteitem_endpoint":"https://test.storefrontcloud.io/api/cart/delete?token={{token}}&cartId={{cartId}}","pull_endpoint":"https://test.storefrontcloud.io/api/cart/pull?token={{token}}&cartId={{cartId}}","totals_endpoint":"https://test.storefrontcloud.io/api/cart/totals?token={{token}}&cartId={{cartId}}","paymentmethods_endpoint":"https://test.storefrontcloud.io/api/cart/payment-methods?token={{token}}&cartId={{cartId}}","shippingmethods_endpoint":"https://test.storefrontcloud.io/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}","shippinginfo_endpoint":"https://test.storefrontcloud.io/api/cart/shipping-information?token={{token}}&cartId={{cartId}}","collecttotals_endpoint":"https://test.storefrontcloud.io/api/cart/collect-totals?token={{token}}&cartId={{cartId}}","deletecoupon_endpoint":"https://test.storefrontcloud.io/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}","applycoupon_endpoint":"https://test.storefrontcloud.io/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}"},"products":{"endpoint":"https://test.storefrontcloud.io/api/product"},"orders":{"endpoint":"https://test.storefrontcloud.io/api/order"},"users":{"endpoint":"https://test.storefrontcloud.io/api/user","history_endpoint":"https://test.storefrontcloud.io/api/user/order-history?token={{token}}","resetPassword_endpoint":"https://test.storefrontcloud.io/api/user/resetPassword","changePassword_endpoint":"https://test.storefrontcloud.io/api/user/changePassword?token={{token}}","login_endpoint":"https://test.storefrontcloud.io/api/user/login","create_endpoint":"https://test.storefrontcloud.io/api/user/create","me_endpoint":"https://test.storefrontcloud.io/api/user/me?token={{token}}","refresh_endpoint":"https://test.storefrontcloud.io/api/user/refresh"},"stock":{"endpoint":"https://test.storefrontcloud.io/api/stock"},"images":{"baseUrl":"https://demo.vuestorefront.io/img/"},"mailchimp":{"endpoint":"https://test.storefrontcloud.io/api/ext/mailchimp-subscribe/subscribe"},"mailer":{"endpoint":{"send":"https://test.storefrontcloud.io/api/ext/mail-service/send-email","token":"https://test.storefrontcloud.io/api/ext/mail-service/get-token"}}}' > config/local-cloud-test.json + node-version: "12.x" - name: Build and publish docker image uses: elgohr/Publish-Docker-Github-Action@master with: - name: demo-storefrontcloud-io/vue-storefront:${{ github.sha }} + name: vsf-next-demo-storefrontcloud-io/vue-storefront:${{ github.sha }} registry: registry.storefrontcloud.io username: ${{ secrets.CLOUD_USERNAME }} password: ${{ secrets.CLOUD_PASSWORD }} @@ -46,31 +34,31 @@ jobs: id: deployment with: token: "${{ github.token }}" - target_url: https://demo.storefrontcloud.io + target_url: https://vsf-next-demo.storefrontcloud.io environment: production initial_status: in_progress - - name: Deploy on demo.storefrontcloud.io - if: github.ref == 'refs/heads/master' + - name: Deploy on lovecrafts-demo.storefrontcloud.io + if: github.ref == 'refs/heads/next' run: | - if curl -s -H "X-User-Id: ${{ secrets.DEMO_CLOUD_USERNAME }}" -H "X-Api-Key: ${{ secrets.DEMO_CLOUD_PASSWORD }}" -H 'Content-Type: application/json' -X POST -d '{"code":"demo","region":"europe-west1.gcp","frontContainerVersion":"${{ github.sha }}"}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then + if curl -s -u ${{ secrets.CLOUD_USERNAME }}:${{ secrets.CLOUD_PASSWORD }} -H 'Content-Type: application/json' -X POST -d '{"code":"lovecrafts-demo","frontContainerVersion":"${{ github.sha }}"}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then echo "Instance updated" else echo "Something went wrong during the update process..." exit 1 fi - - name: Deploy on next.storefrontcloud.io - if: contains(github.ref, 'hotfix/v') + - name: Deploy on vsf-next-demo.storefrontcloud.io + if: github.ref == 'refs/heads/release/next' run: | - if curl -s -u ${{ secrets.CLOUD_USERNAME }}:${{ secrets.CLOUD_PASSWORD }} -H 'Content-Type: application/json' -X POST -d '{"code":"next","frontContainerVersion":"${{ github.sha }}"}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then + if curl -s -u ${{ secrets.CLOUD_USERNAME }}:${{ secrets.CLOUD_PASSWORD }} -H 'Content-Type: application/json' -X POST -d '{"code":"vsf-next-demo","frontContainerVersion":"${{ github.sha }}"}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then echo "Instance updated" else echo "Something went wrong during the update process..." exit 1 fi - - name: Deploy on test.storefrontcloud.io - if: github.ref == 'refs/heads/develop' + - name: Deploy on vsf-next-demo.europe-west1.gcp.storefrontcloud.io + if: github.ref == 'refs/heads/test/next' run: | - if curl -s "X-User-Id: ${{ secrets.DEMO_CLOUD_USERNAME }}" -H "X-Api-Key: ${{ secrets.DEMO_CLOUD_PASSWORD }}" -H 'Content-Type: application/json' -X POST -d '{"code":"test","region":"europe-west1.gcp","frontContainerVersion":"${{ github.sha }}"}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then + if curl -s -H 'X-User-Id: ${{ secrets.DOCS_CLOUD_USERNAME }}' -H 'X-Api-Key: ${{ secrets.DOCS_CLOUD_PASSWORD }}' -H 'Content-Type: application/json' -X POST -d '{"code":"vsf-next-demo","region":"europe-west1.gcp","frontContainerVersion":"${{ github.sha }}"}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then echo "Instance updated" else echo "Something went wrong during the update process..." @@ -81,7 +69,7 @@ jobs: uses: chrnorm/deployment-status@releases/v1 with: token: "${{ github.token }}" - target_url: https://demo.storefrontcloud.io + target_url: https://vsf-next-demo.storefrontcloud.io state: "success" description: Congratulations! The deploy is done. deployment_id: ${{ steps.deployment.outputs.deployment_id }} @@ -90,7 +78,7 @@ jobs: uses: chrnorm/deployment-status@releases/v1 with: token: "${{ github.token }}" - target_url: https://demo.storefrontcloud.io + target_url: https://vsf-next-demo.storefrontcloud.io description: Unfortunately, the instance hasn't been updated. state: "failure" deployment_id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/docs-v1-deployment.yml b/.github/workflows/docs-v1-deployment.yml deleted file mode 100644 index 07e3bdc3f2..0000000000 --- a/.github/workflows/docs-v1-deployment.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Deploy Docs -on: - push: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v1 - - name: Build and publish docker image - uses: elgohr/Publish-Docker-Github-Action@master - with: - name: docs-storefrontcloud-io/v1:${{ github.sha }} - registry: registry.storefrontcloud.io - username: ${{ secrets.DOCS_CLOUD_USERNAME }} - password: ${{ secrets.DOCS_CLOUD_PASSWORD }} - workdir: docs - dockerfile: Dockerfile - buildoptions: "--compress" - deploy: - runs-on: ubuntu-latest - needs: build - steps: - - uses: chrnorm/deployment-action@releases/v1 - name: Create GitHub deployment - id: deployment - with: - token: "${{ github.token }}" - target_url: https://docs.europe-west1.gcp.storefrontcloud.io/v1 - environment: production - initial_status: in_progress - - name: Deploy on docs.europe-west1.gcp.storefrontcloud.io/v1 - run: | - if curl -s -H 'X-User-Id: ${{ secrets.DOCS_CLOUD_USERNAME }}' -H 'X-Api-Key: ${{ secrets.DOCS_CLOUD_PASSWORD }}' -H 'Content-Type: application/json' -X POST -d '{"code":"docs","region":"europe-west1.gcp","additionalApps":{"apps":[{"name":"docs-v1","tag":"${{ github.sha }}","image":"registry.storefrontcloud.io/docs-storefrontcloud-io/v1","path":"/v1","port":"80"}]}}' https://farmer.storefrontcloud.io/instances | grep -q '{"code":200,"result":"Instance updated!"}'; then - echo "Instance updated" - else - echo "Something went wrong during the update process..." - exit 1 - fi - - name: Update deployment status (success) - if: success() - uses: chrnorm/deployment-status@releases/v1 - with: - token: "${{ github.token }}" - target_url: https://docs.europe-west1.gcp.storefrontcloud.io/v1 - state: "success" - description: Congratulations! The deploy is done. - deployment_id: ${{ steps.deployment.outputs.deployment_id }} - - name: Update deployment status (failure) - if: failure() - uses: chrnorm/deployment-status@releases/v1 - with: - token: "${{ github.token }}" - target_url: https://docs.europe-west1.gcp.storefrontcloud.io/v1 - description: Unfortunately, the instance hasn't been updated. - state: "failure" - deployment_id: ${{ steps.deployment.outputs.deployment_id }} diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 0000000000..a904b6e0ce --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,19 @@ +# This workflow will triage pull requests and apply a label based on the +# paths that are modified in the pull request. +# +# To use this workflow, you will need to set up a .github/labeler.yml +# file with configuration. For more information, see: +# https://github.com/actions/labeler + +name: Labeler +on: [pull_request] + +jobs: + label: + + runs-on: ubuntu-latest + + steps: + - uses: actions/labeler@master + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/run-e2e-regression.yml b/.github/workflows/run-e2e-regression.yml index 342f4624ac..c98daeb901 100644 --- a/.github/workflows/run-e2e-regression.yml +++ b/.github/workflows/run-e2e-regression.yml @@ -7,13 +7,12 @@ jobs: name: Run E2E Tests runs-on: ubuntu-latest strategy: + fail-fast: false matrix: package: ["ct", "bp"] steps: - name: Checkout code uses: actions/checkout@v2 - with: - ref: next - name: Setup node uses: actions/setup-node@v2 @@ -31,14 +30,17 @@ jobs: - name: Install dependencies run: yarn install - - name: Build tools + - name: Build package run: yarn build:${{ matrix.package }} + env: + NUXT_ENV_E2E: true - name: Run cypress tests uses: cypress-io/github-action@v2.8.2 with: start: yarn dev:${{ matrix.package }} wait-on: 'http://localhost:3000' + wait-on-timeout: 180 command: yarn run test:e2e:${{ matrix.package }}:hl browser: chrome headless: true @@ -50,12 +52,18 @@ jobs: if: ${{ always() }} run: yarn test:e2e:${{ matrix.package }}:generate:report - - name: Upload report artifact + - name: Upload bp report artifact uses: actions/upload-artifact@v2 if: ${{ always() }} with: - name: report - path: "packages/$${{ matrix.package }}/theme/tests/e2e/report" - env: - bp: boilerplate - ct: commercetools + name: report-bp + path: "packages/boilerplate/theme/tests/e2e/report" + if-no-files-found: ignore + + - name: Upload ct report artifact + uses: actions/upload-artifact@v2 + if: ${{ always() }} + with: + name: report-ct + path: "packages/commercetools/theme/tests/e2e/report" + if-no-files-found: ignore diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index ee730efc5e..5cdc5bee8b 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -10,12 +10,12 @@ on: required: false package: description: "Package" - default: "ct" required: true + default: "ct" browser: description: "Browser" - default: "chrome" required: true + default: "chrome" jobs: run_e2e_tests: name: Run E2E Tests @@ -40,14 +40,17 @@ jobs: - name: Install dependencies run: yarn install - - name: Build tools + - name: Build package run: yarn build:${{ github.event.inputs.package }} + env: + NUXT_ENV_E2E: true - name: Run cypress tests uses: cypress-io/github-action@v2.8.2 with: - start: yarn dev:${{ github.event.inputs.package }} + start: yarn start:${{ github.event.inputs.package }} wait-on: 'http://localhost:3000' + wait-on-timeout: 180 command: yarn run test:e2e:${{ github.event.inputs.package }}:hl browser: ${{ github.event.inputs.browser }} headless: true @@ -58,14 +61,20 @@ jobs: - name: Generate report if: ${{ always() }} - run: yarn test:e2e:${{ github.event.inputs.tags_exclude }}:generate:report + run: yarn test:e2e:${{ github.event.inputs.package }}:generate:report - - name: Upload report artifact + - name: Upload bp report artifact + if: ${{ always() }} uses: actions/upload-artifact@v2 + with: + name: report-bp + path: "packages/boilerplate/theme/tests/e2e/report" + if-no-files-found: ignore + + - name: Upload ct report artifact if: ${{ always() }} + uses: actions/upload-artifact@v2 with: - name: report - path: "packages/$${{ github.event.inputs.package }}/theme/tests/e2e/report" - env: - bp: boilerplate - ct: commercetools + name: report-ct + path: "packages/commercetools/theme/tests/e2e/report" + if-no-files-found: ignore diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..f31348e589 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,148 @@ +name: Run tests + +on: + push: + branches: + - master + - main + pull_request: + branches: + - master + - main + +jobs: + prepare_dependencies: + name: Prepare dependencies + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: '12' + + - name: Get cached dependencies + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Install dependencies + run: yarn --network-concurrency 1 --frozen-lockfile + + lint: + name: Lint + needs: prepare_dependencies + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: '12' + + - name: Get cached dependencies + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Run linter + run: yarn lint + + validate_core: + name: Validate core + needs: prepare_dependencies + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: '12' + + - name: Get cached dependencies + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Build core + run: yarn build:core + + - name: Test cache + run: yarn test:cache --coverage + + - name: Test CLI + run: yarn test:cli --coverage + + - name: Test core + run: yarn test:core --coverage + + - name: Test middleware + run: yarn test:core --coverage + + validate_integrations: + name: Validate ${{ matrix.integration }} + needs: validate_core + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + integration: + - boilerplate + - commercetools + # - shopify + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup node + uses: actions/setup-node@v1 + with: + node-version: '12' + + - name: Get cached dependencies + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Build core + run: yarn build:core + + - name: Build middleware + run: yarn build:middleware + + - name: Build api-client + run: cd packages/${{ matrix.integration }}/api-client && yarn build + + - name: Test api-client + run: cd packages/${{ matrix.integration }}/api-client && yarn test --passWithNoTests --coverage + + - name: Build composables + run: cd packages/${{ matrix.integration }}/composables && yarn build + + - name: Test composables + run: cd packages/${{ matrix.integration }}/composables && yarn test --passWithNoTests --coverage + + - name: Build theme + run: cd packages/${{ matrix.integration }}/theme && yarn build + + - name: Test theme + run: cd packages/${{ matrix.integration }}/theme && yarn test --passWithNoTests + + - name: api-client coverage file + id: api-coverage + run: echo "::set-output name=exists::$((test -s packages/${{ matrix.integration }}/api-client/coverage/lcov.info && echo 'true') || echo 'false')" + + - name: composables coverage file + id: composables-coverage + run: echo "::set-output name=exists::$((test -s packages/${{ matrix.integration }}/composables/coverage/lcov.info && echo 'true') || echo 'false')" diff --git a/.github/workflows/test_vsf1.yml b/.github/workflows/test_vsf1.yml deleted file mode 100644 index f413b21eac..0000000000 --- a/.github/workflows/test_vsf1.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Run VSF1 tests - -on: - push: - branches: - - master - - develop - - 'release/v**' - - 'hotfix/v**' - pull_request: - branches: - - master - - develop - - 'release/v**' - - 'hotfix/v**' - -jobs: - prepare_dependencies: - name: Prepare dependencies - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup node - uses: actions/setup-node@v1 - with: - node-version: '10' - - - name: Clone default theme - run: git clone --quiet --single-branch --branch master https://github.com/vuestorefront/vsf-default.git ./src/themes/default - - - name: Get cached dependencies - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Install dependencies - run: yarn --frozen-lockfile - - lint: - name: Lint - needs: prepare_dependencies - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup node - uses: actions/setup-node@v1 - with: - node-version: '10' - - - name: Get cached dependencies - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Run linter - run: yarn lint - - unit_tests: - name: Unit tests - needs: prepare_dependencies - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup node - uses: actions/setup-node@v1 - with: - node-version: '10' - - - name: Get cached dependencies - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Run unit tests - run: yarn test:unit - - build: - name: Build - needs: prepare_dependencies - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup node - uses: actions/setup-node@v1 - with: - node-version: '10' - - - name: Clone default theme - run: git clone --quiet --single-branch --branch master https://github.com/vuestorefront/vsf-default.git ./src/themes/default - - - name: Get cached dependencies - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Run build - run: yarn build diff --git a/.gitignore b/.gitignore index 2ecc09f384..50f7470d83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,27 @@ -.DS_Store -node_modules/ -dist/ -*.icloud -npm-debug.log -.vscode/ -.idea/ -config/local.json -var -build/config.json -core/build/config.json -core/build/cache-version.json -theme.js -desktop.ini -src/themes/catalog/resource/i18n.json -src/themes/default/resource/i18n.json -yarn-error.log -package-lock.json -cypress/videos -cypress/screenshots -core/resource/i18n/de-DE.json -core/resource/i18n/en-US.json -core/resource/i18n/es-ES.json -core/resource/i18n/fr-FR.json -core/resource/i18n/it-IT.json -core/resource/i18n/ja-JP.json -core/resource/i18n/multistoreLanguages.json -core/resource/i18n/nl-NL.json -core/resource/i18n/pl-PL.json -core/resource/i18n/pt-BR.json -core/resource/i18n/ru-RU.json -*.iml +# Dependency directories +node_modules -#unit testing -/test/unit/coverage -/static +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# NPM config +.npmrc + +# Yarn Integrity file +.yarn-integrity + +# Rollup generate output +lib + +# Coverage directory used by tools like istanbul +coverage + +# Editor directories and files +.idea +.vscode + +# OS generated files +.DS_STORE diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 2e81d78770..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,8 +0,0 @@ -[submodule "src/modules/vsf-cache-nginx"] - path = src/modules/vsf-cache-nginx - url = https://github.com/new-fantastic/vsf-cache-nginx.git - branch = master -[submodule "src/modules/vsf-cache-varnish"] - path = src/modules/vsf-cache-varnish - url = https://github.com/new-fantastic/vsf-cache-varnish.git - branch = master diff --git a/.gitpod.yml b/.gitpod.yml index acf0da7d59..c87d1a99bf 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,19 +1,6 @@ tasks: - - init: | - yarn install - echo '{ "api": { "url": "https://next.storefrontcloud.io" }}' > config/local.json - yarn build - command: yarn dev - -ports: - - port: 3000 - onOpen: open-preview + - init: yarn install vscode: extensions: - - octref.vetur@0.23.0:TEzauMObB6f3i2JqlvrOpA== - - dbaeumer.vscode-eslint@2.0.15:/v3eRFwBI38JLZJv5ExY5g== - - eg2.vscode-npm-script@0.3.11:peDPJqeL8FmmJiabU4fAJQ== - - formulahendry.auto-close-tag@0.5.6:oZ/8R2VhZEhkHsoeO57hSw== - - formulahendry.auto-rename-tag@0.1.1:lKCmLIZAiCM0M8AjDnwCLQ== - - dariofuzinato.vue-peek@1.0.2:oYJg0oZA/6FBnFfW599HRg== + - octref.vetur@0.28.0:bW1RGNnmWYQO43JdJgK34w== diff --git a/.huskyrc.js b/.huskyrc.js deleted file mode 100644 index 08aa197b01..0000000000 --- a/.huskyrc.js +++ /dev/null @@ -1,12 +0,0 @@ -const tasks = arr => arr.join(' && ') - -module.exports = { - 'hooks': { - 'pre-commit': tasks([ - 'lint-staged' - ]), - 'pre-push': tasks([ - 'yarn test:unit' - ]) - } -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 2b6f469f0c..0000000000 --- a/.jshintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "esversion": 6 -} diff --git a/.lintstagedrc.js b/.lintstagedrc.js deleted file mode 100644 index 29c2ede191..0000000000 --- a/.lintstagedrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - "*.{js,vue,ts}": "eslint", - "**/i18n/*.csv": ["node ./core/scripts/utils/sort-translations.js", "git add"] -} diff --git a/.node-version b/.node-version deleted file mode 100644 index 4ad20b12dd..0000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ ->=8.0.0 diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index f599e28b8a..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/.postcssrc b/.postcssrc deleted file mode 100644 index f053ebf797..0000000000 --- a/.postcssrc +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..1a172b10e3 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "endOfLine": "lf", + "semi": true, + "singleQuote": true, + "arrowParens": "always", + "tabWidth": 2, + "bracketSpacing": true +} diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9926539384..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1443 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [1.13.0] - UNRELEASED - -### Added -- Added information about i18n & Unit tests in modules to docs - @lukaszjedrasik (#4991) -- Updated VSF1 insfrastructure's schema - @lukaszjedrasik ([#5531](https://github.com/vuestorefront/vue-storefront/issues/5531)) - -### Fixed -- Fixed some typo in docs (#5071) - -## [1.12.3] - 2021.06.24 - -### Added - -- Added states.json in core/i18n/resource (#4531) -- Added phone validation helper (#4980) -- Configurable enabling min & max price aggregations -- Storing totals in localStorage to sync it between tabs ([#4733](https://github.com/vuestorefront/vue-storefront/issues/4733)) -- Support for trailing slashes in the route paths - @tdugue @gibkigonzo @Fifciu ([#5372](https://github.com/vuestorefront/vue-storefront/pull/5372)) - -### Fixed - -- Remove redundant user token invalidation code - TerrorSquad (#4533) -- configurableChildrenImages helper function incorrect loading images - @RakowskiPrzem (#4575) -- Fix user/register call by adding storeId - @haelbichalex (#4697) -- refresh categoryMappedFilters after loading attributes in PLP - @gibkigonzo (#4695) -- Check module registration for wishlist and compare list - gibkigonzo (#4764) -- Fix gallery image generation by checking if image exists - simonmaass (#4908) -- Fix getSelectedOption based on attribute_code check - simonmaass (#4851) -- add new resetUserInvalidation action to clear invalidation state before login, - clear order history and refresh token after logout, - add redirection when user is logged out and sees myaccount page - gibkigonzo (#4882) -- Fix `config.products.filterUnavailableVariants` feature - Ogek (#4923) -- added 'afterUserProfileUpdated' hook which allows us to take control over update address flow (send notification if something failed) - move pick allowed modification just before sending data to task managment - add 'beforeUserProfileUpdate' which allows to modify user object before update - gibkigonzo (#4427) -- replace lodash with lodash-es for client files - gibkigonzo (#5019) -- add default personal detail loading on shipment step in checkout when user is logged - (#5040) -- Got rid of inifnity redirect or page-not-found on refresh category/product view -- Got rid of memory leak related to dynamic config - tomasz-duda (#4768) -- servercart-after-diff event payload - Fifciu (#5365) -- Fix Original Price Calculation typo - @akucharczyk / @lukaszjedrasik (#5472) -- Purge loader works properly with dynamic config reload - @Fifciu -- Multi-tab cart-sync in multi-store environment - @cewald (#5711, #5732) -- Incorrect load of default address in checkout - @lukaszjedrasik ([#4682](https://github.com/vuestorefront/vue-storefront/issues/4682)) -- Error with unknown theme/index.js alias - @Fifciuu (https://github.com/vuestorefront/vue-storefront/pull/5813) -- ESLint warnings caused by the double import - @lukaszjedrasik -- Fix Order History Pagination - @AishwaryShrivastav / @lukaszjedrasik ([#4599](https://github.com/vuestorefront/vue-storefront/issues/4599)) -- Fix: Updating URL's params/query params with proper child SKU if options changes - @lukaszjedrasik ([#5981](https://github.com/vuestorefront/vue-storefront/issues/5981)) -- Fix: Passing `newToken: null` as a payload inside `clearCurrentUser` action - @lukaszjedrasik ([#5565](https://github.com/vuestorefront/vue-storefront/issues/5565)) - -### Changed / Improved - -- Moved hardcoded fields from omitSelectedVariantFields.ts to config (#4679) -- Bump dependencies versions (#4715, #4696, #4951) -- Using dayjs for dates in taxCalc.ts to make it work properly in Safari (#5364) -- Awaiting addItem action call inside mergeServerItem action (#5165) -- Moved `phoneNum` to proper branch - @lukaszjedrasik ([#5730](https://github.com/vuestorefront/vue-storefront/issues/5730)) -- Development hot-reload speed webpack config - ([#5559](https://github.com/vuestorefront/vue-storefront/issues/5559)) -- Set correct type for `productPageVisited` hook - @lukaszjedrasik ([#5997](https://github.com/vuestorefront/vue-storefront/issues/5997)) - -## [1.12.2] - 2020.07.28 - -### Added - -- **IMPORTANT** for security reasons we added new config `users.allowModification`. - This can help to dissallow modifying fields that should not be changed by user. -- Add helmet - enabled by default, you can pass configuration by adding `config.server.helmet.config`. - More info about helmet configuration https://helmetjs.github.io/docs/ -- Add config `users.tokenInHeader` which allows to send token in header instead in query. Require to set on true same config in vsf-api. -- Make calculation of bundled products price by options optional - @cewald (#4556) - -### Fixed - -- remove deprecated value from attributesListQuery query - @gibkigonzo (#4572) -- Fixed dutch translations - @1070rik (#4587) -- localForage memory overload fixed. `localForage.preserveCollections` keeps names of collections to be preserved from being cleared. - @prakowski -- Fixed bug in `restoreQuantity` - getItem never returns cart item - @gibkigonzo (#4619) -- Separate variant in findProductOption to get parent sku - @gibkigonzo (#4641) -- Fix wrong value in Cache-Control header for max-age - boehsermoe (#4657) - -### Changed / Improved - -## [1.12.1] - 2020.06.22 - -### Added - -- Add `purgeConfig` to default.json and purge-config loader - @gibkigonzo (#4540) -- Load attributes data of attribute-meta for bundled and grouped products - @cewald (#4551) -- Separate theme installation and add it as yarn init:theme or as a step in yarn installer. - @gibkigonzo (4534, #4552) - -### Fixed - -- use `config.i18n.defaultLocale` as fallback locale instead of `'en-US'` - @gibkigonzo (#4489) -- use Math.abs on raw price - @gibkigonzo (#4521) -- Clears vuex warnings about overriding state by module - @gibkigonzo (#4541) - -### Changed / Improved - -## [1.12.0] - 2020.06.01 - -### Added - -- Add `vsf-capybara` support as a dependency and extend CLI to support customization - @psmyrek (#4209) -- Support theme configuration via CLI - @psmyrek (#4395) -- Allow parent_ids field on product as an alternative to urlpath based breadcrumb navigation (#4219) -- Pass the original item_id when updating/deleting a cart entry @carlokok (#4218) -- Separating endpoints for CSR/SSR - @Fifciu (#2861) -- Added short hands for version and help flags - @jamesgeorge007 (#3946) -- Add `or` operator for Elasticsearch filters in `quickSearchByQuery` and use exists if value is `null` - @cewald (#3960) -- Add unified fetch in mappingFallback for all searched entities - @gibkigonzo (#3942) -- add npm-run-all for parallel build - @gibkigonzo (#3819) -- Add OutputCaching support for x-vs-store-code - @benjick (#3979) -- The new search adapter `api-search-query` has been added. When you switch to it, by setting the `config.server.api = "api-search-query"` the ElasticSearch query is being built in the [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api/pull/390) which saves around 400kB in the bundle size as `bodybuilder` is no longer needed in the frontend - @pkarw - #2167 -- This new `api-search-query` adapter supports the `response_format` query parameter which now is sent to the `/api/catalog` endpoint. Currently there is just one additional format supported: `response_format=compact`. When used, the response format got optimized by: a) remapping the results, removing the `_source` from the `hits.hits`; b) compressing the JSON fields names according to the `config.products.fieldsToCompact`; c) removing the JSON fields from the `product.configurable_children` when their values === parent product values; overall response size reduced over -70% - @pkarw -- The `amp-renderer` module has been disabled by default to save the bundle size; If you'd like to enable it uncomment the module from the `src/modules` and uncomment the `product-amp` and `category-amp` links that are added to the `` section in the `src/themes/default/Product.vue` and `src/themes/default/Category.vue` -- Reset Password confirmation page - @Fifciu (#2576) -- Add `Intl.NumberFormat()`/`toLocaleString()` via polyfill support in NodeJs - @cewald (#3836, #4040) -- Added `saveBandwidthOverCache` parameter for skipping caching for products data - @andrzejewsky (#3706) -- New zoom effect for product gallery images - @Michal-Dziedzinski (#2755) -- Add custom currency separators and amount of fraction digits - @EndPositive (#3553) -- Product Page Schema implementation as JSON-LD - @Michal-Dziedzinski (#3704) -- Add `/cache-version.json` route to get current cache version -- Built-in module for detecting device type based on UserAgent with SSR support - @Fifciu -- Update to `storefront-query-builder` version `1.0.0` - @cewald (#4234) -- Move generating files from webpack config to script @gibkigonzo (#4236) -- Add correct type matching to `getConfigurationMatchLevel` - @cewald (#4241) -- Support `useSpecificImagePaths` with `useExactUrlsNoProxy` - @cewald (#4243) -- Adds module which handles cache invalidation for Fastly. - @gibkigonzo (#4096) -- Add vsf-cache-nginx and vsf-cache-varnish modules - @gibkigonzo (#4096) -- Added meta info for CMS pages from Magento @mdanilowicz (#4392) -- Add useful core events to server & logger - @cewald (#4419) - -### Fixed - -- Fixed `resultPorcessor` typo - @psmyrek -- Negative price has doubled minus sign - @psmyrek (#4353) -- Fixed Search product fails for category filter when categoryId is string - @adityasharma7 (#3929) -- Revert init filters in Vue app - @gibkigonzo (#3929) -- All categories disappearing if you add the child category name to includeFields - @1070rik (#4015) -- Fix overlapping text in PersonalDetails component - @jakubmakielkowski (#4024) -- Redirect from checkout to home with a proper store code - @Fifciu -- Added back error notification when user selects invalid configuration - @1070rik (#4033) -- findConfigurableChildAsync - return best match for configurable variant - @gibkigonzo, @cewald (#4042, #4216) -- use storeCode for mappingFallback url - @gibkigonzo (#4050) -- `getVariantWithLowestPrice` uses inexistent `final_price` property - @cewald (#4091) -- Fixed `NOT_ALLOWED_SSR_EXTENSIONS_REGEX` to only match with file extensions having a dot - @haelbichalex (#4100) -- Fixed problem with not showing error message when placing an order fails - @qiqqq -- Invoking afterCacheInvalidated server hook in a proper moment - @Fifciu (#4176) -- Fixed `cart/isVirtualCart` to return `false` when cart is empty - @haelbichalex(#4182) -- Use `setProductGallery` in `product/setCurrent` to use logic of the action - @cewald (#4153) -- Use same data format in getConfigurationMatchLevel - @gibkigonzo (#4208) -- removed possible memory leak in ssr - @resubaka (#4247) -- Bugfix for reactivity of `current_configuration` in `populateProductConfigurationAsync` - @cewald (#4258) -- Bugfix for build exception in Node v13.13+ - @cewald (#4249) -- Convert option ids to string while comparing them in `getProductConfiguration` - @gibkigonzo (#4484) -- change value to number in price filter - @gibkigonzo (#4478) - -### Changed / Improved - -- Optimized `translation.processor` to process only enabled locale CSV files - @pkarw (#3950) -- Remove commit register mapping - @gibkigonzo (#3875) -- Improved method `findConfigurableChildAsync` - find variant with lowest price - @gibkigonzo (#3939) -- Removed `product/loadConfigurableAttributes` calls - @andrzejewsky (#3336) -- Removed unused locales in disabled multistore - @gibkigonzo (#4072) -- Optimized attributes loading - @andrzejewsky (#3948) -- Cart optimization can now be used regardless if entity optimization is enabled - @juho-jaakkola (#4198) -- Improve typescript support for test utils - @resubaka (#4067) -- Removed `product/loadConfigurableAttributes` calls - @andrzejewsky, @gibkigonzo (#3336) -- Disable `mapFallback` url by default - @gibkigonzo(#4092) -- Include token in pricing sync - @carlokok (#4156) -- Move 'graphql' search adapter from core to src (deprecated) - @gibkigonzo (#4214) -- Homepage, new products query, uses now `new` attribute - @mdanilwoicz -- Refactor product module, more info in upgrade notes- @gibkigonzo (#3952, #4459) -- Move default theme to separate repository https://github.com/vuestorefront/vsf-default - @gibkigonzo (#4255) -- add two numbers after dot to price by default, calculate default price for bundle or grouped main product, update typing, add fallback to attribute options - @gibkigonzo (#4476) -- udpate yarn and filter shipping methods for instant checkout - @gibkigonzo (#4480) -- add attribute metadata search query, add parentId - @gibkigonzo (#4491) - -## [1.11.4] - 2020.05.26 - -### Added - - -### Changed / Improved - -- use yarn in cli installer - @gibkigonzo (#4292) -- disable out of stock notification when config.stock.allowOutOfStockInCart is true - @gibigonzo (#4340) - - -### Fixed - -- Use LRU as object contructor based on newest changes in module - @gibkigonzo (#4242) -- Fixed ESC button action (minicart, wishlist) - @mdanilowicz (#4393) -- Fixes problems related to tax calculation and price filter in multistore setup - @juho-jaakkola (#4376) -- Blank order details page - @mdanilowicz (#4382) -- upadate cart hash after sync with backend - @gibkigonzo (#4387) -- exit from errorHandler after redirection - @gibkigonzo (#4246) -- add redirection in component for simple product related to configurable product - @gibkigonzo (#4359) -- disable sending carrier_code or method_code for virtual products, - adjust vue-carousel and vuelidate to newest versions api, - add aplha validators for register fields - @gibkigonzo (#4455, #4461) - -## [1.11.3] - 2020.04.27 - -### Changed / Improved - -- The default config file is now in more human-readable format - @juho-jaakkola (#4197) -- Create only once aside async component - @gibkigonzo (#4229, #4268) - -### Fixed -- Fixes when having multiple custom options with overlapping option_type_id values, selecting 1 changes the others - @carlokok (#4196) -- Update eslint and fix code style. - @gibkigonzo (#4179 #4181) -- Fixes bug that caused addToCart action not to display messages to user - @juho-jaakkola (#4185) -- add missing cache tags for category and product - @gibkigonzo (#4173) -- add ssrAppId to avoid second meta render on csr - @gibkigonzo (#4203) -- take control over default broswer behavior and use saved category page size to load prev products - @gibkigonzo (#4201) -- update getCurrentCartHash after add/remove coupon - @gibkigonzo (#4220) -- update replaceNumberToString, so it will change ONLY numbers to string - @gibkigonzo (#4217) -- allow empty shipping methods in checkout - @gibkigozno (#4192) -- configure products before price update - this is needed to have variant sku as product sku - @gibkigonzo (#4053) -- omit stock and totals when creating cart hash, it is not needed to compare products - @gibkigozno (#4235, #4273) - -## [1.11.2] - 2020.03.10 - -### Added - -- Add `isBackRoute` that informs if user returns to route, skip loading products for category if he does - @gibkigonzo (#4066) -- Add server context to async data loader - @gibkigonzo (#4113) -- Add preload and preconnect for google font - @gibkigonzo (#4121) - -### Changed / Improved - -- optimizations - improved prefetch strategy - @gibkigonzo (#4080) -- improvements to Finnish translations - @evktalo (#4116) -- Radio button now allows separate checked, value and name attributes - @EndPositive (#4098) -- Update backwards compatible dependencies - @simonmaass (#4126) - -### Fixed - -- add disconnect and sync options for cart/clear - @gibkigonzo (#4062) -- add '1' as searched value for 'is_user_defined' and 'is_visible' (createAttributesListQuery) - @gibkigonzo (#4075) -- Fix possibility to add same SKU with different custom options to the cart - @Michal-Dziedzinski (#3595) -- Fix `calculateProductTax` to find matching tax rules from ES for current product - @DylannCordel (#4056) -- Set `totals` in products in cart always in reactive way - @psmyrek (#4079) -- Fix sync cart between tabs - @Michal-Dziedzinski (#3838) -- Add currentRoute to url module and return cached requests - @gibkigonzo (#4077, #4066) -- Hide original radio button behind built label - @EndPositive (#4098) -- Disable overriding `route` state in **INITIAL_STATE** - @gibkigonzo (#4095) -- Fix gtm order placement event when user was guest - @Michal-Dziedzinski (#4064) -- Fix gtm event switched properties - @Michal-Dziedzinski (#4106) -- Group 'productChecksum' and 'productsEquals' logic for all supported products types. Remove 'checksum' when editing product. - Remove and add coupon when user login Remove 'NA' as default company. Show qty in microcart for all types of product. - Remove preload font - it gives good performance, but vue-meta refresh page, because there is script onload. - @gibkigonzo (#4128) -- Keep old category before route is resolved - @gibkigonzo (#4124) -- Added comments in 'productsEqual' and change logic for different types of products. Remove login user after order in Checkout. Allow changing qty for 'group' and 'bundle'.products - @gibkigonzo (#4144) -- Fix incorrect root categories when extending includeFields - @Michal-Dziedzinski (#4090) -- Add onlyPositive prop to BaseInputNumber to not allow user type negative value - @Michal-Dziedzinski (#4136) -- Await for cart/authorize while login user - @gibkigonzo (#4133) -- Fixed `NOT_ALLOWED_SSR_EXTENSIONS_REGEX` to only match with file extensions having a dot - @haelbichalex (#4100) -- Add lazy load for vue-carousel - @gibkigonzo (#4157) - -## [1.11.1] - 2020.02.05 - -### Added - -- Add `ProductPrice` component with bundleOptions and customOptions prices - @gibkigonzo (#3978) -- Add lazy create cart token - @gibkigonzo (#3994) - -### Changed / Improved - -- Set cache tag when loading a category - @haelbichalex (#3940) -- In development build `webpack.config.js` in theme folder is now called without the `default` key - @psmyrek - -### Fixed - -- Added Finnish translations - @mattiteraslahti and @alphpkeemik -- Updated Estonian translations to match 1.11 - @alphpkeemik -- CookieNotification CSR&SSR mismatch fixed - @Fifciu (#3922) -- The attribute filter in `attribute/list` was not filtering the already loaded attributes properly - @pkarw (#3964) -- Update `hasProductErrors` in Product component and support additional sku in custom options - @gibkigonzo (#3976) -- Fixed logic for generating \${lang}.json files in multi-store setup - @jpkempf -- Fixed logic for collecting valid locales in single-store, multi-lang setup - @jpkempf -- Make initial custom option value reactive - @gibkigonzo -- Fixed No image thumbnails leaded on 404 page - @andrzejewsky (#3955) -- Fixed Stock logic not working with manage_stock set to false - @andrzejewsky - (#3957) -- Support old price format in `ProductPrice` - @gibkigonzo (#3978) -- Fixed product bundle comparison condition - @gk-daniel (#4004) -- Add event callback for checkout load initial data - @gibkigonzo(#3985) -- Fixed `Processing order...` modal closing too early - @grimasod (#4021) -- Keep registered payment methods after `syncTotals` - @grimasod (#4020) -- Added status code to the cache content and use it in cache response - @resubaka (#4014) -- Fixed sku attribute is missing on compare page - @gibkigonzo (#4036) -- Fixed z-index for aside in compare list - @gibkigonzo (#4037) -- Disable checking max quantity when manage stock is set to false - @gibkigonzo (#4038) -- Add products quantity only when token is created - @gibkigonzo (#4017) -- Revert init filters in Vue app - add storeView to global/store and pass it to filters - @gibkigonzo (#3929) -- Fix v-model not working in BaseRadioButton - @lukeromanowicz (#4035) -- always keep filters values as array of object - @gibkigonzo (#4045) -- Fix ecosystem config to work with ts-node - @andrzejewsky (#3981) - -## [1.11.0] - 2019.12.20 - -### Added - -- Add unit tests for `core/modules/url` - @dz3n (#3469) -- Add unit test for `core/modules/checkout` - @psmyrek (#3460) -- Add defense against incomplete config in ssr renderer - @oskar1233 (#3774) -- Add unit tests for `core/modules/order` - @dz3n (#3466) -- Add unit tests for `core/modules/user` - @dz3n (#3470) -- Add to cart from Wishlist and Product listing for simple products - @Dnd-Dboy, @dz3n (#2637) -- Add global Category and Breadcrumb filters, defined in local.json - @grimasod (#3691) -- Add constant which conditions the number of products loading per page - @AdKamil (#3630) -- Added price filtering key as config - @roywcm - -### Fixed - -- Fixed missing parameter to query function from cms/store/block/actions - @georgiev-ivan (#3909) -- Always close zoom overlay after changing product - @psmyrek (#3818) -- Fixed problem with cutting image height in category page on 1024px+ screen res - @AdKamil (#3781) -- Fixed null value of search input - @AdKamil (#3778) -- Fixed product sorting - @AdKamil (#3785) -- Fixed displaying `sale` and `new` mark - @andrzejewsky (#3800) -- Fixed sorting on category page and product tile sizing - @andrzejewsky (#3817) -- Redirect from simple product using url_path - @benjick (#3804) -- Mount app in 'beforeResolve' if it's not dispatched in 'onReady' - @gibkigonzo (#3669) -- Fixed AMP pages - @andrzejewsky (#3799) -- Fixed Product page breadcrumbs problem when products are in multiple categories in different branches of the category tree - @grimasod (#3691) -- Change translation from jp-JP to ja-JP - @gibkigonzo (#3824) -- Fixed ecosystem config for pm2 - @andrzejewsky (#3842) -- Fixed `mappingFallback` for extending modules - @andrzejewsky (#3822) -- Fixed adding products search results to category-next product store - @grimasod (#3877) -- Use `defaultSortBy` for sorting category products by default @haelbichalex (#3873) -- Fixed some potential mutations of Config object in `catalog` and `catalog-next` - @grimasod (#3843) -- Set `null` as default value for custom option in product page - @gibkigonzo (#3885) -- Fixed Breadcrumb filters - apply to second category fetch - @grimasod (#3887) -- Fixed `config.storeViews.commonCache` being ignored - @grimasod (#3895) -- Fixed static pages, password notification, offline mode #3902 - @andrzejewsky (#3902) -- Fixed error page display with enabled multistore - @gibkigonzo (#3890) -- Fixed edit shipping address in my account - @gibkigonzo (#3921) -- Fetch cms_block content in serverPrefetch method - @gibkigonzo (#3910) -- Fixed saving invalidated user token - @andrzejewsky (#3923) -- Keep category products objects on ssr - @gibkigonzo (#3924) -- product breadcrumbs - check if current category is not highest one - @gibkigonzo (#3933) - -### Changed / Improved - -- Changed pre commit hook to use NODE_ENV production to check for debugger statements - @resubaka (#3686) -- Improve the readability of 'getShippingDetails()' and 'updateDetails()' method of UserShippingDetails component - @adityasharma7 (#3770) -- Keep git after yarn install in dockerfile - @ddanier (#3826) -- Update the Storage Manager shipping details cache immediately when then Vuex checkout store is updated - @grimasod (#3894) - -## [1.11.0-rc.2] - 2019.10.31 - -### Added - -- Add defense for incomplete config in preferchCachedAttributes helper -- Add unit test for \`core/modules/cms\` - @krskibin (#3738) - -### Fixed - -- Fixed deprecated getter in cmsBlock store - @resubaka (#3683) -- Fixed problem around dynamic urls when default storeView is set with appendStoreCode false and url set to / . @resubaka (#3685) -- Fixed three problems you can run into when you have bundle products - @resubaka (#3692) -- Reset nested menu after logout - @gibkigonzo (#3680) -- Fixed handling checkbox custom option - @gibkigonzo (#2781) -- Fixed typos in docs - @afozbek (#3709) -- Fixed VSF build fails for some people due to lack of dependencies in the container - @krskibin (#3699) -- Fixed two graphql problems, one with cms_blocks and the other with default sort order - @resubaka (#3718) -- Allow falsy value for `parent_id` when searching category - @gibkigonzo (#3732) -- Remove including .map files in service worker cache - @gibkigonzo (#3734) -- Changed notification message object to factory fn - @gibkigozno (#3716) -- Load recently viewed module in my account page - @gibkigonzo (#3722) -- Added validation message for city field on checkout page - @dz3n (#3723) -- Make price calculation based on saved original prices - @gibkigonzo (#3740) -- Improving is_comparable to work with booleans and digits - @dz3n (#3697) -- Fixed displaying categories on search menu - @andrzejewsky (#3758) -- Fixed broken link for store locator - @andrzejewsky (#3754) -- Fixed instant checkout functionality - @andrzejewsky (#3765) -- Fixed links to the promoted banners - @andrzejewsky (#3753) -- Fixed missing parameter in the compare list - @andrzejewsky (#3757) -- Fixed product link on mobile - @andrzejewsky (#3772) -- Custom module `ConfigProvider` aren't called anymore - @cewald (#3797) - -### Added - -- Added Estonian translations - @alphpkeemik -- Added support for ES7 - @andrzejewsky (#3690) -- Added unit tests for `core/modules/mailer` - @krskibin (#3710) -- Get payment methods with billing address data - @rain2o (#2878) -- Added custom page-size parameter for `category-next/loadCategoryProducts` action - @cewald (#3713, #3714) -- Remove unused dayjs locales - @gibkigonzo (#3498) -- check max quantity in microcart - @gibkigonzo (#3314) -- Add unit tests for `core/modules/newsletter` - @psmyrek (#3464) -- Add unit test for `core/modules/wishlist` - @psmyrek (#3471) - -### Changed / Improved - -- Use `encodeURIComponent` to encode get parameters in `multimatch.js` - @adityasharma7 (#3736) - -## [1.11.0-rc.1] - 2019.10.03 - -### Added - -- Add unit testing to Husky on pre-push hook - @mattheo-geoffray (#3475) -- Add unit testing on breadcrumbs feature - @mattheo-geoffray (#3457) -- HTML Minifier has been added, to enable it please switch the `config.server.useHtmlMinifier` - @pkarw (#2182) -- Output compression module has been added; it's enabled by default on production builds; to disable it please switch the `src/modules/serrver.ts` configuration - @pkarw (#2182) -- Sort CSV i18n files alphabetically in pre-commit Git hook - @defudef (#2657) -- Cache invalidate requests forwarding support - @pkarw (#3367) -- Extend storeview config after another storeview in multistore mode - @lukeromanowicz (#3057, #3270) -- Default storeview settings are now overridden by specific storeview settings - @lukeromanowicz (#3057) -- Apache2 proxy header support for store based on host - @resubaka (#3143) -- Items count badges for Compare products and wishlist icons at header - @vishal-7037 (#3047) -- Added product image in order summary - @obsceniczny (#2544) -- Add icons on the product tiles that allow to add to the wish list and to the list to compare products from the list of products - @Michal-Dziedzinski (#2773) -- Get also none product image thumbnails via API - @cewald, @resubaka (#3207) -- Added a config option `optimizeShoppingCartOmitFields` - @EmilsM (#3222) -- Added information on the number of available products - @Michal-Dziedzinski (#2733) -- Added possibility to change color or size of the product that is already in the cart - @andrzejewsky (#2346) -- Experimental static files generator - @pkarw (#3246) -- Added price formatting based on locales in multistore - @andrzejewsky (#3060) -- Added support for tax calculation where the values from customer_tax_class_ids is used - @resubaka (#3245) -- Added loading product attributes (`entities.productListWithChildren.includeFields`) on category page - @andrzejewsky (#3220) -- Added config to set Cache-Control header for static assets based on mime type - @phoenix-bjoern (#3268) -- Improve `category-next/getCategoryFrom` and `category-next/getCurrentCategory` to be more flexible - @cewald (#3295) -- Added test:unit:watch with a workaround of a jest problem with template strings - @resubaka (#3351) -- Added test to multistore.ts so it is nearly fully unit tested - @resubaka (#3352) -- Added test:unit:watch with a workaround of a jest problem with template strings - @resubaka (#3351, #3354) -- Added test to helpers/index.ts so it is partly tested - @resubaka (#3376, 3377) -- Added hooks in cart module - @andrzejewsky (#3388) -- Added config for the defaultTitle compitable with multistore - @cnviradiya (#3282) -- Added husky package to manage lint check only for staged files in git @lorenaramonda (#3444) -- Change text from "is out of the stock" to "is out of stock" - @indiebytes (#3452) -- Added general purpose hooks - @andrzejewsky (#3389) -- Added loading of your own searchAdaptor - @resubaka (#3405K) -- Added lazy hydration for home page - @filrak (#3496, #3565) -- Added i18n support for modules - @dz3n (#3369) -- Added support for creating localized child routes - @gibkigonzo (#3489) -- Added tests for actions and mutations in 'core/modules/recently-viewed' - @gibkigonzo (#3467) -- Added tests for actions, mutations and components in 'core/modules/compare' - @gibkigonzo (#3467) -- Added support to load tracing libs at the start of the app - @resubaka (#3514, #3566) -- Added tests for actions and mutations in 'core/modules/notification' - @gibkigonzo (#3465) -- Added tests for actions, mutations and helpers in 'core/modules/review' - @gibkigonzo (#3468) -- Add new Google-Tag-Manager module using new module registration - @cewald (#3524, #3509) -- Exclude GTM product attributes setup into config json - @dlandmann, @cewald (#3509, #3524) -- Add configuration option to format currency sign placement and space in price - @cewald (#3574) -- Add ability to pass `pageSize` and `currentPage` to order history API call for pagination - @rain2o -- Added italian translations - @lorenaramonda (3076) -- Route Manager Queue for adding routes efficiently and with an optional priority - @grimasod (#3540) -- Added tests for cart module actions - @andrzejewsky (#3023) -- Fixed a problem with type changes in the state when extending a store - @resubaka (#3618) - -### Fixed - -- Attributes loader, breadcrumbs loader fixes - @pkarw (#3636) -- Fix for the product attribute labels displayedd on the PDP - @pkarw (#3530) -- Fix the mix of informal and polite personal pronouns for German translations - @nhp (#3533) -- Fix for comparison list being not preserved between page reloads - @vue-kacper (#3508) -- Fix 'fist' typos - @jakubmakielkowski (#3491) -- Fix for wrong breadcrumb urls in the multistore mode - @pkarw (#3359) -- Fix for displaying gallery images for unavaialble product variants - @pkarw (#3436) -- Fix for `null` in search query input - @pkarw (#3474) -- Unable to place order has been fixed; the `entities` module was wrongly imported - @pkarw (#3453) -- Fixed product link in wishlist and microcart - @michasik (#2987) -- Fixed naming strategy for product prices - `special_priceInclTax` -> `special_price_incl_tax`, `priceInclTax` -> `price_incl_tax`, `priceTax` -> `price_tax`; old names have been kept as @deprecated - @pkarw (#2918) -- The `final_price` field is now being used for setting the `special_price` or `price` of the product (depending on the value); `final_price` might been used along with `special_price` with Magento for the products with activated catalog pricing rules - @pkarw (#3099) -- Resolve problem with getting CMS block from cache - @qiqqq (#2499) -- Make image proxy url work with relative base url - @cewald (#3158) -- Fixed memory leak with enabled dynamicConfigReload - @dimasch (#3075) -- Fixed error for the orderhistory null for google-tag-manager extension - @cnviradiya (#3195) -- Fixed swatches not rendering properly at product detail page issue - @vishal-7037 (#3206) -- Fixed label of configurable options in cart after product just added - @cheeerd (#3164) -- Fixed eslint warning in Product Page, removed v-if from v-for node - @przspa (#3181) -- Fixed aspect ratio in ProductImage component - @przspa (#3187) -- Fixed AMP Product page - @przspa (#3227) -- Fixed when store has updated, but plugin didn't called - @serzilo (#3238) -- Fixed first call of prepareStoreView when SSR - @resubaka (#3244) -- Add ./packages as volume to docker-compose.yml - @cewald (#3251) -- Fixed mail sending and add error logger - @Michal-Dziedzinski (#3265) -- Fixed page not found http status code - @phoenix-bjoern (#3243) -- Fixed missing coupon code after user logged in - @andrzejewsky (#3153) -- Fixed bug around appendStoreCode in formatCategoryLink. - @resubaka (#3306) -- Fixed static category links in cms contents on homepage and MinimalFooter - @MariaKern (#3292) -- Fixed tax calulaction where products was send as parameter but products.items where the right paramater - @resubaka (#3308) -- Fixed module extendStore for array property inside store - @przspa (#3311) -- Fixed ordering of the categories and subcategories in sidebar - @andrzejewsky (#2665) -- Some SSR problems with urlDispatcher during multireloading page - @patzick (#3323) -- Fixed two bugs in `category-next/getCategoryFrom` (#3286) and `category-next/getCurrentCategory` (#3332) - @cewald (#3295) -- Fixed login popup close icon position - @przspa (#3393) -- Fixed styles for original price on Wishlist sidebar - @przspa (#3392) -- Redirect loop on dispatching dynamic routes in CSR running multistore mode - @cewald, @lukeromanowicz, @resubaka (#3396) -- Adjusted ProductVideo props to right names - @przspa (#3263) -- Fixed Doubled SKU row in compare tab - @manvendra-singh1506 (#3447) -- Fixed warning in product details because of duplicate `product` property in `AddToCompare` mixin - @cewald (#3428) -- Fixed adding unconfigured product to cart from homepage - @lukeromanowicz (#3512) -- Fixed "Clear Wishlist" Button - @dz3n (#3522) -- Fixed hash in dynamically resolved urls causing resolving issues - @lukeromanowicz (#3515) -- Fix invalid routes in ButtonOutline and ButtonFull - @lukeromanowicz (#3541, #3545) -- Fix adding notification with 'hasNoTimeout' after normal notification - @gibkigonzo (#3465) -- Logged-in user's shipping address on checkout page - @przspa (#2636) -- Fix for the "add to cart" test -- Fixed error with dayjs when locale is 2-digit (without a '-') @rain2o (#3581) -- Fix applying coupon - @andrzejewsky (#3578) -- Prevent caching storage instance in plugin module scope - @gibkigonzo (#3571) -- Fixed incorrect image sizes in related section on product page - @andrzejewsky (#3590) -- Fix typo on default language - @lorenaramonda (#3076) -- Remove race condition while loading locale messages - @gibkigonzo (#3602) -- Fix displaying same country twice in the in the country switcher - @andrzejewsky (#3587) -- Fixed resolving store code on SSR - @andrzejewsky (#3576) -- Clear user data if error occurs while login - @gibkigonzo (#3588) -- Fix loading bestsellers on 404 error page - @andrzejewsky (#3540) -- Remove modifying config by reference in multistore - @gibkigonzo (#3617) -- Add translation key for add review - @gibkigonzo (#3611) -- Add product name prop to reviews component - @gibkigonzo (#3607) -- Show default cms pages when current store code is not equals to default - @andrzejewsky (#3579) -- Fix login errors with mailchimp - @gibkigonzo (#3612) -- Hydration error on homepage - @patzick (#3609) -- Fix adding products with custom options - @andrzejewsky (#3597) -- check silentMode in errors on the same level as task.silent - @gibkigonzo (#3621) -- Add missing parameters (`size`,`start`) to `quickSearchByQuery()` in `attribute/list` action - @cewald (#3627) -- Fix breadcrumb homepage link in cms static pages - @andrzejewsky (#3631) -- Fixed special price that can break when you change pages (browser navigation for/back) or just go from category to product page - @resubaka (#3638) -- Fixed problem with losing browser history - @andrzejewsky (#3642) -- Fixed wrong links on the static pages - @andrzejewsky (#3659) -- Fixed problem with changing quantity in offline mode on product page - @andrzejewsky (#3662) -- Fixed problem with extending storeView configuration - @andrzejewsky (#3655) -- Removed infinite loop when changing checkbox in shipping details - @gibkigonzo (#3656) -- Fixed displaying single order in the profile - @andrzejewsky (#3663) -- Make microcart ui consistent for all types of products - @gibkigonzo (#3673) -- Fixed missing storeCode in metaInfo - @andrzejewsky (#3674) -- Removed showing popup when you have just logged out - @andrzejewsky (#3680) - -### Changed / Improved - -- Change Product quantity field validation - @jakubmakielkowski (#3560) -- Update confirmation page in offline mode - @jakubmakielkowski (#3100) -- Removed server order id from ThankYouPage - @federivo (#3480) -- Shipping address is saved as default when not logged in user chooses to create account during checkout - @iwonapiotrowska (#2636) -- The `attribute.list_by_id` and `attribute.list_by_code` from the `window.__INITIAL_STATE__` which could be even up to 50% of the product page size. - @pkarw (#3281) -- Can set transition style for Modal content - @grimasod (#3146) -- Added stock to cart items - @cheeerd (#3166) -- Moves theme specific stores and components into themes - @michasik (#3139) -- Decreased the `localStorage` quota usage + error handling by introducing new config variables: `config.products.disablePersistentProductsCache` to not store products by SKU (by default it's on). Products are cached in ServiceWorker cache anyway so the `product/list` will populate the in-memory cache (`cache.setItem(..., memoryOnly = true)`); `config.seo.disableUrlRoutesPersistentCache` - to not store the url mappings; they're stored in in-memory cache anyway so no additional requests will be made to the backend for url mapping; however it might cause some issues with url routing in the offline mode (when the offline mode PWA installed on homescreen got reloaded, the in-memory cache will be cleared so there won't potentially be the url mappings; however the same like with `product/list` the ServiceWorker cache SHOULD populate url mappings anyway); `config.syncTasks.disablePersistentTaskQueue` to not store the network requests queue in service worker. Currently only the stock-check and user-data changes were using this queue. The only downside it introuces can be related to the offline mode and these tasks will not be re-executed after connectivity established, but just in a case when the page got reloaded while offline (yeah it might happen using ServiceWorker; `syncTasks` can't be re-populated in cache from SW) - @pkarw (#3180) -- Translation file improvements - @vishal-7037 (#3198) -- Added configuration for max attempt task & cart by pass - @cnviradiya (#3193) -- Added catching of errors when ES is down - @qiqqq -- Added debounce for updating quantity method in the cart - @andrzejewsky (#3191) -- New modules API and rewrite - @filrak, @JCown (#3144) -- Refactored the vuex user module - @andrzejewsky (#3095) -- Brazilian Portuguese (pt_BR) translation improved - @pxfm (#3288) -- Moved store/lib to /lib - @pxfm (#3253) -- Corrected usage of "configurableChildrenStockPrefetchStatic" setting, refactored logic to tested helper - @philippsander (#859) -- Improved some of the german translations in spelling and wording - @MariaKern (#3297) -- Added lazy-hydrate for category products - @andrzejewsky (#3327) -- Refactored vuex order module - @andrzejewsky (#3337) -- Changed body no-scroll behavior for overlapped element - @przspa (#3363) -- `config.dynamicConfigReload` option should use deep copy for `Object.assign()` - @cewald (#3372) -- Add translation for the defaultTitle - @cnviradiya (#3282) -- Refactored vuex tax module - @andrzejewsky (#3337) -- Refactored vuex stock module - @andrzejewsky (#3337) -- Removed extra unnecessary code from BaseInputNumber - @cnviradiya (#3410) -- Refactored vuex checkout module - @andrzejewsky (#3337) -- Moved my-account authentication guard to MyAccount core page - @przspa (#3325) -- Refactored vuex compare module - @andrzejewsky (#3337) -- Refactored vuex whishlist module - @andrzejewsky (#3337) -- Refactored vuex cms module - @andrzejewsky (#3337) -- Refactored vuex review module - @andrzejewsky (#3337) -- Refactored vuex newsletter module - @andrzejewsky (#3337) -- Changed type of Id fields related to product, category and attribute to support numeric as well as string - @adityasharma7 (#3456) -- Optimized fetching product data on homepage - @lukeromanowicz (#3512) -- `localizedRoute()` now supports path (and prefers over fullPath) in LocalizedRoute objects - @lukeromanowicz (#3515) -- Move setting review_status from VSF to VSF-API - @afirlejczyk -- `localizedRoute()` doesn't return urlDispatcher routes anymore. Use localizedDispatcherRoute instead - @lukeromanowicz (#3548) -- Improved scrolling in Safari on iOS devices (sidebars) - @phoenixdev-kl (#3551) -- Improved cookie and offline badges (z-index, overflow) - @phoenixdev-kl (#3552) -- Improved translations: Replaced concatenations with "named formatting" (see http://kazupon.github.io/vue-i18n/guide/formatting.html#named-formatting) - @phoenixdev-kl (#3550) -- Added `filterMinimumShouldMatch` to ES queries in order to support ES7 - @pkarw (#1692) -- Pass `RouteManager` as proxy for router.addRoutes - @gibkigonzo (#3479) -- Added generic types to hooks - @gibkigonzo -- Change sku to string when checking products equality - @gibkigonzo (#3606) -- Pass to `registerModule` all parameters as one object - @gibkigonzo (#3634) -- Include shipping address data in request for shipping methods for more accurate filtering - @rain2o (#2515) -- remove 'disabled' flag in storeViews config - @gibkigonzo (#3659) - -## [1.10.5] - 28.11.2019 - -### Fixed - -- Disable product mutation when assigning product variant - @gibkigonzo (#3735) -- Fix issue with Cannot assign to read only property 'storeCode' - @yuriboyko (#3748) -- Render correct category links when multistore is active - @gibkigonzo (#3753) -- Disable product mutation when assigning product variant - @gibkigonzo (#3735) -- Fixed null value of search input - @AdKamil (#3778) -- Sorting fixed on category page - @AdKamil (#3785) -- Mount app in 'beforeResolve' if it's not dispatched in 'onReady' - @gibkigonzo (#3669) -- change translation from jp-JP to ja-JP - @gibkigonzo (#3824) -- Fix product images, my account link, warnings in console - @andrzejewsky (#3850) - -## [1.10.4] - 18.10.2019 - -### Fixed - -- Added try/catch for fetching single product in cart synchronization - @gibkigonzo (#3632) -- Removed infinite loop when changing checkbox in shipping details - @gibkigonzo (#3656) -- Remove modifying config by reference in multistore - @gibkigonzo (#3617) -- Fix displaying same country twice in the in the country switcher - @andrzejewsky (#3587) -- Remove race condition while loading locale messages - @gibkigonzo (#3602) -- Fixed special price that can break when you change pages (browser navigation for/back) or just go from category to product page - @resubaka (#3638) -- Change sku to string when checking products equality - @gibkigonzo (#3606) -- Fixed problem with losing browser history - @andrzejewsky (#3642) -- Fixed resolving store code on SSR - @andrzejewsky (#3576) -- Fixed styles for original price on Wishlist sidebar - @przspa (#3392) -- Added debounce for updating quantity method in the cart - @andrzejewsky (#3191) -- Improved scrolling in Safari on iOS devices (sidebars) - @phoenixdev-kl (#3551) -- Improved cookie and offline badges (z-index, overflow) - @phoenixdev-kl (#3552) -- Added config to set Cache-Control header for static assets based on mime type - @phoenix-bjoern (#3268) -- Added catching of errors when ES is down - @qiqqq -- `localizedRoute()` doesn't return urlDispatcher routes anymore. Use localizedDispatcherRoute instead - @lukeromanowicz (#3548) -- Fixed hash in dynamically resolved urls causing resolving issues - @lukeromanowicz (#3515) -- `localizedRoute()` now supports path (and prefers over fullPath) in LocalizedRoute objects - @lukeromanowicz (#3515) -- Decreased the `localStorage` quota usage + error handling by introducing new config variables: `config.products.disablePersistentProductsCache` to not store products by SKU (by default it's on). Products are cached in ServiceWorker cache anyway so the `product/list` will populate the in-memory cache (`cache.setItem(..., memoryOnly = true)`); `config.seo.disableUrlRoutesPersistentCache` - to not store the url mappings; they're stored in in-memory cache anyway so no additional requests will be made to the backend for url mapping; however it might cause some issues with url routing in the offline mode (when the offline mode PWA installed on homescreen got reloaded, the in-memory cache will be cleared so there won't potentially be the url mappings; however the same like with `product/list` the ServiceWorker cache SHOULD populate url mappings anyway); `config.syncTasks.disablePersistentTaskQueue` to not store the network requests queue in service worker. Currently only the stock-check and user-data changes were using this queue. The only downside it introuces can be related to the offline mode and these tasks will not be re-executed after connectivity established, but just in a case when the page got reloaded while offline (yeah it might happen using ServiceWorker; `syncTasks` can't be re-populated in cache from SW) - @pkarw (#2985) -- Fixed evaluate detailsLink in the cookie notification - @benjick (#3689) - -## Added - -- Added german translations - @schwerdt-ke (3076) - -## [1.10.3] - 2019.09.18 - -### Fixed - -- Broken sidebar menu in mobile view - @przspa (#3549) -- UrlDispatcher issues with multistore routes - @pkarw (#3568) - -## [1.10.2] - 2019.09.06 - -### Fixed - -- Product image is missing on PDP - @przspa, @NavaneethVijay (#3483) -- Mounting app when routes are resolved, should completly remove recent SSR errors - patzick (#3499) -- Fixed `categoriesDynamicPrefetchLevel` that now can be equal to 0 - @pkarw (#3495) - -## [1.10.1] - 2019.09.03 - -### Fixed - -- Invalid Discount code error handled by theme - @grimasod (#3385) -- Fallback for empty value or no_selection child image - @ngongoll (#3397) -- `order.order_id` was not assigned in the `orders.directBackendSync` mode - @pkarw (#3398) -- Hydration problems with UrlDispatcher :rocket: - @patzick (#3412) -- if condition of quoteId from the `_serverDeleteItem` method on core/modules/cart/store/action.ts - @AshishSuhane (#3415) -- Router beforeEach hooks running many times - @grimasod (#3443) -- test:unit:watch with a workaround of a jest problem with template strings - @resubaka (#3450, #3351) -- changed the theme test path so test in theme are going to work - @resubaka (#3455) - -## [1.10.0] - 2019.08.10 - -### Added - -- Cast cart_id as string - Order schema expects string, Magento does not generate a string as cart id in every case - @DaanKouters (#3097) -- Make installer work for windows - @Flyingmana (#2616) -- "Clear cart" button in the cart - @jablpiotrek (#2587) -- Global config api path under `api.url` - @BartoszLiburski (#2622) -- Google Tag Manager integration - @talalus (#841) -- Portuguese (pt-PT) translation - @xlcnd (#2695) -- Module Mailchimp is removed in favor of more generic Newsletter - @mdesmet (#2558) -- `syncTasks` cleanup, `elasticCacheQuota` lowered to 3096KB - @pkarw (#2729) -- Back-button on orde detail page [#2819] -- Elastic Search Suggestions in the Search Response - @jpetar (#2853) -- Linting for typescript files @ResuBaka (#2843) -- Back to top functionality - @vishal-7037 (#2866) -- Thumbnail sizes are now configurable within the `config.products.thumbnails` and `config.cart.thumbnails` - @pkarw (#2897) -- In multistore mode it's now possible to configure multiple instances with different hosts, not only the paths - @lukeromanowicz (#3048, #3052). -- In multistore mode now there is a possibility to skip appending storecode to url with `appendStoreCode` config option - @lukeromanowicz (#3048, #3052, #3074). -- Add support for api.url in the Task module - @basvanpoppel (#3011) -- Products column change functionality - @vishal-7037 (#3017) -- New Module order-history this provides the pagination via lazy laod - @hackbard (#2810) -- OrderNumber on ThankYouPage - @Flyingmana (#2743) - -### Removed - -- The getter `cart/totals` has ben replaced with `cart/getTotals` - @pkarw (#2522) -- The getter `cart/coupon` has ben replaced with `cart/getCoupon` - @pkarw (#2522) -- The getter `cart/totalQuantity` has ben replaced with `cart/getItemsTotalQuantity` - @pkarw (#2522) -- The event `cart-before-save` has been removed - @pkarw (#2522) -- The action `cart/save` has been removed - @pkarw - (#2522) -- Some deprecated config options: `useShortCatalogUrls` and `setupVariantByAttributeCode` have been removed - @pkarw (#2915) -- Button for filters acceptance added with new styles for clear filters button - @965750 (#2811) -- Added "Clear wishlist" button - @aniamusial (#2806) -- Make all links with the primary color - @hackbard (#2932) - -### Fixed - -- Back button on the Error page has been fixed - @pkarw (#3077) -- Special price got zeroed - @pkarw (#2940) -- Microcart tax + discount totals fix - @pkarw (#2892) -- Microcart offline prices now forced down to original prices - @pkarw (#3012) -- Login/Register errorr message added in case of FetchError (no network connectivity) - @pkarw -- Products removed from the cart are no longer add back on the conectivity return - @pkarw (#2898) -- Sidebar menu wasn't possible to scroll - @PanMisza (#2627) -- Confirmation popup 'Product has beed added to cart' is displayed only once - @JKrupinski (#2610) -- Moved My Account options from Categories - @bartdominiak (#2612) -- Fix displaying (and adding) reviews for configurable products - @afirlejczyk (#2660) -- Image switching fix - @pkarw (#2709) -- Respect store code on order/PROCESS_QUEUE for shop store - @zulcom (#2727) -- Unexpected `window.localStorage` use in user module actions - @zulcom (#2735) -- Fix handling state of same address checkbox in the checkout - @lukeromanowicz (#2730) -- Fix for `everythingNew` collection on home page - @vishal-7037 (#2761) -- Fixed display of chevron arrows when there is only one product image - RGijsberts - (#2911) -- Fixed `Clear cart` option as it previously was not syncing the changes with server - therefore when the user was logged in and cleard the cart all the products were restored - @pkarw (#2587) -- Fixed the cart sync for a rare case that current cart token was empty - @pkarw (#2592) -- Use event bus to emit 'application-after-init' event (#2852) -- Validation of fields 'company name' and 'tax' in checkout doesn't work correctly - @dimasch (#2741) -- Fixed wrong price displayed in instant checkout module - @vishal-7037 (#2884) -- Incorrect working of checkboxes in checkout - @dimasch (#2730) -- Fixed ios input zoom on category page - @victorkadup (#2815) -- Fixed Load more in Search Results not working when typed to fast - @Flyingmana (#2659, #2946) -- Subscribe button responsive - @exlo89, @webdiver, @przemyslawspaczek (#2886) -- Multiple instances for searchAdapter invocations - @bratok (#2960) -- Fixed issue with login popup state not resetting on mobile devices - @aniamusial (#2699) -- Fix sortBy for the category page - @Jensderond (#2868) -- Fixed incorrect prices in Instant Checkout (PR API) - @qiqqq (#2874) -- Fixed placeholders in gallery in offline mode - @przspa (#2863) -- Incorrect `user_id` set on the order object - @pkarw (#2966) -- Problem with SSR render on product page with logged in user - @patzick (#2888) -- NaN displayed as shipping method - button disabled - @aniamusial (#2881) -- Logo on the Error page has been fixed - @przspa (#3077) -- No placeholders / no photos for Get Inspire section in offline - @przspa (#3072) -- Back icon on product page causing inconsistent behavior - @patzick (#3056) -- Remove static definition of `cashondelivery` in payment module - @danielmaier42 (#2983) -- Fixed wrong meta description attribute by page overwrite - @przspa (#3091) -- Fixed the `AddToCart` button behavior in case of synchronization errors - @pkarw (#3150) -- User token re-validation fixed to use proper HTTP codes - @pkarw (#3151, #3178) -- Fixed undefined id of color swatches issue for simple product - @vishal-7037 (#3239) -- Date filter ignoring format param and locales - @grimasod, @patzick (#3102) -- Problem with placing an order if shipping method is different than default one - @patzick (#3203) -- Fixed product video embed on PDP - @juho-jaakkola (#3263) -- Fixed memory leak with loading DayJS in SSR - @lukeromanowicz (#3310) -- Fixed invalid localized routes in SSR content of multistore configuration - @lukeromanowicz (#3262) -- Fixed startSession which loaded from the wrong place the user when multistore was active - @resubaka (#3322) -- Login after registration - @patzick (#3343) -- Clear compare list after logout - @patzick (#3348) - -### Changed / Improved - -- The `cart/sync`, `cart/addItems`, `cart/removeItem` and `cart/updateQuantity` now returns the `diffLog` object with all the notifications, server statuses and items changed during the shopping cart sync -- The `cart/addItem` is no longer displaying the error messages - please use the `diffLog.clientNorifications` to update the UI instead (take a look at the `AddToCart.ts` for a reference) -- The action `cart/userAfterLoggedin` got renamed to `cart/authorize` - @pkarw (#2522) -- The action `cart/refreshTotals` got renamed to `cart/syncTotals` - @pkarw (#2522) -- The action `cart/serverPull` got renamed to `cart/sync` - @pkarw - (#2522) -- The way we're getting the user and cart tokens got refactored - @pkarw (#2513) -- Changed the way to access the configuration. Currently the `rootStore.state.config` is deprecated. Please do use the `import config from 'config'` > `config` instead - @pkarw (#2649) -- Changed the order number (from `entity_id` to `increment_id`) on MyOrders and MyOrder pages - @pkarw (#2743) -- Disabled the server cart sync in case user is in the checkout - @pkarw (#2749) -- Improved ProductGalleryCarousel component to handle nonnumeric options id’s - @danieldomurad (#2586) -- Number of displayed products is now visible on PLP on desktop - @awierzbiak (#2504) -- Improved visibility of product SKU in wishlist - @PanMisza (#2606) -- Instant focus to search input field after click on search icon in navbar - @ca1zr (#2608) -- Login flow from authorized pages after session expired, show the modal with new error message and redirect after login - @gdomiciano, @natalledm (#2674) -- Added support for the newest node version - @gdomiciano (#2669) -- Default storeId from `0` to `1` for multistore and cmsdata logic - @janmyszkier (#2590) -- Used `$bus` plugin instead of EventBus import - @szafran89 (#2630) -- BaseCheckbox now uses v-model. @click is not needed anymore - @haukebri (#2630) -- Image selection supporting multiple configurable options - @mdesmet (#2599) -- Product video - retrieve video id from 'video_id' field (if set) instead of 'id' - @afirlejczyk -- Webpack config improvement - @yogeshsuhagiya (#2689) -- BaseSelect input event - @ResuBaka (#2683) -- Fixed static file handler to immediately return 404 status for missing files - @grimasod (#2685) -- Fixed maxAge Response Header for static files and Content-Type for Service Worker - @grimasod (#2686) -- Default log verbosity is changed to show only errors - @lromanowicz (#2717) -- Remembering last search query - @webdiver, @patzick (#2787) -- Extracted ProductImage component to support faster images loading - @przemyslawspaczek (#2925) -- Improve performace with preventing render 404 page on the server side if some of static content is missed, simple 404 response uses instead - [PHOENIX MEDIA](https://www.phoenix-media.eu/) - Yuri Boyko @yuriboyko, Anton Lobodenko @sniffy1988 (#3002) -- Logger refactor + now it takes `showErrorOnProduction` into account - @lromanowicz - (#2717) -- Jest updated from 24.1 to 24.7 along with typings - @lromanowicz - (#2717) -- Jest globals added to .eslint - @lromanowicz (#2717) -- The default storeId is taken from the configurations - @nuovecode (#2718) -- Multitab cart sync - @BartoszLiburski (#2547) -- Back to login button now shows the Login modal window instead of closing it - @RGijsberts (#2882) -- Status filter in Related Products query (#2805) -- The "Apply button was too big, I have reduced its size - @idodidodi (#2807) -- Added return to shopping button on ThenkYou page - @ZeevGerstner (#2818) -- Added optional attributes to catalog/product.ts - @ZeevGerstner (#2792) -- Formatted dates in CHANGELOG.md to match ISO standard - @phoenixdev-kl (#2839) -- Moved Filter Price Ranges (used for ES aggregations and UI Filter) to the config - @jpetar (#2873) -- Extra space if not found products in everything new section home page - @cnviradiya (#2846) -- Load custom fonts without webfont.js - @jahvi (#2944) -- Added some structured data to product page - @cewald (#2910) -- Improved the Size Guide feature so it opens in a modal popup instead of a new page - @RGijsberts - (#2913) -- Refactored Travis config - @Tjitse-E (#3035) -- Renamed the `stock/check` to `stock/queueCheck` to better emphasize it's async nature; added `stock/check` which does exactly what name suggests - returning the true stock values - @pkarw (#3150) -- Cart unit tests throwing lots of type warnings - @lukeromanowicz (#3185) -- Lack of possibility to mock src modules and theme components - @lukeromanowicz (#3185) -- Outdated signature of Registration hooks for google-tag-manager - @vishal-7037 (#3208) -- Added serveral missing german translations and fixed german language file structure - @unherz (#3202) -- Refactored the informal way of adressing to formal in german translation files - @unherz (#3213) - -## [1.9.2] - 2019.06.10 - -### Fixed - -- Instant Checkout visible on Safari - @przspa (#2991) -- Search Sidebar on Safari - @przspa (#2990) -- Country label style - @przspa (#2989) -- BaseInputNumber for qty of the product in the cart can change by using arrows - @przspa (#2988) -- Category load depending on zoom level - @przspa (#2704) -- Add yarn.lock to dockerfile build - @Flyingmana (#3006) -- Inconsistent behaviour of picture slider on PDP - @przspa (#2757) - -## [1.9.1] - 2019.05.27 - -### Fixed - -- Remove security vulnerabilities by updating project dependencies - @patzick (#2942) -- Fix Configurable Products not accessible in CSR when children visibility is set to "not visible individually" - @revlis-x (#2933) -- ProductTile placeholders are visible on SSR - @patzick (#2939) - -## [1.9.0] - 2019.05.06 - -### Added - -- The Url Dispatcher feature added for friendly URLs. When `config.seo.useUrlDispatcher` set to true the `product.url_path` and `category.url_path` fields are used as absolute URL addresses (no `/c` and `/p` prefixes anymore). Check the latest `mage2vuestorefront` snapshot and reimport Your products to properly set `url_path` fields - #2010 - @pkarw -- Unit tests of cart module written in jest - @lukeromanowicz (#2305) -- validation for UTF8 alpha and alphanumeric characters in most checkout fields - @lromanowicz (#2653) -- helper to process config urls with default endpoint host `config.api.host` - @patzick (#2858) - -### Changed / Improved - -- The `core/helpers` parsing URL methods exchanged to `query-string` package - @pkarw (#2446) -- Unit tests in Karma are now removed in favor of jest - @lukeromanowicz (#2305) -- Material Icons are loaded asynchronously - @JKrupinski, @filrak (#2060) -- Update to babel 7 - @lukeromanowicz (#2554) - -### Fixed - -- For first time setup of the SSR Cache, a local cache-version.json file is required. The path has been removed from .gitignore and a template has been added. - @rio-vps -- Gallery low quality image in offline mode when high quality already cached - @patzick (#2557) -- Payment issue when no address set - @szafran89 (#2593) -- Search component result message when search term is less than 3 letters - @robwozniak (#2561) -- Removed childSku parameter in url for non-configurable products when using urlDispatcher - @Aekal (#2605) -- Image lazy loading after SSR reload - @pkarw (#2641) -- Modules can add custom URL - @pkarw (#2601) -- Url routes fixes - @pkarw (#2598, #2645, #2614) -- Fix for shopping cart actions when the `cartId` has been cleared out - @pkarw (#2567) -- Fixed always common cache issue for multistore - @filrak (#2595) -- Checkout copy address data will sync on later change - @haukebri (#2661) -- Fixed Safari style for sort-by select - @haukebri (#2642) -- fixed My orders in My Profile not refreshed after putting an order - @filrak (#2559) -- Refreshing product page on mobile device - @patzick (#2484) -- ESlint throwing errors about undefined jest globals in tests - @lukeromanowicz (#2702) -- Fixed changing the country when entering shipping address in checkout not updating shipping costs - @revlis-x (#2691) -- Instant Checkout fix - @qiqqq (#2750) -- Infinite loop on multistore page after reload - @patzick (#2713) -- Refreshing MyAccount page on multistore - @patzick (#2780) -- "Toggle password visible" button in password fields works the right way - @lromanowicz (#2772) -- Range queries to elasticsearch - @oskar1233 (#2746) -- BaseInput has min height now to avoid jumping on forms - @patzick (#2771) -- Orders with invalid address don't stack anymore in the queue and have proper notification popup - @AndreiBelokopytov, @lukeromanowicz (#2663) -- Offline orders with out of stock products don't stack anymore and get canceled after going back to online - @lukeromanowicz (#2740) -- Build ServiceWorker on Docker - @patzick (#2793) -- Product image load after comming back to online - @patzick (#2573) -- Insufficent validation for city field in checkout address - @lromanowicz (#2653) -- Incorrect hover activity on the 'filter by categories' in the search view on mobile - @idodidodi (#2783) -- Unit tests written in JavaScript now support async/await functions and dynamic import - @michaelKurowski, @lukeromanowicz (#2851) - -## [1.8.5] - 2019-04-17 - -### Fixed - -- Memory leaks on SSR with Vue.use - @patzick (#2745) - -## [1.8.4] - 2019-03-26 - -### Fixed - -- Problem with incomplete category products load for offline use - @patzick (#2543) -- Category products view crash on scrolling down in offline mode - @patzick (#2569) -- Default propery issue for the col-xs-\* classes - @cnviradiya (#2558) -- Wishlist and compare list not cached properly - @filrak (#2580) - -### Changed / Improved - -- Category and Homepage products are now cached for offline use on SSR entry - @patzick (@1698) - -## [1.8.3] - 2019-03-03 - -### Added - -- Payment Request API integration - @qiqqq (#2306) -- New reactive helper to check online state. Usage: `import { onlineHelper } from '@vue-storefront/core/helpers'` and then `onlineHelper.isOnline` - @patzick (#2510) -- Cart count config, allows you to display the item count instead of a sum of the item quantities - @pauluse (#2483) -- Video support in Product Gallery component. - @rain2o (#2433) - -### Fixed - -- Problem with placing second order (unbinding payment methods after first order) - @patzick (#2195, #2503) -- Remaking order on user orders page - @patzick (#2480) -- Images blinking on category page - @pkarw (#2523) -- state.ts not bound in the module-template - @pkarw (#2496) -- Validation in the Myprofile section for postcode field - @pkarw (#1317) -- Non-integer qty of product added to the cart - @pkarw (#2517) - -### Changed / Improved - -- Fixed an issue where the correct image for a product configuration wasn't set on the product page image carousel. Also added the fix on the productcarousel in the zoom component - @DaanKouters (#2419) -- Way of creating VS Modules was changed to use factory method instead of explict object creation. - @filrak (#2434) -- Added clear filters button on desktop also and only show if filters are applied - @DaanKouters (#2342) -- Improved docs at contributing.md and configuration.md (spelling etc.) - @ruthgeridema (#2421, #2422, #2423, #2425, #2426) -- Fixed design issue of Country label on Edge 17 & Firefox - @ananth-iyer (#2390, #2399) -- Wishlist and compare items are loaded from local cache only once, instead of every time when module component is rendered - @patzick (#2431) -- Country field is filled by first counry from the list in cart in paymen section - @RakowskiPrzemyslaw (#2428) -- Improved product quantity change component in product and cart - @patzick (#2398, #2437) -- Updated to Vue 2.6.6 - @filrak (#2456) -- Null sidebar menu data on static page fixed - @filrak (#2449, #2441) -- Fix cannot edit previous steps in checkout - @filrak, @patzick (#2438) -- Fixed route guard ssr problem - @vue-kacper (#2364) -- Fix links in footer to static pages bug - @filrak (#2452) -- Fix links at docs, Basics/Configuration file explained - @daksamit (#2490) -- Improve images loading on category page, corrected alt view and blinking problem - @patzick (#2465) -- Improve tsconfig for better IDE paths support - @patzick, @filrak (#2474) -- fix breadcrumbs changing too early - @filrak, @pkarw (#2469, #2529) -- improved product gallery load view, shows correct image on reload - @patzick (#2481, #2482, #2488, #2501) -- Fix an issue where the index.html template within a theme is ignored - @EnthrallRecords (#2489) -- Added async sidebar component with async off-screen components error handling and fetch retrying after coming back online - @filrak (#2408, #2451) -- Inconsistent filters behaviour - clear filters on page load - @patzick (#2435) -- fix price is never below 0 and user can't add 0 or below 0 products to cart - @RakowskiPrzemyslaw (#2437) -- Check for placing single order in case of error in any payment module - @patzick (#2409) -- Display prices in products added in offline mode. - @patzick (#2450) -- Updated cypress dependency for e2e tests - @lukeromanowicz (#2518) -- Improved styles on recommendation filters, product tile and numeric input - @patzick (#2458) -- Removed editing mode from My Newsletter section - @aniamusial (#2766) -- Clicking Remake order now adds your items and redirects you to the checkout - @mikesheward (#2710) - -### Deprecated / Removed - -- `@vue-storefront/store` package deprecated - @filrak - -## [1.8.2] - 2019-02-11 - -- Fixed docker-compose configuration for network_mode and TS build config - @lukeromanowicz (#2415) - -## [1.8.1] - 2019-02-10 - -This is hot-fix release for fixing the payment methods switching issue when both: `payments-cash-on-delivery` and `payments-backend-methods` modules enabled. - -### Changed / Improved - -- Fixed doubled invlication of `placeOrder` when both: `payments-cash-on-delivery` and `payments-backend-methods` modules enabled - #2405 - -## [1.8.0] - 2019-02-07 - -Additional migration tips are available [here](https://github.com/vuestorefront/vue-storefront/blob/master/docs/guide/upgrade-notes/README.md). - -### Added - -- Chinese translation added - @wadereye (#2265) -- Categories filter in search view - @kjugi, @patzick (#1710) -- AsyncDataLoader feature - @pkarw (#2300) -- Events list page in docs - @jablpiotrek (#776) -- Keyboard support for account and cookie close buttons - @anqaka (#2258) -- Support typescript in build scripts - @marlass, @patzick (#2260, #2273, #2324) -- Possibility to have sticky notifications - @phoenixdev-kl (#2307) -- Added a scss to manage global form style - @lorenaramonda (#2316) -- Manage products with zero price - @MarcoGrecoBitbull (#2327) -- Hotjar integration - @lukeromanowicz (#840) - -### Changed / Improved - -- Theme structure improvements - @filrak (#2223) -- Type interfaces and refactor - @filrak (#2227, #2267) -- Changed beforeRegistration and afterRegistration hooks signature. Now it contains only one object VSF. The subfields are the same as before so changing `beforeRegistration( Vue, config, store, isServer )` to `beforeRegistration({ Vue, config, store, isServer })`(and same with `afterRegistration`) is enough to make a proper migration to new API. - @filrak (#2330) -- Typo fixes - @youanden, Micheledinocera (#2229, #2329) -- Bundle products price calculation fix - @pkarw (#2371) -- Fixed isServer flag in module registration hooks - @lukeromanowicz (#840) -- Location of type files - @kruchy8 (#2226) -- Improved theme registration - @lukeromanowicz (#2233) -- SSR renderings for logged in users - @vue-kacper (#2234) -- ElasticSearch fuzzy search - @qbo-tech (#2340, #2354) -- Documentation improvements - @martaradziszewska, @wilfriedwolf, @fvillata, @pkarw (#2210, #2244, #2289, #2369) -- Support regional characters in urls - @Aekal (#2243) -- `store/lib/search` has been moved to `core/lib/search` - @lukeromanowicz (#2225) -- `store/lib/multistore` has been moved to `core/lib/multistore` - @lukeromanowicz (#2224) -- BaseSelect syntax improvements - @jszczech (#2237) -- Optional cart discounts display on side cart - @mcspronko (#1758) -- Special price dates checking - backport of @igloczek's (#2245) -- Category filters reset functionality on mobile - @vue-kacper, @patzick, @renatocason (#2262) -- Improve sortBy mobile view - @martaradziszewska (#2251) -- Slide animations to menu, search, wishlist and minicart components - @Aekal (#2256) -- Fixed wishlist store module to not be lazy loaded - @vue-kacper (#2249) -- Share webpack typescript config with Docker container - @lukeromanowicz (#2269) -- After checkout create logged-in cart for logged-in users if using order Direct Backend Sync - @grimasod (#2302) -- Output cache clearing supports versioning - @igloczek (#2333, #2359) -- Cash on delivery + Shipping addresses fixed for virtual products - @pkarw (#2366) -- Improved static pages caching strategy - @pkarw (#2281) -- Magento 2.3 MSI work-around (it's still not supported fully) - @pkarw (#2366) -- Product zoom picture centered - @ptylek (#2178) -- Fixed tracking in analytics module - @jahvi (#2278) -- Improved merge the store modules array with extended module config - @DaanKouters (#2274) -- ElasticSearch fuzzy search, scoring, boosting + other improvements - @qbo-tech (#2340) -- Turned off compression plugin, nginx serves brotli compression  — @patzick (#2254) -- Improved user account menu UX on desktop - @vue-kacper (#2363) -- Added About us missing route - @lorenaramonda (#2320) -- Fixed used variable for products count in category - @renatocason (#2304) -- Override console with logger - @daaru00 (#2235) -- Fixed variable call about feedback email - @PhantomDraven (#2318) -- Output cache clearing versioning - @igloczek (#2333) -- Improved paddings on select fields - @patzick (#2361) -- Fixed lack of modal backdrop - @vue-kacper, @giuliachiola (#2319) -- Form validations and improvements - @vue-kacper (#2348, #2349, #2347) -- Changing product quantity in catr - @mdanilowicz (#2345) -- Product attribute values as array - @afirlejczyk (#2379) -- Improved fetching customAttributes - @afirlejczyk (#2107) -- Removed compare button from product mobile view - @patzick (#2370) -- Configurable options attribute descriptor - @pkarw (#2384) - -## [1.7.3] - 2019-01-31 - -### Fixed - -- Output cache between build, cache versioning added - @igloczek (#2309) -- Missing `no-ssr` wrapper around user specific content, which leads to broken app in production mode - @igloczek (#2314) - -## [1.7.2] - 2019-01-28 - -### Fixed - -- clear search filters on mobile - @patzick (#2282) -- SSR problem on checkout page on reload - @vue-kacper (#2220) -- Improved offline mode handlers - @pkarw (#2217) -- url_key adjustment after m2vs fix - @pkarw (#2215) -- Service worker removed from dev mode because of the side effects - @pkarw -- `networkFirst` first caching strategy for /api/catalog - @pkarw -- SSR detection in components - @patzick (#2173) - -### Added - -- Hotjar extension (#840) - -### Changed - -- compress banner images - @patzick (#2280) -- Dynamic attributes loader (#2137) -- Dynamic categories prefetching (#2076) -- New payment's module architecture (#2135) -- Support regional characters in urls - Backport of @aekal's (#2243) - -### Added - -- Translations of banners - @patzick (#2276) -- Banners title background on mobile - @patzick (#2272) -- New main site look - @patzick (#2266) - -## [1.7.1] - 2019-01-15 - -### Fixed - -- Corrected scrolled sidebar menu position - -## [1.7.0] - 2019-01-15 - -### Added - -- Dynamic categories prefetching — @pkarw #2100 -- Per-route codesplitting for SSR pages — @patzick #2068 -- async/await support — @patzick #2092 -- IndexedDB replacement and new caching mechanism — @pkarw #2112 -- Web Share module — @filrak #2143 -- Backward compatibility option for dynamic attribute loader — @pkarw #2137 -- Japanese translation — @moksahero #2150 -- Dutch translation — @StefGeraets #2163 -- Using meta_title and meta_description fields from Magento on product/category page — @qiqqq #2158 -- Color mapping feature — @pkarw #2175 -- Out of the box GZIP compression and sourcemap removal in prod mode — @patzick #2186 - -### Changed / Improved - -- Invalidate output cache using POST - @Cyclonecode #2084 -- NGNIX installation improvements for docs — @janmyszkier #2080 -- HTML semantics improvements — @patzick #2094 -- Lazy loading of non-critical third party libs and vendor optimization — @patzick @filrak @qiqqq -- Extra NL translation keys — @nlekv #2104 -- Optimization for the number of attributes to be stored in Vuex store — @pkarw #1654 -- Service Worker registration from any route — @patzick #2070 -- Production setup docs improvements — @janmyszkier #2126 -- Various changes and additions to our docs by @NataliaTepluhina -- Payment docs update — @pkarw #2135 -- Added bash command for collecting i18n phrases to docs — @qbo-tech #2149 -- SEO and scrolling performance fixes — @filrak #2066 -- Established Vuex naming conventions. TLDR - we strongly recommend to use vuex getters instead of mapping state itself (#2069) -- IndexedDb changed to LocalStorage + ServiceWorker native caching (#2112) - -### Fixed - -- Fix Notification.vue compiling issue on prod - @ladrua #2079 -- Fix wishlist toggle bug — @shkodasv #2086 -- findConfigurableChildAsync — fix checking stock for configurable child — @afirlejczyk #2097 -- Fix cart synchronization — @valeriish #2106 -- Fix hydration issue for lazy loaded chunks — @patzick #2115 -- Clear missing fields after user logout — @sniffy1988 #2117 -- Fix AMP naming ( ^^ ) for docs -@pgol #2118 -- Fix Cart Configurable Item pulled from Magento — @valeriish #2119 -- Fix product configuration after cart items server pull — @valeriish #2122 -- Fix gallery switching when entering product — @vue-kacper #2123 -- Fix multiple placing order invocation after changing payment methods — @patzick #2133 -- Remove extra space after every comma for non-(multi)select product attributes — @patzick #2133 -- Fix side-menu scrolling — @patzick #2140 -- Fix back button not properly working from a configurable product page — @qiqqq #2151 -- Fix submenu not visible on a deeper level — @patzick #2152 -- vue-carousel removed from homepage - @patzick #2153 #2154 -- Use localized routes for redirects to home page and account page — @grimasod #2157 -- ProductLinks fixed in Related products component — @pkarw #2168 -- Fix Cart Configurable Item pulled from Magento loaded as Simple — @pkarw @valeriish #2169 #2181 - -### Depreciated - -- extendStore depreciation - @filrak #2143 -- ValidationError class depreciation - @filrak #2143 - -## [1.6.0] - 2018-12-05 - -### Added - -- Lazy loading for SSR and non-SSR routes -- app splitted into modules - -### Removed - -- `vsf-payment-stripe` module integration removed from core - -### Changed - -- There is new config option `config.orders.directBackendSync` that changes the behavior of placing an order. Please do read [more on this change](https://github.com/vuestorefront/vue-storefront/commit/e73f2ca19a5d33a39f8b0fd6346543eced24167e) and [more on vue-storefront-api change](https://github.com/vuestorefront/vue-storefront-api/commit/80c497f72362c72983db4fdcac14c8ba6f8729a8) -- ProductSlider, ProductLinks, ProductListing moved to theme. -- Many theme-related logic moved to theme (+ deleted empty core components just with `name`) -- Components required for backward compatibility moved to `compatibility` folder. For all this files you just need to add `compatibility` after `core` in import path to make them work like before. -- Better Vuex extensibility with modules -- VSModule `store` object changed to fulfil need of multiple vuex modules (see modules docs) -- UI Store logic for Microcart moved to cart module -- Extensions are now depreciated, theme-level extensions removed and src-level extension to be depreciated in 1.7 -- Theme-starter depreciated and removed (will be replaced with theme 2.0) -- Header, Form components, (baseCheckbox, BaseInput, BaseRadioButton, BaseSelect, Basetextarea) Loader, MainSlider, Footer, SearchIcon, ForgotPass, SignUp and Modal core components moved to theme -- extendStore deprecaiated and moved to compatibility folder - -## [1.5.0] - 2018-10-22 - -### Added - -- Contact form mailer - #1875 - Akbar Abdrakhmanov @akbarik -- oauth2 configuration in setup - #1865 - Krister Andersson @Cyclonecode -- GraphQL schema extendibility in the API - Yoann Vié -- A lot of new docs - Natalia Tepluhina @NataliTepluhina -- Magento2 integrated importer -- 'Apply' filters button on mobile category - #1709 - Damian Fiałkiewicz @Aekal - -### Changed - -- New Modules API, and base modules (cart, wishlist, newsletter ...) refactored [read more...](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/about-modules.md) - Filip Rakowski @filrak - -### Fixed - -- The `regionId` field added to Order interface - #1258 - Jim Hil @jimcreate78 -- SSR Memory leaks fixed - #1882 Tomasz Duda @tomasz-duda -- E2E tests fixed - #1861 - Patryk Tomczyk @patzik -- UI animations - #1857 - Javier Villanueva @jahvi -- Disabled buttons fixed - #1852 - Patryk Tomczyk @patzik -- Mailchimp / Newsletter modules rebuilt - Filip Rakowski @filrak -- Search component UX fixes - #1862 - Adrian Cagaanan @diboy2 - -## [1.4.0] - 2018-10-05 - -### Added - -- GraphQL support - #1616 - Yuri Boyko @yuriboyko, Vladimir Plastovets @VladimirPlastovets => [PHOENIX MEDIA](https://www.phoenix-media.eu/) -- Layout switching + Advanced output mechanisms - #1787 - Piotr Karwatka @pkarw -- Dynamic config reload - #1800 - Piotr Karwatka @pkarw -- VuePress based docs - #1728 - Natalia Tepluhina - @NataliaTepluhina -- Output Cache - #1664, #1641 - Piotr Karwatka - @pkarw -- Instalation docs improvements - #1735 - Aleksander Grygier - @allozaur -- Magento Product Reviews support - Agata Firlejczyk @afirlejczyk, Tomek Kikowski @qiqqq -- Console silent mode (disabled by default) - #1752 - Piotr Karwatka - @pkarw - -### Changed - -- Please check the [Upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/develop/doc/Upgrade%20notes.md) for the full list - -### Fixed - -- `docker-compose.yml` files updated - @kovinka -- Non-core translations moved to theme resource files (i18n) - #1747 - David Rouyer @DavidRouyer -- Non-core assets moved to the theme - #1739, #1740 - David Rouyer @DavidRouyer -- Bug fixes: #1715, #1718, #1670 -- NPM packages cleanup - #1748 - David Rouyer @DavidRouyer -- Filters were not updating - #1649 - Kacper Wierzbicki @vue-kacper -- Breadcrumbs on the product page - #1745 - Agata Firlejczyk @afirlejczyk -- Infinite scroll on mobile browsers - #1755 - Kacper Wierzbicki @vue-kacper -- Coupon codes - #1759 - Tomek Kikowski @qiqqq - -## [1.3.0] - 2018-08-31 - -### Added - -- TypeScript support - please check [TypeScript Action Plan](https://github.com/vuestorefront/vue-storefront/blob/master/docs/guide/basics/typescript.md) for details -- New `core/modules` added regarding the [Refactor to modules plan](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/refactoring-to-modules.md) -- Price tier's support #1625 -- Qty field on product page #1617 -- Offline orders confirmation dialog has been added #1430 -- `pwa-compat` library has been added to support fully PWA manifests on legacy browsers -- dynamic port allocation #1511 - -### Removed - -- unused `libs`, `components`, `core/api/cart` webpack aliases -- `global.$VS` has been replaced with `rootStore` #1624 - -### Changed - -- `core` directory is now a `@vue-storefront/core` package, webpack alias and all related imports reflect this change [#1513] -- `core/api` renamed to `core/modules`, mixin features moved to `core/modules/module_name/features` -- `core/lib/i18n` moved into separate `@vue-storefront/i18n` package - -### Fixed - -- installer paths are now normalized (to support paths including spaces) #1645 -- status check added to the configurable_children products #1639 -- product info update when clicking the related products #1601 -- media gallery issues + mobile view -- product slider fixes #1561 -- shipping carrier code is now passed with order #1520 -- SEO support fixes #1514 -- UX fixes -- bundle size optimizations (translations) -- password validation rules are now aligned (server/client) #1476 - -## [1.2.0] - 2018-08-01 - -### Fixed - -- Improved integration tests [#1471] -- Minor taxcalc.js improvements [#1467] -- Search by SKU fixed [#1455] -- ProductList dbl click fix [#1438] - -### Added - -- Docker support for vue-storefront -- Production config docs added [#1450] -- Integration tests for Compare products added [#1422] -- Wishlist module refactored to the new core/api standard + unit tests [#1434] -- Dropdown components in MyProfile replaced with the base-select [#1463] -- Magento2/CMS integration by block/page identifiers [#1452] - -## [1.1.0] - 2018-07-02 - -Please keep an eye on the **[UPGRADE NOTES](https://github.com/vuestorefront/vue-storefront/blob/master/doc/Upgrade%20notes.md)** - -### Fixed - -- Zip Code validation [#1372] -- Get inpspired block [#968] -- Favicon [#836] -- Webpack config + refactoring [#1250] -- Account page updates [#1323] -- UI fixes [#901] -- Vuex Store extensions fixes [#1028, #1102] -- MS Edge + IE10 fixes [#1266] -- IndexedDB locking issue - -### Added - -- Added PM2 process manager [#1162] -- Added billing data phone number support [#1338] -- Added validation labels + generic control for CountrySelector [#1227] -- Offline mode Push Notification support [#1348, #1122, #1317] -- Added billing data phone number support [#1338] -- PoC of API refactoring for the cart module [#1316] -- Sort feature added [#671] -- Page loader [#1240] -- Production ready Docker config for vue-storefront-api - -## [1.0.5] - 2018-06-04 - -### Fixed - -- Shipping region fix -- Hotfix for missing config.storeViews.multistore check -- Minor fixes - -## [1.0.4] - 2018-06-02 - -### Fixed - -- defaultCountry fix for IT -- Tax classes hotfix -- tax_class_id is required by taxcalc - restored along with version inc -- Minor fixes - -## [1.0.3] - 2018-06-02 - -### Fixed - -- Minor fixes - -## [1.0.2] - 2018-06-02 - -### Fixed - -- vue-storefront-stripe renamed to vsf-payment-stripe hotfix -- Minor fixes - -## [1.0.1] - 2018-05-31 - -### Fixed - -- Minor fixes - -## [1.0.0] - 2018-05-30 - -### Added - -- **Multistore** - now it's possible to manage the store views with all the features like translations, custom category, and products content, shipping rates - basically all Magento2 features are supported! You can read more on how to setup Multistore here. -- **Bundle products** - support for the Magento-like bundle products with all the custom options, pricing rules etc. -- **Configurable options** - that allows users to select radio/checkbox options + put some custom notes (textboxes) on the products they like to order, -- **Crossell, Upsell, Related products** - are now synchronized with Magento2, -- **Webpack4 support** - we've migrated from Webpack2 -> Webpack4 and now the build process takes much less time while providing some cool new features, -- **Core components refactor** - without changing APIs, we've stripped the core components from s to improve the performance and improve the code readability, -- **PWA Manifest fixes** - iOS PWA support required us to adjust some settings, -- **Improved translations** - we're constantly tweaking the translation files :) We've just added it-IT and pl-PL (finally!) support recently -- **Improved Travis-CI pipeline** - and added support for end-2-end testing, -- **Lot of bugfixes + UX fixes** - countless hours spent on improving the code and UI quality! -- **Please check it out:** visit: https://demo.vuestorefront.io/ - -## [1.0.0-rc.3] - 2018-04-29 - -### Added - -- Performance tweaks: improved service worker config, reduced JSONs, two-stage caching, -- User token auto refresh, -- My Account fixes -- Translations: RU, IT -- UX fixes: navigation, notifications, product compare, product page -- Host and port setup in the config, -- Refactored Vuex store - prepared to be separated as the npm module which will give the Vue.js developers access to Magento backend -- Product Gallery, -- Infinite scroll, -- Product and Category page refactoring - -## [1.0.0-rc.2] - 2018-03-29 - -### Added - -- Basic Magento 1.9 support, -- Translations: ES, DE, NL, FR -- Lerna support for managing the npm packages within the one repository, -- Installer fixes (Linux support), -- Orders history, -- Discount codes support, -- Stripe Payments support, -- External Checkout support for Magento 2.x, -- Basic Travis checks, -- Other fixes. - -## [1.0.0-rc.0] - 2018-03-01 - -### Added - -- i18n (internationalization) support for the UI, -- Support for Magento2 dynamic cart totals - which enables the shopping cart rules mechanism of Magento to work with VS, -- ESlint-plugin-vue installed, -- CSS properties moved to atomic classes -- New SASS structure, -- Architectural change: core extracted from src - preparation for publishing the official npm package, -- Refactored vuex stores - we separated actions, getters and the state for better maintainability, -- UI improvements: look & feel, accessibility, color palette normalization, -- Assets can be now managed by theme developers, -- Service-workers and webpack config can be now extended by theme developers, -- Droppoints shipping methods (NL support) added. - -## [0.4.0] - 2018-01-28 - -### Added - -- Improved theming support + B2B product catalog theme included (original github repo); it's PoC made in just one week! Isn't it amazing that you can customize VS in just one week to this extent? :) -- Pimcore support (more on this, github repo) -- Customer's dashboard + address book, integration with Checkout -- Adjustments on product card on mobile -- Adjustments on home page on mobile -- Rebuilt checkout - UI + customer accounts support -- Google Analytics eCommerce extension -- order_2_magento rebuilt from scratch, supporting customer accounts and authorized carts -- Real-time cart synchronization with Magento - (last step before synchronizing the checkout promo rules with Magento!) -- Product comparison -- Themes refactor -- Lot of smaller tweaks - -## [0.3.0] - 2017-12-21 - -### Added - -- Bundle products support, -- Tax calculation regarding the Magento's logic for different rates per countries, states. -- User registration, log-in, password reset -- Refactor of Product and Category pages with support for updating product photos regarding selected filters (red t-shirts are now red on the list etc) -- MailChimp support, -- Stock Quantity check support -- Special prices for products (catalog rules) are now fully supported for simple, bundled and configurable products, -- 404 page, -- Checkout tweaks and refactor, -- Offline notification badge, -- Wishlist, -- Cookie notification bar -- Security improvements (checksums for client-side processed data) -- Lot of UI tweaks and refactors, -- Updated installer with support for Linux and MacOSX - -## [0.2.1-alpha.0] - 2017-11-16 - -### Added - -- Homepage -- Category page -- Product page -- Cart -- Checkout + validation -- Basic Search -- Magento2 synchronization: products, attributes, media, categories, orders -- Offline support using service workers + indexedDb -- PWA manifest + basic optimizations -- SSR support -- Filters + Configurable products -- RWD (except some checkout issues to be fixed) - -## [0.2.0-alpha.0] - 2017-11-15 - -### Fixed - -- Lazy loaded blocks size fixed diff --git a/CLA.md b/CLA.md new file mode 100644 index 0000000000..55aeac6a3b --- /dev/null +++ b/CLA.md @@ -0,0 +1,24 @@ +**Vue Storefront Individual Contributor License Agreement** + +Thank you for your interest in contributing to open source software projects (“Projects”) made available by VSF Sp. z o.o. (“Vue Storefront”) or its affiliates. This Individual Contributor License Agreement (“Agreement”) sets out the terms governing any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that you submit or have submitted, in any form and in any manner, to Vue Storefront in respect of any of the Projects (collectively “Contributions”). If you have any questions respecting this Agreement, please contact contributors@vuestorefront.io. + +You agree that the following terms apply to all of your past, present and future Contributions. Except for the licenses granted in this Agreement, you retain all of your right, title and interest in and to your Contributions. + +**Copyright License.** You hereby grant, and agree to grant, to Vue Storefront a non-exclusive, perpetual, irrevocable, worldwide, fully-paid, royalty-free, transferable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute your Contributions and such derivative works, with the right to sublicense the foregoing rights through multiple tiers of sublicensees. + +**Patent License.** You hereby grant, and agree to grant, to Vue Storefront a non-exclusive, perpetual, irrevocable, worldwide, fully-paid, royalty-free, transferable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer your Contributions, where such license applies only to those patent claims licensable by you that are necessarily infringed by your Contributions alone or by combination of your Contributions with the Project to which such Contributions were submitted, with the right to sublicense the foregoing rights through multiple tiers of sublicensees. + +**Moral Rights.** To the fullest extent permitted under applicable law, you hereby waive, and agree not to assert, all of your “moral rights” in or relating to your Contributions for the benefit of Vue Storefront, its assigns, and their respective direct and indirect sublicensees. + +**Third Party Content/Rights.** If your Contribution includes or is based on any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that were not authored by you (“Third Party Content”) or if you are aware of any third party intellectual property or proprietary rights associated with your Contribution (“Third Party Rights”), then you agree to include with the submission of your Contribution full details respecting such Third Party Content and Third Party Rights, including, without limitation, identification of which aspects of your Contribution contain Third Party Content or are associated with Third Party Rights, the owner/author of the Third Party Content and Third Party Rights, where you obtained the Third Party Content, and any applicable third party license terms or restrictions respecting the Third Party Content and Third Party Rights. For greater certainty, the foregoing obligations respecting the identification of Third Party Content and Third Party Rights do not apply to any portion of a Project that is incorporated into your Contribution to that same Project. + +**Representations.** You represent that, other than the Third Party Content and Third Party Rights identified by you in accordance with this Agreement, you are the sole author of your Contributions and are legally entitled to grant the foregoing licenses and waivers in respect of your Contributions. If your Contributions were created in the course of your employment with your past or present employer(s), you represent that such employer(s) has authorized you to make your Contributions on behalf of such employer(s) or such employer (s) has waived all of their right, title or interest in or to your Contributions. + + +**Disclaimer.** To the fullest extent permitted under applicable law, your Contributions are provided on an "as- is" basis, without any warranties or conditions, express or implied, including, without limitation, any implied warranties or conditions of non-infringement, merchantability or fitness for a particular purpose. You are not required to provide support for your Contributions, except to the extent you desire to provide support. + +**No Obligation.** You acknowledge that Vue Storefront is under no obligation to use or incorporate your Contributions into any of the Projects. The decision to use or incorporate your Contributions into any of the Projects will be made at the sole discretion of Vue Storefront or its authorized delegates. + +**Disputes.** This Agreement shall be governed by and construed in accordance with the laws of the Delaware, United States of America, without giving effect to its principles or rules regarding conflicts of laws, other than such principles directing application of Delaware law. The parties hereby submit to venue in, and jurisdiction of the courts located in Delaware, US for purposes relating to this Agreement. In the event that any of the provisions of this Agreement shall be held by a court or other tribunal of competent jurisdiction to be unenforceable, the remaining portions hereof shall remain in full force and effect. + +**Assignment.** You agree that Vue Storefront may assign this Agreement, and all of its rights, obligations and licenses hereunder. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index f0c9be787b..5dd51f7a66 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,45 +2,132 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. ## Our Standards -Examples of behavior that contributes to creating a positive environment include: +Examples of behavior that contributes to a positive environment for our +community include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting -## Our Responsibilities +## Enforcement Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contributors@vuestorefront.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +contributors@vuestorefront.io + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ed9425ab97..c57158bce4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,60 +1,62 @@ -# How to Contribute +# Contributing -Already a JavaScript/Vue.js developer? Pick an issue, push a pull request (PR) and instantly become a member of the vue-storefront contributors community. -We've marked some issues as "Easy first pick" to make it easier for newcomers to begin! +We are very happy that you want to contribute to Vue Storefront. +To create a perfect experience for everyone in the community, there are a set of rules and best practices which our repository got. -You can start a ready-to-code development environment in your browser, by clicking the button below: +Please take note of the following guidelines and rules. -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/) +## Found an Issue? -Thank you for your interest in, and engagement! +Thank you for reporting any issues you find. -Before you type an issue please read about out [release lifecycle](https://docs.vuestorefront.io/guide/basics/release-cycle.html). +We do our best to test and make our repository as solid as possible, but any reported issue is a real help. -# Branches +Please follow these guidelines when reporting issues: -You should fork the project or create a branch for new features. -The main branches used by the core team are: +- Provide a title in the format of Ex: `[BUG]: when `, `[Issue]: When I try to , an appears` +- Tag your issue with the tag `triage-needed` +- Provide a short summary of what you are trying to do +- Provide the log of the encountered error if applicable +- Provide the exact version of the framework you are using. +- Be awesome and consider contributing a [pull request](#want-to-contribute) -- master - where we store the stable release of the app (that can be deployed to our demo instances), -- develop - the most recent version of the app - kind of "nightly" build. -- RC-x (`x` is current version) - release candidate branch with features that will land in next version. +## Want to contribute? -Please use "develop" or "RC" for development purposes as the "master" can be merged just as the new release is coming out (about once a month)! +You consider contributing changes to our framework, this is awesome! -## Issue Reporting Guidelines +Please consider these guidelines when filing a pull request: -Always define the type of issue: -* Bug report -* Feature request +- Follow the [Coding Rules](#coding-rules) +- Follow the [Commit Rules](#commit-rules) +- Make sure you rebased the current master branch when filing the pull request +- Squash your commits when filing the pull request +- Provide a short title with a maximum of 100 characters +- Provide a more detailed description containing + _ What you want to achieve + _ What you changed + _ What you added + _ What you removed -While writing issues, be as specific as possible. All requests regarding support with implementation or application setup should be sent to contributors@vuestorefront.io. +## Coding Rules -**Tag your issues properly**. If you found a bug, tag it with `bug` label. If you're requesting new feature, tag it with `feature request` label. +To keep the code base of our repository neat and tidy, we apply a set of rules to every change -## Git Flow +> Coding standards -We're introducing TypeScript to Vue Storefront core, so you can use it where it's appropriate - but please be pragmatic. -Here are some thoughts on how to use TypeScript features in Vue Storefront: [TypeScript Action Plan](https://github.com/vuestorefront/vue-storefront/blob/master/doc/TypeScript%20Action%20Plan.md). +- `eslint` is king +- Favor micro library over swiss army knives (rimraf, ncp vs. fs-extra) - Just in case you really need one :) +- Be awesome -## Pull Request Checklist +## Commit Rules -**ALWAYS** use [Pull Request template](https://github.com/vuestorefront/vue-storefront/blob/master/PULL_REQUEST_TEMPLATE.md) it's automatically added to each PR. -1. Fork the repository and clone it locally from the 'develop' branch. Make sure it's up to date with current `develop` branch -2. Create a branch for your edits. Use the following branch naming conventions: - * bugfix/task-title - * feature/task-name -3. Use Pull Request template and fill as much fields as possible to describe your solution. -4. Reference any relevant issues or supporting documentation in your PR (ex. “Issue: 39. Issue title.”). -5. If you are adding new feature provide documentation along with the PR. Also, add it to [upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/master/doc/Upgrade%20notes.md) -6. If you are removing/renaming something or changing its behavior also include it in [upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/master/doc/Upgrade%20notes.md) -7. Test your changes! Run your changes against any existing tests and create new ones when needed. Make sure your changes don’t break the existing project. Make sure that your branch is passing Travis CI build. -8. If you have found a potential security vulnerability, please DO NOT report it on the public issue tracker. Instead, send it to us at contributors@vuestorefront.io. We will work with you to verify and fix it as soon as possible. -(https://github.com/vuestorefront/vue-storefront/blob/master/README.md#documentation--table-of-contents)) +To help everyone with understanding the commit history of commits the following rules are enforced. -## Acceptance Criteria +To make your life easier our repository is commitizen-friendly and provides the npm run-script `commit`. -Your pull request will be merged after meeting following criteria: -- Everything from "Pull Request Checklist" -- PR is proposed to appropriate branch -- There are at least two approvals from core team members +> Commit standards + +- [conventional-changelog](https://github.com/conventional-changelog) +- husky commit message hook available +- present tense +- maximum of 100 characters +- message format of `$type($scope): $message` diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7a7f031b68..0000000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 Divante Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..2801ae1d5b --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Vue Storefront + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 08e1a8886b..a7cc33ad76 100644 --- a/README.md +++ b/README.md @@ -1,304 +1,72 @@ +![Vue Storefront](https://camo.githubusercontent.com/48c886ac0703e3a46bc0ec963e20f126337229fc/68747470733a2f2f643968687267346d6e767a6f772e636c6f756466726f6e742e6e65742f7777772e76756573746f726566726f6e742e696f2f32383062313964302d6c6f676f2d76735f3062793032633062793032633030303030302e6a7067) +# Vue Storefront 2 - -# Vue Storefront - Headless PWA for any eCommerce +[![Coverage Status](https://coveralls.io/repos/github/vuestorefront/vue-storefront/badge.svg?branch=next) ](https://coveralls.io/github/vuestorefront/vue-storefront/?branch=next) +[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) +[![Discord](https://img.shields.io/discord/770285988244750366?label=join%20discord&logo=Discord&logoColor=white)](https://discord.vuestorefront.io) -build:passed -![version](https://img.shields.io/badge/node-v10.x-blue.svg) -Branch stable -Branch Develop -![Branch Develop](https://img.shields.io/badge/community%20chat-slack-FF1493.svg) +Vue Storefront is the most popular and most advanced Frontend Platform for eCommerce +- [Documentation](https://docs.vuestorefront.io/v2/) +- [Demo](https://demo-ct.vuestorefront.io) +- [Installation](https://docs.vuestorefront.io/v2/general/installation.html) -Vue Storefront is a standalone PWA storefront for your eCommerce, possible to connect with any eCommerce backends, for example: -- Magento -- Shopware -- commercetools -- About You Cloud -- Pimcore/CoreShop -- [BigCommerce](https://github.com/vuestorefront/bigcommerce2vuestorefront) +![Screenshots](https://blog.vuestorefront.io/wp-content/uploads/2020/03/3-views-Vue-Storefront-.png) -Vue Storefront is and always will be **open source** (MIT Licence). Anyone can use and support the project, we want it to be a tool for the improvement of the shopping experience. -The project is in the **production ready** phase. +# Supported platforms -## Links - -- 📘 Documentation: [docs.vuestorefront.io](https://docs.vuestorefront.io) -- 👥 Slack Community: [slack.vuestorefront.io](https://slack.vuestorefront.io/) -- 🐦 Twitter: [@VueStorefront](https://twitter.com/VueStorefront) -- 💬 Forum: [forum.vuestorefront.io](https://forum.vuestorefront.io/) -- 🌟 [Live Projects List](https://www.vuestorefront.io/live-projects/?utm_source=github.com&utm_medium=referral&utm_campaign=readme) - -## How to start? - -Which Vue Storefront should I choose for my next project? - - - If you’re on **Magento 1 or Magento 2** choose Vue Storefront 1.x - with vsf-capybara theme, [Install it using CLI](https://docs.vuestorefront.io/guide/cookbook/setup.html) - - - If you’re on **commercetools / About You Cloud** choose Vue Storefront Next clone it from [`next`](https://github.com/vuestorefront/vue-storefront/tree/next) - - - If you’re on **Shopware 6** go to [`shopware-pwa`](https://github.com/vuestorefront/shopware-pwa) sub-project and use the Shopware PWA powered by Vue Storefront - -Check our Rodmap -> link do [https://github.com/vuestorefront/vue-storefront#roadmap](https://github.com/vuestorefront/vue-storefront#roadmap) - -## About Vue Storefront Next - -We're developing a next version of Vue Storefront on the [`next` branch](https://github.com/vuestorefront/vue-storefront/tree/next). - -We're building the following integrations within Next architecture: - -- Shopware 6 (developer preview) -- Commercetools (developer preview) -- AboutYou Cloud -- Shopify -- Salesforce Commerce Cloud - -You can learn more about Vue Storefront Next from the README on the `next` branch and [this](https://www.youtube.com/watch?v=0e2wyhR0ZyQ&t=3s) video - - - - - - - -
-If you want to learn more about Vue Storefront Next, contribute or build an integration reach to Filip Rakowski on our Slack
- -## About Vue Storefront 1.x - -**Important note to developers:** From 1.0RC we started using [develop](https://github.com/vuestorefront/vue-storefront/tree/develop) branch for nightly builds (contains all new features) and [master](https://github.com/vuestorefront/vue-storefront/tree/master) branch for stable. Please make sure you're working on right branch. Please take a look at [Contributing guidelines](https://github.com/vuestorefront/vue-storefront/blob/master/CONTRIBUTING.md). - - - - - - - - -
-If you're new and need some guidance feel free to visit out forum or reach to Filip Jędrasik (@Fifciuu) from the core team on our Slack
- - - -Want to invest some time in building the future of eCommerce? we are looking for agencies and developers willing to help us make VS even more awesome. Interested - contact `@Filip Rakowski` on slack - -**We are looking for Contributors and Designer willing to help us in the solution development.** - -**Read [contribution rules](https://github.com/vuestorefront/vue-storefront/blob/master/CONTRIBUTING.md) before making any pull request. Pull request that doesn't meet these requirements will not be merged** - -PS: Check [Storefront UI](https://github.com/vuestorefront/storefront-ui/) - our UI library for eCommerce. - - -## See it in action - - - - - - - - -
- - B2C Theme demo - - - Try out our open demo and if you like it first give us some star on Github ★ and then contact us on Slack or via contributors@vuestorefront.io.

This demo site is connected to Magento 2.2 with shopping carts and users synchronization so You can make an order (which unfortunately won't be shipped ;P).

If You like to see Magento 1 integration demo please do contact us. -
- -## 🆕 Capybara Theme - -Starting new project on Vue Storefront? Try out the new Capybara Theme. Based on StorefrontUI Design System - - - - - - - - - -
- - B2C Theme demo - - - Try out our open demo and if you like it first give us some star on Github ★ and then contact us on Vue Storefront Official Slack or via contributors@vuestorefront.io.

This demo site is connected to Magento2.
-
- -## Be up to date with the news - -We're trying to be very open regarding our development plans, news, roadmap and in general: sharing a lot. Please do bookmark our [Official blog](https://blog.vuestorefront.io/) to be always up to date! - -## Foundations | Vue Storefront 1.x - -[![See how it works!](https://blog.vuestorefront.io/wp-content/uploads/2020/03/Foundations-_-Vue-Storefront.png)](https://www.youtube.com/watch?v=o10oinxCYfY&list=PLIDwzUVxSXjN9pK1NzBTcirV3-K0OCay0) - -## How to install Vue Storefront on Windows? - -[![Demo and the architecture of Vue Storefront](https://blog.vuestorefront.io/wp-content/uploads/2020/03/How-to-install-Vue-Storefront-on-Windows_.png)](https://www.youtube.com/watch?v=zL_T3WzVLG0&list=PLIDwzUVxSXjN9pK1NzBTcirV3-K0OCay0&index=2) - -- [Read on how to integrate it with Magento2](https://docs.vuestorefront.io/guide/installation/magento.html#using-native-magento-2-module), [Read how to create Vue Storefront theme](https://docs.vuestorefront.io/guide/core-themes/themes.html), -- [Read the interviews with authors of first production deployments](http://blog.vuestorefront.io/vsf-on-production-interview-with-gogetgold-com/) - -## Is it production ready? - -Yes! There are more than **140 implementations** happening right now and many live shops (check [awesome live projects on Vue Storefront](https://www.vuestorefront.io/live-projects/?utm_source=github.com&utm_medium=referral&utm_campaign=readme)). - -## Browser Compatibility - -* last 2 Chrome versions -* last 2 Firefox versions -* last 2 Edge versions -* modern browsers - -For an up-to-date list of supported browsers please see "browserslist" in package.json - -## Join the community on Slack - -If you have any questions or ideas feel free to join our slack via invitation link: [https://slack.vuestorefront.io/](https://slack.vuestorefront.io/) - -## Roadmap +- [Commercetools](https://github.com/vuestorefront/commercetools) +- [Shopify](https://github.com/vuestorefront/shopify) +- [Magento 2](https://github.com/vuestorefront/magento2) [Beta] +- Spryker [Beta] +- [Salesforce Commerce Cloud](https://github.com/vuestorefront/salesforce-commerce-cloud) [Beta] -[Here](https://github.com/vuestorefront/vue-storefront/milestones) you can find the accepted roadmap for current milestone and what you can expect with next release. -#### Roadmap planning -[Here](https://github.com/vuestorefront/vue-storefront/projects/3) you can vote for feature requests and see which ones were accepted. The most upvoted ones will be added to the next milestones. You can also join the slack channel `#roadmap-planning` where we discuss the next milestones. +[Learn more about available integrations](https://docs.vuestorefront.io/v2/integrations/) -The process of adding new features to the roadmap looks like this: -1. You create an issue and label it as `feature request`. -2. One of VS Core team verifies the feature request and if the explanation is clear, it is added to the `Roadmap` project so it's visible in the board. -3. Now people can vote for this feature to be added into next milestone with `thumb up` emoji. -4. Feature requests with the biggest popularity will be added into next milestones. -We are planning 1-2 milestones ahead. Our milestones are based on requirements from community, partners and production implementations. - -Please note that bugfixes are treated separately and in most cases added to the milestones immediately. - - - -[Check the feature list of 1.0](https://docs.vuestorefront.io/guide/basics/feature-list.html). - -If youd like to take part in roadmap planning feel free to join #roadmap-planning channel on our slack - -## Documentation + table of contents - -The documentation is always THE HARDEST PART of each open source project! But we're trying hard. - -**Please find out what we've already managed to prepare:** [available on Github Pages](https://docs.vuestorefront.io/). Please note that new docs are still Work In Progress and will be successfully updated. You can find them also under the `docs` folder. - -You can find some tutorials and explanations on our [YouTube channel](https://www.youtube.com/vuestorefront) - -### Installation - -- [Starter pack for install](https://docs.vuestorefront.io/guide/cookbook/setup.html) -- [Installing on Linux/MacOS](https://docs.vuestorefront.io/guide/installation/linux-mac.html) -- [Installing on Windows](https://docs.vuestorefront.io/guide/installation/windows.html) -- [How to install and integrate with Magento2](https://docs.vuestorefront.io/guide/installation/magento.html) -- [Production setup](https://docs.vuestorefront.io/guide/installation/production-setup.html) - -### Cookbooks - -- [Ch1. Data Imports](https://docs.vuestorefront.io/guide/cookbook/data-import.html) -- [Ch2. Elasticsearch in the VSF context](https://docs.vuestorefront.io/guide/cookbook/elastic.html) -- [Ch3. Starter pack for new comers (Install)](https://docs.vuestorefront.io/guide/cookbook/setup.html) -- [Ch5. Building a Module from scratch](https://docs.vuestorefront.io/guide/cookbook/module.html) -- [Ch6. Theming in depth](https://docs.vuestorefront.io/guide/cookbook/theme.html) -- [Chef's secret note: Hardcore training for serious business](https://docs.vuestorefront.io/guide/cookbook/checklist.html) - -## Awesome projects on Vue Storefront - -Check [**Vue Storefront Live Projects**](https://www.vuestorefront.io/live-projects/?utm_source=github.com&utm_medium=referral&utm_campaign=readme) - -![Vue Storefront Live Projects](https://uploads-ssl.webflow.com/5e7cf661c23ac9df156d9c3d/5eff4e5149334a10bb672790_GitHub_Live%20Projects.png) +## Links +- 📘 Documentation: [docs.vuestorefront.io](https://docs.vuestorefront.io/v2/) +- 👥 Discord Community: [discord.vuestorefront.io](https://discord.vuestorefront.io/) +- 🐦 Twitter: [@VueStorefront](https://twitter.com/VueStorefront) +- 💬 Forum: [forum.vuestorefront.io](https://forum.vuestorefront.io/) +- 🌟 [Live Projects List](https://www.vuestorefront.io/live-projects/?utm_source=github.com&utm_medium=referral&utm_campaign=readme) ## The business challenges -Vue Storefront was created to solve a set of key business challenges from the world of the shopping experience. Our goal for the application is to provide the solution with: - -- The ultrafast front-end for the store - with the PWA approach we can now render the catalog of products within milliseconds; -- The endurance for traffic overloads on the store; -- The off-line shopping capabilities; -- The smooth shopping experience close to the user experience from the native mobile applications; -- The all-in-one front-end for desktop and mobile screens with no necessity for maintaining 3 or more applications for different touchpoints (web browser, Android, iOS etc.). -- Rapid development without architecture limitations. - -## The technology +Vue Storefront solves a set of key business challenges from the world of the shopping experience. Our goal is to provide the solution with: -Vue Storefront was built as an all-in-one front-end for eCommerce. For providing the best performance we decided to use Vue.js as a front-end library, Node.js + Express (and maybe GraphQL support) as a server-API, Elastic Search as a database of products and full PWA/off-line support. -Here you can read more about the proof of concept for [Vue Storefront connected with Magento2](https://www.linkedin.com/pulse/magento2-nosql-database-pwa-support-piotr-karwatka). +- ultrafast front-end for the store - with the PWA approach, we can now render the catalog of products within milliseconds; +- endurance for traffic overloads on the store; +- off-line shopping capabilities; +- smooth shopping experience close to the user experience from the native mobile applications; +- all-in-one front-end for desktop and mobile screens with no necessity for maintaining 3 or more applications for different touchpoints (web browser, Android, iOS, etc.). +- rapid development without architecture limitations. -Besides a big improvement for the shopping experience, we also want to create a great code base for every developer who needs to work on a front-end application for the eCommerce. ## The headless architecture ![Vue Storefront - Headless Architecture](https://uploads-ssl.webflow.com/5e7cf661c23ac9df156d9c3d/5eff4a2497a1546ca057dcca_github_headless_architecture.png) -## Design - -Vue Storefront supports by default **two different themes:** -1. **[Capybara Theme based on Storefront UI](https://github.com/vuestorefront/vsf-capybara)** - -Vue Storefront - Capybara Theme - - -2. **[Classic/Default ](https://github.com/vuestorefront/vsf-default)** - -The application is prepared to be fully customized in design through the theming system. -With the current version we work on raw, basic template of typical eCommerce for a fashion industry. In the project we used [Material Icons](https://github.com/google/material-design-icons). - -Vue Storefront - Annimations in sidebar menu - -Here you can read more about the process of [designing PWA for eCommerce](https://www.linkedin.com/pulse/designing-pwa-ecommerce-karl-bzik/). - -The design is available in open source in the Figma file format under the URL https://www.figma.com/file/VKyqbHFI55TKIKcQlFLiVpVF/Vue-Storefront-Open-Source. - -## Concerns when hosting -When hosting NodeJS applications there are some differences compared to, for example, hosting PHP or Java applications. -Server Side Rendering via NodeJS can have memory leaks because of suboptimal code. Although core code is optimized, project specific features or misaligned hosting configuration can introduce this. More on how to avoid these for VueJS can be ready in [this article](https://vuejs.org/v2/cookbook/avoiding-memory-leaks.html). We also recommend reading about [VueJS best practices](https://blog.usejournal.com/vue-js-best-practices-c5da8d7af48d). - - On the server we advice to run [PM2](http://pm2.keymetrics.io/) which offers features to keep your NodeJS application stable. When hosting on Kubernetes the checks and memory limits can be leveraged to kill unhealthy containers. -More on hosting can be found in [the documentation](https://docs.vuestorefront.io/guide/installation/production-setup.html#production-setup-bare-vps). - -## Other platforms -Vue Storefront is platform agnostic which means it can be connected to virtually any CMS. Please take a look at [Pimcore bridge](https://github.com/vuestorefront/coreshop-vsbridge) to give you an idea of how other platforms can be connected. Any support for integrating Prestashop, Shopify ... - much appreciated. ## Contributing -If you like the idea behind Vue Storefront and want to become a contributor - do not hesitate and check our [list of the active issues](https://github.com/vuestorefront/vue-storefront/issues) or contact us directly via contributors@vuestorefront.io. - -If you have discovered a 🐜 or have a feature suggestion, feel free to create an issue on Github. +If you like the ideas behind Vue Storefront and want to become a contributor - join our [Discord server](https://discord.vuestorefront.io), check the [list of the active issues](https://github.com/vuestorefront/vue-storefront/issues) or contact us directly via contributors@vuestorefront.io. -## Workshops +If you have discovered a 🐜 or have feature suggestion, feel free to create an issue on Github. -If you like our project and would like to learn more on how to create Progressive Web Apps you can ask us for a dedicated workshop at your office! Conducted by Vue Storefront core contributors! All the profits are used for supporting Vue Storefront development. [Learn more]([https://divante.com/products/vue-storefront](https://divante.com/products/vue-storefront)) ## Support us! **Vue Storefront is and always will be Open Source, released under MIT Licence.** -Most of the core team members, VS contributors and contributors in the ecosystem do this open source work in their free time. If you use Vue Storefront for a serious task, and you'd like us to invest more time on it, you can donate the project! You can support us in various ways: +You can support us in various ways: - **Contribute** - this is how the Core Team is supporting the project! -- **Evangelize** - tweet about us, take some speaking slot at tech conference etc. -- **Sponsor** - if you're doing serious business on VS maybe You would like to donate the project and put your logo in here? - -This is how we will use the donations: - -- Allow the core team to work on VS -- Thank contributors if they invested a large amount of time in contributing -- Support projects in the ecosystem that are of great value for users -- Infrastructure cost -- Fees for money handling - -**If you would like to support us please just let us know: contributors@vuestorefront.io** +- **Evangelize** - tweet about us, take some speaking slot at a tech conference, etc. ## Partners @@ -306,21 +74,7 @@ Vue Storefront is a Community effort brought to You by our great Core Team and s [**See Vue Storefront partners directory**](https://www.vuestorefront.io/partner-agencies?utm_source=github.com&utm_medium=referral&utm_campaign=readme) -![enter image description here](https://uploads-ssl.webflow.com/5e7cf661c23ac9df156d9c3d/5eff4e56262af66301c950e4_github_partner_agencies.png) - - -Partners are encouraged to support the project in various ways - mostly by contributing the source code, marketing activities, evangelizing and of course - implementing the production projects. We do support our partners by dedicated contact channels, workshops and by sharing the leads from merchants interested in implementations. - -If you like to become our Partner just let us know via contributors@vuestorefront.io. - -## The screenshots - -Vue Storefront - Annimations in the sidebar cart - - - -## The license Vue Storefront source code is completely free and released under the [MIT License](https://github.com/vuestorefront/vue-storefront/blob/master/LICENSE). -[![analytics](http://www.google-analytics.com/collect?v=1&t=pageview&_s=1&dl=https%3A%2F%2Fgithub.com%2FDivanteLtd%2Fvue-storefront&_u=MAC~&cid=1757014354.1393964045&tid=UA-108235765-10)]() \ No newline at end of file +[![analytics](http://www.google-analytics.com/collect?v=1&t=pageview&_s=1&dl=https%3A%2F%2Fgithub.com%2FDivanteLtd%2Fvue-storefront&_u=MAC~&cid=1757014354.1393964045&tid=UA-108235765-10)]() diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 5219ca3b33..0000000000 --- a/babel.config.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - presets: [ - [ - '@babel/preset-env', - { - 'modules': false, - 'useBuiltIns': 'entry', - 'corejs': 2 - } - ] - ], - plugins: ['@babel/plugin-syntax-dynamic-import'], - env: { - test: { - plugins: ['transform-es2015-modules-commonjs', 'babel-plugin-dynamic-import-node'], - ignore: [/node_modules\/(?!lodash-es|@vue\/test-utils)/] - } - } -} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..850aad312a --- /dev/null +++ b/codecov.yml @@ -0,0 +1,24 @@ +comment: false + +coverage: + precision: 2 + round: down + range: "70...100" + + status: + patch: no + project: + default: no + commercetools-api-client: + flags: commercetools-api-client + target: 90% + commercetools-composables: + flags: commercetools-composables + target: 90% +flags: + commercetools-api-client: + paths: + - packages/commercetools/api-client + commercetools-composables: + paths: + - packages/commercetools/composables diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000000..6fdb2aef92 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,6 @@ +module.exports = { + extends: [ + '@commitlint/config-conventional', + '@commitlint/config-lerna-scopes' + ] +}; diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json deleted file mode 100644 index d4816ac526..0000000000 --- a/config/custom-environment-variables.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "server": { - "host": "BIND_HOST", - "port": "BIND_PORT" - } -} diff --git a/config/default.json b/config/default.json deleted file mode 100644 index 16199e4c71..0000000000 --- a/config/default.json +++ /dev/null @@ -1,906 +0,0 @@ -{ - "server": { - "host": "localhost", - "port": 3000, - "protocol": "http", - "api": "api", - "devServiceWorker": false, - "useHtmlMinifier": true, - "htmlMinifierOptions": { - "minifyJS": true, - "minifyCSS": true - }, - "useOutputCacheTagging": false, - "useOutputCache": false, - "outputCacheDefaultTtl": 86400, - "availableCacheTags": [ - "attribute", - "C", - "category", - "checkout", - "compare", - "error", - "home", - "my-account", - "P", - "page-not-found", - "product", - "taxrule" - ], - "invalidateCacheKey": "aeSu7aip", - "invalidateCacheForwarding": false, - "invalidateCacheForwardUrl": "http://localhost:8080/invalidate?key=aeSu7aip&tag=", - "dynamicConfigReload": true, - "dynamicConfigReloadWithEachRequest": false, - "dynamicConfigContinueOnError": false, - "dynamicConfigExclude": [ - "entities", - "boost", - "localForage", - "query", - "shipping", - "ssr", - "storeViews" - ], - "dynamicConfigInclude": [], - "elasticCacheQuota": 4096, - "ssrDisabledFor": { - "extensions": [ - "css", - "eot", - "gif", - "ico", - "jpg", - "jpeg", - "js", - "json", - "png", - "raw", - "svg", - "tiff", - "tif", - "ttf", - "woff", - "woff2" - ] - }, - "trace": { - "enabled": false, - "config": {} - }, - "helmet": { - "enabled": true - } - }, - "initialResources": [ - { - "filters": ["vsf-newsletter-modal", "vsf-languages-modal", "vsf-layout-empty", "vsf-layout-minimal", "vsf-order-confirmation", "vsf-search-panel"], - "type": "script", - "onload": true, - "rel": "prefetch" - }, - { - "filters": ["vsf-category", "vsf-home", "vsf-not-found", "vsf-error", "vsf-product", "vsf-cms", "vsf-checkout", "vsf-compare", "vsf-my-account", "vsf-static", "vsf-reset-password"], - "type": "script", - "onload": true, - "rel": "prefetch" - } - ], - "staticPages": { - "updateOnRequest": true, - "destPath": "static" - }, - "seo": { - "useUrlDispatcher": true, - "disableUrlRoutesPersistentCache": true, - "defaultTitle": "Vue Storefront" - }, - "console": { - "showErrorOnProduction": false, - "verbosityLevel": "display-everything" - }, - "redis": { - "host": "localhost", - "port": 6379, - "db": 0 - }, - "graphql": { - "host": "localhost", - "port": 8080 - }, - "api": { - "url": "http://localhost:8080", - "saveBandwidthOverCache": true - }, - "elasticsearch": { - "httpAuth": "", - "host": "/api/catalog", - "index": "vue_storefront_catalog", - "min_score": 0.02, - "csrTimeout": 5000, - "ssrTimeout": 1000, - "queryMethod": "GET", - "disablePersistentQueriesCache": true, - "searchScoring": { - "attributes": { - "attribute_code": { - "scoreValues": { - "attribute_value": { - "weight": 1 - } - } - } - }, - "fuzziness": 2, - "cutoff_frequency": 0.01, - "max_expansions": 3, - "minimum_should_match": "75%", - "prefix_length": 2, - "boost_mode": "multiply", - "score_mode": "multiply", - "max_boost": 100, - "function_min_score": 1 - }, - "searchableAttributes": { - "name": { - "boost": 4 - }, - "sku": { - "boost": 2 - }, - "category.name": { - "boost": 1 - } - } - }, - "ssr": { - "templates": { - "default": "dist/index.html", - "minimal": "dist/index.minimal.html", - "basic": "dist/index.basic.html", - "amp": "dist/index.amp.html" - }, - "lazyHydrateFor": [ - "category-next.products", - "homepage.new_collection" - ], - "executeMixedinAsyncData": true, - "initialStateFilter": [ - "__DEMO_MODE__", - "version", - "storeView", - "attribute.list_by_id" - ], - "useInitialStateFilter": true - }, - "queues": { - "maxNetworkTaskAttempts": 1, - "maxCartBypassAttempts": 1 - }, - "defaultStoreCode": "", - "storeViews": { - "multistore": false, - "commonCache": false, - "mapStoreUrlsFor": [ - "de", - "it" - ], - "de": { - "storeCode": "de", - "storeId": 3, - "name": "German Store", - "url": "/de", - "appendStoreCode": true, - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_de" - }, - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - }, - "seo": { - "defaultTitle": "Vue Storefront" - } - }, - "it": { - "extend": "de", - "storeCode": "it", - "storeId": 4, - "name": "Italian Store", - "url": "/it", - "appendStoreCode": true, - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_it" - }, - "tax": { - "defaultCountry": "IT" - }, - "i18n": { - "fullCountryName": "Italy", - "fullLanguageName": "Italian", - "defaultCountry": "IT", - "defaultLanguage": "IT", - "defaultLocale": "it-IT" - }, - "seo": { - "defaultTitle": "Vue Storefront" - } - } - }, - "entities": { - "optimize": true, - "twoStageCaching": true, - "optimizeShoppingCart": true, - "optimizeShoppingCartOmitFields": [ - "category", - "category_ids", - "configurable_children", - "configurable_options", - "description", - "media_gallery", - "product_links", - "stock" - ], - "category": { - "includeFields": [ - "children_count", - "id", - "is_active", - "level", - "name", - "parent_id", - "path", - "position", - "product_count", - "sku", - "url_key", - "url_path", - "*.children_data.id", - "*.id" - ], - "excludeFields": [ - "sgn" - ], - "filterFields": {}, - "breadcrumbFilterFields": {}, - "categoriesRootCategorylId": 2, - "categoriesDynamicPrefetchLevel": 2, - "categoriesDynamicPrefetch": true, - "validSearchOptionsFromRouteParams": [ - "url-key", - "slug", - "id" - ] - }, - "attribute": { - "includeFields": [ - "activity", - "attribute_code", - "attribute_id", - "default_frontend_label", - "default_value", - "entity_type_id", - "frontend_input", - "frontend_label", - "id", - "is_user_defined", - "is_visible_on_front", - "is_visible", - "is_comparable", - "options", - "tier_prices" - ], - "loadByAttributeMetadata": false - }, - "productList": { - "sort": "updated_at:desc", - "includeFields": [ - "activity", - "configurable_children.attributes", - "configurable_children.id", - "configurable_children.final_price", - "configurable_children.color", - "configurable_children.original_price", - "configurable_children.original_price_incl_tax", - "configurable_children.price", - "configurable_children.price_incl_tax", - "configurable_children.size", - "configurable_children.sku", - "configurable_children.special_price", - "configurable_children.special_price_incl_tax", - "configurable_children.tier_prices", - "final_price", - "id", - "image", - "name", - "new", - "original_price_incl_tax", - "original_price", - "price", - "price_incl_tax", - "product_links", - "sale", - "special_price", - "special_to_date", - "special_from_date", - "special_price_incl_tax", - "status", - "tax_class_id", - "tier_prices", - "type_id", - "url_path", - "url_key", - "*image", - "*sku", - "*small_image" - ], - "excludeFields": [ - "attribute_set_id", - "configurable_options", - "description", - "sgn", - "*.sgn", - "msrp_display_actual_price_type", - "*.msrp_display_actual_price_type", - "required_options", - "media_gallery", - "stock.use_config_min_qty", - "stock.use_config_notify_stock_qty", - "stock.stock_id", - "stock.use_config_backorders", - "stock.use_config_enable_qty_inc", - "stock.enable_qty_increments", - "stock.use_config_manage_stock", - "stock.use_config_min_sale_qty", - "stock.notify_stock_qty", - "stock.use_config_max_sale_qty", - "stock.use_config_max_sale_qty", - "stock.qty_increments", - "stock.stock_status_changed_auto", - "stock.show_default_notification_message", - "stock.use_config_qty_increments", - "stock.is_decimal_divided" - ] - }, - "productListWithChildren": { - "includeFields": [ - "activity", - "configurable_children.attributes", - "configurable_children.image", - "configurable_children.sku", - "configurable_children.price", - "configurable_children.special_price", - "configurable_children.price_incl_tax", - "configurable_children.special_price_incl_tax", - "configurable_children.original_price", - "configurable_children.original_price_incl_tax", - "configurable_children.color", - "configurable_children.size", - "configurable_children.id", - "configurable_children.tier_prices", - "configurable_children.special_to_date", - "configurable_children.special_from_date", - "configurable_children.regular_price", - "configurable_children.final_price", - "final_price", - "id", - "image", - "name", - "new", - "original_price", - "original_price_incl_tax", - "price", - "price_incl_tax", - "product_links", - "sale", - "sku", - "special_price", - "special_price_incl_tax", - "special_from_date", - "special_to_date", - "status", - "tax_class_id", - "tier_prices", - "type_id", - "url_path", - "url_key" - ], - "excludeFields": [ - "attribute_set_id", - "description", - "sgn", - "*.sgn", - "msrp_display_actual_price_type", - "*.msrp_display_actual_price_type", - "required_options", - "media_gallery", - "stock.use_config_min_qty", - "stock.use_config_notify_stock_qty", - "stock.stock_id", - "stock.use_config_backorders", - "stock.use_config_enable_qty_inc", - "stock.enable_qty_increments", - "stock.use_config_manage_stock", - "stock.use_config_min_sale_qty", - "stock.notify_stock_qty", - "stock.use_config_max_sale_qty", - "stock.use_config_max_sale_qty", - "stock.qty_increments", - "stock.stock_status_changed_auto", - "stock.show_default_notification_message", - "stock.use_config_qty_increments", - "stock.is_decimal_divided" - ] - }, - "review": { - "excludeFields": [ - "review_entity", - "review_status" - ] - }, - "product": { - "excludeFields": [ - "attribute_set_id", - "created_at", - "has_options", - "msrp_display_actual_price_type", - "*.msrp_display_actual_price_type", - "options_container", - "required_options", - "small_image", - "stock.enable_qty_increments", - "stock.is_decimal_divided", - "stock.manage_stock", - "stock.notify_stock_qty", - "stock.qty_increments", - "stock.show_default_notification_message", - "stock.stock_id", - "stock.stock_status_changed_auto", - "stock.use_config_qty_increments", - "stock.use_config_min_qty", - "stock.use_config_notify_stock_qty", - "stock.use_config_backorders", - "stock.use_config_enable_qty_inc", - "stock.use_config_manage_stock", - "stock.use_config_min_sale_qty", - "stock.use_config_max_sale_qty", - "sgn", - "*.sgn", - "updated_at" - ], - "includeFields": null, - "useDynamicAttributeLoader": true, - "standardSystemFields": [ - "category", - "category_ids", - "color_options", - "configurable_children", - "configurable_options", - "custom_attributes", - "custom_design_from", - "description", - "erin_recommends", - "errors", - "final_price", - "final_price_incl_tax", - "final_price_tax", - "gift_message_available", - "id", - "image", - "info", - "is_configured", - "links", - "max_price", - "max_regular_price", - "media_gallery", - "minimal_regular_price", - "minimal_price", - "name", - "news_from_date", - "original_price", - "original_price_incl_tax", - "options", - "parentSku", - "priceTax", - "priceInclTax", - "product_option", - "price", - "price_incl_tax", - "price_tax", - "qty", - "regular_price", - "size_options", - "sku", - "slug", - "specialPriceInclTax", - "specialPriceTax", - "special_price_tax", - "special_price_incl_tax", - "special_from_date", - "special_price", - "status", - "stock", - "_score", - "tax_class_id", - "thumbnail", - "tsk", - "type_id", - "url_key", - "url_path", - "visibility" - ] - } - }, - "cart": { - "thumbnails": { - "width": 150, - "height": 150 - }, - "serverMergeByDefault": true, - "serverSyncCanRemoveLocalItems": false, - "serverSyncCanModifyLocalItems": false, - "synchronize": true, - "synchronize_totals": true, - "setCustomProductOptions": true, - "setConfigurableProductOptions": true, - "askBeforeRemoveProduct": true, - "displayItemDiscounts": true, - "productsAreReconfigurable": true, - "minicartCountType": "quantities", - "create_endpoint": "/api/cart/create?token={{token}}", - "updateitem_endpoint": "/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "/api/cart/totals?token={{token}}&cartId={{cartId}}", - "paymentmethods_endpoint": "/api/cart/payment-methods?token={{token}}&cartId={{cartId}}", - "shippingmethods_endpoint": "/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}", - "shippinginfo_endpoint": "/api/cart/shipping-information?token={{token}}&cartId={{cartId}}", - "collecttotals_endpoint": "/api/cart/collect-totals?token={{token}}&cartId={{cartId}}", - "deletecoupon_endpoint": "/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}", - "applycoupon_endpoint": "/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}" - }, - "attributes": { - "disablePersistentAttributesCache": false - }, - "products": { - "fieldsToCompact": { - "minimal_price": "mp", - "has_options": "ho", - "url_key": "u", - "status": "s", - "required_options": "ro", - "name": "nm", - "tax_class_id": "tci", - "description": "desc", - "minimal_regular_price": "mrp", - "final_price": "fp", - "price": "p", - "special_price": "sp", - "original_final_price": "ofp", - "original_price": "op", - "original_special_price": "osp", - "final_price_incl_tax": "fpit", - "original_price_incl_tax": "opit", - "price_incl_tax": "pit", - "special_price_incl_tax": "spit", - "final_price_tax": "fpt", - "price_tax": "pt", - "special_price_tax": "spt", - "original_price_tax": "opt", - "image": "i", - "small_image": "si", - "thumbnail": "t" - }, - "disablePersistentProductsCache": true, - "useMagentoUrlKeys": true, - "setFirstVarianAsDefaultInURL": false, - "configurableChildrenStockPrefetchStatic": false, - "configurableChildrenStockPrefetchDynamic": true, - "configurableChildrenStockPrefetchStaticPrefetchCount": 8, - "filterUnavailableVariants": false, - "listOutOfStockProducts": true, - "preventConfigurableChildrenDirectAccess": true, - "alwaysSyncPlatformPricesOver": false, - "alwaysSyncPricesClientSide": false, - "clearPricesBeforePlatformSync": false, - "waitForPlatformSync": false, - "setupVariantByAttributeCode": true, - "omitVariantFields": [ - "name", - "visibility" - ], - "calculateBundlePriceByOptions": true, - "endpoint": "/api/product", - "defaultFilters": [ - "color", - "erin_recommends", - "price", - "size" - ], - "systemFilterNames": [ - "sort" - ], - "maxFiltersQuerySize": 999, - "routerFiltersSource": "query", - "filterFieldMapping": { - "category.name": "category.name.keyword" - }, - "colorMappings": { - "Melange graphite": "#eeeeee" - }, - "defaultSortBy": { - "attribute": "updated_at", - "order": "desc" - }, - "sortByAttributes": { - "Latest": "updated_at:desc", - "Price: Low to high": "final_price", - "Price: High to low": "final_price:desc" - }, - "gallery": { - "mergeConfigurableChildren": true, - "imageAttributes": [ - "image", - "thumbnail", - "small_image" - ], - "width": 600, - "height": 744 - }, - "thumbnails": { - "width": 310, - "height": 300 - }, - "filterAggregationSize": { - "default": 10, - "size": 10, - "color": 10 - }, - "priceFilterKey": "final_price", - "priceFilters": { - "ranges": [ - { "from": 0, "to": 50 }, - { "from": 50, "to": 100 }, - { "from": 100, "to": 150 }, - { "from": 150 } - ] - }, - "aggregate": { - "maxPrice": false, - "minPrice": false - } - }, - "orders": { - "directBackendSync": true, - "endpoint": "/api/order", - "payment_methods_mapping": { - }, - "offline_orders": { - "automatic_transmission_enabled": false, - "notification": { - "enabled": true, - "title": "Order waiting!", - "message": "Click here to confirm the order that you made offline.", - "icon": "/assets/logo.png" - } - } - }, - "localForage": { - "defaultDrivers": { - "user": "LOCALSTORAGE", - "cmspage": "LOCALSTORAGE", - "cmsblock": "LOCALSTORAGE", - "carts": "LOCALSTORAGE", - "orders": "LOCALSTORAGE", - "wishlist": "LOCALSTORAGE", - "categories": "LOCALSTORAGE", - "attributes": "LOCALSTORAGE", - "elasticCache": "LOCALSTORAGE", - "claims": "LOCALSTORAGE", - "syncTasks": "LOCALSTORAGE", - "ordersHistory": "LOCALSTORAGE", - "checkout": "LOCALSTORAGE" - }, - "preserveCollections": [ - "cart", - "user" - ] - }, - "reviews": { - "create_endpoint": "/api/review/create" - }, - "users": { - "autoRefreshTokens": true, - "loginAfterCreatePassword": true, - "endpoint": "/api/user", - "history_endpoint": "/api/user/order-history?token={{token}}&pageSize={{pageSize}}¤tPage={{currentPage}}", - "resetPassword_endpoint": "/api/user/reset-password", - "createPassword_endpoint": "http://localhost:8080/api/user/create-password", - "changePassword_endpoint": "/api/user/change-password?token={{token}}", - "login_endpoint": "/api/user/login", - "create_endpoint": "/api/user/create", - "me_endpoint": "/api/user/me?token={{token}}", - "refresh_endpoint": "/api/user/refresh", - "allowModification": ["firstname", "lastname", "email", "addresses"], - "tokenInHeader": false - }, - "stock": { - "synchronize": true, - "allowOutOfStockInCart": true, - "endpoint": "/api/stock" - }, - "images": { - "useExactUrlsNoProxy": false, - "baseUrl": "https://demo.vuestorefront.io/img/", - "useSpecificImagePaths": false, - "paths": { - "product": "/catalog/product" - }, - "productPlaceholder": "/assets/placeholder.jpg" - }, - "install": { - "is_local_backend": true, - "backend_dir": "../vue-storefront-api" - }, - "demomode": false, - "tax": { - "defaultCountry": "US", - "defaultRegion": "", - "sourcePriceIncludesTax": false, - "calculateServerSide": true, - "userGroupId": null, - "useOnlyDefaultUserGroupId": false, - "deprecatedPriceFieldsSupport": true, - "finalPriceIncludesTax": false - }, - "shipping": { - "methods": [ - { - "method_title": "DPD Courier", - "method_code": "flatrate", - "carrier_code": "flatrate", - "amount": 4, - "price_incl_tax": 5, - "default": true, - "offline": true - } - ] - }, - "syncTasks": { - "disablePersistentTaskQueue": true - }, - "i18n": { - "defaultCountry": "US", - "defaultLanguage": "EN", - "availableLocale": [ - "en-US" - ], - "defaultLocale": "en-US", - "currencyCode": "USD", - "currencySign": "$", - "currencyDecimal": "", - "currencyGroup": "", - "fractionDigits": 2, - "priceFormat": "{sign}{amount}", - "dateFormat": "HH:mm D/M/YYYY", - "fullCountryName": "United States", - "fullLanguageName": "English", - "bundleAllStoreviewLanguages": false - }, - "expireHeaders": { - "default": "30d", - "application/json": "24h", - "image/png": "7d" - }, - "newsletter": { - "endpoint": "/api/ext/mailchimp-subscribe/subscribe" - }, - "mailer": { - "endpoint": { - "send": "/api/ext/mail-service/send-email", - "token": "/api/ext/mail-service/get-token" - }, - "contactAddress": "contributors@vuestorefront.io", - "sendConfirmation": true - }, - "theme": "@vue-storefront/theme-default", - "analytics": { - "id": false - }, - "googleTagManager": { - "id": false, - "debug": true, - "product_attributes": [ - "name", - "id", - "sku", - { - "priceInclTax": "price" - }, - { - "qty": "quantity" - } - ] - }, - "hotjar": { - "id": false - }, - "cms": { - "endpoint": "/api/ext/cms-data/cms{{type}}/{{cmsId}}", - "endpointIdentifier": "/api/ext/cms-data/cms{{type}}Identifier/{{cmsIdentifier}}/storeId/{{storeId}}" - }, - "cms_block": { - "max_count": 500 - }, - "cms_page": { - "max_count": 500 - }, - "usePriceTiers": false, - "useZeroPriceProduct": true, - "query": { - "inspirations": { - "filter": [ - { - "key": "category.name", - "value": { "eq": "Performance Fabrics" } - } - ] - }, - "newProducts": { - "filter": [ - { - "key": "new", - "value": { "eq": 1 } - } - ] - }, - "bestSellers": { - "filter": [ - { - "key": "category.name", - "value": { "eq": "Tees" } - } - ] - } - }, - "urlModule": { - "enableMapFallbackUrl": false, - "endpoint": "/api/url", - "map_endpoint": "/api/url/map" - }, - "fastly": { - "enabled":false - }, - "nginx": { - "enabled":false - }, - "varnish": { - "enabled":false - }, - "purgeConfig": [ - "server.invalidateCacheKey", - "server.invalidateCacheForwardUrl", - "server.trace", - "redis", - "install", - "expireHeaders", - "fastly", - "nginx", - "varnish", - "cloudflare" - ] -} diff --git a/config/test.json b/config/test.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/config/test.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/core/app.ts b/core/app.ts deleted file mode 100755 index 72fc80ed66..0000000000 --- a/core/app.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Store } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import Vue from 'vue' -import { isServer } from '@vue-storefront/core/helpers' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import i18n from '@vue-storefront/i18n' -import VueRouter from 'vue-router' -import VueLazyload from 'vue-lazyload' -import Vuelidate from 'vuelidate' -import Meta from 'vue-meta' -import { sync } from 'vuex-router-sync' -import VueObserveVisibility from 'vue-observe-visibility' -import { getApolloProvider } from './scripts/resolvers/resolveGraphQL' -// TODO simplify by removing global mixins, plugins and filters - it can be done in normal 'vue' way -import { registerTheme } from '@vue-storefront/core/lib/themes' -import { themeEntry } from 'theme/index.js' -import { registerModules } from '@vue-storefront/core/lib/module' -import { prepareStoreView, currentStoreView } from '@vue-storefront/core/lib/multistore' -import * as coreMixins from '@vue-storefront/core/mixins' -import * as coreFilters from '@vue-storefront/core/filters' -import * as corePlugins from '@vue-storefront/core/compatibility/plugins' -import { once } from '@vue-storefront/core/helpers' -import store from '@vue-storefront/core/store' -import { enabledModules } from './modules-entry' -import globalConfig from 'config' -import { injectReferences } from '@vue-storefront/core/lib/modules' -import { coreHooksExecutors } from '@vue-storefront/core/hooks' -import { registerClientModules } from 'src/modules/client' -import initialStateFactory from '@vue-storefront/core/helpers/initialStateFactory' -import { createRouter, createRouterProxy } from '@vue-storefront/core/helpers/router' -import { checkForIntlPolyfill } from '@vue-storefront/i18n/intl' - -const stateFactory = initialStateFactory(store.state) - -let router: VueRouter = null -let routerProxy: VueRouter = null - -once('__VUE_EXTEND_RR__', () => { - Vue.use(VueRouter) -}) - -const createApp = async (ssrContext, config, storeCode = null): Promise<{app: Vue, router: VueRouter, store: Store, initialState: RootState}> => { - router = createRouter() - routerProxy = createRouterProxy(router) - // sync router with vuex 'router' store - sync(store, routerProxy) - // TODO: Don't mutate the state directly, use mutation instead - store.state.version = process.env.APPVERSION - store.state.config = config // @deprecated - store.state.__DEMO_MODE__ = (config.demomode === true) - if (ssrContext) { - // @deprecated - we shouldn't share server context between requests - Vue.prototype.$ssrRequestContext = { - output: { - cacheTags: ssrContext.output.cacheTags - }, - userAgent: ssrContext.server.request.headers['user-agent'] - } - - Vue.prototype.$cacheTags = ssrContext.output.cacheTags - } - if (!store.state.config) store.state.config = globalConfig // @deprecated - we should avoid the `config` - const storeView = await prepareStoreView(storeCode) // prepare the default storeView - store.state.storeView = storeView - - // @deprecated from 2.0 - once('__VUE_EXTEND__', () => { - Vue.use(Vuelidate) - Vue.use(VueLazyload, { attempt: 2, preLoad: 1.5 }) - Vue.use(Meta, { - ssrAppId: 1 - }) - Vue.use(VueObserveVisibility) - - Object.keys(corePlugins).forEach(key => { - Vue.use(corePlugins[key]) - }) - - Object.keys(coreMixins).forEach(key => { - Vue.mixin(coreMixins[key]) - }) - - Object.keys(coreFilters).forEach(key => { - Vue.filter(key, coreFilters[key]) - }) - }) - - let vueOptions = { - router: routerProxy, - store, - i18n, - render: h => h(themeEntry) - } - - const apolloProvider = await getApolloProvider() - if (apolloProvider) Object.assign(vueOptions, { provider: apolloProvider }) - - const app = new Vue(vueOptions) - - const appContext = { - isServer, - ssrContext - } - - injectReferences(app, store, routerProxy, globalConfig) - registerClientModules() - registerModules(enabledModules, appContext) - registerTheme(globalConfig.theme, app, routerProxy, store, globalConfig, ssrContext) - - await checkForIntlPolyfill(storeView) - - coreHooksExecutors.afterAppInit() - // @deprecated from 2.0 - EventBus.$emit('application-after-init', app) - - return { app, router: routerProxy, store, initialState: stateFactory.createInitialState(store.state) } -} - -export { routerProxy as router, createApp, router as baseRouter } diff --git a/core/build/dev-server.js b/core/build/dev-server.js deleted file mode 100644 index b40d7554ff..0000000000 --- a/core/build/dev-server.js +++ /dev/null @@ -1,67 +0,0 @@ -const path = require('path') -const webpack = require('webpack') -const MFS = require('memory-fs') - -let baseClientConfig = require('./webpack.client.config').default -let baseServerConfig = require('./webpack.server.config').default - -const themeRoot = require('./theme-path') -const extendedConfig = require(path.join(themeRoot, '/webpack.config.js')) - -let clientConfig = extendedConfig(baseClientConfig, { isClient: true, isDev: true }) -let serverConfig = extendedConfig(baseServerConfig, { isClient: false, isDev: true }) - -module.exports = function setupDevServer (app, cb) { - let bundle - let template - - // Modify client config to work with hot middleware - clientConfig.entry.app = ['webpack-hot-middleware/client', ...clientConfig.entry.app] - clientConfig.output.filename = '[name].js' - clientConfig.plugins.push( - new webpack.HotModuleReplacementPlugin(), - new webpack.NoEmitOnErrorsPlugin() - ) - - // Dev middleware - const clientCompiler = webpack(clientConfig) - const devMiddleware = require('webpack-dev-middleware')(clientCompiler, { - publicPath: clientConfig.output.publicPath, - stats: { - colors: true, - chunks: false - } - }) - app.use(devMiddleware) - clientCompiler.plugin('done', () => { - const fs = devMiddleware.fileSystem - const filePath = path.join(clientConfig.output.path, 'index.html') - if (fs.existsSync(filePath)) { - template = fs.readFileSync(filePath, 'utf-8') - if (bundle) { - cb(bundle, template) - } - } - }) - - // Hot middleware - app.use(require('webpack-hot-middleware')(clientCompiler)) - - // watch and update server renderer - const serverCompiler = webpack(serverConfig) - const mfs = new MFS() - serverCompiler.outputFileSystem = mfs - serverCompiler.watch({}, (err, stats) => { - if (err) throw err - stats = stats.toJson() - stats.errors.forEach(err => console.error(err)) - stats.warnings.forEach(err => console.warn(err)) - - // Read bundle generated by vue-ssr-webpack-plugin - const bundlePath = path.join(serverConfig.output.path, 'vue-ssr-bundle.json') - bundle = JSON.parse(mfs.readFileSync(bundlePath, 'utf-8')) - if (template) { - cb(bundle, template) - } - }) -} diff --git a/core/build/module-build.config.js b/core/build/module-build.config.js deleted file mode 100644 index 82dc4d6c48..0000000000 --- a/core/build/module-build.config.js +++ /dev/null @@ -1,25 +0,0 @@ -// Webpack config used to build VS modules -module.exports = { - mode: 'production', - entry: './src/index.ts', - output: { - libraryTarget: 'umd', - globalObject: 'typeof self !== \'undefined\' ? self : this' - }, - resolve: { - extensions: ['.ts', '.js', '.json'] - }, - module: { - rules: [ - { - test: /\.ts$/, - use: ['ts-loader'], - options: { - transpileOnly: true - }, - exclude: /node_modules/ - } - ] - }, - externals: ['@vue-storefront/core'] -} diff --git a/core/build/purge-config/purgeConfig.ts b/core/build/purge-config/purgeConfig.ts deleted file mode 100644 index afd4f4d9b5..0000000000 --- a/core/build/purge-config/purgeConfig.ts +++ /dev/null @@ -1,10 +0,0 @@ -import omit from 'lodash/omit'; - -export default config => { - const purgeConfig = (config.purgeConfig || []).slice(); - - config = omit(config, purgeConfig); - delete config['purgeConfig']; - - return config; -} diff --git a/core/build/purge-config/purgeConfigLoader.ts b/core/build/purge-config/purgeConfigLoader.ts deleted file mode 100644 index 9615509060..0000000000 --- a/core/build/purge-config/purgeConfigLoader.ts +++ /dev/null @@ -1,11 +0,0 @@ -import purgeConfig from './purgeConfig'; - -/** - * clear config properties that shouldn't be visible on frontend - */ -export default function loader (source) { - let config = JSON.parse(source); - config = purgeConfig(config); - - return JSON.stringify(config); -} diff --git a/core/build/theme-path.js b/core/build/theme-path.js deleted file mode 100644 index 671fa7000f..0000000000 --- a/core/build/theme-path.js +++ /dev/null @@ -1,24 +0,0 @@ -const path = require('path') -const detectInstalled = require('detect-installed') -const config = require('./config.json') -const fs = require("fs") - -// TODO: Refactor and simplify themePath resoultion -let themePath = '' -let themeName = config.theme -if (detectInstalled.sync(config.theme, { local: true })) { - themePath = path.resolve(__dirname, '../../node_modules/' + themeName) -} -else { - themeName = themeName.replace('@vue-storefront/theme-', '') - themePath = path.resolve(__dirname, '../../src/themes/' + themeName) - if(!fs.existsSync(themePath)) { - console.error(` - The theme you want to use does not exist. - Please check theme installation: https://docs.vuestorefront.io/guide/installation/theme.html - `) - process.exit(1) - } -} - -module.exports = themePath diff --git a/core/build/webpack.base.config.ts b/core/build/webpack.base.config.ts deleted file mode 100644 index fabf6cdf48..0000000000 --- a/core/build/webpack.base.config.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { buildLocaleIgnorePattern } from './../i18n/helpers'; -import path from 'path'; -import fs from 'fs'; -import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin'; -import VueLoaderPlugin from 'vue-loader/lib/plugin'; -import autoprefixer from 'autoprefixer'; -import HTMLPlugin from 'html-webpack-plugin'; -import webpack from 'webpack'; -import dayjs from 'dayjs'; -import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin' -// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - -// eslint-disable-next-line import/first -import themeRoot from './theme-path'; - -const themesRoot = '../../src/themes' -const themeResources = themeRoot + '/resource' -const themeCSS = themeRoot + '/css' -const themeApp = themeRoot + '/App.vue' -const themedIndex = path.join(themeRoot, '/templates/index.template.html') -const themedIndexMinimal = path.join(themeRoot, '/templates/index.minimal.template.html') -const themedIndexBasic = path.join(themeRoot, '/templates/index.basic.template.html') -const themedIndexAmp = path.join(themeRoot, '/templates/index.amp.template.html') - -const postcssConfig = { - loader: 'postcss-loader', - options: { - ident: 'postcss', - plugins: (loader) => [ - require('postcss-flexbugs-fixes'), - require('autoprefixer')({ - flexbox: 'no-2009' - }) - ] - } -}; -const isProd = process.env.NODE_ENV === 'production' -// todo: usemultipage-webpack-plugin for multistore -export default { - plugins: [ - new webpack.ContextReplacementPlugin(/dayjs[/\\]locale$/, buildLocaleIgnorePattern()), - new webpack.ProgressPlugin(), - /* new BundleAnalyzerPlugin({ - generateStatsFile: true - }), */ - new CaseSensitivePathsPlugin(), - new VueLoaderPlugin(), - // generate output HTML - new HTMLPlugin({ - template: fs.existsSync(themedIndex) ? themedIndex : 'src/index.template.html', - filename: 'index.html', - chunksSortMode: 'none', - inject: isProd === false // in dev mode we're not using clientManifest therefore renderScripts() is returning empty string and we need to inject scripts using HTMLPlugin - }), - new HTMLPlugin({ - template: fs.existsSync(themedIndexMinimal) ? themedIndexMinimal : 'src/index.minimal.template.html', - filename: 'index.minimal.html', - chunksSortMode: 'none', - inject: isProd === false - }), - new HTMLPlugin({ - template: fs.existsSync(themedIndexBasic) ? themedIndexBasic : 'src/index.basic.template.html', - filename: 'index.basic.html', - chunksSortMode: 'none', - inject: isProd === false - }), - new HTMLPlugin({ - template: fs.existsSync(themedIndexAmp) ? themedIndexAmp : 'src/index.amp.template.html', - filename: 'index.amp.html', - chunksSortMode: 'none', - inject: isProd === false - }), - new webpack.DefinePlugin({ - 'process.env.__APPVERSION__': JSON.stringify(require('../../package.json').version), - 'process.env.__BUILDTIME__': JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss')) - }), - new ForkTsCheckerWebpackPlugin({ - typescript: { - extensions: { - vue: true - } - } - }) - ], - devtool: 'source-map', - entry: { - app: ['@babel/polyfill', './core/client-entry.ts'] - }, - output: { - path: path.resolve(__dirname, '../../dist'), - publicPath: '/dist/', - filename: '[name].[hash].js' - }, - resolveLoader: { - modules: [ - 'node_modules', - path.resolve(__dirname, themesRoot) - ] - }, - resolve: { - modules: [ - 'node_modules', - path.resolve(__dirname, themesRoot) - ], - extensions: ['.js', '.vue', '.gql', '.graphqls', '.ts'], - alias: { - // Main aliases - 'config': path.resolve(__dirname, './config.json'), - 'src': path.resolve(__dirname, '../../src'), - - // Theme aliases - 'theme': themeRoot, - 'theme/app': themeApp, - 'theme/css': themeCSS, - 'theme/resource': themeResources, - - // Backward compatible - '@vue-storefront/core/lib/store/multistore': path.resolve(__dirname, '../lib/multistore.ts'), - 'src/modules/order-history/components/UserOrders': path.resolve(__dirname, '../../core/modules/order/components/UserOrdersHistory'), - '@vue-storefront/core/modules/social-share/components/WebShare': path.resolve(__dirname, '../../src/themes/default/components/theme/WebShare.vue'), - '@vue-storefront/core/helpers/initCacheStorage': path.resolve(__dirname, '../lib/storage-manager.ts') - } - }, - module: { - rules: [ - { - enforce: 'pre', - test: /\.(js|vue,ts)$/, - loader: 'eslint-loader', - exclude: [/node_modules/, /test/] - }, - { - test: /\.vue$/, - loader: 'vue-loader', - options: { - preserveWhitespace: false, - postcss: [autoprefixer()] - } - }, - { - test: /\.ts$/, - loader: 'ts-loader', - options: { - appendTsSuffixTo: [/\.vue$/], - transpileOnly: true - }, - exclude: /node_modules/ - }, - { - test: /\.js$/, - loader: 'babel-loader', - include: [ - path.resolve(__dirname, '../../node_modules/@vue-storefront'), - path.resolve(__dirname, '../../src'), - path.resolve(__dirname, '../../core') - ] - }, - { - test: /\.(png|jpg|gif|svg)$/, - loader: 'file-loader', - options: { - name: '[name].[ext]?[hash]' - } - }, - { - test: /\.css$/, - use: [ - 'vue-style-loader', - 'css-loader', - postcssConfig - ] - }, - { - test: /\.scss$/, - use: [ - 'vue-style-loader', - 'css-loader', - postcssConfig, - 'sass-loader' - ] - }, - { - test: /\.sass$/, - use: [ - 'vue-style-loader', - 'css-loader', - postcssConfig, - { - loader: 'sass-loader', - options: { - indentedSyntax: true - } - } - ] - }, - { - test: /\.(woff|woff2|eot|ttf)(\?.*$|$)/, - loader: 'url-loader?importLoaders=1&limit=10000' - }, - { - test: /\.(graphqls|gql)$/, - exclude: /node_modules/, - loader: ['graphql-tag/loader'] - }, - { - test: /core\/build\/config\.json$/, - loader: path.resolve('core/build/purge-config/purgeConfigLoader.ts') - } - ] - } -} diff --git a/core/build/webpack.client.config.ts b/core/build/webpack.client.config.ts deleted file mode 100644 index 553d7d8ece..0000000000 --- a/core/build/webpack.client.config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import webpack from 'webpack' -import merge from 'webpack-merge' -import base from './webpack.base.config' -import VueSSRClientPlugin from 'vue-server-renderer/client-plugin' -// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin - -const config = merge(base, { - optimization: { - splitChunks: { - cacheGroups: { - commons: { - // create 'vendor' group from initial packages from node_modules - test: /node_modules/, - name: 'vendor', - chunks: 'initial', - priority: 1 - } - } - }, - runtimeChunk: { - name: 'manifest' - } - }, - mode: 'development', - resolve: { - alias: { - 'create-api': './create-api-client.js' - } - }, - plugins: [ - // new BundleAnalyzerPlugin(), - // strip dev-only code in Vue source - new webpack.DefinePlugin({ - 'process.env.VUE_ENV': '"client"' - }), - new VueSSRClientPlugin() - ] -}) - -export default config; diff --git a/core/build/webpack.prod.client.config.ts b/core/build/webpack.prod.client.config.ts deleted file mode 100644 index 426e14b57f..0000000000 --- a/core/build/webpack.prod.client.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import path from 'path'; -import merge from 'webpack-merge'; -import baseClientConfig from './webpack.client.config'; -const themeRoot = require('./theme-path'); - -const extendedConfig = require(path.join(themeRoot, '/webpack.config.js')) - -const prodClientConfig = merge(baseClientConfig, { - mode: 'production', - devtool: 'nosources-source-map', - plugins: [ - ] -}) - -module.exports = extendedConfig(prodClientConfig, { - isClient: true, - isDev: false -}) diff --git a/core/build/webpack.prod.server.config.ts b/core/build/webpack.prod.server.config.ts deleted file mode 100644 index 3c22944a12..0000000000 --- a/core/build/webpack.prod.server.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import path from 'path'; - -import baseServerConfig from './webpack.server.config'; - -import themeRoot from './theme-path'; - -const extendedConfig = require(path.join(themeRoot, '/webpack.config.js')) - -export default extendedConfig(baseServerConfig, { - mode: 'production', - devtool: 'nosources-source-map', - isClient: false, - isDev: false -}) diff --git a/core/build/webpack.prod.sw.config.ts b/core/build/webpack.prod.sw.config.ts deleted file mode 100644 index 64f90aa020..0000000000 --- a/core/build/webpack.prod.sw.config.ts +++ /dev/null @@ -1,103 +0,0 @@ -import webpack from 'webpack'; -import merge from 'webpack-merge'; -import base from './webpack.base.config'; -import SWPrecachePlugin from 'sw-precache-webpack-plugin'; - -module.exports = merge(base, { - mode: 'production', - target: 'web', - entry: ['@babel/polyfill', './core/service-worker/index.js'], - output: { - filename: 'core-service-worker.js' - }, - plugins: [ - new webpack.DefinePlugin({ - 'process.env.VUE_ENV': '"client"' - }), - // auto generate service worker - new SWPrecachePlugin({ - cacheId: 'vue-sfr', - filename: 'service-worker.js', - staticFileGlobsIgnorePatterns: [/\.map$/], - staticFileGlobs: [ - 'dist/**.*.js', - 'dist/**.*.json', - 'dist/**.*.css', - 'assets/**.*', - 'assets/ig/**.*', - 'index.html', - '/' - ], - runtimeCaching: [ - { - // eslint-disable-next-line no-useless-escape - urlPattern: '^https://fonts\.googleapis\.com/', /** cache the html stub */ - handler: 'cacheFirst' - }, - { - // eslint-disable-next-line no-useless-escape - urlPattern: '^https://fonts\.gstatic\.com/', /** cache the html stub */ - handler: 'cacheFirst' - }, - { - // eslint-disable-next-line no-useless-escape - urlPattern: '^https://unpkg\.com/', /** cache the html stub */ - handler: 'cacheFirst' - }, - { - urlPattern: '/pwa.html', /** cache the html stub */ - handler: 'networkFirst' - }, { - urlPattern: '/', /** cache the html stub for homepage */ - handler: 'networkFirst' - }, - { - urlPattern: '/p/*', /** cache the html stub */ - handler: 'networkFirst' - }, - { - urlPattern: '/c/*', /** cache the html stub */ - handler: 'networkFirst' - }, - { - urlPattern: '/img/(.*)', - handler: 'fastest' - }, - { - urlPattern: /(http[s]?:\/\/)?(\/)?([^\/\s]+\/)?(api\/catalog\/)(.*)/g, // eslint-disable-line no-useless-escape - handler: 'networkFirst' - }, - { - urlPattern: '/api/*', - handler: 'networkFirst' - }, { - urlPattern: '/assets/logo.svg', - handler: 'networkFirst' - }, { - urlPattern: '/index.html', - handler: 'networkFirst' - }, { - urlPattern: '/assets/*', - handler: 'fastest' - }, { - urlPattern: '/assets/ig/(.*)', - handler: 'fastest' - }, { - urlPattern: '/dist/(.*)', - handler: 'fastest' - }, { - urlPattern: '/*/*', /** this is new product URL format */ - handler: 'networkFirst' - }, - { - urlPattern: '/*/*/*', /** this is new product URL format */ - handler: 'networkFirst' - }, - { - urlPattern: '/*', /** this is new category URL format */ - handler: 'networkFirst' - }], - 'importScripts': ['/dist/core-service-worker.js'] /* custom logic */ - }) - ] -}) diff --git a/core/build/webpack.server.config.ts b/core/build/webpack.server.config.ts deleted file mode 100644 index 729ff89a57..0000000000 --- a/core/build/webpack.server.config.ts +++ /dev/null @@ -1,39 +0,0 @@ -import webpack from 'webpack'; -import merge from 'webpack-merge'; -import base from './webpack.base.config'; -import VueSSRPlugin from 'vue-ssr-webpack-plugin'; - -// when output cache is enabled generate cache version key -import config from 'config' -import fs from 'fs' -import path from 'path' -import uuid from 'uuid/v4' - -if (config.server.useOutputCache) { - fs.writeFileSync( - path.join(__dirname, 'cache-version.json'), - JSON.stringify(uuid()) - ) -} - -export default merge(base, { - mode: 'development', - target: 'node', - entry: ['@babel/polyfill', './core/server-entry.ts'], - output: { - filename: 'server-bundle.js', - libraryTarget: 'commonjs2' - }, - resolve: { - alias: { - 'create-api': './create-api-server.js' - } - }, - externals: Object.keys(require('../../package.json').dependencies), - plugins: [ - new webpack.DefinePlugin({ - 'process.env.VUE_ENV': '"server"' - }), - new VueSSRPlugin() - ] -}) diff --git a/core/client-entry.ts b/core/client-entry.ts deleted file mode 100755 index e197396125..0000000000 --- a/core/client-entry.ts +++ /dev/null @@ -1,131 +0,0 @@ -import Vue from 'vue' -import union from 'lodash-es/union' -import { createApp } from '@vue-storefront/core/app' -import rootStore from '@vue-storefront/core/store' -import { registerSyncTaskProcessor } from '@vue-storefront/core/lib/sync/task' -import i18n from '@vue-storefront/i18n' -import omit from 'lodash-es/omit' -import storeCodeFromRoute from '@vue-storefront/core/lib/storeCodeFromRoute' -import { currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore' -import { onNetworkStatusChange } from '@vue-storefront/core/modules/offline-order/helpers/onNetworkStatusChange' -import '@vue-storefront/core/service-worker/registration' // register the service worker -import { AsyncDataLoader } from './lib/async-data-loader' -import { Logger } from '@vue-storefront/core/lib/logger' -import globalConfig from 'config' -import { coreHooksExecutors } from './hooks' -import { RouterManager } from './lib/router-manager'; -declare var window: any - -const invokeClientEntry = async () => { - const dynamicRuntimeConfig = window.__INITIAL_STATE__.config ? Object.assign(globalConfig, window.__INITIAL_STATE__.config) : globalConfig - // Get storeCode from server (received either from cache header or env variable) - let storeCode = window.__INITIAL_STATE__.storeView.storeCode - const { app, router, store } = await createApp(null, dynamicRuntimeConfig, storeCode) - - if (window.__INITIAL_STATE__) { - // skip fields that were set by createApp - const initialState = coreHooksExecutors.beforeHydrated( - omit(window.__INITIAL_STATE__, ['storeView', 'config', 'version', 'route']) - ) - store.replaceState(Object.assign({}, store.state, initialState, { config: globalConfig })) - } - - await store.dispatch('url/registerDynamicRoutes') - RouterManager.flushRouteQueue() - - function _commonErrorHandler (err, reject) { - if (err.message.indexOf('query returned empty result') > 0) { - rootStore.dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t('The product, category or CMS page is not available in Offline mode. Redirecting to Home.'), - action1: { label: i18n.t('OK') } - }) - router.push(localizedRoute('/', currentStoreView().storeCode)) - } else { - rootStore.dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t(err.message), - action1: { label: i18n.t('OK') } - }) - reject() - } - } - - function _ssrHydrateSubcomponents (components, next, to) { - Promise.all(components.map(SubComponent => { - if (SubComponent.asyncData) { - return SubComponent.asyncData({ - store, - route: to - }) - } else { - return Promise.resolve(null) - } - })).then(() => { - AsyncDataLoader.flush({ store, route: to, context: null }).then(next).catch(err => { - _commonErrorHandler(err, next) - }) - }).catch(err => { - _commonErrorHandler(err, next) - }) - } - router.onReady(async () => { - // check if app can be mounted - const canBeMounted = () => RouterManager.isRouteDispatched() && // route is dispatched - !(router as any).history.pending && // there is no pending in router history - !(app as any)._isMounted // it's not mounted before - - if (canBeMounted()) { - app.$mount('#app') - } - router.beforeResolve((to, from, next) => { - if (!from.name) { - next() - if (canBeMounted()) { - app.$mount('#app') - } - return // do not resolve asyncData on server render - already been done - } - if (!Vue.prototype.$cacheTags) Vue.prototype.$cacheTags = new Set() - const matched = router.getMatchedComponents(to) - if (to) { // this is from url - if (globalConfig.storeViews.multistore === true) { - const currentRoute = Object.assign({}, to, { host: window.location.host }) - const storeCode = storeCodeFromRoute(currentRoute) - const currentStore = currentStoreView() - if (storeCode !== '' && storeCode !== null) { - if (storeCode !== currentStore.storeCode) { - (document as any).location = to.path // full reload - } - } - } - } - if (!matched.length || !matched[0]) { - return next() - } - - store.dispatch('url/setCurrentRoute', { to, from }) - - Promise.all(matched.map((c: any) => { // TODO: update me for mixins support - const components = c.mixins && globalConfig.ssr.executeMixedinAsyncData ? Array.from(c.mixins) : [] - union(components, [c]).map(SubComponent => { - if (SubComponent.preAsyncData) { - SubComponent.preAsyncData({ store, route: to }) - } - }) - if (c.asyncData) { - c.asyncData({ store, route: to }).then(result => { // always execute the asyncData() from the top most component first - Logger.debug('Top-most asyncData executed')() - _ssrHydrateSubcomponents(components, next, to) - }).catch(next) - } else { - _ssrHydrateSubcomponents(components, next, to) - } - })) - }) - }) - registerSyncTaskProcessor() - window.addEventListener('online', () => { onNetworkStatusChange(store) }) -} - -invokeClientEntry() diff --git a/core/compatibility/README.md b/core/compatibility/README.md deleted file mode 100644 index 11196bc1cf..0000000000 --- a/core/compatibility/README.md +++ /dev/null @@ -1 +0,0 @@ -Outdated VS APIs that shouldn't be used in new projects. They are safe to use in current ones tho since we are providing backward support for them. Most of the components here was refactored and placed inside core modules. \ No newline at end of file diff --git a/core/compatibility/components/AddToCart.js b/core/compatibility/components/AddToCart.js deleted file mode 100644 index be5981cf12..0000000000 --- a/core/compatibility/components/AddToCart.js +++ /dev/null @@ -1,6 +0,0 @@ -import { AddToCart } from '@vue-storefront/core/modules/cart/components/AddToCart.ts' - -export default { - name: 'AddToCart', - mixins: [ AddToCart ] -} diff --git a/core/compatibility/components/Breadcrumbs.js b/core/compatibility/components/Breadcrumbs.js deleted file mode 100644 index b7f1c0b0c7..0000000000 --- a/core/compatibility/components/Breadcrumbs.js +++ /dev/null @@ -1,14 +0,0 @@ -// breadcrumbs functionality will be rewritten, and this component is theme-specific -export default { - name: 'Breadcrumbs', - props: { - routes: { - type: Array, - required: true - }, - activeRoute: { - type: String, - default: '' - } - } -} diff --git a/core/compatibility/components/GenericSelector.js b/core/compatibility/components/GenericSelector.js deleted file mode 100644 index 880a39dc33..0000000000 --- a/core/compatibility/components/GenericSelector.js +++ /dev/null @@ -1,6 +0,0 @@ -import { ProductCustomOption } from '@vue-storefront/core/modules/catalog/components/ProductCustomOption' -// renamed to ProductCustomOption and placed in catalog module -export default { - name: 'GenericSelector', - mixins: [ProductCustomOption] -} diff --git a/core/compatibility/components/Notification.js b/core/compatibility/components/Notification.js deleted file mode 100644 index ee260ca989..0000000000 --- a/core/compatibility/components/Notification.js +++ /dev/null @@ -1,40 +0,0 @@ -// deprecated moved to store -export default { - name: 'Notification', - data () { - return { - notifications: [] - } - }, - beforeMount () { - this.$bus.$on('notification', this.onNotification) - }, - beforeDestroy () { - this.$bus.$off('notification', this.onNotification) - }, - methods: { - onNotification (data) { - if (this.notifications.length > 0 && this.notifications[this.notifications.length - 1].message === data.message) { - return - } - this.notifications.push(data) - - if (!data.hasNoTimeout) { - setTimeout(() => { - this.action('close', this.notifications.length - 1) - }, data.timeToLive || 5000) - } - }, - action (action, id, notification) { - this.$bus.$emit('notification-after-' + action, notification) - switch (action) { - case 'goToCheckout': - this.$router.push(this.localizedRoute('/checkout')) - this.notifications.splice(id, 1) - break - default: - this.notifications.splice(id, 1) - } - } - } -} diff --git a/core/compatibility/components/Overlay.js b/core/compatibility/components/Overlay.js deleted file mode 100644 index 7822e68f12..0000000000 --- a/core/compatibility/components/Overlay.js +++ /dev/null @@ -1,14 +0,0 @@ -// theme-specific component -export default { - name: 'Overlay', - computed: { - isVisible () { - return this.$store.state.ui.overlay - } - }, - methods: { - close () { - this.$store.commit('ui/setOverlay', false) - } - } -} diff --git a/core/compatibility/components/PriceSelector.js b/core/compatibility/components/PriceSelector.js deleted file mode 100644 index 93de829a8b..0000000000 --- a/core/compatibility/components/PriceSelector.js +++ /dev/null @@ -1,65 +0,0 @@ -// replaced with generic ProductCustomOption -export default { - name: 'PriceSelector', - props: { - content: { - type: null, - default: '' - }, - id: { - type: null, - required: true - }, - code: { - type: null, - required: true - }, - from: { - type: null, - required: true - }, - to: { - type: null, - required: true - }, - context: { - type: null, - default: '' - } - }, - data () { - return { - active: false - } - }, - beforeMount () { - this.$bus.$on('filter-reset', this.filterReset) - this.$bus.$on('filter-changed-' + this.context, this.filterChanged) - }, - beforeDestroy () { - this.$bus.$off('filter-reset', this.filterReset) - this.$bus.$off('filter-changed-' + this.context, this.filterChanged) - }, - methods: { - filterChanged (filterOption) { - if (filterOption.attribute_code === this.code) { - if (filterOption.id === this.id) { - if (this.active) { - this.active = false - } else { - this.active = true - } - } else { - this.active = false - } - // filterOption.id === this.id ? this.active = true : this.active = false - } - }, - filterReset (filterOption) { - this.active = false - }, - switchFilter (id, from, to) { - this.$bus.$emit('filter-changed-' + this.context, { attribute_code: this.code, id: id, from: from, to: to }) - } - } -} diff --git a/core/compatibility/components/SortBy.js b/core/compatibility/components/SortBy.js deleted file mode 100644 index db004c6050..0000000000 --- a/core/compatibility/components/SortBy.js +++ /dev/null @@ -1,19 +0,0 @@ -import { CategorySort } from '@vue-storefront/core/modules/catalog/components/CategorySort' -import config from 'config' - -export default { - name: 'SortBy', - methods: { - changeOrder () { - // renamed to sort - this.sort() - } - }, - computed: { - sortByAttribute () { - // renamed to sortingOptions - return config.products.sortByAttributes - } - }, - mixins: [CategorySort] -} diff --git a/core/compatibility/components/ValidationError.js b/core/compatibility/components/ValidationError.js deleted file mode 100644 index 2b2036d3a9..0000000000 --- a/core/compatibility/components/ValidationError.js +++ /dev/null @@ -1,10 +0,0 @@ -// theme-specific component -export default { - name: 'ValidationError', - props: { - message: { - type: String, - default: '' - } - } -} diff --git a/core/compatibility/components/blocks/Auth/Login.js b/core/compatibility/components/blocks/Auth/Login.js deleted file mode 100644 index bf1ebe4d0e..0000000000 --- a/core/compatibility/components/blocks/Auth/Login.js +++ /dev/null @@ -1,4 +0,0 @@ -import { Login } from '@vue-storefront/core/modules/user/components/Login' -export default { - mixins: [Login] -} diff --git a/core/compatibility/components/blocks/Auth/Register.js b/core/compatibility/components/blocks/Auth/Register.js deleted file mode 100644 index 52de38a824..0000000000 --- a/core/compatibility/components/blocks/Auth/Register.js +++ /dev/null @@ -1,4 +0,0 @@ -import { Register } from '@vue-storefront/core/modules/user/components/Register' -export default { - mixins: [Register] -} diff --git a/core/compatibility/components/blocks/Category/Sidebar.js b/core/compatibility/components/blocks/Category/Sidebar.js deleted file mode 100644 index c422975fa5..0000000000 --- a/core/compatibility/components/blocks/Category/Sidebar.js +++ /dev/null @@ -1,47 +0,0 @@ -import { buildFilterProductsQuery } from '@vue-storefront/core/helpers' -import { mapGetters } from 'vuex' -import pickBy from 'lodash-es/pickBy' - -export default { - name: 'CategorySidebar', - props: { - filters: { - type: Object, - required: true - } - }, - computed: { - ...mapGetters('category', ['getCurrentCategory', 'getActiveCategoryFilters', 'getCurrentCategoryProductQuery']), - category () { - return this.getCurrentCategory - }, - activeFilters () { - return this.getActiveCategoryFilters - }, - availableFilters () { - return pickBy(this.filters, (filter, filterType) => { return (filter.length && !this.$store.getters['category-next/getSystemFilterNames'].includes(filterType)) }) - }, - hasActiveFilters () { - return Object.keys(this.activeFilters).length !== 0 - } - }, - mounted () { - this.resetAllFilters() - }, - methods: { - sortById (filters) { - return [...filters].sort((a, b) => { return a.id - b.id }) - }, - resetAllFilters () { - if (this.hasActiveFilters) { - this.$bus.$emit('filter-reset') - this.$store.dispatch('category/resetFilters') - this.$store.dispatch('category/searchProductQuery', {}) - this.$store.dispatch('category/mergeSearchOptions', { - searchProductQuery: buildFilterProductsQuery(this.category, this.activeFilters) - }) - this.$store.dispatch('category/products', this.getCurrentCategoryProductQuery) - } - } - } -} diff --git a/core/compatibility/components/blocks/Header/AccountIcon.js b/core/compatibility/components/blocks/Header/AccountIcon.js deleted file mode 100644 index 8dae2e3c03..0000000000 --- a/core/compatibility/components/blocks/Header/AccountIcon.js +++ /dev/null @@ -1,18 +0,0 @@ -import { AccountButton } from '@vue-storefront/core/modules/user/components/AccountButton' - -export default { - name: 'AccountIcon', - data () { - // theme-specific, deprecated - return { - navigation: [] - } - }, - computed: { - currentUser () { - // renamed to 'user' - return this.user - } - }, - mixins: [AccountButton] -} diff --git a/core/compatibility/components/blocks/Header/CompareIcon.js b/core/compatibility/components/blocks/Header/CompareIcon.js deleted file mode 100644 index 5b12a1638c..0000000000 --- a/core/compatibility/components/blocks/Header/CompareIcon.js +++ /dev/null @@ -1,12 +0,0 @@ -import { CompareButton } from '@vue-storefront/core/modules/compare/components/CompareButton.ts' - -export default { - name: 'CompareIcon', - mixins: [CompareButton], - computed: { - isActive () { - // Computed Property renamed to 'isEmpty' - return !this.isEmpty - } - } -} diff --git a/core/compatibility/components/blocks/Header/HamburgerIcon.js b/core/compatibility/components/blocks/Header/HamburgerIcon.js deleted file mode 100644 index f830f3a4f2..0000000000 --- a/core/compatibility/components/blocks/Header/HamburgerIcon.js +++ /dev/null @@ -1,14 +0,0 @@ -import { mapState } from 'vuex' - -// deprecated as theme specific -export default { - name: 'HamburgerIcon', - computed: mapState({ - isOpen: state => state.ui.sidebar - }), - methods: { - openSidebarMenu () { - this.$store.commit('ui/setSidebar', !this.isOpen) - } - } -} diff --git a/core/compatibility/components/blocks/Header/MicrocartIcon.js b/core/compatibility/components/blocks/Header/MicrocartIcon.js deleted file mode 100644 index 00ebeff97d..0000000000 --- a/core/compatibility/components/blocks/Header/MicrocartIcon.js +++ /dev/null @@ -1,19 +0,0 @@ -import { MicrocartButton } from '@vue-storefront/core/modules/cart/components/MicrocartButton.ts' - -export default { - methods: { - openMicrocart () { - // Method renamed to 'toggleMicrocart' and is using cart store now - this.$store.dispatch('ui/toggleMicrocart') - } - }, - computed: { - totalQuantity () { - // Data field renamed to 'quantity' - return this.quantity - } - }, - mixins: [ - MicrocartButton - ] -} diff --git a/core/compatibility/components/blocks/Header/ReturnIcon.js b/core/compatibility/components/blocks/Header/ReturnIcon.js deleted file mode 100644 index 5d3956049d..0000000000 --- a/core/compatibility/components/blocks/Header/ReturnIcon.js +++ /dev/null @@ -1,19 +0,0 @@ -// deprecated as theme-specific -export default { - name: 'ReturnIcon', - props: { - to: { - type: String | Object, - default: null - } - }, - methods: { - goBack () { - if (this.to) { - this.$router.push(this.to) - } else { - this.$router.back() - } - } - } -} diff --git a/core/compatibility/components/blocks/Header/SearchIcon.js b/core/compatibility/components/blocks/Header/SearchIcon.js deleted file mode 100644 index c904d46c79..0000000000 --- a/core/compatibility/components/blocks/Header/SearchIcon.js +++ /dev/null @@ -1,15 +0,0 @@ -import { mapState } from 'vuex' - -export default { - name: 'SearchIcon', - computed: { - ...mapState({ - isOpen: state => state.ui.searchpanel - }) - }, - methods: { - toggleSearchpanel () { - this.$store.commit('ui/setSearchpanel', !this.isOpen) - } - } -} diff --git a/core/compatibility/components/blocks/Header/WishlistIcon.js b/core/compatibility/components/blocks/Header/WishlistIcon.js deleted file mode 100644 index 8cc9d63afe..0000000000 --- a/core/compatibility/components/blocks/Header/WishlistIcon.js +++ /dev/null @@ -1,32 +0,0 @@ -import { WishlistButton } from '@vue-storefront/core/modules/wishlist/components/WishlistButton' -export default { - name: 'WishlistIcon', - mixins: [ WishlistButton ], - props: { - product: { - type: Object, - required: false, - default: () => { } - } - }, - computed: { - // deprecated in this component - isWishlistOpen () { - return this.$store.state.ui.wishlist - } - }, - methods: { - // deprecated - closeWishlist () { - this.$store.commit('ui/setWishlist', false) - }, - // deprecated - openWishlist () { - this.$store.commit('ui/setWishlist', true) - }, - // method renamed to toggleWishlist - toggleWishlistPanel () { - this.toggleWishlist() - } - } -} diff --git a/core/compatibility/components/blocks/Microcart/Microcart.js b/core/compatibility/components/blocks/Microcart/Microcart.js deleted file mode 100644 index ac43fbd7a8..0000000000 --- a/core/compatibility/components/blocks/Microcart/Microcart.js +++ /dev/null @@ -1,19 +0,0 @@ -// Core dependecies -import { Microcart } from '@vue-storefront/core/modules/cart/components/Microcart.ts' - -export default { - methods: { - closeMicrocart () { - // Method renamed to 'toggleMicrocart' - this.toggleMicrocart() - } - }, - computed: { - isMicrocartOpen () { - return this.$store.state.ui.microcart - } - }, - mixins: [ - Microcart - ] -} diff --git a/core/compatibility/components/blocks/Microcart/Product.js b/core/compatibility/components/blocks/Microcart/Product.js deleted file mode 100644 index 50326a8b53..0000000000 --- a/core/compatibility/components/blocks/Microcart/Product.js +++ /dev/null @@ -1,56 +0,0 @@ -import { MicrocartProduct } from '@vue-storefront/core/modules/cart/components/Product.ts' -import i18n from '@vue-storefront/i18n' -import debounce from 'lodash-es/debounce' -import config from 'config' - -export default { - data () { - // deprecated - return { - } - }, - beforeMount () { - // deprecated, will be moved to theme or removed in the near future #1742 - this.$bus.$on('cart-after-itemchanged', this.onProductChanged) - this.$bus.$on('notification-after-itemremoved', this.onProductRemoved) - this.updateQuantity = debounce(this.updateQuantity, 1000) - }, - beforeDestroy () { - // deprecated, will be moved to theme or removed in the near future #1742 - this.$bus.$off('cart-after-itemchanged', this.onProductChanged) - this.$bus.$off('notification-after-itemremoved', this.onProductRemoved) - this.updateQuantity.cancel() - }, - methods: { - removeItem () { - if (config.cart.askBeforeRemoveProduct) { - this.$store.dispatch('notification/spawnNotification', { - type: 'warning', - item: this.product, - message: i18n.t('Are you sure you would like to remove this item from the shopping cart?'), - action2: { label: i18n.t('OK'), action: this.removeFromCart }, - action1: { label: i18n.t('Cancel'), action: 'close' }, - hasNoTimeout: true - }) - } else { - this.removeFromCart() - } - }, - updateQuantity (newQuantity) { - let quantity = parseInt(newQuantity) - if (quantity < 1) quantity = 1 - MicrocartProduct.methods.updateQuantity.call(this, quantity) - }, - onProductChanged (event) { - // deprecated, will be moved to theme or removed in the near future #1742 - if (event.item.sku === this.product.sku) { - this.$forceUpdate() - } - }, - onProductRemoved (event) { - if (event.item.sku === this.product.sku) { - this.removeFromCart(event.item) - } - } - } -} diff --git a/core/compatibility/components/blocks/MyAccount/MyOrder.js b/core/compatibility/components/blocks/MyAccount/MyOrder.js deleted file mode 100644 index b107d19ead..0000000000 --- a/core/compatibility/components/blocks/MyAccount/MyOrder.js +++ /dev/null @@ -1,7 +0,0 @@ -import { UserSingleOrder } from '@vue-storefront/core/modules/order/components/UserSingleOrder' - -// Component deprecated, now in Order module -export default { - name: 'MyOrder', - mixins: [UserSingleOrder] -} diff --git a/core/compatibility/components/blocks/MyAccount/MyOrders.js b/core/compatibility/components/blocks/MyAccount/MyOrders.js deleted file mode 100644 index e63625750c..0000000000 --- a/core/compatibility/components/blocks/MyAccount/MyOrders.js +++ /dev/null @@ -1,7 +0,0 @@ -import { UserOrders } from '@vue-storefront/core/modules/order/components/UserOrders' - -// component fully deprecated. Use user/components/Orders instead -export default { - name: 'MyOrders', - mixins: [UserOrders] -} diff --git a/core/compatibility/components/blocks/MyAccount/MyProfile.js b/core/compatibility/components/blocks/MyAccount/MyProfile.js deleted file mode 100644 index 89081c635d..0000000000 --- a/core/compatibility/components/blocks/MyAccount/MyProfile.js +++ /dev/null @@ -1,8 +0,0 @@ -import { UserAccount } from '@vue-storefront/core/modules/user/components/UserAccount' - -// Component deprecated, now in User module - -export default { - name: 'MyProfile', - mixins: [UserAccount] -} diff --git a/core/compatibility/components/blocks/MyAccount/MyShippingDetails.js b/core/compatibility/components/blocks/MyAccount/MyShippingDetails.js deleted file mode 100644 index 006c946401..0000000000 --- a/core/compatibility/components/blocks/MyAccount/MyShippingDetails.js +++ /dev/null @@ -1,4 +0,0 @@ -import { UserShippingDetails } from '@vue-storefront/core/modules/user/components/UserShippingDetails' -export default { - mixins: [UserShippingDetails] -} diff --git a/core/compatibility/components/blocks/SearchPanel/SearchPanel.js b/core/compatibility/components/blocks/SearchPanel/SearchPanel.js deleted file mode 100644 index 48614f244c..0000000000 --- a/core/compatibility/components/blocks/SearchPanel/SearchPanel.js +++ /dev/null @@ -1,16 +0,0 @@ -import { Search } from '@vue-storefront/core/modules/catalog/components/Search' - -// Moved to search module -export default { - mixins: [Search], - computed: { - showPanel () { - return this.isOpen && this.componentLoaded - } - }, - mounted () { - this.$nextTick(() => { - this.componentLoaded = true - }) - } -} diff --git a/core/compatibility/components/blocks/SidebarMenu/SidebarMenu.js b/core/compatibility/components/blocks/SidebarMenu/SidebarMenu.js deleted file mode 100644 index a92416ec26..0000000000 --- a/core/compatibility/components/blocks/SidebarMenu/SidebarMenu.js +++ /dev/null @@ -1,39 +0,0 @@ -import { mapState, mapGetters } from 'vuex' -import onEscapePress from '@vue-storefront/core/mixins/onEscapePress' -import { CompareButton } from '@vue-storefront/core/modules/compare/components/CompareButton.ts' -import config from 'config' - -// deprecated as theme-specific -export default { - name: 'SidebarMenu', - mixins: [onEscapePress, CompareButton], - computed: { - ...mapGetters('category-next', ['getMenuCategories']), - getCategories () { - return this.getMenuCategories - }, - categories () { - return this.getCategories.filter((op) => { - return op.level === (config.entities.category.categoriesDynamicPrefetchLevel >= 0 ? config.entities.category.categoriesDynamicPrefetchLevel : 2) // display only the root level (level =1 => Default Category), categoriesDynamicPrefetchLevel = 2 by default - }) - }, - ...mapState({ - isOpen: state => state.ui.sidebar - }), - compareIsActive () { - // Computed property renamed to 'isEmpty' - return !this.isEmpty - } - }, - created () { - }, - methods: { - onEscapePress () { - this.closeMenu() - }, - closeMenu () { - this.$store.commit('ui/setSidebar', false) - this.$store.commit('ui/setMicrocart', false) - } - } -} diff --git a/core/compatibility/components/blocks/Wishlist/Product.js b/core/compatibility/components/blocks/Wishlist/Product.js deleted file mode 100644 index de87541b62..0000000000 --- a/core/compatibility/components/blocks/Wishlist/Product.js +++ /dev/null @@ -1,11 +0,0 @@ -import { WishlistProduct } from '@vue-storefront/core/modules/wishlist/components/Product' -export default { - name: 'Product', - methods: { - // deprecated - closeWishlist () { - this.$store.commit('ui/setWishlist', false) - } - }, - mixins: [WishlistProduct] -} diff --git a/core/compatibility/components/blocks/Wishlist/Wishlist.js b/core/compatibility/components/blocks/Wishlist/Wishlist.js deleted file mode 100644 index 97d9235b75..0000000000 --- a/core/compatibility/components/blocks/Wishlist/Wishlist.js +++ /dev/null @@ -1,20 +0,0 @@ -import onEscapePress from '@vue-storefront/core/mixins/onEscapePress' -import { Wishlist } from '@vue-storefront/core/modules/wishlist/components/Wishlist' -export default { - name: 'Wishlist', - props: { - // deprecated - product: { - type: Object, - required: false, - default: () => { } - } - }, - methods: { - // theme-specific - onEscapePress () { - this.$store.dispatch('ui/closeWishlist') - } - }, - mixins: [ Wishlist, onEscapePress ] -} diff --git a/core/compatibility/plugins/config/index.js b/core/compatibility/plugins/config/index.js deleted file mode 100644 index db7ff3eb99..0000000000 --- a/core/compatibility/plugins/config/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import config from 'config' - -// deprecated, use vuex store instead -const ConfigPlugin = { - install (Vue) { - if (!Vue.prototype.$config) { - Object.defineProperties(Vue.prototype, { - $config: { - get: function () { - return config - } - } - }) - } - } -} - -export { config as default, ConfigPlugin } diff --git a/core/compatibility/plugins/event-bus/index.js b/core/compatibility/plugins/event-bus/index.js deleted file mode 100644 index a81ebee444..0000000000 --- a/core/compatibility/plugins/event-bus/index.js +++ /dev/null @@ -1,75 +0,0 @@ -import Vue from 'vue' -// will be replaced with new mechanism with code-completion (via modules), don't use if you don't need to -/** - * Filter extension is for running async data filters as event handlers - * Example: - * let product = {} - * EventBus.$filter('after-product-changed', (product) => { - * return Promise ((resolve, reject) => { - * product.sku = 'abc' - * resolve (product) - * }) - * }) - * EventBus.$filter('after-product-changed', (product) => { - * return Promise ((resolve, reject) => { - * product.name = 'ABC' - * resolve (product) - * }) - * }) - * EventBus.$emitFilter('after-product-changed', product).then((resultsFromEventHanlders) => { - * // here you have data modified by extensions - * // resultsFromEventHanlders = [ { sku: abc, name: 'ABC' }, { sku: abc, name: 'ABC' } ] - * }) - */ -const filterExt = { - $dataFilters: { - value: [], - writable: true - }, // data filters to be registered by extension developers - $filter: { - get: function () { - return (eventName, callback) => { - if (!this.$dataFilters[eventName]) { - this.$dataFilters[eventName] = [] - } - this.$dataFilters[eventName].push(callback) - } - } - }, - $emitFilter: { - get: function () { - return (eventName, ...args) => { - if (args.length === 1) { - args = args[0] - } - this.$emit(eventName, args) - let promises = [] - if (this.$dataFilters[eventName]) { - for (let cb of this.$dataFilters[eventName]) { - promises.push(cb(args)) - } - } - return Promise.all(promises) - } - } - } -} -const EventBus = new Vue() -if (!EventBus.$dataFilters) { - Object.defineProperties(EventBus, filterExt) -} - -const EventBusPlugin = { - install (Vue) { - if (!Vue.prototype.$bus) { /** Vue.prototype.$bus is now @deprecated please do use `EventBus` instead */ - Object.defineProperties(Vue.prototype, { - $bus: { - get: function () { - return EventBus - } - } - }) - } - } -} -export { EventBus as default, EventBusPlugin } diff --git a/core/compatibility/plugins/index.js b/core/compatibility/plugins/index.js deleted file mode 100644 index 42a61e6953..0000000000 --- a/core/compatibility/plugins/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import { EventBusPlugin } from '@vue-storefront/core/compatibility/plugins/event-bus' -import { ConfigPlugin } from '@vue-storefront/core/compatibility/plugins/config' -// used in core entrys and themes.js, deprecated bc of redundancy and for simplification of a project -export { - EventBusPlugin, - ConfigPlugin -} diff --git a/core/data-resolver/CartService.ts b/core/data-resolver/CartService.ts deleted file mode 100644 index 770d2a7295..0000000000 --- a/core/data-resolver/CartService.ts +++ /dev/null @@ -1,154 +0,0 @@ -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; -import { DataResolver } from './types/DataResolver' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import { processLocalizedURLAddress } from '@vue-storefront/core/helpers' -import config from 'config'; - -const setShippingInfo = async (addressInformation: any): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'shippinginfo_endpoint')), - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ addressInformation }) - }, - silent: true - }); - -const getTotals = async (): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'totals_endpoint')), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - silent: true - }); - -const getCartToken = async (guestCart: boolean = false, forceClientState: boolean = false): Promise => { - const url = processLocalizedURLAddress(guestCart - ? getApiEndpointUrl(config.cart, 'create_endpoint').replace('{{token}}', '') - : getApiEndpointUrl(config.cart, 'create_endpoint')) - - return TaskQueue.execute({ - url, - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - force_client_state: forceClientState, - silent: true - }); -} - -const updateItem = async (cartServerToken: string, cartItem: CartItem): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'updateitem_endpoint')), - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ - cartItem: { - ...cartItem, - quoteId: cartItem.quoteId || cartServerToken - } - }) - } - }); - -const deleteItem = async (cartServerToken: string, cartItem: CartItem): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'deleteitem_endpoint')), - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ - cartItem: { - ...cartItem, - quoteId: cartServerToken - } - }) - }, - silent: true - }); - -const getPaymentMethods = async (): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'paymentmethods_endpoint')), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - silent: true - }); - -const getShippingMethods = async (address: any): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'shippingmethods_endpoint')), - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ - address - }) - }, - silent: true - }); - -const getItems = async (): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'pull_endpoint')), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - silent: true - }); - -const applyCoupon = async (couponCode: string): Promise => { - const url = processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'applycoupon_endpoint').replace('{{coupon}}', couponCode)) - - return TaskQueue.execute({ - url, - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - silent: false - }); -} - -const removeCoupon = async (): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.cart, 'deletecoupon_endpoint')), - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - silent: false - }); - -export const CartService: DataResolver.CartService = { - setShippingInfo, - getTotals, - getCartToken, - updateItem, - deleteItem, - getPaymentMethods, - getShippingMethods, - getItems, - applyCoupon, - removeCoupon -} diff --git a/core/data-resolver/CategoryService.ts b/core/data-resolver/CategoryService.ts deleted file mode 100644 index 1505f8eda0..0000000000 --- a/core/data-resolver/CategoryService.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { quickSearchByQuery } from '@vue-storefront/core/lib/search'; -import { SearchQuery } from 'storefront-query-builder' -import config from 'config'; -import { DataResolver } from './types/DataResolver'; -import { Category } from 'core/modules/catalog-next/types/Category'; - -const getCategories = async ({ - parentId = null, - filters = {}, - level = null, - onlyActive = true, - onlyNotEmpty = false, - size = 4000, - start = 0, - sort = 'position:asc', - includeFields = config.entities.optimize ? config.entities.category.includeFields : null, - excludeFields = config.entities.optimize ? config.entities.category.excludeFields : null -}: DataResolver.CategorySearchOptions = {}): Promise => { - let searchQuery = new SearchQuery() - if (parentId) { - searchQuery = searchQuery.applyFilter({ key: 'parent_id', value: { 'eq': parentId } }) - } - if (level) { - searchQuery = searchQuery.applyFilter({ key: 'level', value: { 'eq': level } }) - } - - for (var [key, value] of Object.entries(filters)) { - if (value !== null) { - if (Array.isArray(value)) { - searchQuery = searchQuery.applyFilter({ key: key, value: { 'in': value } }) - } else if (typeof value === 'object') { - searchQuery = searchQuery.applyFilter({ key: key, value: value }) - } else { - searchQuery = searchQuery.applyFilter({ key: key, value: { 'eq': value } }) - } - } - } - - if (onlyActive === true) { - searchQuery = searchQuery.applyFilter({ key: 'is_active', value: { 'eq': true } }) - } - - if (onlyNotEmpty === true) { - searchQuery = searchQuery.applyFilter({ key: 'product_count', value: { 'gt': 0 } }) - } - const response = await quickSearchByQuery({ entityType: 'category', query: searchQuery, sort: sort, size: size, start: start, includeFields: includeFields, excludeFields: excludeFields }) - return response.items as Category[] -} - -export const CategoryService: DataResolver.CategoryService = { - getCategories -} diff --git a/core/data-resolver/NewsletterService.ts b/core/data-resolver/NewsletterService.ts deleted file mode 100644 index 7aaee8e32d..0000000000 --- a/core/data-resolver/NewsletterService.ts +++ /dev/null @@ -1,44 +0,0 @@ -import config from 'config'; -import { DataResolver } from './types/DataResolver'; -import { processURLAddress } from '@vue-storefront/core/helpers'; -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -const isSubscribed = (email: string): Promise => - TaskQueue.execute({ - url: processURLAddress(getApiEndpointUrl(config.newsletter, 'endpoint')) + '?email=' + encodeURIComponent(email), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - silent: true - }).then(({ result }) => result === 'subscribed') - -const subscribe = (email: string): Promise => - TaskQueue.execute({ - url: processURLAddress(getApiEndpointUrl(config.newsletter, 'endpoint')), - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ email }) - } - }).then(({ code }) => code === 200) - -const unsubscribe = (email: string): Promise => - TaskQueue.execute({ - url: processURLAddress(getApiEndpointUrl(config.newsletter, 'endpoint')), - payload: { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify({ email }) - } - }).then(({ code }) => code === 200) - -export const NewsletterService: DataResolver.NewsletterService = { - isSubscribed, - subscribe, - unsubscribe -} diff --git a/core/data-resolver/OrderService.ts b/core/data-resolver/OrderService.ts deleted file mode 100644 index 96054296ef..0000000000 --- a/core/data-resolver/OrderService.ts +++ /dev/null @@ -1,20 +0,0 @@ -import config from 'config'; -import { DataResolver } from './types/DataResolver'; -import { Order } from '@vue-storefront/core/modules/order/types/Order' -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -const placeOrder = (order: Order): Promise => - TaskQueue.execute({ url: getApiEndpointUrl(config.orders, 'endpoint'), // sync the order - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify(order) - } - }) - -export const OrderService: DataResolver.OrderService = { - placeOrder -} diff --git a/core/data-resolver/ProductService.ts b/core/data-resolver/ProductService.ts deleted file mode 100644 index 3dd1622d38..0000000000 --- a/core/data-resolver/ProductService.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { getOptimizedFields } from '@vue-storefront/core/modules/catalog/helpers/search'; -import { canCache, storeProductToCache } from './../modules/catalog/helpers/search'; -import { doPlatformPricesSync } from '@vue-storefront/core/modules/catalog/helpers'; -import { isServer } from '@vue-storefront/core/helpers'; -import { quickSearchByQuery, isOnline } from '@vue-storefront/core/lib/search'; -import { SearchQuery } from 'storefront-query-builder' -import config from 'config'; -import { DataResolver } from './types/DataResolver'; -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import { entityKeyName } from '@vue-storefront/core/lib/store/entities' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { Logger } from '@vue-storefront/core/lib/logger'; -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { prepareProducts } from '@vue-storefront/core/modules/catalog/helpers/prepare'; -import { configureProducts } from '@vue-storefront/core/modules/catalog/helpers/configure'; - -const getProducts = async ({ - query, - start = 0, - size = 50, - sort = '', - excludeFields = null, - includeFields = null, - configuration = null, - options: { - prefetchGroupProducts = !isServer, - fallbackToDefaultWhenNoAvailable = true, - setProductErrors = false, - setConfigurableProductOptions = config.cart.setConfigurableProductOptions, - filterUnavailableVariants = config.products.filterUnavailableVariants, - assignProductConfiguration = false, - separateSelectedVariant = false - } = {} -}: DataResolver.ProductSearchOptions): Promise => { - const isCacheable = canCache({ includeFields, excludeFields }) - const { excluded, included } = getOptimizedFields({ excludeFields, includeFields }) - let { - items: products = [], - attributeMetadata = [], - aggregations = [], - total, - perPage - } = await quickSearchByQuery({ - query, - start, - size, - entityType: 'product', - sort, - excludeFields: excluded, - includeFields: included - }) - - products = prepareProducts(products) - - for (let product of products) { // we store each product separately in cache to have offline access to products/single method - if (isCacheable) { // store cache only for full loads - storeProductToCache(product, 'sku') - } - } - - const configuredProducts = await configureProducts({ - products, - attributes_metadata: attributeMetadata, - configuration, - options: { - prefetchGroupProducts, - fallbackToDefaultWhenNoAvailable, - setProductErrors, - setConfigurableProductOptions, - filterUnavailableVariants, - assignProductConfiguration, - separateSelectedVariant - }, - excludeFields: excluded, - includeFields: included - }) - - return { - items: configuredProducts, - perPage, - start, - total, - aggregations, - attributeMetadata - } -} - -const getProductRenderList = async ({ - skus, - isUserGroupedTaxActive, - userGroupId, - token -}): Promise => { - const { i18n, storeId } = currentStoreView() - let url = [ - `${getApiEndpointUrl(config.products, 'endpoint')}/render-list`, - `?skus=${encodeURIComponent(skus.join(','))}`, - `¤cyCode=${encodeURIComponent(i18n.currencyCode)}`, - `&storeId=${encodeURIComponent(storeId)}` - ].join('') - if (isUserGroupedTaxActive) { - url = `${url}&userGroupId=${userGroupId}` - } - - if (token && !config.users.tokenInHeader) { - url = `${url}&token=${token}` - } - - try { - const task = await TaskQueue.execute({ url, // sync the cart - payload: { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token && config.users.tokenInHeader ? { authorization: `Bearer ${token}` } : {}) - }, - mode: 'cors' - }, - callback_event: 'prices-after-sync' - }) - return task.result as DataResolver.ProductsListResponse - } catch (err) { - console.error(err) - return { items: [] } - } -} - -const getProduct = async (options: { [key: string]: string }, key: string): Promise => { - let searchQuery = new SearchQuery() - searchQuery = searchQuery.applyFilter({ key: key, value: { 'eq': options[key] } }) - const { items = [] } = await getProducts({ - query: searchQuery, - size: 1, - configuration: { sku: options.childSku }, - options: { - prefetchGroupProducts: true, - assignProductConfiguration: true - } - }) - return items[0] || null -} - -const getProductFromCache = async (options: { [key: string]: string }, key: string): Promise => { - try { - const cacheKey = entityKeyName(key, options[key]) - const cache = StorageManager.get('elasticCache') - const result = await cache.getItem(cacheKey) - if (result !== null) { - if (config.products.alwaysSyncPlatformPricesOver) { - if (!config.products.waitForPlatformSync) { - await doPlatformPricesSync([result]) - } else { - doPlatformPricesSync([result]) - } - } - const { excluded, included } = getOptimizedFields({ excludeFields: null, includeFields: null }) - const [product] = await configureProducts({ - products: [result], - attributes_metadata: [], - configuration: { [key]: options.childSku || options.sku || options[key] }, - options: { - prefetchGroupProducts: true, - setConfigurableProductOptions: config.cart.setConfigurableProductOptions, - filterUnavailableVariants: config.products.filterUnavailableVariants, - assignProductConfiguration: true - }, - excludeFields: excluded, - includeFields: included - }) - return product - } else { - return getProduct(options, key) - } - } catch (err) { - // report errors - if (err) { - Logger.error(err, 'product')() - } - return getProduct(options, key) - } -} - -const getProductByKey = async ({ options, key, skipCache }: DataResolver.ProductByKeySearchOptions): Promise => { - if (!isOnline()) { - return getProductFromCache(options, key) - } - const result = skipCache - ? await getProduct(options, key) - : await getProductFromCache(options, key) - return result -} - -export const ProductService: DataResolver.ProductService = { - getProducts, - getProductRenderList, - getProductByKey -} diff --git a/core/data-resolver/ReviewsService.ts b/core/data-resolver/ReviewsService.ts deleted file mode 100644 index da3fa3e88e..0000000000 --- a/core/data-resolver/ReviewsService.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { DataResolver } from './types/DataResolver'; -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import { processLocalizedURLAddress } from '@vue-storefront/core/helpers' -import config from 'config' -import Review from 'core/modules/review/types/Review'; -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -const createReview = (review: Review): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.reviews, 'create_endpoint')), - payload: { - method: 'POST', - mode: 'cors', - headers: { - 'Accept': 'application/json, text/plain, */*', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ review }) - } - }).then(({ code }) => code === 200) - -export const ReviewsService: DataResolver.ReviewsService = { - createReview -} diff --git a/core/data-resolver/StockService.ts b/core/data-resolver/StockService.ts deleted file mode 100644 index 7c89bc5961..0000000000 --- a/core/data-resolver/StockService.ts +++ /dev/null @@ -1,52 +0,0 @@ -import config from 'config'; -import { DataResolver } from './types/DataResolver'; -import { TaskQueue } from '@vue-storefront/core/lib/sync'; -import Task from '@vue-storefront/core/lib/sync/types/Task'; -import { processURLAddress } from '@vue-storefront/core/helpers'; -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -const queueCheck = (sku: string, actionName: string): Promise => - TaskQueue.queue({ - url: processURLAddress(`${getApiEndpointUrl(config.stock, 'endpoint')}/check?sku=${encodeURIComponent(sku)}`), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - is_result_cacheable: true, - product_sku: sku, - callback_event: `store:${actionName}` - }) - -const check = (sku: string): Promise => - TaskQueue.execute({ - url: processURLAddress(`${getApiEndpointUrl(config.stock, 'endpoint')}/check?sku=${encodeURIComponent(sku)}`), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - is_result_cacheable: true, - product_sku: sku - }) - -const list = (skuList: string[]): Promise => - TaskQueue.execute({ - url: processURLAddress( - `${getApiEndpointUrl(config.stock, 'endpoint')}/list?skus=${encodeURIComponent( - skuList.join(',') - )}` - ), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors' - }, - skus: skuList - }) - -export const StockService: DataResolver.StockService = { - check, - list, - queueCheck -} diff --git a/core/data-resolver/UserService.ts b/core/data-resolver/UserService.ts deleted file mode 100644 index 97ad0aa499..0000000000 --- a/core/data-resolver/UserService.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { DataResolver } from './types/DataResolver'; -import { UserProfile } from '@vue-storefront/core/modules/user/types/UserProfile' -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import { processLocalizedURLAddress } from '@vue-storefront/core/helpers' -import config from 'config' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -const headers = { - 'Accept': 'application/json, text/plain, */*', - 'Content-Type': 'application/json' -} - -const resetPassword = async (email: string): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'resetPassword_endpoint')), - payload: { - method: 'POST', - mode: 'cors', - headers, - body: JSON.stringify({ email }) - } - }) - -const createPassword = async (email: string, newPassword: string, resetToken: string): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(config.users.createPassword_endpoint), - payload: { - method: 'POST', - mode: 'cors', - headers, - body: JSON.stringify({ email, newPassword, resetToken }) - } - }) - -const login = async (username: string, password: string): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'login_endpoint')), - payload: { - method: 'POST', - mode: 'cors', - headers, - body: JSON.stringify({ username, password }) - } - }) - -const register = async (customer: DataResolver.Customer, password: string): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'create_endpoint')), - payload: { - method: 'POST', - headers, - body: JSON.stringify({ customer, password }) - } - }) - -const updateProfile = async (userProfile: UserProfile, actionName: string): Promise => - TaskQueue.queue({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'me_endpoint')), - payload: { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - body: JSON.stringify(userProfile) - }, - callback_event: `store:${actionName}` - }) - -const getProfile = async () => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'me_endpoint')), - payload: { - method: 'GET', - mode: 'cors', - headers - } - }) - -const getOrdersHistory = async (pageSize = 20, currentPage = 1): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress( - getApiEndpointUrl(config.users, 'history_endpoint').replace('{{pageSize}}', pageSize + '').replace('{{currentPage}}', currentPage + '') - ), - payload: { - method: 'GET', - mode: 'cors', - headers - } - }) - -const changePassword = async (passwordData: DataResolver.PasswordData): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(getApiEndpointUrl(config.users, 'changePassword_endpoint')), - payload: { - method: 'POST', - mode: 'cors', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(passwordData) - } - }) - -const refreshToken = async (refreshToken: string): Promise => - fetch(processLocalizedURLAddress(getApiEndpointUrl(config.users, 'refresh_endpoint')), { - method: 'POST', - mode: 'cors', - headers, - body: JSON.stringify({ refreshToken }) - }).then(resp => resp.json()) - .then(resp => resp.result) - -export const UserService: DataResolver.UserService = { - resetPassword, - createPassword, - login, - register, - updateProfile, - getProfile, - getOrdersHistory, - changePassword, - refreshToken -} diff --git a/core/data-resolver/index.ts b/core/data-resolver/index.ts deleted file mode 100644 index 35504d297e..0000000000 --- a/core/data-resolver/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { CategoryService } from './CategoryService' -import { UserService } from './UserService' -import { CartService } from './CartService' -import { OrderService } from './OrderService' -import { StockService } from './StockService' -import { ReviewsService } from './ReviewsService' -import { NewsletterService } from './NewsletterService' - -export { - CategoryService, - UserService, - CartService, - OrderService, - StockService, - ReviewsService, - NewsletterService -} diff --git a/core/data-resolver/types/DataResolver.d.ts b/core/data-resolver/types/DataResolver.d.ts deleted file mode 100644 index 88631409dc..0000000000 --- a/core/data-resolver/types/DataResolver.d.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { AttributesMetadata } from '@vue-storefront/core/modules/catalog/types/Attribute'; -import { Category } from '@vue-storefront/core/modules/catalog-next/types/Category'; -import { UserProfile } from '@vue-storefront/core/modules/user/types/UserProfile' -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import { Order } from '@vue-storefront/core/modules/order/types/Order' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import Review from '@vue-storefront/core/modules/review/types/Review'; -import { SearchQuery } from 'storefront-query-builder'; -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -declare namespace DataResolver { - - interface CategorySearchOptions { - parentId?: number | string, - filters?: { [key: string]: string[] | string }, - level?: number, - onlyActive?: boolean, - onlyNotEmpty?: boolean, - size?: number, - start?: number, - sort?: string, - includeFields?: string[], - excludeFields?: string[], - reloadAll?: boolean - } - - interface ProductSearchOptions { - query: SearchQuery, - size?: number, - start?: number, - sort?: string, - includeFields?: string[], - excludeFields?: string[], - configuration?: { [key: string]: string[] | string }, - options?: { - prefetchGroupProducts?: boolean, - fallbackToDefaultWhenNoAvailable?: boolean, - setProductErrors?: boolean, - setConfigurableProductOptions?: boolean, - filterUnavailableVariants?: boolean, - assignProductConfiguration?: boolean, - separateSelectedVariant?: boolean - } - } - - interface ProductRenderListSearchOptions { - skus: string[], - isUserGroupedTaxActive?: boolean, - userGroupId?: string, - token?: string - } - - interface ProductByKeySearchOptions { - options: { [key: string]: string }, - key?: string, - skipCache?: boolean - } - - interface Customer { - email: string, - firstname: string, - lastname: string, - addresses: string - } - - interface PasswordData { - currentPassword: string, - newPassword: string - } - - interface ProductsListResponse { - items: Product[], - perPage?: number, - start?: number, - total?: number, - aggregations?: any[], - attributeMetadata?: AttributesMetadata[] - } - - interface ProductService { - getProducts: (searchRequest: ProductSearchOptions) => Promise, - getProductRenderList: (searchRequest: ProductRenderListSearchOptions) => Promise, - getProductByKey: (searchRequest: ProductByKeySearchOptions) => Promise - } - - interface CategoryService { - getCategories: (searchRequest?: CategorySearchOptions) => Promise - } - - interface UserService { - resetPassword: (email: string) => Promise, - createPassword: (email: string, newPassword: string, resetToken: string) => Promise, - login: (username: string, password: string) => Promise, - register: (customer: Customer, pssword: string) => Promise, - updateProfile: (userProfile: UserProfile, actionName: string) => Promise, - getProfile: () => Promise, - getOrdersHistory: (pageSize?: number, currentPage?: number) => Promise, - changePassword: (passwordData: PasswordData) => Promise, - refreshToken: (refreshToken: string) => Promise - } - - interface CartService { - setShippingInfo: (methodsData: any /*: ShippingMethodsData */) => Promise, - getTotals: () => Promise, - getCartToken: (guestCart: boolean, forceClientState: boolean) => Promise, - updateItem: (cartServerToken: string, cartItem: CartItem) => Promise, - deleteItem: (cartServerToken: string, cartItem: CartItem) => Promise, - getPaymentMethods: () => Promise, - getShippingMethods: (address: any /*: ShippingMethodsData */) => Promise, - getItems: () => Promise, - applyCoupon: (couponCode: string) => Promise, - removeCoupon: () => Promise - } - - interface OrderService { - placeOrder: (order: Order) => Promise - } - - interface StockService { - check: (sku: string) => Promise, - queueCheck: (sku: string, actionName: string) => Promise, - list: (skuList: string[]) => Promise - } - - interface ReviewsService { - createReview: (review: Review) => Promise - } - - interface NewsletterService { - isSubscribed: (email: string) => Promise, - subscribe: (email: string) => Promise, - unsubscribe: (email: string) => Promise - } -} diff --git a/core/filters/capitalize.js b/core/filters/capitalize.js deleted file mode 100644 index 072a7ef840..0000000000 --- a/core/filters/capitalize.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Capitalize first letter of provided text - * @param {String} text - */ -export function capitalize (text) { - if (!text) return '' - text = text.toString() - return text.charAt(0).toUpperCase() + text.slice(1) -} diff --git a/core/filters/date.js b/core/filters/date.js deleted file mode 100644 index 1bcd01b9c2..0000000000 --- a/core/filters/date.js +++ /dev/null @@ -1,28 +0,0 @@ -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import dayjs from 'dayjs' -import dayjsLocalizedFormat from 'dayjs/plugin/localizedFormat' -import { once } from '../helpers'; - -once('__VUE_EXTEND_DAYJS_LOCALIZED_FORMAT__', () => { - dayjs.extend(dayjsLocalizedFormat) -}) - -/** - * Converts date to format provided as an argument or defined in config file (if argument not provided) - * @param {String} date - * @param {String} format - */ -export function date (date, format, storeView) { - const _storeView = storeView || currentStoreView() - const displayFormat = format || _storeView.i18n.dateFormat - let storeLocale = _storeView.i18n.defaultLocale.toLocaleLowerCase() - const separatorIndex = storeLocale.indexOf('-') - const languageCode = (separatorIndex > -1) ? storeLocale.substr(0, separatorIndex) : storeLocale - - const isStoreLocale = dayjs().locale(storeLocale).locale() - const isLanguageLocale = dayjs().locale(languageCode).locale() - const locale = isStoreLocale || isLanguageLocale - - if (locale) return dayjs(date).locale(languageCode).format(displayFormat) - return dayjs(date).format(displayFormat) -} diff --git a/core/filters/html-decode.js b/core/filters/html-decode.js deleted file mode 100644 index c40b677072..0000000000 --- a/core/filters/html-decode.js +++ /dev/null @@ -1,9 +0,0 @@ -import decode from 'lean-he/decode' - -/** - * Decodes any named and numerical character references in text - * @param {String} value - */ -export function htmlDecode (value) { - return value ? decode(value) : '' -} diff --git a/core/filters/index.js b/core/filters/index.js deleted file mode 100644 index f649e07fa5..0000000000 --- a/core/filters/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import { price } from './price' -import { htmlDecode } from './html-decode' -import { date } from './date' -import { capitalize } from './capitalize' -import { formatProductMessages } from './product-messages' -import { stripHTML } from './strip-html' - -export { - price, - htmlDecode, - date, - capitalize, - formatProductMessages, - stripHTML -} diff --git a/core/filters/price.js b/core/filters/price.js deleted file mode 100644 index c7e5558bba..0000000000 --- a/core/filters/price.js +++ /dev/null @@ -1,46 +0,0 @@ -import { currentStoreView } from '@vue-storefront/core/lib/multistore' - -const applyCurrencySign = (formattedPrice, { currencySign, priceFormat }) => { - return priceFormat.replace('{sign}', currencySign).replace('{amount}', formattedPrice) -}; - -const getLocaleSeparators = (defaultLocale) => { - return { - decimal: (0.01).toLocaleString(defaultLocale).replace(/[0-9]/g, ''), - group: (1000).toLocaleString(defaultLocale).replace(/[0-9]/g, '') - } -}; - -const replaceSeparators = (formattedPrice, currencySeparators, separators) => { - if (currencySeparators.decimal) formattedPrice = formattedPrice.replace(separators.decimal, currencySeparators.decimal); - if (currencySeparators.group) formattedPrice = formattedPrice.replace(separators.group, currencySeparators.group); - return formattedPrice; -}; - -/** - * Converts number to price string - * @param {Number} value - */ -export function price (value, storeView) { - if (isNaN(value)) { - return value - } - const _storeView = storeView || currentStoreView(); - if (!_storeView.i18n) { - return Number(value).toFixed(2) - } - - const { defaultLocale, currencySign, currencyDecimal, currencyGroup, fractionDigits, priceFormat } = _storeView.i18n; - - const options = { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits }; - - let localePrice = Math.abs(value).toLocaleString(defaultLocale, options); - - if (currencyDecimal !== '' || currencyGroup !== '') { - localePrice = replaceSeparators(localePrice, { decimal: currencyDecimal, group: currencyGroup }, getLocaleSeparators(defaultLocale)); - } - - const valueWithSign = applyCurrencySign(localePrice, { currencySign, priceFormat }); - - return value >= 0 ? valueWithSign : '-' + valueWithSign; -} diff --git a/core/filters/product-messages.ts b/core/filters/product-messages.ts deleted file mode 100644 index 2442a66f4b..0000000000 --- a/core/filters/product-messages.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Format message string for product validation messages object - */ -export function formatProductMessages (messages: Record): string { - const msgs = [] - for (const infoKey in messages) { - if (messages[infoKey]) { - msgs.push(messages[infoKey]) - } - } - return msgs.join(', ') -} diff --git a/core/filters/strip-html.js b/core/filters/strip-html.js deleted file mode 100644 index 2f6c5f6d29..0000000000 --- a/core/filters/strip-html.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Strip HTML tags - * @param {String} html - */ -export function stripHTML (html) { - if (!html) return '' - return html.replace(/<[^>]+>/g, '').trim() -} diff --git a/core/helpers/getApiEndpointUrl.ts b/core/helpers/getApiEndpointUrl.ts deleted file mode 100644 index 86b5a4f0ca..0000000000 --- a/core/helpers/getApiEndpointUrl.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers'; - -// object - parent object in the config, e.g. config.cart -// field - field inside the object, e.g. create_endpoint - -// returns - object.[field]_ssr if it exists and it is a server, -// object.field otherwise - -export default (object: Record, field: string): string => { - return isServer && object[`${field}_ssr`] ? object[`${field}_ssr`] : object[field] -} diff --git a/core/helpers/index.ts b/core/helpers/index.ts deleted file mode 100644 index 7cd2e1eded..0000000000 --- a/core/helpers/index.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' -import { remove as removeAccents } from 'remove-accents' -import { formatCategoryLink } from '@vue-storefront/core/modules/url/helpers' -import Vue from 'vue' -import config from 'config' -import { sha3_224 } from 'js-sha3' -import store from '@vue-storefront/core/store' -import { adjustMultistoreApiUrl } from '@vue-storefront/core/lib/multistore' -import { coreHooksExecutors } from '@vue-storefront/core/hooks'; -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; -import omit from 'lodash-es/omit' - -export const processURLAddress = (url: string = '') => { - if (url.startsWith('/')) return `${getApiEndpointUrl(config.api, 'url')}${url}` - return url -} - -export const processLocalizedURLAddress = (url: string = '') => { - if (config.storeViews.multistore) { - return processURLAddress(adjustMultistoreApiUrl(url)) - } - - return processURLAddress(url) -} - -/** - * Create slugify -> "create-slugify" permalink of text - * @param {String} text - */ -export function slugify (text) { - // remove regional characters - text = removeAccents(text) - - return text - .toString() - .toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/&/g, '-and-') // Replace & with 'and' - .replace(/[^\w-]+/g, '') // Remove all non-word chars - .replace(/--+/g, '-') // Replace multiple - with single - -} - -/** - * @param {string} relativeUrl - * @param {number} width - * @param {number} height - * @param {string} pathType - * @returns {string} - */ -export function getThumbnailPath (relativeUrl: string, width: number = 0, height: number = 0, pathType: string = 'product'): string { - if (config.images.useSpecificImagePaths) { - const path = config.images.paths[pathType] !== undefined ? config.images.paths[pathType] : '' - relativeUrl = path + relativeUrl - } - - if (config.images.useExactUrlsNoProxy) { - return coreHooksExecutors.afterProductThumbnailPathGenerate({ path: relativeUrl, sizeX: width, sizeY: height, pathType }).path // this is exact url mode - } else { - let resultUrl - if (relativeUrl && (relativeUrl.indexOf('://') > 0 || relativeUrl.indexOf('?') > 0 || relativeUrl.indexOf('&') > 0)) relativeUrl = encodeURIComponent(relativeUrl) - // proxyUrl is not a url base path but contains {{url}} parameters and so on to use the relativeUrl as a template value and then do the image proxy opertions - let baseUrl = processURLAddress(config.images.proxyUrl ? config.images.proxyUrl : config.images.baseUrl) - if (baseUrl.indexOf('{{') >= 0) { - baseUrl = baseUrl.replace('{{url}}', relativeUrl) - baseUrl = baseUrl.replace('{{width}}', width.toString()) - baseUrl = baseUrl.replace('{{height}}', height.toString()) - resultUrl = baseUrl - } else { - resultUrl = `${baseUrl}${width.toString()}/${height.toString()}/resize${relativeUrl}` - } - const path = relativeUrl && relativeUrl.indexOf('no_selection') < 0 ? resultUrl : config.images.productPlaceholder || '' - - return coreHooksExecutors.afterProductThumbnailPathGenerate({ path, sizeX: width, sizeY: height, pathType }).path - } -} - -/** - * Re-format category path to be suitable for breadcrumb - * @param {Array} categoryPath - */ -export function formatBreadCrumbRoutes (categoryPath) { - const breadCrumbRoutesArray = [] - for (let category of categoryPath) { - breadCrumbRoutesArray.push({ - name: category.name, - route_link: formatCategoryLink(category) - }) - } - return breadCrumbRoutesArray -} - -/** - * Return configurable product thumbnail depending on the configurable_children - * @param {object} product - * @param {bool} ignoreConfig - */ -export function productThumbnailPath (product, ignoreConfig = false) { - let thumbnail = product.image - if ((!thumbnail && product.type_id && product.type_id === 'configurable') && product.hasOwnProperty('configurable_children') && - product.configurable_children.length && (ignoreConfig || !product.is_configured) && - ('image' in product.configurable_children[0]) - ) { - thumbnail = product.configurable_children[0].image - if (!thumbnail || thumbnail === 'no_selection') { - const childWithImg = product.configurable_children.find(f => f.image && f.image !== 'no_selection') - if (childWithImg) { - thumbnail = childWithImg.image - } else { - thumbnail = product.image - } - } - } - return thumbnail -} - -export function baseFilterProductsQuery (parentCategory, filters = []) { // TODO add aggregation of color_options and size_options fields - let searchProductQuery = new SearchQuery() - searchProductQuery = searchProductQuery - .applyFilter({ key: 'visibility', value: { 'in': [2, 3, 4] } }) - .applyFilter({ key: 'status', value: { 'in': [0, 1] } }) /* 2 = disabled, 4 = out of stock */ - - if (config.products.listOutOfStockProducts === false) { - searchProductQuery = searchProductQuery.applyFilter({ key: 'stock.is_in_stock', value: { 'eq': true } }) - } - // Add available catalog filters - for (let attrToFilter of filters) { - searchProductQuery = searchProductQuery.addAvailableFilter({ field: attrToFilter, scope: 'catalog' }) - } - - let childCats = [parentCategory.id] - if (parentCategory.children_data) { - let recurCatFinderBuilder = (category) => { - if (!category) { - return - } - - if (!category.children_data) { - return - } - - for (let sc of category.children_data) { - if (sc && sc.id) { - childCats.push(sc.id) - } - recurCatFinderBuilder(sc) - } - } - recurCatFinderBuilder(parentCategory) - } - searchProductQuery = searchProductQuery.applyFilter({ key: 'category_ids', value: { 'in': childCats } }) - return searchProductQuery -} - -export function buildFilterProductsQuery (currentCategory, chosenFilters = {}, defaultFilters = null) { - let filterQr = baseFilterProductsQuery(currentCategory, defaultFilters == null ? config.products.defaultFilters : defaultFilters) - - // add choosedn filters - for (let code of Object.keys(chosenFilters)) { - const filter = chosenFilters[code] - const attributeCode = Array.isArray(filter) ? filter[0].attribute_code : filter.attribute_code - - if (Array.isArray(filter) && attributeCode !== 'price') { - const values = filter.map(filter => filter.id) - filterQr = filterQr.applyFilter({ key: attributeCode, value: { 'in': values }, scope: 'catalog' }) - } else if (attributeCode !== 'price') { - filterQr = filterQr.applyFilter({ key: attributeCode, value: { 'eq': filter.id }, scope: 'catalog' }) - } else { // multi should be possible filter here? - const rangeqr = {} - const filterValues = Array.isArray(filter) ? filter : [filter] - filterValues.forEach(singleFilter => { - if (singleFilter.from) rangeqr['gte'] = singleFilter.from - if (singleFilter.to) rangeqr['lte'] = singleFilter.to - }) - filterQr = filterQr.applyFilter({ key: attributeCode, value: rangeqr, scope: 'catalog' }) - } - } - - return filterQr -} - -export function once (key, fn) { - const { process = {} } = global - const processKey = key + '__ONCE__' - if (!process.hasOwnProperty(processKey)) { - // Logger.debug(`Once ${key}`, 'helper')() - process[processKey] = true - fn() - } -} - -export const isServer: boolean = typeof window === 'undefined' - -// Online/Offline helper -export const onlineHelper = Vue.observable({ - isOnline: isServer || navigator.onLine -}) - -export const routerHelper = Vue.observable({ - popStateDetected: false -}) - -!isServer && window.addEventListener('online', () => { onlineHelper.isOnline = true }) -!isServer && window.addEventListener('offline', () => { onlineHelper.isOnline = false }) -!isServer && window.addEventListener('popstate', () => { routerHelper.popStateDetected = true }) -if (!isServer && 'scrollRestoration' in history) { - history.scrollRestoration = 'manual' -} - -/* - * serial executes Promises sequentially. - * @param {funcs} An array of funcs that return promises. - * @example - * const urls = ['/url1', '/url2', '/url3'] - * serial(urls.map(url => () => $.ajax(url))) - * .then(Logger.log.bind(Logger))() - */ -export const serial = async promises => { - const results = [] - for (const item of promises) { - const result = await item; - results.push(result) - } - return results -} - -// helper to calculate the hash of the shopping cart -export const calcItemsHmac = (items = [], token) => { - return sha3_224(JSON.stringify({ - // we need to omit those properties because they are loaded async and added to product data - // and they are not needed to compare products - items: items.map(item => omit(item, ['stock', 'totals'])), - token: token - })) -} - -export function extendStore (moduleName: string | string[], module: any) { - const merge = function (object: any = {}, source: any) { - for (let key in source) { - if (Array.isArray(source[key])) { - object[key] = merge([], source[key]) - } else if (source[key] === null && !object[key]) { - object[key] = null - } else if (typeof source[key] === 'object' && Object.keys(source[key]).length > 0) { - object[key] = merge(object[key], source[key]) - } else if (typeof source[key] === 'object' && object === null) { - object = {} - object[key] = source[key] - } else { - object[key] = source[key] - } - } - return object - }; - moduleName = Array.isArray(moduleName) ? moduleName : [moduleName] - const originalModule: any = moduleName.reduce( - (state: any, moduleName: string) => state._children[moduleName], - (store as any)._modules.root - ) - const rawModule: any = merge({}, originalModule._rawModule) - const extendedModule: any = merge(rawModule, module) - - store.unregisterModule(moduleName) - store.registerModule(moduleName, extendedModule) -} - -export function reviewJsonLd (reviews, { name, category, mpn, url_path, price, stock, is_in_stock, sku, image, description }, priceCurrency) { - return reviews.map(({ title, detail, nickname, created_at }) => ( - { - '@context': 'http://schema.org/', - '@type': 'Review', - reviewAspect: title, - reviewBody: detail, - datePublished: created_at, - author: nickname, - itemReviewed: { - '@type': 'Product', - name, - sku, - image, - description, - offers: { - '@type': 'Offer', - category: category - ? category - .map(({ name }) => name || null) - .filter(name => name !== null) - : null, - mpn, - url: url_path, - priceCurrency, - price, - itemCondition: 'https://schema.org/NewCondition', - availability: stock && is_in_stock ? 'InStock' : 'OutOfStock' - } - } - } - ) - ) -} - -function getMaterials (material, customAttributes) { - const materialsArr = [] - if (customAttributes && customAttributes.length && customAttributes.length > 0 && material && material.length && material.length > 0) { - const materialOptions = customAttributes.find(({ attribute_code }) => attribute_code === 'material').options - if (Array.isArray(material)) { - for (let key in materialOptions) { - material.forEach(el => { - if (String(el) === materialOptions[key].value) { - materialsArr.push(materialOptions[key].label) - } - }) - } - } else { - for (let key in materialOptions) { - if (material === materialOptions[key].value) { - materialsArr.push(materialOptions[key].label) - } - } - } - } - return materialsArr -} - -export function productJsonLd ({ category, image, name, id, sku, mpn, description, price, url_path, stock, is_in_stock, material }, color, priceCurrency, customAttributes) { - return { - '@context': 'http://schema.org', - '@type': 'Product', - category: category - ? category - .map(({ name }) => name || null) - .filter(name => name !== null) - : null, - color, - description, - image, - itemCondition: 'http://schema.org/NewCondition', - material: getMaterials(material, customAttributes), - name, - productID: id, - sku, - mpn, - offers: { - '@type': 'Offer', - category: category - ? category - .map(({ name }) => name || null) - .filter(name => name !== null) - : null, - mpn, - url: url_path, - priceCurrency, - price, - itemCondition: 'https://schema.org/NewCondition', - availability: stock && is_in_stock ? 'InStock' : 'OutOfStock', - sku - } - } -} diff --git a/core/helpers/initialStateFactory.ts b/core/helpers/initialStateFactory.ts deleted file mode 100644 index b1e2884028..0000000000 --- a/core/helpers/initialStateFactory.ts +++ /dev/null @@ -1,17 +0,0 @@ -import cloneDeep from 'lodash-es/cloneDeep' -import pick from 'lodash-es/pick' - -const initialStateFactory = (defaultState) => { - // storing default values for the fields that will be set in createApp - const defaultFields = pick(defaultState, ['version', 'config', '__DEMO_MODE__', 'storeView']) - - const createInitialState = (currentState) => ({ - ...cloneDeep(currentState), - ...defaultFields, - storeView: { storeCode: currentState.storeView.storeCode } - }) - - return { createInitialState } -} - -export default initialStateFactory diff --git a/core/helpers/internal.ts b/core/helpers/internal.ts deleted file mode 100644 index 71eb8ab624..0000000000 --- a/core/helpers/internal.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers' - -/** - * ValidationError to be used with multiple validation errors return from Ajv or other validators -*/ -export class HttpError { - private message: string - private code: string | number - private name: string - - public constructor (message, code) { - this.message = message - this.code = code - this.name = 'ValidationError' - } - public toString () { - return 'HttpError' + this.code + ': ' + this.message - } -} - -/** - * @param {string} level available options: 'no-console', 'only-errors', 'all' - */ -export function takeOverConsole (level = 'no-console') { - const console = !isServer ? window.console : global.console - if (!console) return - - function intercept (method) { - const original = console[method] - console[method] = function () { - let filterMethods = [] - - if (level === 'no-console') { - filterMethods = ['warn', 'debug', 'log', 'error'] - } - if (level === 'only-errors') { - filterMethods = ['warn', 'debug', 'log'] - } - - if (filterMethods.indexOf(method) >= 0) { - return - } - // do sneaky stuff - if (original.apply) { - // Do this for normal browsers - original.apply(console, arguments) - } else { - // Do this for IE - const message = Array.prototype.slice.apply(arguments).join(' ') - original(message) - } - } - } - const methods = ['log', 'warn', 'error', 'debug'] - for (let i = 0; i < methods.length; i++) { - intercept(methods[i]) - } -} diff --git a/core/helpers/router.ts b/core/helpers/router.ts deleted file mode 100644 index 331beb48cf..0000000000 --- a/core/helpers/router.ts +++ /dev/null @@ -1,51 +0,0 @@ -import rootStore from '@vue-storefront/core/store'; -import VueRouter, { RouteConfig } from 'vue-router' -import { RouterManager } from '@vue-storefront/core/lib/router-manager' -import { ErrorHandler, RawLocation, Route } from 'vue-router/types/router' -import { once } from '@vue-storefront/core/helpers' - -once('__VUE_EXTEND_PUSH_RR__', () => { - const originalPush = VueRouter.prototype.push - VueRouter.prototype.push = function push (location: RawLocation, onComplete: Function = () => {}, onAbort?: ErrorHandler): Promise { - if (onComplete || onAbort) return originalPush.call(this, location, onComplete, onAbort) - return originalPush.call(this, location).catch(err => err) - } -}) - -export const createRouter = (): VueRouter => { - return new VueRouter({ - mode: 'history', - base: __dirname, - scrollBehavior: (to, from) => { - if (to.hash) { - return { - selector: to.hash - } - } - if (rootStore.getters['url/isBackRoute']) { - const { scrollPosition = { x: 0, y: 0 } } = rootStore.getters['url/getCurrentRoute'] - return scrollPosition - } else if (to.path !== from.path) { // do not change scroll position when navigating on the same page (ex. change filters) - return { x: 0, y: 0 } - } - } - }) -} - -export const createRouterProxy = (router: VueRouter): VueRouter => { - const ProxyConstructor = Proxy || require('proxy-polyfill/src/proxy') - - return new ProxyConstructor(router, { - get (target, propKey) { - const origMethod = target[propKey] - - if (propKey === 'addRoutes') { - return function (routes: RouteConfig[], ...args): void { - return RouterManager.addRoutes(routes, ...args) - } - } - - return origMethod - } - }) -} diff --git a/core/helpers/test/unit/getThumbnailPath.spec.ts b/core/helpers/test/unit/getThumbnailPath.spec.ts deleted file mode 100644 index dc85506d96..0000000000 --- a/core/helpers/test/unit/getThumbnailPath.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { slugify } from '@vue-storefront/core/helpers' -import config from 'config' - -jest.clearAllMocks() -jest.mock('config', () => ({})) -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: {} -})) -jest.mock('@vue-storefront/core/store', () => ({})) -jest.mock('@vue-storefront/core/modules/url/helpers', () => ({})) -jest.mock('@vue-storefront/core/lib/multistore', () => ({})) - -describe('slugify', () => { - it('Check if all strings are replaced to the right chars and that text is lowercase in the return', () => { - expect(slugify('testing')).toBe('testing') - expect(slugify('testing--')).toBe('testing-') - expect(slugify('TESTING--&')).toBe('testing-and-') - expect(slugify('TESTING--& ')).toBe('testing-and-') - expect(slugify('TES TING--& ')).toBe('tes-ting-and-') - }) - - it('Check that an error is thrown when the parameter is not an string', () => { - expect(() => slugify(12)).toThrow('string.replace is not a function') - }) -}) diff --git a/core/helpers/test/unit/processURLAddress.spec.ts b/core/helpers/test/unit/processURLAddress.spec.ts deleted file mode 100644 index d44e041261..0000000000 --- a/core/helpers/test/unit/processURLAddress.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { processURLAddress } from '@vue-storefront/core/helpers' -import config from 'config' - -jest.clearAllMocks() -jest.mock('config', () => ({})) -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: {} -})) -jest.mock('@vue-storefront/core/store', () => ({})) -jest.mock('@vue-storefront/core/modules/url/helpers', () => ({})) -jest.mock('@vue-storefront/core/lib/multistore', () => ({})) - -describe('processURLAddress', () => { - it('Check that the url that comes back has the right value', () => { - config.api = { - url: 'api' - } - expect(processURLAddress('/testing')).toBe('api/testing') - expect(processURLAddress('testing')).toBe('testing') - }) -}) diff --git a/core/helpers/test/unit/slugify.spec.ts b/core/helpers/test/unit/slugify.spec.ts deleted file mode 100644 index b9bdb582f4..0000000000 --- a/core/helpers/test/unit/slugify.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { getThumbnailPath } from '@vue-storefront/core/helpers' -import config from 'config' - -jest.clearAllMocks() -jest.mock('config', () => ({})) -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: {} -})) -jest.mock('@vue-storefront/core/store', () => ({})) -jest.mock('@vue-storefront/core/modules/url/helpers', () => ({})) -jest.mock('@vue-storefront/core/lib/multistore', () => ({})) - -describe('getThumbnailPath', () => { - it('Get right value when useExactUrlsNoProxy is set', () => { - config.images = { - useExactUrlsNoProxy: true - } - expect(getThumbnailPath('testing')).toBe('testing') - }) - - it('Get right value when productPlaceholder is set', () => { - config.images = { - productPlaceholder: 'productPlaceholder' - } - expect(getThumbnailPath('no_selection')).toBe('productPlaceholder') - }) - - it('Get right value when useSpecificImagePaths is set', () => { - config.images = { - useSpecificImagePaths: true, - paths: { - product: '/catalog/product', - test: '/test' - } - } - expect(getThumbnailPath('/prod', 10, 10, 'test')).toBe('10/10/resize/test/prod') - expect(getThumbnailPath('/prod', 10, 10)).toBe('10/10/resize/catalog/product/prod') - }) - it('Get right value when useSpecificImagePaths and baseUrl are set', () => { - config.images = { - useSpecificImagePaths: true, - baseUrl: 'test/', - paths: { - product: '/catalog/product', - test: '/test' - } - } - expect(getThumbnailPath('/prod', 10, 10, 'test')).toBe('test/10/10/resize/test/prod') - expect(getThumbnailPath('/prod', 10, 10)).toBe('test/10/10/resize/catalog/product/prod') - }) - - it('Get right value when baseUrl is set', () => { - config.images = { - baseUrl: 'test/' - } - expect(getThumbnailPath('/test')).toBe('test/0/0/resize/test') - expect(getThumbnailPath('/test', 10, 20)).toBe('test/10/20/resize/test') - expect(getThumbnailPath('/test', 30, 20)).toBe('test/30/20/resize/test') - }) -}) diff --git a/core/helpers/validators/index.ts b/core/helpers/validators/index.ts deleted file mode 100644 index 26c866282c..0000000000 --- a/core/helpers/validators/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** check if string contains some any unicode alphabet characters */ -export const unicodeAlpha = (value: string): boolean => /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7C6\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB67\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC\u{10000}-\u{1000B}\u{1000D}-\u{10026}\u{10028}-\u{1003A}\u{1003C}\u{1003D}\u{1003F}-\u{1004D}\u{10050}-\u{1005D}\u{10080}-\u{100FA}\u{10280}-\u{1029C}\u{102A0}-\u{102D0}\u{10300}-\u{1031F}\u{1032D}-\u{10340}\u{10342}-\u{10349}\u{10350}-\u{10375}\u{10380}-\u{1039D}\u{103A0}-\u{103C3}\u{103C8}-\u{103CF}\u{10400}-\u{1049D}\u{104B0}-\u{104D3}\u{104D8}-\u{104FB}\u{10500}-\u{10527}\u{10530}-\u{10563}\u{10600}-\u{10736}\u{10740}-\u{10755}\u{10760}-\u{10767}\u{10800}-\u{10805}\u{10808}\u{1080A}-\u{10835}\u{10837}\u{10838}\u{1083C}\u{1083F}-\u{10855}\u{10860}-\u{10876}\u{10880}-\u{1089E}\u{108E0}-\u{108F2}\u{108F4}\u{108F5}\u{10900}-\u{10915}\u{10920}-\u{10939}\u{10980}-\u{109B7}\u{109BE}\u{109BF}\u{10A00}\u{10A10}-\u{10A13}\u{10A15}-\u{10A17}\u{10A19}-\u{10A35}\u{10A60}-\u{10A7C}\u{10A80}-\u{10A9C}\u{10AC0}-\u{10AC7}\u{10AC9}-\u{10AE4}\u{10B00}-\u{10B35}\u{10B40}-\u{10B55}\u{10B60}-\u{10B72}\u{10B80}-\u{10B91}\u{10C00}-\u{10C48}\u{10C80}-\u{10CB2}\u{10CC0}-\u{10CF2}\u{10D00}-\u{10D23}\u{10F00}-\u{10F1C}\u{10F27}\u{10F30}-\u{10F45}\u{10FE0}-\u{10FF6}\u{11003}-\u{11037}\u{11083}-\u{110AF}\u{110D0}-\u{110E8}\u{11103}-\u{11126}\u{11144}\u{11150}-\u{11172}\u{11176}\u{11183}-\u{111B2}\u{111C1}-\u{111C4}\u{111DA}\u{111DC}\u{11200}-\u{11211}\u{11213}-\u{1122B}\u{11280}-\u{11286}\u{11288}\u{1128A}-\u{1128D}\u{1128F}-\u{1129D}\u{1129F}-\u{112A8}\u{112B0}-\u{112DE}\u{11305}-\u{1130C}\u{1130F}\u{11310}\u{11313}-\u{11328}\u{1132A}-\u{11330}\u{11332}\u{11333}\u{11335}-\u{11339}\u{1133D}\u{11350}\u{1135D}-\u{11361}\u{11400}-\u{11434}\u{11447}-\u{1144A}\u{1145F}\u{11480}-\u{114AF}\u{114C4}\u{114C5}\u{114C7}\u{11580}-\u{115AE}\u{115D8}-\u{115DB}\u{11600}-\u{1162F}\u{11644}\u{11680}-\u{116AA}\u{116B8}\u{11700}-\u{1171A}\u{11800}-\u{1182B}\u{118A0}-\u{118DF}\u{118FF}\u{119A0}-\u{119A7}\u{119AA}-\u{119D0}\u{119E1}\u{119E3}\u{11A00}\u{11A0B}-\u{11A32}\u{11A3A}\u{11A50}\u{11A5C}-\u{11A89}\u{11A9D}\u{11AC0}-\u{11AF8}\u{11C00}-\u{11C08}\u{11C0A}-\u{11C2E}\u{11C40}\u{11C72}-\u{11C8F}\u{11D00}-\u{11D06}\u{11D08}\u{11D09}\u{11D0B}-\u{11D30}\u{11D46}\u{11D60}-\u{11D65}\u{11D67}\u{11D68}\u{11D6A}-\u{11D89}\u{11D98}\u{11EE0}-\u{11EF2}\u{12000}-\u{12399}\u{12480}-\u{12543}\u{13000}-\u{1342E}\u{14400}-\u{14646}\u{16800}-\u{16A38}\u{16A40}-\u{16A5E}\u{16AD0}-\u{16AED}\u{16B00}-\u{16B2F}\u{16B40}-\u{16B43}\u{16B63}-\u{16B77}\u{16B7D}-\u{16B8F}\u{16E40}-\u{16E7F}\u{16F00}-\u{16F4A}\u{16F50}\u{16F93}-\u{16F9F}\u{16FE0}\u{16FE1}\u{16FE3}\u{17000}-\u{187F7}\u{18800}-\u{18AF2}\u{1B000}-\u{1B11E}\u{1B150}-\u{1B152}\u{1B164}-\u{1B167}\u{1B170}-\u{1B2FB}\u{1BC00}-\u{1BC6A}\u{1BC70}-\u{1BC7C}\u{1BC80}-\u{1BC88}\u{1BC90}-\u{1BC99}\u{1D400}-\u{1D454}\u{1D456}-\u{1D49C}\u{1D49E}\u{1D49F}\u{1D4A2}\u{1D4A5}\u{1D4A6}\u{1D4A9}-\u{1D4AC}\u{1D4AE}-\u{1D4B9}\u{1D4BB}\u{1D4BD}-\u{1D4C3}\u{1D4C5}-\u{1D505}\u{1D507}-\u{1D50A}\u{1D50D}-\u{1D514}\u{1D516}-\u{1D51C}\u{1D51E}-\u{1D539}\u{1D53B}-\u{1D53E}\u{1D540}-\u{1D544}\u{1D546}\u{1D54A}-\u{1D550}\u{1D552}-\u{1D6A5}\u{1D6A8}-\u{1D6C0}\u{1D6C2}-\u{1D6DA}\u{1D6DC}-\u{1D6FA}\u{1D6FC}-\u{1D714}\u{1D716}-\u{1D734}\u{1D736}-\u{1D74E}\u{1D750}-\u{1D76E}\u{1D770}-\u{1D788}\u{1D78A}-\u{1D7A8}\u{1D7AA}-\u{1D7C2}\u{1D7C4}-\u{1D7CB}\u{1E100}-\u{1E12C}\u{1E137}-\u{1E13D}\u{1E14E}\u{1E2C0}-\u{1E2EB}\u{1E800}-\u{1E8C4}\u{1E900}-\u{1E943}\u{1E94B}\u{1EE00}-\u{1EE03}\u{1EE05}-\u{1EE1F}\u{1EE21}\u{1EE22}\u{1EE24}\u{1EE27}\u{1EE29}-\u{1EE32}\u{1EE34}-\u{1EE37}\u{1EE39}\u{1EE3B}\u{1EE42}\u{1EE47}\u{1EE49}\u{1EE4B}\u{1EE4D}-\u{1EE4F}\u{1EE51}\u{1EE52}\u{1EE54}\u{1EE57}\u{1EE59}\u{1EE5B}\u{1EE5D}\u{1EE5F}\u{1EE61}\u{1EE62}\u{1EE64}\u{1EE67}-\u{1EE6A}\u{1EE6C}-\u{1EE72}\u{1EE74}-\u{1EE77}\u{1EE79}-\u{1EE7C}\u{1EE7E}\u{1EE80}-\u{1EE89}\u{1EE8B}-\u{1EE9B}\u{1EEA1}-\u{1EEA3}\u{1EEA5}-\u{1EEA9}\u{1EEAB}-\u{1EEBB}\u{20000}-\u{2A6D6}\u{2A700}-\u{2B734}\u{2B740}-\u{2B81D}\u{2B820}-\u{2CEA1}\u{2CEB0}-\u{2EBE0}\u{2F800}-\u{2FA1D}]+/u.test(value) - -/** check if string contains some any unicode alphabet characters or digits */ -export const unicodeAlphaNum = (value: string): boolean => unicodeAlpha(value) || /[0-9]+/.test(value) diff --git a/core/hooks.ts b/core/hooks.ts deleted file mode 100644 index 8d4152b2bb..0000000000 --- a/core/hooks.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createListenerHook, createMutatorHook } from '@vue-storefront/core/lib/hooks' - -const { - hook: beforeStoreViewChangedHook, - executor: beforeStoreViewChangedExecutor -} = createMutatorHook() - -const { - hook: afterStoreViewChangedHook, - executor: afterStoreViewChangedExecutor -} = createListenerHook() - -const { - hook: afterAppInitHook, - executor: afterAppInitExecutor -} = createListenerHook() - -const { - hook: beforeHydratedHook, - executor: beforeHydratedExecutor -} = createMutatorHook() - -const { - hook: afterProductThumbnailPathGeneratedHook, - executor: afterProductThumbnailPathGeneratedExecutor -} = createMutatorHook<{ path: string, pathType: string, sizeX: number, sizeY: number }, { path: string }>() - -const { - hook: beforeLogRenderedHook, - executor: beforeLogRenderedExecutor -} = createMutatorHook<{ type: string, message: any, tag: any, context: any, noDefaultOutput?: boolean }, { message: any, tag: any, context: any, noDefaultOutput?: boolean }>() - -/** Only for internal usage in core */ -const coreHooksExecutors = { - afterAppInit: afterAppInitExecutor, - beforeStoreViewChanged: beforeStoreViewChangedExecutor, - afterStoreViewChanged: afterStoreViewChangedExecutor, - beforeHydrated: beforeHydratedExecutor, - afterProductThumbnailPathGenerate: afterProductThumbnailPathGeneratedExecutor, - beforeLogRendered: beforeLogRenderedExecutor -} - -const coreHooks = { - /** Hook is fired right after whole application is initialized. Modules are registered and theme setted up */ - afterAppInit: afterAppInitHook, - /** Hook is fired directly before changing current storeView (multistrore) - * @param storeView Inside this function you have access to order object that you can access and modify. It should return order object. - */ - beforeStoreViewChanged: beforeStoreViewChangedHook, - /** Hook is fired right after storeView (multistore) is changed - * @param storeView current storeView - */ - afterStoreViewChanged: afterStoreViewChangedHook, - beforeHydrated: beforeHydratedHook, - afterProductThumbnailPathGenerate: afterProductThumbnailPathGeneratedHook, - beforeLogRendered: beforeLogRenderedHook -} - -export { - coreHooks, - coreHooksExecutors -} diff --git a/core/i18n/.gitignore b/core/i18n/.gitignore deleted file mode 100644 index c61a80f825..0000000000 --- a/core/i18n/.gitignore +++ /dev/null @@ -1 +0,0 @@ -resource/i18n/*.json \ No newline at end of file diff --git a/core/i18n/helpers.ts b/core/i18n/helpers.ts deleted file mode 100644 index f816174c5c..0000000000 --- a/core/i18n/helpers.ts +++ /dev/null @@ -1,29 +0,0 @@ -import config from 'config' - -export const currentBuildLocales = (): string[] => { - const defaultLocale = config.i18n.defaultLocale || 'en-US' - const multistoreLocales = config.storeViews.multistore - ? Object.values(config.storeViews) - .map((store: any) => store && typeof store === 'object' && store.i18n && store.i18n.defaultLocale) - .filter(Boolean) - : config.i18n.availableLocale - const locales = multistoreLocales.includes(defaultLocale) - ? multistoreLocales - : [defaultLocale, ...multistoreLocales] - - return locales -} - -export const transformToShortLocales = (locales: string[]): string[] => locales.map(locale => { - const separatorIndex = locale.indexOf('-') - const shortLocale = separatorIndex ? locale.substr(0, separatorIndex) : locale - - return shortLocale -}) - -export const buildLocaleIgnorePattern = (): RegExp => { - const locales = transformToShortLocales(currentBuildLocales()) - const localesRegex = locales.map(locale => `${locale}$`).join('|') - - return new RegExp(localesRegex) -} diff --git a/core/i18n/index.ts b/core/i18n/index.ts deleted file mode 100644 index c807b29e37..0000000000 --- a/core/i18n/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -import Vue from 'vue' -import VueI18n from 'vue-i18n' -import { Logger } from '@vue-storefront/core/lib/logger' -import { once } from '@vue-storefront/core/helpers' -import config from 'config' - -once('__VUE_EXTEND_I18N__', () => { - Vue.use(VueI18n) -}) - -const defaultLocale = config.i18n.defaultLocale || 'en-US' -const loadedLanguages = [defaultLocale] -const i18n = new VueI18n({ - locale: defaultLocale, // set locale - fallbackLocale: defaultLocale, - messages: config.i18n.bundleAllStoreviewLanguages ? require('./resource/i18n/multistoreLanguages.json') : { - [defaultLocale]: require(`./resource/i18n/${defaultLocale}.json`) - } -}) - -function setI18nLanguage (lang: string): string { - i18n.locale = lang - return lang -} - -/** - * Lazy load date locales file for current switched language. - */ -const loadDateLocales = async (lang: string = 'en'): Promise => { - let localeCode = lang.toLocaleLowerCase() - try { // try to load full locale name - await import(/* webpackChunkName: "dayjs-locales-[request]" */ `dayjs/locale/${localeCode}`) - } catch (e) { // load simplified locale name, example: de-DE -> de - const separatorIndex = localeCode.indexOf('-') - if (separatorIndex) { - localeCode = separatorIndex ? localeCode.substr(0, separatorIndex) : localeCode - try { - await import(/* webpackChunkName: "dayjs-locales-[request]" */ `dayjs/locale/${localeCode}`) - } catch (err) { - Logger.debug('Unable to load translation from dayjs')() - } - } - } -} - -export async function loadLanguageAsync (lang: string): Promise { - await loadDateLocales(lang) - if (!config.i18n.bundleAllStoreviewLanguages) { - if (i18n.locale !== lang) { - if (!loadedLanguages.includes(lang)) { - try { - const msgs = await import(/* webpackChunkName: "lang-[request]" */ `./resource/i18n/${lang}.json`) - i18n.setLocaleMessage(lang, msgs.default) - loadedLanguages.push(lang) - return setI18nLanguage(lang) - } catch (e) { // eslint-disable-line handle-callback-err - Logger.debug('Unable to load translation')() - return '' - } - } - return setI18nLanguage(lang) - } - } else { - loadedLanguages.push(lang) - return setI18nLanguage(lang) - } - return lang -} - -export default i18n diff --git a/core/i18n/intl.ts b/core/i18n/intl.ts deleted file mode 100644 index 05a548f95c..0000000000 --- a/core/i18n/intl.ts +++ /dev/null @@ -1,13 +0,0 @@ -import areIntlLocalesSupported from 'intl-locales-supported' - -export const importIntlPolyfill = async () => { - const IntlPolyfill = await import('intl') - global.Intl = IntlPolyfill.default -} - -export const checkForIntlPolyfill = async (storeView) => { - const globDTO = typeof window !== 'undefined' ? window : global - if (!globDTO.hasOwnProperty('Intl') || !areIntlLocalesSupported(storeView.i18n.defaultLocale)) { - await importIntlPolyfill() - } -} diff --git a/core/i18n/package.json b/core/i18n/package.json deleted file mode 100644 index 873aa226c4..0000000000 --- a/core/i18n/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "@vue-storefront/i18n", - "version": "1.12.2", - "description": "Vue Storefront i18n", - "license": "MIT", - "main": "index.ts", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "dependencies": { - "vue-i18n": "^8.0.0" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/core/i18n/resource/countries.json b/core/i18n/resource/countries.json deleted file mode 100644 index 66c08c9fc9..0000000000 --- a/core/i18n/resource/countries.json +++ /dev/null @@ -1,986 +0,0 @@ -[ - { - "name": "Afghanistan", - "code": "AF" - }, - { - "name": "Albania", - "code": "AL" - }, - { - "name": "Algeria", - "code": "DZ" - }, - { - "name": "American Samoa", - "code": "AS" - }, - { - "name": "Andorra", - "code": "AD" - }, - { - "name": "Angola", - "code": "AO" - }, - { - "name": "Anguilla", - "code": "AI" - }, - { - "name": "Antarctica", - "code": "AQ" - }, - { - "name": "Antigua and Barbuda", - "code": "AG" - }, - { - "name": "Argentina", - "code": "AR" - }, - { - "name": "Armenia", - "code": "AM" - }, - { - "name": "Aruba", - "code": "AW" - }, - { - "name": "Australia", - "code": "AU" - }, - { - "name": "Austria", - "code": "AT" - }, - { - "name": "Azerbaijan", - "code": "AZ" - }, - { - "name": "Bahamas", - "code": "BS" - }, - { - "name": "Bahrain", - "code": "BH" - }, - { - "name": "Bangladesh", - "code": "BD" - }, - { - "name": "Barbados", - "code": "BB" - }, - { - "name": "Belarus", - "code": "BY" - }, - { - "name": "Belgium", - "code": "BE" - }, - { - "name": "Belize", - "code": "BZ" - }, - { - "name": "Benin", - "code": "BJ" - }, - { - "name": "Bermuda", - "code": "BM" - }, - { - "name": "Bhutan", - "code": "BT" - }, - { - "name": "Bolivia", - "code": "BO" - }, - { - "name": "Bosnia and Herzegovina", - "code": "BA" - }, - { - "name": "Botswana", - "code": "BW" - }, - { - "name": "Bouvet Island", - "code": "BV" - }, - { - "name": "Brazil", - "code": "BR" - }, - { - "name": "British Indian Ocean Territory", - "code": "IO" - }, - { - "name": "British Virgin Islands", - "code": "VG" - }, - { - "name": "Brunei", - "code": "BN" - }, - { - "name": "Bulgaria", - "code": "BG" - }, - { - "name": "Burkina Faso", - "code": "BF" - }, - { - "name": "Burundi", - "code": "BI" - }, - { - "name": "Cambodia", - "code": "KH" - }, - { - "name": "Cameroon", - "code": "CM" - }, - { - "name": "Canada", - "code": "CA" - }, - { - "name": "Cape Verde", - "code": "CV" - }, - { - "name": "Cayman Islands", - "code": "KY" - }, - { - "name": "Central African Republic", - "code": "CF" - }, - { - "name": "Chad", - "code": "TD" - }, - { - "name": "Chile", - "code": "CL" - }, - { - "name": "China", - "code": "CN" - }, - { - "name": "Christmas Island", - "code": "CX" - }, - { - "name": "Cocos [Keeling] Islands", - "code": "CC" - }, - { - "name": "Colombia", - "code": "CO" - }, - { - "name": "Comoros", - "code": "KM" - }, - { - "name": "Congo", - "code": "CG" - }, - { - "name": "Congo", - "code": "CD" - }, - { - "name": "Cook Islands", - "code": "CK" - }, - { - "name": "Costa Rica", - "code": "CR" - }, - { - "name": "Croatia", - "code": "HR" - }, - { - "name": "Cuba", - "code": "CU" - }, - { - "name": "Cyprus", - "code": "CY" - }, - { - "name": "Czech Republic", - "code": "CZ" - }, - { - "name": "Côte d’Ivoire", - "code": "CI" - }, - { - "name": "Denmark", - "code": "DK" - }, - { - "name": "Djibouti", - "code": "DJ" - }, - { - "name": "Dominica", - "code": "DM" - }, - { - "name": "Dominican Republic", - "code": "DO" - }, - { - "name": "Ecuador", - "code": "EC" - }, - { - "name": "Egypt", - "code": "EG" - }, - { - "name": "El Salvador", - "code": "SV" - }, - { - "name": "Equatorial Guinea", - "code": "GQ" - }, - { - "name": "Eritrea", - "code": "ER" - }, - { - "name": "Estonia", - "code": "EE" - }, - { - "name": "Ethiopia", - "code": "ET" - }, - { - "name": "Falkland Islands", - "code": "FK" - }, - { - "name": "Faroe Islands", - "code": "FO" - }, - { - "name": "Fiji", - "code": "FJ" - }, - { - "name": "Finland", - "code": "FI" - }, - { - "name": "France", - "code": "FR" - }, - { - "name": "French Guiana", - "code": "GF" - }, - { - "name": "French Polynesia", - "code": "PF" - }, - { - "name": "French Southern Territories", - "code": "TF" - }, - { - "name": "Gabon", - "code": "GA" - }, - { - "name": "Gambia", - "code": "GM" - }, - { - "name": "Georgia", - "code": "GE" - }, - { - "name": "Germany", - "code": "DE" - }, - { - "name": "Ghana", - "code": "GH" - }, - { - "name": "Gibraltar", - "code": "GI" - }, - { - "name": "Greece", - "code": "GR" - }, - { - "name": "Greenland", - "code": "GL" - }, - { - "name": "Grenada", - "code": "GD" - }, - { - "name": "Guadeloupe", - "code": "GP" - }, - { - "name": "Guam", - "code": "GU" - }, - { - "name": "Guatemala", - "code": "GT" - }, - { - "name": "Guernsey", - "code": "GG" - }, - { - "name": "Guinea", - "code": "GN" - }, - { - "name": "Guinea", - "code": "GW" - }, - { - "name": "Guyana", - "code": "GY" - }, - { - "name": "Haiti", - "code": "HT" - }, - { - "name": "Heard Island and McDonald Islands", - "code": "HM" - }, - { - "name": "Honduras", - "code": "HN" - }, - { - "name": "Hong Kong SAR China", - "code": "HK" - }, - { - "name": "Hungary", - "code": "HU" - }, - { - "name": "Iceland", - "code": "IS" - }, - { - "name": "India", - "code": "IN" - }, - { - "name": "Indonesia", - "code": "ID" - }, - { - "name": "Iran", - "code": "IR" - }, - { - "name": "Iraq", - "code": "IQ" - }, - { - "name": "Ireland", - "code": "IE" - }, - { - "name": "Isle of Man", - "code": "IM" - }, - { - "name": "Israel", - "code": "IL" - }, - { - "name": "Italy", - "code": "IT" - }, - { - "name": "Jamaica", - "code": "JM" - }, - { - "name": "Japan", - "code": "JP" - }, - { - "name": "Jersey", - "code": "JE" - }, - { - "name": "Jordan", - "code": "JO" - }, - { - "name": "Kazakhstan", - "code": "KZ" - }, - { - "name": "Kenya", - "code": "KE" - }, - { - "name": "Kiribati", - "code": "KI" - }, - { - "name": "Kuwait", - "code": "KW" - }, - { - "name": "Kyrgyzstan", - "code": "KG" - }, - { - "name": "Laos", - "code": "LA" - }, - { - "name": "Latvia", - "code": "LV" - }, - { - "name": "Lebanon", - "code": "LB" - }, - { - "name": "Lesotho", - "code": "LS" - }, - { - "name": "Liberia", - "code": "LR" - }, - { - "name": "Libya", - "code": "LY" - }, - { - "name": "Liechtenstein", - "code": "LI" - }, - { - "name": "Lithuania", - "code": "LT" - }, - { - "name": "Luxembourg", - "code": "LU" - }, - { - "name": "Macau SAR China", - "code": "MO" - }, - { - "name": "Macedonia", - "code": "MK" - }, - { - "name": "Madagascar", - "code": "MG" - }, - { - "name": "Malawi", - "code": "MW" - }, - { - "name": "Malaysia", - "code": "MY" - }, - { - "name": "Maldives", - "code": "MV" - }, - { - "name": "Mali", - "code": "ML" - }, - { - "name": "Malta", - "code": "MT" - }, - { - "name": "Marshall Islands", - "code": "MH" - }, - { - "name": "Martinique", - "code": "MQ" - }, - { - "name": "Mauritania", - "code": "MR" - }, - { - "name": "Mauritius", - "code": "MU" - }, - { - "name": "Mayotte", - "code": "YT" - }, - { - "name": "Mexico", - "code": "MX" - }, - { - "name": "Micronesia", - "code": "FM" - }, - { - "name": "Moldova", - "code": "MD" - }, - { - "name": "Monaco", - "code": "MC" - }, - { - "name": "Mongolia", - "code": "MN" - }, - { - "name": "Montenegro", - "code": "ME" - }, - { - "name": "Montserrat", - "code": "MS" - }, - { - "name": "Morocco", - "code": "MA" - }, - { - "name": "Mozambique", - "code": "MZ" - }, - { - "name": "Myanmar [Burma]", - "code": "MM" - }, - { - "name": "Namibia", - "code": "NA" - }, - { - "name": "Nauru", - "code": "NR" - }, - { - "name": "Nepal", - "code": "NP" - }, - { - "name": "Netherlands", - "code": "NL" - }, - { - "name": "Netherlands Antilles", - "code": "AN" - }, - { - "name": "New Caledonia", - "code": "NC" - }, - { - "name": "New Zealand", - "code": "NZ" - }, - { - "name": "Nicaragua", - "code": "NI" - }, - { - "name": "Niger", - "code": "NE" - }, - { - "name": "Nigeria", - "code": "NG" - }, - { - "name": "Niue", - "code": "NU" - }, - { - "name": "Norfolk Island", - "code": "NF" - }, - { - "name": "North Korea", - "code": "KP" - }, - { - "name": "Northern Mariana Islands", - "code": "MP" - }, - { - "name": "Norway", - "code": "NO" - }, - { - "name": "Oman", - "code": "OM" - }, - { - "name": "Pakistan", - "code": "PK" - }, - { - "name": "Palau", - "code": "PW" - }, - { - "name": "Palestinian Territories", - "code": "PS" - }, - { - "name": "Panama", - "code": "PA" - }, - { - "name": "Papua New Guinea", - "code": "PG" - }, - { - "name": "Paraguay", - "code": "PY" - }, - { - "name": "Peru", - "code": "PE" - }, - { - "name": "Philippines", - "code": "PH" - }, - { - "name": "Pitcairn Islands", - "code": "PN" - }, - { - "name": "Poland", - "code": "PL" - }, - { - "name": "Portugal", - "code": "PT" - }, - { - "name": "Puerto Rico", - "code": "PR" - }, - { - "name": "Qatar", - "code": "QA" - }, - { - "name": "Romania", - "code": "RO" - }, - { - "name": "Russia", - "code": "RU" - }, - { - "name": "Rwanda", - "code": "RW" - }, - { - "name": "Réunion", - "code": "RE" - }, - { - "name": "Saint Barthélemy", - "code": "BL" - }, - { - "name": "Saint Helena", - "code": "SH" - }, - { - "name": "Saint Kitts and Nevis", - "code": "KN" - }, - { - "name": "Saint Lucia", - "code": "LC" - }, - { - "name": "Saint Martin", - "code": "MF" - }, - { - "name": "Saint Pierre and Miquelon", - "code": "PM" - }, - { - "name": "Saint Vincent and the Grenadines", - "code": "VC" - }, - { - "name": "Samoa", - "code": "WS" - }, - { - "name": "San Marino", - "code": "SM" - }, - { - "name": "Saudi Arabia", - "code": "SA" - }, - { - "name": "Senegal", - "code": "SN" - }, - { - "name": "Serbia", - "code": "RS" - }, - { - "name": "Seychelles", - "code": "SC" - }, - { - "name": "Sierra Leone", - "code": "SL" - }, - { - "name": "Singapore", - "code": "SG" - }, - { - "name": "Slovakia", - "code": "SK" - }, - { - "name": "Slovenia", - "code": "SI" - }, - { - "name": "Solomon Islands", - "code": "SB" - }, - { - "name": "Somalia", - "code": "SO" - }, - { - "name": "South Africa", - "code": "ZA" - }, - { - "name": "South Georgia and the South Sandwich Islands", - "code": "GS" - }, - { - "name": "South Korea", - "code": "KR" - }, - { - "name": "Spain", - "code": "ES" - }, - { - "name": "Sri Lanka", - "code": "LK" - }, - { - "name": "Sudan", - "code": "SD" - }, - { - "name": "Suriname", - "code": "SR" - }, - { - "name": "Svalbard and Jan Mayen", - "code": "SJ" - }, - { - "name": "Swaziland", - "code": "SZ" - }, - { - "name": "Sweden", - "code": "SE" - }, - { - "name": "Switzerland", - "code": "CH" - }, - { - "name": "Syria", - "code": "SY" - }, - { - "name": "São Tomé and Príncipe", - "code": "ST" - }, - { - "name": "Taiwan", - "code": "TW" - }, - { - "name": "Tajikistan", - "code": "TJ" - }, - { - "name": "Tanzania", - "code": "TZ" - }, - { - "name": "Thailand", - "code": "TH" - }, - { - "name": "Timor", - "code": "TL" - }, - { - "name": "Togo", - "code": "TG" - }, - { - "name": "Tokelau", - "code": "TK" - }, - { - "name": "Tonga", - "code": "TO" - }, - { - "name": "Trinidad and Tobago", - "code": "TT" - }, - { - "name": "Tunisia", - "code": "TN" - }, - { - "name": "Turkey", - "code": "TR" - }, - { - "name": "Turkmenistan", - "code": "TM" - }, - { - "name": "Turks and Caicos Islands", - "code": "TC" - }, - { - "name": "Tuvalu", - "code": "TV" - }, - { - "name": "U.S. Minor Outlying Islands", - "code": "UM" - }, - { - "name": "U.S. Virgin Islands", - "code": "VI" - }, - { - "name": "Uganda", - "code": "UG" - }, - { - "name": "Ukraine", - "code": "UA" - }, - { - "name": "United Arab Emirates", - "code": "AE" - }, - { - "name": "United Kingdom", - "code": "GB" - }, - { - "name": "United States", - "code": "US" - }, - { - "name": "Uruguay", - "code": "UY" - }, - { - "name": "Uzbekistan", - "code": "UZ" - }, - { - "name": "Vanuatu", - "code": "VU" - }, - { - "name": "Vatican City", - "code": "VA" - }, - { - "name": "Venezuela", - "code": "VE" - }, - { - "name": "Vietnam", - "code": "VN" - }, - { - "name": "Wallis and Futuna", - "code": "WF" - }, - { - "name": "Western Sahara", - "code": "EH" - }, - { - "name": "Yemen", - "code": "YE" - }, - { - "name": "Zambia", - "code": "ZM" - }, - { - "name": "Zimbabwe", - "code": "ZW" - }, - { - "name": "Åland Islands", - "code": "AX" - } -] diff --git a/core/i18n/resource/i18n/ar-SA.csv b/core/i18n/resource/i18n/ar-SA.csv deleted file mode 100644 index 4213bb2038..0000000000 --- a/core/i18n/resource/i18n/ar-SA.csv +++ /dev/null @@ -1,77 +0,0 @@ - is out of the stock!,غير متوفر بالمخزون! -"Product price is unknown, product cannot be added to the cart!",سعر المنتج غير معلوم، لايمكن إضافته للسلة. -"Stock check in progress, please wait while available stock quantities are checked",جاري فحص المخزون، يرجى الانتظار حتى يتم التأكد من التوفر -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.",المنتج أو التصنيف أو الصفحة غير متوفرة بدون اتصال إنترنت. جاري التحويل للصفحة الرئيسية. -"Unhandled error, wrong response format!","Unhandled error, wrong response format!" -404 Page Not Found,404 الصفحة غير متوفرة -Account data has successfully been updated,تم تحديث الحساب بنجاح -Add review,أضف مراجعة -Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.,العنوان المسجل في إنهاء الطلب يحتوي معلومات غير صالحة. يرجى التأكد من تعبئة جميع الحقول المطلوبة أيضاً يمكنك التواص معنا عبر {email} للمساعدة. تم إلغاء الطلب. -Allow notification about the order,السماح بتنبيهات الطلب -Are you sure you would like to remove this item from the shopping cart?,هل أنت متأكد من إزالة هذا المنتج من سلة التسوق -Checkout,إتمام الطلب -Columns,أعمدة -Compare Products,مقارنة المنتجات -Compare products,مقارنة المنتجات -Confirm your order,أكّد طلبك -Error refreshing user token. User is not authorized to access the resource,خطأ في تحديث التشفير. المستخدم غير مصرح له بالدخول -Error with response - bad content-type!,خطأ بالرد - محتوى غير صالح! -Error: Error while adding products,خطأ: خطأ أثناء إضافة المنتجات -Extension developers would like to thank you for placing an order!,Extension developers would like to thank you for placing an order! -Field is required,حقل مطلوب -Field is required.,حقل مطلوب. -Grand total,المجموع النهائي -Home Page,الصفحة الرئيسية -In stock!,متوفر! -Internal Application error while refreshing the tokens. Please clear the storage and refresh page.,Internal Application error while refreshing the tokens. Please clear the storage and refresh page. -Internal validation error. Please check if all required fields are filled in. Please contact us on {email},خطأ تحقق داخلي. يرجى التأكد من تعبئة جميع الحقول المطلوبة. فضلاً تواصل معنا على {email} -Must be greater than 0,يجب أن يكون أكثر من 0 -My Account,حسابي -Newsletter preferences have successfully been updated,تم تحديث تفضيلات النشرة البريدية -No available product variants,لا يوجد منتجات متوفرة -No products synchronized for this category. Please come back while online!,.لا يوجد منتجات تمت مزامنتها لهذا التصنيف. يرجى العودة مرة أخرى إذا تم الاتصال بالإنترنت -No such configuration for the product. Please do choose another combination of attributes.,لا يوجد خيارات منطبقة لهذا المنتج من فضلك قم بتعديل الخيارات. -OK,موافق -Out of stock!,غير متوفر! -Out of the stock!,غير متوفر بالمخزون! -Payment Information,معلومات الدفع -Please configure product bundle options and fix the validation errors,يرجى تحديد الخيارات وإصلاح خطأ التحقق -Please configure product custom options and fix the validation errors,من فضلك حدد خيارات المنتج لإصلاح خطأ التحقق -Please confirm order you placed when you was offline,فضلاً أكد الطلب الذي أنشأته بدون اتصال إنترنت -Please fix the validation errors,يرجى إصلاح خطأ التحقق -Please select the field which You like to sort by,اختر حقل للترتيب به -Please wait ...,انتظر من فضلك… -Proceed to checkout,إتمام الطلب -Processing order...,جاري معالجة الطلب… -Product has been added to the cart!,تمت إضافة المنتج للسلة! -Product quantity has been updated!,تم تحديث الكمية! -Product {productName} has been added to the compare!,تمت إضافة المنتج {productName} إلى المقارنة! -Product {productName} has been added to wishlist!,تمت إضافة المنتج {productName} إلى قائمة الأمنيات بنجاح! -Product {productName} has been removed from compare!,تمت إزالة المنتج {productName} من المقارنة! -Product {productName} has been removed from wishlit!,تمت إزالة المنتج {productName} من قائمة الأمنيات بنجاح! -Quantity must be above 0,يجب أن تكون الكمية أكثر من 0 -Registering the account ...,جاري تسجيل الحساب -Reset password feature does not work while offline!,لا يمكن إستعادة كلمة المرور بدون اتصال بالشبكة! -Review,مراجعة -Reviews,مراجعات -Shopping cart is empty. Please add some products before entering Checkout,سلة التسوق فارغة. يرجى إضافة بعض المنتجات للانتقال إلى صفحة إتمام الطلب -Some of the ordered products are not available!,بعض المنتجات المطلوبة غير متاحة -Subtotal incl. tax,المجموع متضمن الضريبة -Summary,ملخص -The product is out of stock and cannot be added to the cart!,المنتج غير متوفر بالمخزون ولا يمكن إضافته للسلة -The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.,الكمية غير مؤكدة (volatile). تمت إضافة المنتج للسلة للحجز المسبق. -There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.,لا يوجد اتصال بالإنترنت. مازال بإمكانك إتمام الطلب. وسنقوم بإخطارك إذا كان أي من المنتجات المطلوبة غير متوفر، لايمكننا التحقق الآن. -This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!,This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap! -Type what you are looking for...,اكتب ما تبحث عنه… -Unexpected authorization error. Check your Network conection.,خطأ تحقق غير متوقع. تحقق من اتصالك بالشبكة. -You are logged in!,تم تسجيل دخولك! -You are to pay for this order upon delivery.,ستقوم بالدفع لهذا الطلب عند الاستلام. -You need to be logged in to see this page,يجب تسجيل الدخول لعرض الصفحة -You're logged out,تم تسجيل خروجك -email,البريد الإلكتروني -have as many,have as many -login,تسجيل الدخول -most you may purchase,most you may purchase -not authorized,غير مصرح -password,كلمة المرور -to account,إلى الحساب diff --git a/core/i18n/resource/i18n/cs-CZ.csv b/core/i18n/resource/i18n/cs-CZ.csv deleted file mode 100644 index c567556e61..0000000000 --- a/core/i18n/resource/i18n/cs-CZ.csv +++ /dev/null @@ -1,72 +0,0 @@ -" is out of stock!"," není na skladě!" -"404 Page Not Found","404 Stránka nenalezena" -"Account data has successfully been updated","Údaje účtu byly úspěšně aktualizovány" -"Add review","Přidat recenzi" -"Adding a review ...","Adding a review ..." -"Allow notification about the order","Povolit oznámení o objednávce" -"Are you sure you would like to remove this item from the shopping cart?","Opravdu chcete tuto položku odebrat z nákupního košíku?" -"Checkout","Koupit" -"Compare Products","Porovnání produktů" -"Compare products","Porovnejte produkty" -"Confirm your order","Potvrďte svou objednávku" -"Error refreshing user token. User is not authorized to access the resource","Chyba obnovení uživatelského kupónu. Uživatel nemá oprávnění k přístupu" -"Error with response - bad content-type!","Chyba s odpovědí - špatný typ obsahu!" -"Extension developers would like to thank you for placing an order!","Vývojáři rozšíření by rádi poděkovali za zadání vaší objednávky!" -"Field is required","Pole je povinné" -"Field is required.","Pole je povinné." -"Grand total","Celkový součet" -"Home Page","Domovská stránka" -"In stock!","Skladem!" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Chyba interní aplikace při aktualizaci kupónů. Vymažte úložiště a stránku aktualizujte." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Interní chyba ověření. Zkontrolujte, zda jsou vyplněna všechna povinná pole. Kontaktujte nás prosím na {email}" -"Must be greater than 0","Musí být větší než 0" -"My Account","Můj účet" -"Newsletter preferences have successfully been updated","Preference zasílání novinek byly úspěšně aktualizovány" -"No available product variants","Žádné dostupné varianty produktu" -"No products synchronized for this category. Please come back while online!","V této kategorii nemáte žádné synchronizované produkty. Zkuste prosím znovu až budete online!" -"No such configuration for the product. Please do choose another combination of attributes.","Neexistuje žádná taková konfigurace pro daný výrobek. Vyberte prosím jinou kombinaci vlastností." -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","Vyprodáno!" -"Out of the stock!","Není skladem!" -"Payment Information","Informace o platbě" -"Please configure product custom options and fix the validation errors","Přizpůsobte vlastní navolené možnosti produktu a opravte chyby ověření" -"Please confirm order you placed when you was offline","Potvrďte objednávku, kterou jste zadali, když jste byli offline" -"Please fix the validation errors","Opravte chyby ověření" -"Please select the field which You like to sort by","Vyberte pole, dle kterého chcete seřadit" -"Proceed to checkout","Přejděte k nákupu" -"Product has been added to the cart!","Produkt byl přidán do košíku!" -"Product price is unknown, product cannot be added to the cart!","Cena produktu není známa, produkt nelze přidat do košíku!" -"Product quantity has been updated!","Množství produktu bylo aktualizováno!" -"Product {productName} has been added to the compare!","Produkt {productName} byl přidán k porovnání!" -"Product {productName} has been added to wishlist!","Produkt {productName} byl přidán do seznamu přání!" -"Product {productName} has been removed from compare!","Produkt {productName} byl odstraněn z porovnávání!" -"Product {productName} has been removed from wishlist!","Produkt {productName} byl odstraněn ze seznamu přání!" -"Registering the account ...","Registrace účtu ..." -"Reset password feature does not work while offline!","Funkce obnovení hesla nefunguje offline!" -"Review","Recenze" -"Reviews","Recenze" -"Shopping cart is empty. Please add some products before entering Checkout","Nákupní košík je prázdný. Prosím přidejte nějaké produkty než přistoupíte k nákupu" -"Some of the ordered products are not available!","Některé z objednaných produktů nejsou k dispozici!" -"Stock check in progress, please wait while available stock quantities are checked","Kontrola skladových zásob probíhá, prosím, počkejte, až se zkontroluje množství dostupných zásob" -"Subtotal incl. tax","Mezisoučet vč. daně" -"Summary","Shrnutí", -"The product is out of stock and cannot be added to the cart!","Produkt není na skladě a nelze ho přidat do košíku!" -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Systém si není jistý množstvím zásob (volatilní). Produkt byl přidán do košíku pro předběžnou rezervaci." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Připojení k internetu není k dispozici. Stále si však můžete zboží objednat. Budeme vás informovat v případě, že některý z objednaných produktů není k dispozici, protože to v současnosti nelze zkontrolovat." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Tato funkce zatím nebyla zavedena! Prosíme, podívejte se na https://github.com/vuestorefront/vue-storefront/issues pro podrobný návod!" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Zadejte, co hledáte..." -"Unhandled error, wrong response format!","Neošetřená chyba, nesprávný formát odpovědi!" -"You are logged in!","Jste přihlášeni!" -"You are to pay for this order upon delivery.","Tuto objednávku platíte při doručení." -"You need to be logged in to see this page","Pro zobrazení této stránky musíte být přihlášeni" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Jste odhlášeni" -"email","email" -"have as many","mít tolik" -"login","přihlásit se" -"most you may purchase","nejvíce co si můžete koupit" -"not authorized","neautorizováno" -"password","heslo" -"to account","na účet" diff --git a/core/i18n/resource/i18n/de-DE.csv b/core/i18n/resource/i18n/de-DE.csv deleted file mode 100644 index cd9aaf751d..0000000000 --- a/core/i18n/resource/i18n/de-DE.csv +++ /dev/null @@ -1,78 +0,0 @@ -" is out of stock!"," ist nicht auf Lager!" -"404 Page Not Found","404 Seite nicht gefunden" -"Account data has successfully been updated","Kontodaten wurden erfolgreich aktualisiert" -"Add review","Bewertung hinzufügen" -"Adding a review ...","Adding a review ..." -"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","Die angegebene Adresse ist nicht gültig. Bitte überprüfen Sie, ob alle notwenigen Felder ausgefüllt sind und kontaktieren Sie uns per {email} um den Fehler für die Zukunft zu beheben. Ihre Bestellung wurde abgebrochen." -"Allow notification about the order","Erlauben Sie Benachrichtigungen über die Bestellung" -"Are you sure you would like to remove this item from the shopping cart?","Sind Sie sicher, dass Sie diesen Artikel aus Ihrem Warenkorb entfernen wollen?" -"Checkout","Kasse" -"Compare Products","Produkte vergleichen" -"Compare products","Produkte vergleichen" -"Confirm your order","Bestätigen Sie ihre Bestellung" -"Error refreshing user token. User is not authorized to access the resource","Fehler bei der Erneuerung des Benutzer-Tokens. Der Benutzer ist nicht authorisiert auf die Resource zuzugreifen" -"Error with response - bad content-type!","Fehler in der Antwort vom Server - Falscher Inhaltstyp!" -"Error: Error while adding products","Error: Fehler beim hinzufügen der Produkte" -"Extension developers would like to thank you for placing an order!","Extension-Entwickler würden sich bei Ihnen gerne dafür bedanken, dass Sie eine Bestellung getätigt haben!" -"Field is required","Dies ist ein Pflichtfeld" -"Field is required.","Dies ist ein Pflichtfeld." -"Grand total","Gesamtsumme" -"Home Page","Startseite" -"In stock!","Auf Lager!" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Ein interner Anwendungsfehler ist, während der Erneuerung des Tokens, aufgetreten. Bitte leeren Sie den Speicher und laden Sie die Seite neu." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Interner Validierungsfehler. Bitte überprüfen Sie, ob alle erforderlichen Felder ausgefüllt sind. Bei Problemen kontaktieren Sie uns bitte über {email}" -"Must be greater than 0","Muss größer als 0 sein" -"My Account","Mein Konto" -"Newsletter preferences have successfully been updated","Die Newsletter-Einstellungen wurden erfolgreich aktualisiert" -"No available product variants","Keine Produkte verfügbar" -"No products synchronized for this category. Please come back while online!","Es sind keine Produkte für diese Kategorie synchronisiert. Bitte versuchen Sie es erneut, wenn Sie online sind!" -"No such configuration for the product. Please do choose another combination of attributes.","Diese Konfiguration ist für dieses Produkt nicht möglich. Bitte wählen Sie eine andere Kombination von Eigenschaften." -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","Nicht auf Lager!" -"Out of the stock!","Nicht mehr auf Lager!" -"Payment Information","Bezahlinformationen" -"Please configure product bundle options and fix the validation errors","Bitte konfigurieren Sie die Produktbündel-Optionen und beheben Sie die Validierungsfehler" -"Please configure product custom options and fix the validation errors","Bitte konfigurieren Sie die angepassten Produktoptionen und beheben Sie die Validierungsfehler" -"Please confirm order you placed when you was offline","Bitte bestätigen Sie ihre Bestellung, welche Sie getätigt haben während Sie offline waren" -"Please fix the validation errors","Bitte beheben Sie die Validierungsfehler" -"Please select the field which You like to sort by","Bitte wählen Sie das Feld aus, nach dem Sie sortieren möchten" -"Proceed to checkout","Weiter zur Kasse" -"Processing order...","Bestellung wird verarbeitet..." -"Product has been added to the cart!","Produkt wurde zum Warenkorb hinzugefügt!" -"Product price is unknown, product cannot be added to the cart!","Der Produktpreis ist unbekannt, daher kann dieses Produkt nicht zum Warenkorb hinzugefügt werden!" -"Product quantity has been updated!","Produktmenge wurde aktualisiert!" -"Product {productName} has been added to the compare!","Das Produkt {productName} wurde zur Vergleichsliste hinzugefügt!" -"Product {productName} has been added to wishlist!","Das Produkt {productName} wurde der Wunschliste hinzugefügt!" -"Product {productName} has been removed from compare!","Das Produkt {productName} wurde von der Vergleichsliste entfernt!" -"Product {productName} has been removed from wishlist!","Das Produkt {productName} wurde von der Wunschliste entfernt!" -"Quantity must be above 0","Die Menge muss größer als 0 sein" -"Registering the account ...","Registrieren des Kontos ..." -"Reset password feature does not work while offline!","Die Funktion zum Zurücksetzen des Passworts funktioniert nicht im Offline-Modus!" -"Review","Bewertung" -"Reviews","Bewertungen" -"Shopping cart is empty. Please add some products before entering Checkout","Ihr Warenkorb ist leer. Bitte fügen Sie mindestens ein Produkt hinzu bevor Sie zur Kasse gehen" -"Some of the ordered products are not available!","Einige der bestellten Produkte sind nicht auf Lager!" -"Stock check in progress, please wait while available stock quantities are checked","Bestandskontrolle läuft. Bitte warten Sie einen Moment bis die verfügbare Bestandsmenge geprüft worden ist" -"Subtotal incl. tax","Zwischensumme inkl. MwSt." -"Summary","Zusammenfassung" -"The product is out of stock and cannot be added to the cart!","Das Produkt ist nicht auf Lager und kann daher nicht zum Warenkorb hinzugefügt werden!" -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","Das Produkt, die Kategorie oder die CMS Seite ist nicht verfügbar im Offline-Modus. Weiterleitung zur Startseite." -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Das System konnte den genauen Lagerbestand nicht ermitteln, da dieser sehr volatil ist. Das Produkt wurde zur Vorreservierung in den Warenkorb gelegt." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Es besteht aktuell keine Verbindung zum Internet. Sie können ihre Bestellung dennoch aufgeben. Falls eines der bestellten Produkte bei Wiederaufbau der Verbindung nicht mehr verfügbar sein sollte, werden wir Sie umgehend benachrichtigen." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Diese Funktion wurde noch nicht implementiert. Für weitere Details schauen Sie bitte auf https://github.com/vuestorefront/vue-storefront/issues unsere Roadmap an!" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Geben Sie ein, wonach Sie suchen..." -"Unhandled error, wrong response format!","Unbehandelter Fehler. Die Antwort vom Server ist falsch formatiert!" -"You are logged in!","Du wurdest eingeloggt!" -"You are to pay for this order upon delivery.","Sie müssen bei Lieferung bezahlen." -"You need to be logged in to see this page","Sie müssen angemeldet sein, um diese Seite anzuzeigen" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Sie wurden ausgeloggt" -"email","Email" -"have as many","hat so viele" -"login","Login" -"most you may purchase"," maximum das Sie kaufen können" -"not authorized","Nicht authorisiert" -"password","Passwort" -"to account","zum Account" diff --git a/core/i18n/resource/i18n/en-US.csv b/core/i18n/resource/i18n/en-US.csv deleted file mode 100644 index 382225aeae..0000000000 --- a/core/i18n/resource/i18n/en-US.csv +++ /dev/null @@ -1,90 +0,0 @@ -" is out of stock!"," is out of stock!" -"404 Page Not Found","404 Page Not Found" -"Account data has successfully been updated","Account data has successfully been updated" -"Add review","Add review" -"Adding a review ...","Adding a review ..." -"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled." -"Allow notification about the order","Allow notification about the order" -"Are you sure you would like to remove this item from the shopping cart?","Are you sure you would like to remove this item from the shopping cart?" -"Checkout","Checkout" -"Columns","Columns" -"Compare Products","Compare Products" -"Compare products","Compare products" -"Confirm your order","Confirm your order" -"Error refreshing user token. User is not authorized to access the resource","Error refreshing user token. User is not authorized to access the resource" -"Error with response - bad content-type!","Error with response - bad content-type!" -"Error: Error while adding products","Error: Error while adding products" -"Extension developers would like to thank you for placing an order!","Extension developers would like to thank you for placing an order!" -"Field is required","Field is required" -"Field is required.","Field is required." -"Grand total","Grand total" -"Home Page","Home Page" -"In stock!","In stock!" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Internal Application error while refreshing the tokens. Please clear the storage and refresh page." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Internal validation error. Please check if all required fields are filled in. Please contact us on {email}" -"Must be greater than 0","Must be greater than 0" -"My Account","My Account" -"Newsletter preferences have successfully been updated","Newsletter preferences have successfully been updated" -"No available product variants","No available product variants" -"No products synchronized for this category. Please come back while online!","No products synchronized for this category. Please come back while online!" -"No such configuration for the product. Please do choose another combination of attributes.","No such configuration for the product. Please do choose another combination of attributes." -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back." -"Out of stock!","Out of stock!" -"Out of the stock!","Out of the stock!" -"Payment Information","Payment Information" -"Please configure product bundle options and fix the validation errors","Please configure product bundle options and fix the validation errors" -"Please configure product custom options and fix the validation errors","Please configure product custom options and fix the validation errors" -"Please confirm order you placed when you was offline","Please confirm order you placed when you was offline" -"Please fix the validation errors","Please fix the validation errors" -"Please select the field which You like to sort by","Please select the field which You like to sort by" -"Please wait ...","Please wait ..." -"Proceed to checkout","Proceed to checkout" -"Processing order...","Processing order..." -"Product has been added to the cart!","Product has been added to the cart!" -"Product price is unknown, product cannot be added to the cart!","Product price is unknown, product cannot be added to the cart!" -"Product quantity has been updated!","Product quantity has been updated!" -"Product {productName} has been added to the compare!","Product {productName} has been added to the compare!" -"Product {productName} has been added to wishlist!","Product {productName} has been added to wishlist!" -"Product {productName} has been removed from compare!","Product {productName} has been removed from compare!" -"Product {productName} has been removed from wishlist!","Product {productName} has been removed from wishlist!" -"Quantity available offline","Quantity (offline mode)" -"Quantity available","Quantity ({qty} available)" -"Quantity must be above 0","Quantity must be above 0" -"Quantity must be below {quantity}","Quantity must be below {quantity}" -"Quantity must be positive integer","Quantity must be positive integer" -"Registering the account ...","Registering the account ..." -"Reset password feature does not work while offline!","Reset password feature does not work while offline!" -"Review","Review" -"Reviews","Reviews" -"Select 0","Select 0" -"Select 1","Select 1" -"Shopping cart is empty. Please add some products before entering Checkout","Shopping cart is empty. Please add some products before entering Checkout" -"Some of the ordered products are not available!","Some of the ordered products are not available!" -"Stock check in progress, please wait while available stock quantities are checked","Stock check in progress, please wait while available stock quantities are checked" -"Subtotal incl. tax","Subtotal incl. tax" -"Summary","Summary" -"The product is out of stock and cannot be added to the cart!","The product is out of stock and cannot be added to the cart!" -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","The product, category or CMS page is not available in Offline mode. Redirecting to Home." -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not avaiable because we cannot check it right now." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Type what you are looking for..." -"Unexpected authorization error. Check your Network conection.","Unexpected authorization error. Check your Network conection." -"Unhandled error, wrong response format!","Unhandled error, wrong response format!" -"Vue Storefront", "Vue Storefront" -"You are going to pay for this order upon delivery.","You are going to pay for this order upon delivery." -"You are logged in!","You are logged in!" -"You are to pay for this order upon delivery.","You are to pay for this order upon delivery." -"You need to be logged in to see this page","You need to be logged in to see this page" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","You're logged out" -"email","email" -"have as many","have as many" -"login","login" -"most you may purchase","most you may purchase" -"not authorized","not authorized" -"password","password" -"to account","to account" diff --git a/core/i18n/resource/i18n/es-ES.csv b/core/i18n/resource/i18n/es-ES.csv deleted file mode 100644 index 0563763dc1..0000000000 --- a/core/i18n/resource/i18n/es-ES.csv +++ /dev/null @@ -1,44 +0,0 @@ -" is out of stock!"," está agotado!" -"404 Page Not Found","404 Pagina no encontrada" -"Account data has successfully been updated","Los datos de la cuenta se han actualizado con éxito" -"Adding a review ...","Adding a review ..." -"Are you sure you would like to remove this item from the shopping cart?","¿Está seguro de que desea eliminar este artículo de la cesta de la compra?" -"Checkout","Pagar" -"Compare Products","Comparar productos" -"Error with response - bad content-type!","Error con la respuesta - ¡tipo de contenido incorrecto!" -"Field is required","El campo es requerido" -"Field is required.","El campo es requerido." -"Grand total","Gran total" -"Home Page","Página de inicio" -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Error de validación interna. Por favor, compruebe si se completan todos los campos obligatorios. Póngase en contacto con nosotros en {email}" -"My Account","Mi Cuenta" -"Newsletter preferences have successfully been updated","Las preferencias del boletín se han actualizado con éxito" -"No products synchronized for this category. Please come back while online!","No hay productos sincronizados para esta categoría. Por favor regrese mientras esta en linea!" -"No such configuration for the product. Please do choose another combination of attributes.","No hay tal configuración para el producto. Por favor, elija otra combinación de atributos." -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","¡Agotado!" -"Please fix the validation errors","Corrija los errores de validación" -"Product has been added to the cart!","¡El producto ha sido agregado al carrito!" -"Product price is unknown, product cannot be added to the cart!","El precio del producto es desconocido, ¡el producto no se puede agregar al carrito!" -"Product quantity has been updated!","¡La cantidad del producto ha sido actualizada!" -"Product {productName} has been added to the compare!","¡El producto {productName} se ha agregado a la comparación!" -"Product {productName} has been added to wishlist!","¡El producto {productName} ha sido agregado a la lista de deseos!" -"Product {productName} has been removed from compare!","¡El producto {productName} ha sido eliminado de la comparación!" -"Product {productName} has been removed from wishlist!","¡El producto {productName} ha sido eliminado de la lista de deseos!" -"Registering the account ...","Registrando la cuenta ..." -"Reset password feature does not work while offline!","¡La función Restablecer contraseña no funciona sin conexión!" -"Shopping cart is empty. Please add some products before entering Checkout","El Carro de Compras está Vacío. Por favor agregue algunos productos antes de ingresar al Checkout" -"Some of the ordered products are not available!","¡Algunos de los productos pedidos no están disponibles!" -"Stock check in progress, please wait while available stock quantities are checked","Verificación de stock en curso, espere mientras se verifican las cantidades de stock disponibles" -"Subtotal incl. tax","Subtotal incl. impuesto" -"The product is out of stock and cannot be added to the cart!","¡El producto no está disponible y no se puede agregar al carrito!" -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","El sistema no está seguro acerca de la cantidad de stock (volátil). El producto ha sido agregado al carrito para pre-reserva." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not avaiable because we cannot check it right now.","No hay conexión a internet. Todavía puedes hacer tu pedido. Le notificaremos si alguno de los productos solicitados no están disponible porque no podemos verificarlo ahora mismo." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","¡Esta característica aún no está implementada! ¡Por favor, eche un vistazo a https://github.com/vuestorefront/vue-storefront/issues para nuestra Hoja de ruta!" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Escribe lo que estás buscando..." -"Unhandled error, wrong response format!","¡Error no controlado, formato de respuesta incorrecto!" -"You are logged in!","¡Has iniciado sesión!" -"You need to be logged in to see this page","Necesitas iniciar sesión para ver esta página" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Estás desconectado" diff --git a/core/i18n/resource/i18n/et-EE.csv b/core/i18n/resource/i18n/et-EE.csv deleted file mode 100644 index a8500b1552..0000000000 --- a/core/i18n/resource/i18n/et-EE.csv +++ /dev/null @@ -1,90 +0,0 @@ -" is out of stock!","ei ole laos!" -"404 Page Not Found","404 Lehekülge ei leitud" -"Account data has successfully been updated","Konto andmed uuendatud" -"Add review","Lisa kommentaar" -"Adding a review ...","Lisan kommentaari ..." -"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","Ostuvormistamisel sisestatud aadress sisaldab vigu! Palun kontrolli, et kõik kohustuslikud väljad oleksid täidetud või kirjuta meile {email}." -"Allow notification about the order","Luba tellimusge seotud teadete edastamine" -"Are you sure you would like to remove this item from the shopping cart?","Olete kindel, et soovite antud toote ostukorvist eemaldada?" -"Compare Products","Võrdle tooteid" -"Compare products","Võrdle tooteid" -"Confirm your order","Kinnitage oma tellimus" -"Error refreshing user token. User is not authorized to access the resource","Kasutaja tokeni uuendamisel esineb probleeme. Kasutajal puuduvad õigused antud lehele sisenemiseks" -"Error with response - bad content-type!","Viga sisu laadimisel" -"Error: Error while adding products","Viga toote lisamisel." -"Extension developers would like to thank you for placing an order!","Mooduli loojad tänavad tellimuse tegemise eest!" -"Field is required",Kohustuslik -"Field is required.",Kohustuslik. -"Grand total",Kokku -"Home Page",Esileht -"In stock!",Laos -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Tokeni värskendamisel esines viga. Palun tühjendage vahemälu ja uuendage lehekülge." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Viga! Palun kontrolli, et kõik kohustuslikud väljad oleksid täidetud või kirjuta meile {email}." -"Must be greater than 0","Peab olema suurem, kui 0" -"My Account","Minu konto" -"Newsletter preferences have successfully been updated","Uudiskirjaga liitumine uuendatud" -"No available product variants","Toote valikvariandid puuduvad" -"No products synchronized for this category. Please come back while online!","Kategooria on tühi." -"No such configuration for the product. Please do choose another combination of attributes.","Sellise kombinatsiooniga toodet ei saa tellida." -"Only {maxQuantity} products of this type are available!","Ainult {maxQuantity} seda tüüpi toodet on saadaval!" -"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Või kui sa jäää ""Tellimuse kinnitus"" lehele, tellimus esitatakse internetiühenduse taastudes tellimus automaatselt." -"Out of stock!","Laost otsas!" -"Out of the stock!","Laost otsas" -"Payment Information","Makse informatsioon" -"Please configure product bundle options and fix the validation errors","Toode on valikutega. Palun valige sobivad valikud" -"Please configure product custom options and fix the validation errors","Palun valige sobiv toode" -"Please confirm order you placed when you was offline","Palun kinnitage oma tellimuse, mille ilma interneti ühenduseta varasemalt tegite" -"Please fix the validation errors","Palun parandage valideerimise vead" -"Please select the field which You like to sort by","Palun valige sorteerimise viis" -"Please wait ...","Palun oota ..." -"Proceed to checkout","Vormista ost" -"Processing order...","Tellimuse loomine ..." -"Product has been added to the cart!","Toode lisati ostukorvi." -"Product price is unknown, product cannot be added to the cart!","Tootel puudub hind. Toodet ei saa ostukorvi lisada." -"Product quantity has been updated!","Toote laoseis on uuendatud." -"Product {productName} has been added to the compare!","{productName} lisati võrdlusesse." -"Product {productName} has been added to wishlist!","{productName} Lisati soovikorvi." -"Product {productName} has been removed from compare!","{productName} võrdlusest eemaldatud." -"Product {productName} has been removed from wishlist!","{productName} on sooviloendist eemaldatud." -"Quantity available offline","Kogus on saadaval internetiühenduseta" -"Quantity available","Kogus on saadaval" -"Quantity must be above 0","Laokogus peab olema suurem, kui 0" -"Quantity must be below {quantity}","Laokogus peab olema alla {quantity}" -"Quantity must be positive integer","Laokogus peab olema positiivne täisarv" -"Registering the account ...","Konto loomine..." -"Reset password feature does not work while offline!","Parooli ei saa kahjuks ilma interneti ühenduseta muuta." -"Select 0","Vali 0" -"Select 1","Vali 1" -"Shopping cart is empty. Please add some products before entering Checkout","Otsukorv on tühi." -"Some of the ordered products are not available!","Mõned tellitud tooted ei ole kahjuks enam saadaval." -"Stock check in progress, please wait while available stock quantities are checked","Palun oota, kontrollime laoseise." -"Subtotal incl. tax","Kokku (sisaldab käibemaksu)" -"The product is out of stock and cannot be added to the cart!","Toode on kahjuks laost otsa saanud." -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","Antud toode, kategooria või sisuleht ei ole kahjuks ilma internetiühenduseta saadaval. Suuname ümber esilehele." -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Antud toodet ostetakse väga palju ja me ei ole laoseisu osas kindlad. Toode on ostukorvi lisatud ja Teie jaoks broneeritud." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Interneti ühendus puudub. Saad sellegi poolest tellimuse luua. Kontrollime interneti ühenduse taastudes tellitud toodete laoseisu üle. Anname märku, kui mõni tellitud toodetest vahepeal otsa on saanud. " -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Sellist funktsionaalsust ei ole veel lisatud. Saad meie arendusplaanidega tutvuda https://github.com/vuestorefront/vue-storefront/issues." -"Type what you are looking for...","Otsi ..." -"Unexpected authorization error. Check your Network conection.","Internetiühenduse viga. Palun kontrollige oma internetiühendust." -"Unhandled error, wrong response format!","Vale päringu formaat" -"Vue Storefront","Vue Storefront" -"You are going to pay for this order upon delivery.","Maksmine toimub tellimuse kättesaamisel." -"You are logged in!","Olete sisse logitud." -"You are to pay for this order upon delivery.","Saate makse teostada tellimuse kätte saamisel." -"You need to be logged in to see this page","Palun logige lehe nägemisesse sisse" -"You submitted your review for moderation.","Tagasiside on edastatud üle vaatamiseks." -"You're logged out","Olete välja logitud" -"have as many","osta kuni" -"most you may purchase","maksimum ostu kogus" -"not authorized","Ligipääs puudub" -"to account",kontole -Checkout,Ostuvormistamine -Columns,Tulbad -OK,Ok -Review,Kommentaar -Reviews,Kommentaarid -Summary,Kokkuvõte -Thumbnail,Pisipilt -email,E-post -login,"logi sisse" -password,Parool diff --git a/core/i18n/resource/i18n/fi-FI.csv b/core/i18n/resource/i18n/fi-FI.csv deleted file mode 100644 index ebc7bd3a22..0000000000 --- a/core/i18n/resource/i18n/fi-FI.csv +++ /dev/null @@ -1,90 +0,0 @@ -" is out of stock!"," on loppu varastosta" -"404 Page Not Found","404 Sivua ei löydy" -"Account data has successfully been updated","Tilin tiedot on päivitetty" -"Add review","Lisää arvostelu" -"Adding a review ...","Lisätään arvostelua…" -"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","Kassalla annettu osoite on virheellinen. Tarkista, että kaikki pakolliset kentät on täytetty. Tilauksesi on peruttu. Ongelman jatkuessa ota yhteyttä sähköpostilla osoitteeseen {email}." -"Allow notification about the order","Salli ilmoitus tilauksesta" -"Are you sure you would like to remove this item from the shopping cart?","Haluatko varmasti poistaa tuotteen ostoskorista?" -"Compare Products","Vertaile tuotteita" -"Compare products","Vertaile tuotteita" -"Confirm your order","Vahvista tilaus" -"Error refreshing user token. User is not authorized to access the resource","Käyttöoikeus puuttuu." -"Error with response - bad content-type!","Virhe sivun latauksessa - väärä sisältötyyppi." -"Error: Error while adding products","Virhe tuotteiden lisäyksessä." -"Extension developers would like to thank you for placing an order!","Laajennuksen kehittäjät kiittävät tilauksesta!" -"Field is required","Vaadittu tieto" -"Field is required.","Vaadittu tieto" -"Grand total",Yhteensä -"Home Page",Etusivu -"In stock!",Varastossa -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Virhe käyttöoikeuksien tarkistuksessa. Tyhjennä selaimen muisti ja lataa sivu uudelleen." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Tarkista, että kaikki pakolliset kentät on täytetty. Ongelman jatkuessa ota yhteyttä sähköpostilla osoitteeseen {email}." -"Must be greater than 0","Oltava suurempi kuin 0" -"My Account","Oma tili" -"Newsletter preferences have successfully been updated","Uutiskirjeen asetukset on päivitetty" -"No available product variants","Tuotevaihtoehtoja ei ole saatavilla." -"No products synchronized for this category. Please come back while online!","Kategoriassa ei ole tuotteita. Yritä uudelleen, kun verkkoyhteys on palautunut." -"No such configuration for the product. Please do choose another combination of attributes.","Valintaa vastaavaa tuotetta ei löydy." -"Only {maxQuantity} products of this type are available!","Vain {maxQuantity} saatavilla" -"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Tai, jos pysyt tilausvahvistussivulla, tilaus tehdään automaattisesti ilman vahvistusta yhteyden palautuessa." -"Out of stock!","Loppu varastosta" -"Out of the stock!","Loppu varastosta" -"Payment Information","Maksun tiedot" -"Please configure product bundle options and fix the validation errors","Tee valinnat" -"Please configure product custom options and fix the validation errors","Tee valinnat" -"Please confirm order you placed when you was offline","Vahvista yhteydettömässä tilassa tekemäsi tilaus" -"Please fix the validation errors","Korjaa virheelliset tiedot." -"Please select the field which You like to sort by","Valitse tieto, jonka mukaan haluat järjestää" -"Please wait ...","Odota hetki ..." -"Proceed to checkout",Kassalle -"Processing order...","Käsitellään tilausta..." -"Product has been added to the cart!","Tuote on lisätty ostoskoriin." -"Product price is unknown, product cannot be added to the cart!","Tuotteen hinta ei ole saatavilla ja sitä ei voida lisätä ostoskoriin." -"Product quantity has been updated!","Tuotteen varastosaldo on päivitetty." -"Product {productName} has been added to the compare!","{productName} on lisätty tuotevertailuun." -"Product {productName} has been added to wishlist!","{productName} on lisätty toivelistaan." -"Product {productName} has been removed from compare!","{productName} on poistettu tuotevertailusta." -"Product {productName} has been removed from wishlist!","{productName} on poistettu toivelistasta" -"Quantity available offline","Määrä saatavilla yhteydettömässä tilassa" -"Quantity available","Määrä saatavilla" -"Quantity must be above 0","Määrän pitää olla suurempi kuin 0" -"Quantity must be below {quantity}","Määrän pitää olla alle {quantity}" -"Quantity must be positive integer","Määrän pitää olla kokonaisluku" -"Registering the account ...","Luodaan tiliä ..." -"Reset password feature does not work while offline!","Salasanan uusiminen ei ole käytössä yhteydettömässä tilassa." -"Select 0","Valitse 0" -"Select 1","Valitse 1" -"Shopping cart is empty. Please add some products before entering Checkout","Ostoskori on tyhjä. Lisää tuotteita ennen kuin siirryt kassalle." -"Some of the ordered products are not available!","Jotkut tilatuista tuotteista eivät ole saatavilla." -"Stock check in progress, please wait while available stock quantities are checked","Odota hetki, varastosaldoja tarkistetaan" -"Subtotal incl. tax","Välisumma (sis. ALV)" -"The product is out of stock and cannot be added to the cart!","Tuotetta ei ole varastossa." -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","Sivu ei ole saatavilla yhteydettömässä tilassa. Ohjataan etusivulle." -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Tuotteen saatavuus on epävarma. Tuote on lisätty ostoskoriin ennakkovarauksena." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Verkkoyhteys ei ole saatavilla. Voit silti tehdä tilauksen. Saat myöhemmin ilmoituksen, mikäli jokin tilaamistasi tuotteista ei ole saatavilla." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Tätä ominaisuutta ei ole vielä toteutettu. Järjestelmän kehityssuunnitelma löytyy osoitteesta https://github.com/vuestorefront/vue-storefront/issues." -"Type what you are looking for...",Hae -"Unexpected authorization error. Check your Network conection.","Odottamaton virhe oikeuksien tarkistamisessa. Tarkista verkkoyhteys." -"Unhandled error, wrong response format!","Odottamaton virhe, väärä vastauksen formaatti." -"Vue Storefront","Vue Storefront" -"You are going to pay for this order upon delivery.","Maksat tilauksen toimituksen yhteydessä" -"You are logged in!","Olet kirjautuneena." -"You are to pay for this order upon delivery.","Maksu peritään toimituksen yhteydessä" -"You need to be logged in to see this page","Kirjaudu sisään nähdäksesi tämän sivun." -"You submitted your review for moderation.","Arvostelu on lähetetty tarkastettavaksi" -"You're logged out","Et ole kirjautuneena" -"have as many","määrä on" -"most you may purchase",Maksimitilausmäärä -"not authorized","ei sallittu" -"to account",tilille -Checkout,Kassa -Columns,Sarakkeet -OK,Ok -Review,Arvostelu -Reviews,Arvostelut -Summary,Tiivistelmä -Thumbnail,Pienoiskuva -email,sähköposti -login,"kirjaudu sisään" -password,salasana diff --git a/core/i18n/resource/i18n/fr-FR.csv b/core/i18n/resource/i18n/fr-FR.csv deleted file mode 100644 index 6e3b1eb403..0000000000 --- a/core/i18n/resource/i18n/fr-FR.csv +++ /dev/null @@ -1,54 +0,0 @@ -" is out of stock!"," n'est pas en stock !" -"404 Page Not Found","404 Page non trouvée" -"Account data has successfully been updated","Votre compte a été mis à jour avec succès" -"Adding a review ...","Adding a review ..." -"Are you sure you would like to remove this item from the shopping cart?","Etes-vous sûr de vouloir supprimer cet objet de votre panier ?" -"Checkout","Valider" -"Compare Products","Comparer les produits" -"Error refreshing user token. User is not authorized to access the resource","Une erreur est survenue. L'utilisateur n'est pas autorisé à accéder à cette ressource" -"Error with response - bad content-type!","Erreur avec réponse - mauvais type de contenu !" -"Field is required","Champ requis" -"Field is required.","Champ requis." -"Grand total","Total" -"Home Page","Page d'accueil" -"In stock!","En stock !" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Une erreur interne est survenue. Veuillez nettoyer le stockage du site et rafraîchir la page." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Erreur de validation interne Veuillez vérifier si tous les champs requis sont remplis. Veuillez nous contacter sur {email}" -"Must be greater than 0","Doit être supérieur à 0" -"My Account","Mon compte" -"Newsletter preferences have successfully been updated","Les préférences de la newsletter ont été mises à jour avec succès" -"No products synchronized for this category. Please come back while online!","Aucun produit synchronisé dans cette catégorie. Merci de passer en ligne !" -"No such configuration for the product. Please do choose another combination of attributes.","Aucune configuration de ce type pour le produit. Veuillez choisir une autre combinaison d'attributs." -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","Rupture de stock !" -"Out of the stock!","Épuisé !" -"Please configure product custom options and fix the validation errors","Veuillez configurer les options du produit et corriger les erreurs de validation" -"Please fix the validation errors","Veuillez corriger les erreurs de validation" -"Proceed to checkout","Passer la commande" -"Product has been added to the cart!","Le produit a été ajouté au panier !" -"Product price is unknown, product cannot be added to the cart!","Le prix du produit est inconnu, le produit ne peut pas être ajouté au panier !" -"Product quantity has been updated!","La quantité de produit a été mise à jour!" -"Product {productName} has been added to the compare!","Le produit {productName} a été ajouté au comparateur !" -"Product {productName} has been added to wishlist!","Le produit {productName} a été ajouté à la liste des souhaits !" -"Product {productName} has been removed from compare!","Le produit {productName} a été supprimé du comparateur !" -"Product {productName} has been removed from wishlist!","Le produit {productName} a été supprimé de la liste des souhaits !" -"Registering the account ...","Enregistrement du compte..." -"Reset password feature does not work while offline!","La fonction de réinitialisation du mot de passe ne fonctionne pas en mode hors ligne !" -"Shopping cart is empty. Please add some products before entering Checkout","Le panier d'achat est vide. Veuillez ajouter des produits avant de passer à la caisse" -"Some of the ordered products are not available!","Certains des produits commandés ne sont pas disponibles !" -"Stock check in progress, please wait while available stock quantities are checked","Vérification du stock en cours, veuillez patienter pendant que les stocks disponibles sont vérifiés" -"Subtotal incl. tax","Sous-total taxes incluses" -"The product is out of stock and cannot be added to the cart!","Le produit est en rupture de stock et ne peut être ajouté au panier !" -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Le système n'est pas sûr de la quantité de stock (volatile). Le produit a été ajouté au panier pour la pré-réservation." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Il n'y a pas de connexion Internet. Vous pouvez toujours passer votre commande. Nous vous informerons si l'un des produits commandés n'est pas disponible car nous ne pouvons pas le vérifier en mode hors ligne." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Cette fonctionnalité n'est pas encore implémentée! Veuillez vous rendre sur https://github.com/vuestorefront/vue-storefront/issues pour consulter notre Roadmap!" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Saisissez votre recherche ..." -"Unhandled error, wrong response format!","Erreur non prise en charge, format de réponse incorrect !" -"View all","Voir tout" -"You are logged in!","Vous êtes authentifié !" -"You need to be logged in to see this page","Vous devez être connecté pour voir cette page" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Vous êtes déconnecté" -"not authorized","pas autorisé" diff --git a/core/i18n/resource/i18n/it-IT.csv b/core/i18n/resource/i18n/it-IT.csv deleted file mode 100644 index 7876995403..0000000000 --- a/core/i18n/resource/i18n/it-IT.csv +++ /dev/null @@ -1,89 +0,0 @@ -" is out of stock!"," non è disponibile!" -"404 Page Not Found","404 Pagina non trovata" -"Account data has successfully been updated","Le tue informazioni sono state aggiornate con successo" -"Add review","Aggiungi una recensione" -"Adding a review ...","Adding a review ..." -"Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.","L'indirizzo fornito nel checkout contiene dati non validi. Per favore controlla di averi riempito tutti i campi obbligatori e contattaci all'indirizzo email {email} per risolvere questo problema in futuro. Il tuo ordine è stato cancellato." -"Allow notification about the order","Consenti notifiche sull'ordine" -"Are you sure you would like to remove this item from the shopping cart?","Sei sicuro di voler rimuovere questo articolo dal carrello?" -"Checkout","Cassa" -"Columns","Colonne" -"Compare Products","Confronta prodotti" -"Compare products","Confronta prodotti" -"Confirm your order","Conferma il tuo ordine" -"Error refreshing user token. User is not authorized to access the resource","Errore durante l'aggiornamento del token. L'utente non è autorizzato ad accedere alla risorsa" -"Error with response - bad content-type!","Errore nella risposta - content-type non valido!" -"Error: Error while adding products","Errore: c'è stato un errore durante l'aggiunta dei prodotti" -"Extension developers would like to thank you for placing an order!","Gli sviluppatori dell'estensione vorrebbero ringraziarti per aver fatto un ordine!" -"Field is required","Campo obbligatorio" -"Field is required.","Campo obbligatorio" -"Grand total","Totale" -"Home Page","Home" -"In stock!","Disponibile" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Errore interno dell'applicazione durante l'aggiornamento dei token. Per favore svuota lo storage e ricarica la pagina." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Errore di validazione interno. Se tutti i campi sono stati compilati contattare {email}" -"Must be greater than 0","Deve essere maggiore di 0" -"My Account","Il mio account" -"Newsletter preferences have successfully been updated","Le tue preferenze sulla newsletter sono state aggiornate con successo" -"No available product variants","Non ci sono varianti disponibili" -"No products synchronized for this category. Please come back while online!","Nessun prodotto in questa categoria. Verifica la connessione di rete e riprova!" -"No such configuration for the product. Please do choose another combination of attributes.","Configurazione del prodotto inesistente. Scegli un'altra combinazione di attributi" -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Or if you will stay on ""Order confirmation"" page, the order will be placed automatically without confirmation, once the internet connection will be back.","Oppure se rimarrai nella pagina di ""Conferma ordine"", l'ordine verrà automaticamente evaso senza conferma, una volta che la connessione sarà ripristinata." -"Out of stock!","Non disponibile" -"Out of the stock!","Non disponibile" -"Payment Information","Informazioni di pagamento" -"Please configure product bundle options and fix the validation errors","Per favore completa la configurazione del prodotto bundle e verifica la validazione dei dati" -"Please configure product custom options and fix the validation errors","Per favore configura le opzioni personabilizzate del prodotto e verifica di aver compilato correttamente tutti i campi" -"Please confirm order you placed when you was offline","Per favore conferma l'ordine che hai fatto quando eri offline" -"Please fix the validation errors","Verifica di aver compilato correttamente tutti i campi" -"Please select the field which You like to sort by","Scegli il campo con cui vuoi ordinare" -"Please wait ...","Attendere..." -"Proceed to checkout","Vai alla cassa" -"Processing order...","Ordine in corso..." -"Product has been added to the cart!","Il prodotto è stato aggiunto al carrello!" -"Product price is unknown, product cannot be added to the cart!","Il prezzo di questo prodotto è sconosciuto, non è possibile aggiungerlo al carrello!" -"Product quantity has been updated!","La quantità è stata aggiornata!" -"Product {productName} has been added to the compare!","Il prodotto {productName} è stato aggiunto al comparatore!" -"Product {productName} has been added to wishlist!","Il prodotto {productName} è stato aggiunto alla lista dei desideri!" -"Product {productName} has been removed from compare!","Il prodotto {productName} è stato rimosso dal comparatore" -"Product {productName} has been removed from wishlist!","Il prodotto {productName} è stato rimosso dalla lista dei desideri" -"Quantity available","Quantità ({qty} disponibile)" -"Quantity must be above 0","La quantità deve essere superiore a 0" -"Quantity must be below {quantity}","La quantità deve essere inferiore a {quantity}" -"Quantity must be positive integer","La quantità deve essere un numero positivo" -"Registering the account ...","Creazione dell'account..." -"Reset password feature does not work while offline!","Non puoi reimpostare la password quando non sei in linea!" -"Review","Recensione" -"Reviews","Recensioni" -"Select 0","Select 0" -"Select 1","Select 1" -"Shopping cart is empty. Please add some products before entering Checkout","Il tuo carrello è vuoto. Aggiungi almeno un prodotto prima di procedere alla cassa" -"Some of the ordered products are not available!","Alcuni dei prodotti ordinati non sono disponibili!" -"Stock check in progress, please wait while available stock quantities are checked","Verifica disponibilità in corso, attendere la verifica della quantità dei prodotti" -"Subtotal incl. tax","Subtotale tasse incluse" -"Summary","Riassunto" -"The product is out of stock and cannot be added to the cart!","Il prodotto non è più disponibile e non può essere aggiunto al carrello" -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","The product, category or CMS page is not available in Offline mode. Redirecting to Home." -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Il sistema non è sicuro della disponibilità del prodotto (volatile). Il prodotto è stato aggiunto al carrello per prenotazione" -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Non sei connesso ad internet ma puoi ugualmente procedere con l'ordine. Non è possibile verificare adesso la disponibilità dei prodotti, se qualcuno dei prodotti ordinati non sarà più disponibile te lo faremo sapere." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Questa funzionalità non è ancora stata implementata! Dai un'occhiata alla nostra roadmap qui https://github.com/vuestorefront/vue-storefront/issues!" -"Thumbnail","Miniatura" -"Type what you are looking for...","Cerca nel catalogo..." -"Unexpected authorization error. Check your Network conection.","Errore di autorizzazione inaspettato. Verifica la tua connesione." -"Unhandled error, wrong response format!","Errore inatteso, formato della risposta non valido!" -"Vue Storefront", "Vue Storefront" -"You are going to pay for this order upon delivery.","Pagherai questo ordine alla consegna." -"You are logged in!","Accesso eseguito!" -"You are to pay for this order upon delivery.","Pagherai questo ordine alla consegna." -"You need to be logged in to see this page","Devi essere loggato per vedere questa pagina" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Ti sei disconnesso" -"email","email" -"have as many","have as many" -"login","accedi" -"most you may purchase","most you may purchase" -"not authorized","non autorizzato" -"password","password" -"to account","al tuo account" diff --git a/core/i18n/resource/i18n/ja-JP.csv b/core/i18n/resource/i18n/ja-JP.csv deleted file mode 100644 index 122a317f8f..0000000000 --- a/core/i18n/resource/i18n/ja-JP.csv +++ /dev/null @@ -1,172 +0,0 @@ -" is out of stock!"," は在庫がありません!" -"404 Page Not Found","404ページが見つかりません" -"About us (Magento CMS)","弊社について(Magento CMS)" -"Add a discount code","ディスカウントコードの追加" -"Add discount code","ディスカウントコードの追加" -"Add to cart","カートに追加" -"Add to compare","比べるに追加" -"Add to favorite","お気に入りに追加" -"Adding a review ...","Adding a review ..." -"Allow notification about the order","注文に関する通知を送る" -"Are you sure you would like to remove this item from the shopping cart?","この商品をショッピングカートから削除してもいいですか?" -"Author","作者" -"Back to login","ログインに戻る" -"Back","戻る" -"Billing address","請求書先住所" -"Change my password","パスワードを変更する" -"Choose your country","国を選択" -"City","市町村名" -"Clear","クリア" -"Cms Page Sync","CMSページと同期" -"Compare Products","商品を比べる" -"Compare products","商品を比べる" -"Confirmation of receival","商品受け取りの確認" -"Continue to payment","支払いへ進む" -"Continue to shipping","買い物へ進む" -"Copy address data from shipping","配送住所からコピー" -"Create a new account","新しいアカウントの作成" -"Current password *","現在のパスワード *" -"Custom Cms Page","カスタムCMSページ" -"Date and time","日時と時間" -"Discount code","ディスカウントコード" -"Discount","ディスカウント" -"E-mail us at demo@vuestorefront.io with any questions, suggestions how we could improve products or shopping experience","質問や私達のサービスをさらに良くする改善提案のある方はdemo@vuestorefront.ioまでメールで連絡をお願いします。" -"Edit newsletter preferences","ニュースレターの設定を編集" -"Edit payment","支払いの編集" -"Edit personal details","ユーザー情報の編集" -"Edit shipping","配送先の編集" -"Edit your profile","プロフィールの編集" -"Edit your shipping details","配送情報の編集" -"Edit","編集" -"Email address *","メールアドレス *" -"Enter your email to receive instructions on how to reset your password.","メールを読んでそこにあるパスワード再設定の指示に従ってください。" -"Erin recommends","Erinのおすすめ" -"Error while sending reset password e-mail","パスワード再設定のメールの送信でエラー" -"Everything new","全ての新製品" -"Filter","フィルター" -"Filters","フィルター" -"Forgot the password?","パスワードを忘れましたか?" -"General agreement","一般的な合意" -"Get inspired","インスピレーションをもらおう" -"Give a feedback","フィードバックを送る" -"Go review the order","注文のレビュー" -"Go to checkout","支払いへ進む" -"Grand total","総合計額" -"House/Apartment number","部屋番号" -"I accept ","以下に同意します " -"I accept terms and conditions","以下の利用規約に同意します" -"I agree to","以下に合意します" -"I want to create an account","アカウント作成したい" -"I want to generate an invoice for the company","この会社への請求書を作りたい" -"I want to receive a newsletter, and agree to its terms","利用規約に合意してニュースレーターを受け取ります" -"Internal Server Error 500","500サーバーエラー" -"Load more","もっと見る" -"Log in to your account","アカウントにログイン" -"Log in","ログイン" -"Magazine","マガジン" -"New Luma Yoga Collection","新しいLumaのヨガコレクション" -"New password *","新しいパスワード *" -"Newsletter","ニュースレター" -"No orders yet","まだ注文はありません" -"No products yet","商品はありません" -"No reviews have been posted yet. Please don\","まだレビューは投稿されていません。" -"No such configuration for the product. Please do choose another combination of attributes.","この商品へのこの構成はありません。別の組み合わせを試してみてください。" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Open menu","メニューを開く" -"Open microcart","マイクロカートを開く" -"Open my account","アカウントを開く" -"Open search panel","検索パネルを開く" -"Open wishlist","欲しいものリストを開く" -"Order ID","注文ID" -"Order Summary","注文詳細" -"Order informations","注文情報" -"Out of stock!","在庫はありません!" -"Password must have at least 8 letters.","パスワードは英数半角で8文字以上である必要があります。" -"Payment","支払い" -"Personal Details","ユーザーの詳細" -"Place the order","注文する" -"Please check if all data are correct","全てのデータが正しいかチェックしてください" -"Please confirm order you placed when you was offline","オフライン中に注文した商品を確認してください" -"Please select the field which You like to sort by","ソートしたい項目を選んでください" -"Product quantity has been updated!","製品の数量が更新されました!" -"Products","商品" -"Purchase","購入" -"Register an account","アカウントを登録" -"Register","登録" -"Remember me","ログインを覚える" -"Remove from compare","比べるから外す" -"Repeat new password *","新しいパスワード(もう一度) *" -"Reset password feature does not work while offline!","オフラインの間ではパスワードの再設定はできません!" -"Returns","返品" -"Review order","注文のレビュー" -"Review","レビュー" -"Reviews","レビュー" -"Safety","安全" -"Sale","購入" -"See details","詳細を見る" -"See our bestsellers","ベストセラーを見る" -"Select color ","色の選択 " -"Ship to my default address","デフォルトの住所に配送する" -"Shipping address","配送用住所" -"Shopping cart is empty. Please add some products before entering Checkout","ショッピングカートが空です。支払いに進むには商品を追加してください。" -"Shopping cart","ショッピングカート" -"Shopping summary","お買い物詳細" -"Show subcategories","サブカテゴリーを表示" -"Sign up to our newsletter and receive a coupon for 10% off!","ニュースレターに登録をして、10%OFFクーポンを手に入れましょう!" -"Similar products","似ている商品" -"Some of the ordered products are not available!","注文された商品に在庫切れがあります!" -"Sort By","ソート" -"Status","ステータス" -"Stock check in progress, please wait while available stock quantities are checked","在庫チェックをしています。もう少々おまちください。" -"Street name","通りの名前" -"Subscribe to the newsletter and receive a coupon for 10% off","ニュースレターに登録をして、10%OFFクーポンを手に入れましょう!" -"Summary","詳細" -"Tax ID *","税金ID *" -"Tax ID must have at least 3 letters.","税金登録番号は3桁以上である必要があります。" -"Tax identification number *","税金登録番号 *" -"Tax identification number must have at least 3 letters.","税金登録番号は3桁以上である必要があります。" -"Tax","税金" -"The new account will be created with the purchase. You will receive details on e-mail.","この注文後新しいアカウントが作られます。メールで詳細が送られます。" -"The product is out of stock and cannot be added to the cart!","この商品は在庫がなく、カートに追加することができません!" -"The server order id has been set to ","サーバー注文IDが以下に設定されました " -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","現在インターネットへ接続されていません。注文をすることはできますが、在庫がない場合はインターネットに接続後お知らせを送ります。" -"Thumbnail","Thumbnail" -"To finish the order just come back to our store while online. Your order will be sent to the server as soon as you come back here while online and then confirmed regarding the stock quantities of selected items","注文を終了するにはオンラインになったて再度確認する必要があります。オンラインになったら注文がサーバーに送られて在庫チェックが行われます。" -"Type your opinion","意見を入力" -"Type","入力" -"Update my preferences","設定を更新" -"Update my profile","プロフィールを更新" -"Update my shipping details","配送住所を更新" -"Update","更新" -"Use my billing data","請求データを使う" -"Value","価格" -"View all","全てを見る" -"View order","注文を見る" -"We found other products you might like","おすすめの商品がありました" -"We use cookies to give you the best shopping experience.","ベストな買い物体験を提供する為にクッキーが使われています。" -"We will send you details regarding the order","注文についての詳細を送ります" -"We will send you the invoice to given e-mail address","頂いたメールアドレスに請求書を送ります" -"Wishlist","お気に入りリスト" -"You are offline","オフラインです" -"You can also use","またこれらを使うことができます" -"You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.","登録されたメールアドレスとパスワードを使ってログインできます。アカウント内ではプロフィールの編集注文の履歴の確認ニュースレターへの購読が行えます。" -"You have been successfully subscribed to our newsletter!","ニュースレターへの購読が完了しました!" -"You have no items to compare.","比べる商品がありません" -"You have successfuly placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.","注文が終わりました。注文の状態は注文ステータス機能から確認できます。注文の注文の状態を確認できるリンクの入った注文の詳細がメールで送られます。" -"You submitted your review for moderation.","You submitted your review for moderation." -"You will receive Push notification after coming back online. You can confirm the order by clicking on it","オンラインになるとプッシュ通知が送られ、そこから注文の確認ができます。" -"Your Account","アカウント" -"Your feedback is important for us. Let us know what we could improve.","あなたからのフィードバックは重要です。どこを改善できるか是非お伝えください。" -"Your purchase","購入" -"Your shopping cart is empty.","ショッピングカートは空です。" -"Your wishlist is empty.","お気に入りリストは空です。" -"Zip-code","郵便番号" -"Zipcode","郵便番号" -"a chat","チャット" -"notification-progress-start","進捗お知らせ開始" -"or write to us through","か私達に連絡してください" -"or","か" -"register an account","アカウントを登録" -"return to log in","戻ってログイン" -"search","検索" -"to find product you were looking for.","探している商品を見つける。" diff --git a/core/i18n/resource/i18n/nl-NL.csv b/core/i18n/resource/i18n/nl-NL.csv deleted file mode 100644 index f0dcffca16..0000000000 --- a/core/i18n/resource/i18n/nl-NL.csv +++ /dev/null @@ -1,44 +0,0 @@ - is out of stock!, is niet op voorraad! -"Adding a review ...","Review aan het aanmaken ..." -"Are you sure you would like to remove this item from the shopping cart?","Weet u zeker dat u dit artikel uit uw winkelwagen wilt verwijderen?" -"Only {maxQuantity} products of this type are available!","Er zijn {maxQuantity} producten van dit type op voorraad!" -"Product price is unknown, product cannot be added to the cart!",Productprijs onbekend. Dit product kan niet worden toegevoegd aan de winkelwagen! -"Product quantity has been updated!","Producthoeveelheid is aangepast!" -"Shopping cart is empty. Please add some products before entering Checkout",Uw winkelwagen is leeg. Voeg producten toe voordat u gaat afrekenen -"Stock check in progress, please wait while available stock quantities are checked","Voorraadcontrole bezig. Een moment geduld alstublieft, we checken op dit moment de voorraden" -"Thumbnail","Thumbnail" -"Unhandled error, wrong response format!","Unhandled error, wrong response format!" -"You need to be logged in to see this page","U moet ingelogd zijn om deze pagina te bekijken" -"You submitted your review for moderation.","You submitted your review for moderation." -404 Page Not Found,404 Pagina niet gevonden -Account data has successfully been updated,Account gegevens succesvol geüpdatet. -Checkout,Afrekenen -Compare Products,Producten vergelijken -Error with response - bad content-type!,Error with response - bad content-type! -Field is required,Veld is verplicht -Field is required.,Velden zijn verplicht. -Grand total,Eindtotaal -Home Page,Homepage -Internal validation error. Please check if all required fields are filled in. Please contact us on {email},Interne validatie fout. Check of alle verplichte velden ingevuld zijn. Neem contact met ons op via {email}. -My Account,Mijn Account -Newsletter preferences have successfully been updated,Uw nieuwsbrief voorkeur is gewijzigd. -No products synchronized for this category. Please come back while online!,Er zijn geen producten gesynchroniseerd in deze categorie. Kom alstublieft terug terwijl u online bent. -No such configuration for the product. Please do choose another combination of attributes.,De gekozen productconfiguratie bestaat niet. Kies een andere combinatie. -Out of stock!,Niet op voorraad! -Please fix the validation errors,Corrigeer de validatiefouten -Product has been added to the cart!,Product is toegevoegd aan de winkelwagen! -Product {productName} has been added to the compare!,Product {productName} is toegevoegd aan de vergelijking! -Product {productName} has been added to wishlist!,Product {productName} is toegevoegd aan de verlanglijst! -Product {productName} has been removed from compare!,Product {productName} is verwijderd van de vergelijking! -Product {productName} has been removed from wishlist!,Product {productName} is verwijderd van de verlanglijst -Registering the account ...,Account registreren ... -Reset password feature does not work while offline!,De reset wachtwoordfunctie werkt niet terwijl u offline bent! -Some of the ordered products are not available!,Niet alle bestelde producten zijn beschikbaar! -Subtotal incl. tax,Subtotaal incl. BTW -The product is out of stock and cannot be added to the cart!,Dit product is niet in voorraad en kan niet toegevoegd worden aan de winkelwagen! -The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.,Het is niet duidelijk hoeveel producten er exact op voorraad zijn. Het product is toegevoegd aan de winkelwagen als reservering. -There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.,"U heeft geen internetverbinding. U kunt nog steeds een bestelling plaatsen. We laten het weten als de door u bestelde producten niet op voorraad zijn, dit kunnen we momenteel niet controleren." -This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!,"Helaas, deze feature is nog niet geimplementeerd. Kijk op https://github.com/vuestorefront/vue-storefront/issues om de roadmap te zien." -Type what you are looking for...,Waar bent u naar op zoek? -You are logged in!,U bent ingelogd! -You're logged out,U bent uitgelogd diff --git a/core/i18n/resource/i18n/pl-PL.csv b/core/i18n/resource/i18n/pl-PL.csv deleted file mode 100644 index 35536855de..0000000000 --- a/core/i18n/resource/i18n/pl-PL.csv +++ /dev/null @@ -1,53 +0,0 @@ -" is out of stock!"," jest niedostępny!" -"404 Page Not Found","404 Nie ma takiej strony" -"Account data has successfully been updated","Twoje dane zostały zaktualizowane" -"Adding a review ...","Adding a review ..." -"Are you sure you would like to remove this item from the shopping cart?","Czy na pewno chcesz usunąć ten produkt z koszyka?" -"Checkout","Płatność" -"Compare Products","Porównaj produkty" -"Error refreshing user token. User is not authorized to access the resource","Błąd odświeżania tokenu użytkownika. Użytkownik nie dostępu do zasobu" -"Error with response - bad content-type!","Błąd odpowiedzi - bad content-type!" -"Field is required","Pole jest wymagane" -"Field is required.","Pole jest wymagane." -"Grand total","Łączna suma" -"Home Page","Strona główna" -"In stock!","W magazynie!" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Błąd odświeżania tokenów. Proszę wyczyścić pamięć podręczną przeglądarki." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Błąd walidacji. Sprawdź czy wszystkie wymagane pola zostały wybrane. Proszę, skontaktuj się z nami na adres {email}" -"Must be greater than 0","Musi być większa niż 0" -"My Account","Moje konto" -"Newsletter preferences have successfully been updated","Twoje ustawienia subskrypcji zostały zaktualizowane" -"No products synchronized for this category. Please come back while online!","Brak produktów dla tej kategorii. Spróbuj ponownie po uzyskaniu dostępu do Internetu!" -"No such configuration for the product. Please do choose another combination of attributes.","Wybrane opcje są niedostępne. Wybierz inne opcje." -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","Produkt niedostępny!" -"Out of the stock!","Brak w magazynie!" -"Please configure product custom options and fix the validation errors","Skonfiguruj opcje produktu i popraw będy walidacji" -"Please fix the validation errors","Popraw błędy walidacji" -"Proceed to checkout","Przejdź do kasy" -"Product has been added to the cart!","Produkt został dodany do koszyka!" -"Product price is unknown, product cannot be added to the cart!","Nie można dodać produktu do koszyka, nie można potwierdzić ceny produktu!" -"Product quantity has been updated!","Ilość produktu została zaktualizowana!" -"Product {productName} has been added to the compare!","Produkt {productName} został dodany do porównania!" -"Product {productName} has been added to wishlist!","Produkt {productName} został dodany do listy życzeń!" -"Product {productName} has been removed from compare!","Produkt {productName} został usunięty z porównania!" -"Product {productName} has been removed from wishlist!","Produkt {productName} został usunięty z listy życzeń!" -"Registering the account ...","Tworzenie konta ..." -"Reset password feature does not work while offline!","Aby zresetowa hasło musisz posiadać połączenie z Internetem!" -"Shopping cart is empty. Please add some products before entering Checkout","Koszyk jest pusty. Dodaj produkty." -"Some of the ordered products are not available!","Niektóre z produktów są niedostępne!" -"Stock check in progress, please wait while available stock quantities are checked","Trwa sprawdzanie dostępności produktów, proszę czekać" -"Subtotal incl. tax","Kwota częściowa brutto" -"The product is out of stock and cannot be added to the cart!","Produkt jest niedostępny!" -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Brak połączenia z Internetem. Możesz nadal złożyć zamówienie. Poinformujemy Cię jeśli któryś z produktów nie będzie dostępny." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Wybrana opcja jest niedostępna!" -"Thumbnail","Miniaturka" -"Type what you are looking for...","Znajdź..." -"Unhandled error, wrong response format!","Nieobsługiwany błąd, nieprawidłowy format odpowiedzi!" -"You are logged in!","Jesteś zalogowany!" -"You need to be logged in to see this page","Musisz być zalogowany, aby zobaczyć tę stronę" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Jesteś wylogowany" -"not authorized","nieautoryzowany" diff --git a/core/i18n/resource/i18n/pt-BR.csv b/core/i18n/resource/i18n/pt-BR.csv deleted file mode 100644 index 9f53b09c13..0000000000 --- a/core/i18n/resource/i18n/pt-BR.csv +++ /dev/null @@ -1,48 +0,0 @@ -" is out of stock!"," está sem estoque!" -"404 Page Not Found","404 Página Não Encontrada" -"Account data has successfully been updated","Dados da conta foram atualizados" -"Adding a review ...","Adding a review ..." -"Are you sure you would like to remove this item from the shopping cart?","Tem certeza de que deseja remover este item do carrinho de compras?" -"Checkout","Finalizar Compra" -"Compare Products","Comparar Produtos" -"Error with response - bad content-type!","Erro na resposta do servidor - bad content-type!" -"Field is required","Campo obrigatório" -"Field is required.","Campo obrigatório." -"Grand total","Total" -"Home Page","Página Inicial" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Erro Interno da Aplicação ao atualizar os tokens. Por favor limpe o cache do seu navegador e atualize a página." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Erro interno de validação. Por favor verifique se todos os campos obrigatórios estão preenchidos. Por favor contate-nos em {email}" -"My Account","Minha Conta" -"Newsletter preferences have successfully been updated","Configurações de Newsletter foram atualizadas." -"No products synchronized for this category. Please come back while online!","Nenhum produto sincronizado para essa categoria. Por favor volte quando estiver online!" -"No such configuration for the product. Please do choose another combination of attributes.","Nenhuma configuração desse tipo disponível para o produto. Por favor selecione outra combinação de opções." -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","Sem estoque!" -"Please fix the validation errors","Por favor corrija os erros de validação" -"Proceed to checkout","Finalizar Compra" -"Product has been added to the cart!","Produto foi adicionado ao carrinho!" -"Product price is unknown, product cannot be added to the cart!","Preço do produto é desconhecido, produto não pode ser adicionado ao carrinho!" -"Product quantity has been updated!","A quantidade do produto foi atualizada!" -"Product {productName} has been added to the compare!","Produto {productName} foi adicionado para comparação!" -"Product {productName} has been added to wishlist!","Produto {productName} foi adicionado à lista de desejos!" -"Product {productName} has been removed from compare!","Produto {productName} foi removido da comparação!" -"Product {productName} has been removed from wishlist!","Produto {productName} foi removido da lista de desejos!" -"Registering the account ...","Salvando a conta ..." -"Reset password feature does not work while offline!","Recuperação de Senha não funciona sem conexão de internet!" -"Shopping cart is empty. Please add some products before entering Checkout","Carrinho está vazio. Por favor adicione produtos antes de Finalizar a Compra" -"Some of the ordered products are not available!","Alguns dos produtos comprados não estão disponíveis!" -"Stock check in progress, please wait while available stock quantities are checked","Estamos verificando o estoque, por favor aguarde até verificarmos a quantidade solicitada." -"Subtotal incl. tax","Subtotal c/ imposto" -"The product is out of stock and cannot be added to the cart!","Produto não possui estoque e não pode ser adicionado ao carrinho!" -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","O sistema não tem certeza sobre a quantidade em estoque (volátil). O produto foi adicionado ao carrinho em pré-reserva." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Sem conexão de Internet. Você ainda pode finalizar seu pedido. Assim que possível o notificaremos se os produtos solicitados não estiverem disponíveis." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Essa funcionalidade não foi implementada ainda! Por favor verifique nosso planejamento em https://github.com/vuestorefront/vue-storefront/issues" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Digite o que você está buscando..." -"Unhandled error, wrong response format!","Erro desconhecido, formato de retorno errado!" -"You are logged in!","Você está conectado!" -"You need to be logged in to see this page","Você precisa estar logado para ver esta página" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Você foi desconectado" -"not authorized","não autorizado" diff --git a/core/i18n/resource/i18n/pt-PT.csv b/core/i18n/resource/i18n/pt-PT.csv deleted file mode 100644 index b9955e6ded..0000000000 --- a/core/i18n/resource/i18n/pt-PT.csv +++ /dev/null @@ -1,48 +0,0 @@ -" is out of stock!"," está em ruptura de stock!" -"404 Page Not Found","404 Página Não Encontrada" -"Account data has successfully been updated","Os dados da conta foram atualizados" -"Adding a review ...","Adding a review ..." -"Are you sure you would like to remove this item from the shopping cart?","Tem a certeza de que deseja remover este item do Cesto de Compras?" -"Checkout","Finalizar Compra" -"Compare Products","Comparar Produtos" -"Error with response - bad content-type!","Erro na resposta do servidor - bad content-type!" -"Field is required","Campo obrigatório" -"Field is required.","Campo obrigatório." -"Grand total","Total" -"Home Page","Página Inicial" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","Erro Interno da Aplicação ao atualizar as tokens. Por favor limpe o cache do seu browser e recarregue a página." -"Internal validation error. Please check if all required fields are filled in. Please contact us on contributors@vuestorefront.io","Erro interno de validação. Por favor verifique se todos os campos obrigatórios estão preenchidos. Por favor contate-nos em contributors@vuestorefront.io" -"My Account","A Minha Conta" -"Newsletter preferences have successfully been updated","As suas preferências para a Newsletter foram atualizadas." -"No products synchronized for this category. Please come back while online!","Nenhum produto sincronizado para esta categoria. Por favor volte qundo estiver online!" -"No such configuration for the product. Please do choose another combination of attributes.","Esta configuração não é possível para este produto. Por favor selecione outra configuração de opções." -"OK","OK" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","Ruptura de stock!" -"Please fix the validation errors","Por favor corrija os erros de validação" -"Proceed to checkout","Ir para Finalizar Compra" -"Product has been added to the cart!","O produto foi adicionado ao Cesto de Compras!" -"Product price is unknown, product cannot be added to the cart!","O preço do produto é desconhecido, o produto não pode ser adicionado ao Cesto de Compras!" -"Product quantity has been updated!","A quantidade do produto foi atualizada!" -"Product {productName} has been added to the compare!","Produto {productName} foi adicionado a Comparar Produtos!" -"Product {productName} has been added to wishlist!","Produto {productName} foi adicionado à Lista de Desejos!" -"Product {productName} has been removed from compare!","Produto {productName} foi removido de Comparar Produtos!" -"Product {productName} has been removed from wishlist!","Produto {productName} foi removido da Lista de Desejos!" -"Registering the account ...","A registar a conta ..." -"Reset password feature does not work while offline!","A funcionalidade de Recuperação de Senha não funciona sem ligação à internet!" -"Shopping cart is empty. Please add some products before entering Checkout","O Cesto de Compras está vazio. Por favor adicione alguns produtos antes de Finalizar Compra" -"Some of the ordered products are not available!","Alguns dos produtos solicitados não estão disponíveis!" -"Stock check in progress, please wait while available stock quantities are checked","Estamos a verificar o stock, por favor aguarde até que verifiquemos se a quantidade solicitada está disponível." -"Subtotal incl. tax","Subtotal c/ imposto" -"The product is out of stock and cannot be added to the cart!","O produto não se encontra em stock e não pode ser adicionado ao Cesto de Compras!" -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","O sistema não tem certeza sobre a quantidade em stock (volátil). O produto foi adicionado ao Cesto de Compras em pré-reserva." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Sem ligação à Internet. Mas pode finalizar o seu pedido. Assim que possível iremos notifica-lo caso algum dos produtos solicitados não estiverem disponíveis." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Esta funcionalidade ainda não foi implementada! Por favor consulte o nosso Rodmap em https://github.com/vuestorefront/vue-storefront/issues!" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Escreva o que está procurando..." -"Unhandled error, wrong response format!","Erro desconhecido, formato de resposta errado!" -"You are logged in!","Você está com sessão inicializada!" -"You need to be logged in to see this page","Precisa de iniciar sessão para ver esta página" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Você não está com sessão inicializada" -"not authorized","não autorizado" diff --git a/core/i18n/resource/i18n/ru-RU.csv b/core/i18n/resource/i18n/ru-RU.csv deleted file mode 100644 index 9d9be6ccfa..0000000000 --- a/core/i18n/resource/i18n/ru-RU.csv +++ /dev/null @@ -1,44 +0,0 @@ -" is out of stock!"," нет в наличии!" -"404 Page Not Found","404 Страница не найдена" -"Account data has successfully been updated","Данные учетной записи успешно обновлены" -"Adding a review ...","Adding a review ..." -"Are you sure you would like to remove this item from the shopping cart?","Вы уверены, что хотите удалить этот товар из корзины?" -"Checkout","Оформление заказа" -"Compare Products","Сравнить товары" -"Error with response - bad content-type!","Ошибка в ответе - плохой content-type!" -"Field is required","Обязательное поле" -"Field is required.","Обязательное поле." -"Grand total","Общий итог" -"Home Page","Главная страница" -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","Внутренняя валидационная ошибка. Пожалуйста проверьте заполненность всех обязательных полей. Пожалуйста свяжитесь с нами по {email}" -"My Account","Личный кабинет" -"Newsletter preferences have successfully been updated","Предпочтения по новостям успешно обновлены" -"No products synchronized for this category. Please come back while online!","По данной категории товары не синхронизированы. Пожалуйста вернитесь когда будете онлайн!" -"No such configuration for the product. Please do choose another combination of attributes.","У товара отсутствует данная конфигурация. Пожалуйста выберите другие значения атрибутов." -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","Нет в наличии!" -"Please fix the validation errors","Пожалуйста исправьте валидационные ошибки" -"Product has been added to the cart!","Товар добавлен в корзину!" -"Product price is unknown, product cannot be added to the cart!","Цена товара неизвестна, товар нельзя добавить в корзину!" -"Product quantity has been updated!","Количество товара обновлено!" -"Product {productName} has been added to the compare!","Товар {productName} добавлен к странице сравнения!" -"Product {productName} has been added to wishlist!","Товар {productName} добавлен к списку пожеланий!" -"Product {productName} has been removed from compare!","Товар {productName} удален из страницы сравнения!" -"Product {productName} has been removed from wishlist!","Товар {productName} удален из списка пожеланий!" -"Registering the account ...","Создается учетная запись ..." -"Reset password feature does not work while offline!","Возможность переустановки пароля не работает при отсутствующем соединении к интернету!" -"Shopping cart is empty. Please add some products before entering Checkout","Корзина пустая. Пожалуйста добавьте товары перед оформлением заказа" -"Some of the ordered products are not available!","Некоторые из заказанных товаров не доступны!" -"Stock check in progress, please wait while available stock quantities are checked","Производится проверка товаров на наличие, пожалуйста дождитесь завершения процесса проверки доступного количества" -"Subtotal incl. tax","Подитог вкл. налог" -"The product is out of stock and cannot be added to the cart!","Товара нет в наличии и его нельзя добавить в корзину" -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","Система не может удостовериться в наличии необходимого количества (изменчиво). Товар добавлен в корзину для предварительного бронирования." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","Отсутствует соединение к интернету. Однако вы можете оформить заказ. Мы уведомим Вас об отсутствии какого-либо из заказанных товаров позднее, так как не можем проверить это в данный момент." -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","Данная возможность пока не реализована! Пожалуйста ознакомьтесь с нашей дорожной картой тут: https://github.com/vuestorefront/vue-storefront/issues" -"Thumbnail","Thumbnail" -"Type what you are looking for...","Введите то что Вы ищите..." -"Unhandled error, wrong response format!","Необработанная ошибка, неверный формат ответа!" -"You are logged in!","Вы авторизовались!" -"You need to be logged in to see this page","Вы должны войти в систему, чтобы увидеть эту страницу" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","Вы вышли из учетной записи" diff --git a/core/i18n/resource/i18n/zh-cn.csv b/core/i18n/resource/i18n/zh-cn.csv deleted file mode 100644 index 69c51afb8a..0000000000 --- a/core/i18n/resource/i18n/zh-cn.csv +++ /dev/null @@ -1,73 +0,0 @@ -" is out of stock!"," 没有库存!" -"404 Page Not Found","404 出错了,网页未找到。" -"Account data has successfully been updated","帐户数据已更新成功" -"Add review","添加评论" -"Adding a review ...","Adding a review ..." -"Allow notification about the order","允许订阅有关订单的通知" -"Are you sure you would like to remove this item from the shopping cart?","您确定要从购物车中删除此商品吗?" -"Checkout","注销" -"Compare Products","商品比较" -"Compare products","商品比较" -"Confirm your order","确认订单" -"Error refreshing user token. User is not authorized to access the resource","刷新用户令牌时出错。 用户无权访问该资源" -"Error with response - bad content-type!","服务端响应错误 - 错误的 content-type!" -"Extension developers would like to thank you for placing an order!","拓展开发市场人员感谢您下订单!" -"Field is required","必填字段" -"Grand total","合计" -"Home Page","主页" -"In stock!","库存充足!" -"Internal Application error while refreshing the tokens. Please clear the storage and refresh page.","刷新令牌时出现内部应用程序错误。 请清除浏览器缓存,刷新页面重试." -"Internal validation error. Please check if all required fields are filled in. Please contact us on {email}","内部验证错误。 请检查是否填写了所有必填字段。请通过 {email} 与我们联系" -"Must be greater than 0","必须大于0" -"My Account","我的账户" -"Newsletter preferences have successfully been updated","时事通讯首选项已成功更新" -"No available product variants","没有可用的产品" -"No products synchronized for this category. Please come back while online!","没有为此类别的商品。 请等商品上线后再来看看!" -"No such configuration for the product. Please do choose another combination of attributes.","没有这样的商品配置。 请选择其他属性组合." -"OK","确定" -"Only {maxQuantity} products of this type are available!","Only {maxQuantity} products of this type are available!" -"Out of stock!","缺库存!" -"Payment Information","付款信息" -"Please configure product bundle options and fix the validation errors","请配置商品属性选项并修复验证错误" -"Please configure product custom options and fix the validation errors","请配置产品自定义选项并修复验证错误" -"Please confirm order you placed when you was offline","请确认您离线时的订单" -"Please fix the validation errors","请修复验证错误" -"Please select the field which You like to sort by","请选择您要排序的字段" -"Proceed to checkout","去结算" -"Processing order...","订单处理中..." -"Product has been added to the cart!","已成功将商品加入购物车!" -"Product price is unknown, product cannot be added to the cart!","商品价格未确定,无法添加到购物车!" -"Product quantity has been updated!","產品數量已更新!" -"Product {productName} has been added to the compare!","商品 {productName} 已经添加到比较列表中!" -"Product {productName} has been added to wishlist!","商品 {productName} 已添加到心愿单!" -"Product {productName} has been removed from compare!","商品 {productName} 已从比较列表中移除!" -"Product {productName} has been removed from wishlist!","已从心愿单中移除了商品 {productName} !" -"Registering the account ...","注册帐户 ..." -"Reset password feature does not work while offline!","离线状态时,不能重置密码!" -"Review","评论" -"Reviews","评论列表" -"Shopping cart is empty. Please add some products before entering Checkout","亲,购物车空空如也~, 快去选择一些你喜欢的商品吧." -"Some of the ordered products are not available!","部分订购的商品不可用!" -"Stock check in progress, please wait while available stock quantities are checked","正在确认库存数量,请稍候" -"Subtotal incl. tax","含税小计" -"Summary","摘要", -"The product is out of stock and cannot be added to the cart!","该商品缺货,无法添加到购物车!" -"The product, category or CMS page is not available in Offline mode. Redirecting to Home.","在离线模式下,商品,类别或CMS页面不可用。 请返回主页." -"The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.","系统不确定库存量(不稳定)。 产品已添加到购物车中进行预订." -"There is no Internet connection. You can still place your order. We will notify you if any of ordered products is not available because we cannot check it right now.","网络连接不上 我们现在无法确认库存。但是您仍然可以下订单, 如果订购的产品有问题,我们会通知您。" -"This feature is not implemented yet! Please take a look at https://github.com/vuestorefront/vue-storefront/issues for our Roadmap!","此功能尚未实现! 请查看我们的开发路线图https://github.com/vuestorefront/vue-storefront/issues!" -"Thumbnail","Thumbnail" -"Type what you are looking for...","请输入你想要查找的关键词..." -"Unhandled error, wrong response format!","未处理的错误,错误的响应格式!" -"You are logged in!","您已成功登录!" -"You are to pay for this order upon delivery.","您需要在收货时支付此订单." -"You need to be logged in to see this page","您需要登录才能查看此页面" -"You submitted your review for moderation.","You submitted your review for moderation." -"You're logged out","您已注销" -"email","电子邮箱" -"have as many","有尽可能多的" -"login","登录" -"most you may purchase","你最多可以买" -"not authorized","没有权限" -"password","密码" -"to account","我的账户" diff --git a/core/i18n/resource/states.json b/core/i18n/resource/states.json deleted file mode 100644 index 80c553adc3..0000000000 --- a/core/i18n/resource/states.json +++ /dev/null @@ -1 +0,0 @@ -{"US":[{"name":"Alabama","code":"AL"},{"name":"Alaska","code":"AK"},{"name":"American Samoa","code":"AS"},{"name":"Arizona","code":"AZ"},{"name":"Arkansas","code":"AR"},{"name":"California","code":"CA"},{"name":"Colorado","code":"CO"},{"name":"Connecticut","code":"CT"},{"name":"Delaware","code":"DE"},{"name":"District Of Columbia","code":"DC"},{"name":"Federated States Of Micronesia","code":"FM"},{"name":"Florida","code":"FL"},{"name":"Georgia","code":"GA"},{"name":"Guam","code":"GU"},{"name":"Hawaii","code":"HI"},{"name":"Idaho","code":"ID"},{"name":"Illinois","code":"IL"},{"name":"Indiana","code":"IN"},{"name":"Iowa","code":"IA"},{"name":"Kansas","code":"KS"},{"name":"Kentucky","code":"KY"},{"name":"Louisiana","code":"LA"},{"name":"Maine","code":"ME"},{"name":"Marshall Islands","code":"MH"},{"name":"Maryland","code":"MD"},{"name":"Massachusetts","code":"MA"},{"name":"Michigan","code":"MI"},{"name":"Minnesota","code":"MN"},{"name":"Mississippi","code":"MS"},{"name":"Missouri","code":"MO"},{"name":"Montana","code":"MT"},{"name":"Nebraska","code":"NE"},{"name":"Nevada","code":"NV"},{"name":"New Hampshire","code":"NH"},{"name":"New Jersey","code":"NJ"},{"name":"New Mexico","code":"NM"},{"name":"New York","code":"NY"},{"name":"North Carolina","code":"NC"},{"name":"North Dakota","code":"ND"},{"name":"Northern Mariana Islands","code":"MP"},{"name":"Ohio","code":"OH"},{"name":"Oklahoma","code":"OK"},{"name":"Oregon","code":"OR"},{"name":"Palau","code":"PW"},{"name":"Pennsylvania","code":"PA"},{"name":"Puerto Rico","code":"PR"},{"name":"Rhode Island","code":"RI"},{"name":"South Carolina","code":"SC"},{"name":"South Dakota","code":"SD"},{"name":"Tennessee","code":"TN"},{"name":"Texas","code":"TX"},{"name":"Utah","code":"UT"},{"name":"Vermont","code":"VT"},{"name":"Virgin Islands","code":"VI"},{"name":"Virginia","code":"VA"},{"name":"Washington","code":"WA"},{"name":"West Virginia","code":"WV"},{"name":"Wisconsin","code":"WI"},{"name":"Wyoming","code":"WY"}]} \ No newline at end of file diff --git a/core/i18n/scripts/translation.preprocessor.js b/core/i18n/scripts/translation.preprocessor.js deleted file mode 100644 index 8c99aa60f3..0000000000 --- a/core/i18n/scripts/translation.preprocessor.js +++ /dev/null @@ -1,67 +0,0 @@ -const fs = require('fs') -const path = require('path') -const dsvFormat = require('d3-dsv').dsvFormat -const dsv = dsvFormat(',') -const { currentBuildLocales } = require('../helpers') - -/** - * Converts an Array to an Object - */ -function convertToObject (array) { - const obj = [] - array.forEach((element, index, array) => { - obj[element[0]] = element[1] - }) - return obj -} - -module.exports = function (csvDirectories, config = null) { - const currentLocales = currentBuildLocales() - const fallbackLocale = config.i18n.defaultLocale || 'en-US' - let messages = {} - let languages = [] - - // get messages from CSV files - csvDirectories.forEach(directory => { - fs.readdirSync(directory).forEach(file => { - const fullFileName = path.join(directory, file) - const extName = path.extname(fullFileName) - const baseName = path.posix.basename(file, extName) - - if (currentLocales.indexOf(baseName) !== -1) { - if (extName === '.csv') { - const fileContent = fs.readFileSync(fullFileName, 'utf8') - if (languages.indexOf(baseName) === -1) { - languages.push(baseName) - } - console.debug(`Processing translation file: ${fullFileName}`) - messages[baseName] = Object.assign(messages[baseName] ? messages[baseName] : {}, convertToObject(dsv.parseRows(fileContent))) - } - } - }) - }) - - // create fallback - console.debug(`Writing JSON file fallback: ${fallbackLocale}.json`) - fs.writeFileSync(path.join(__dirname, '../resource/i18n', `${fallbackLocale}.json`), JSON.stringify(messages[fallbackLocale] || {})) - - // bundle all messages in one file - if (config && config.i18n.bundleAllStoreviewLanguages) { - const bundledLanguages = { [fallbackLocale]: messages[fallbackLocale] } // fallback locale - bundledLanguages[config.i18n.defaultLocale] = messages[config.i18n.defaultLocale] // default locale - currentLocales.forEach((locale) => { - bundledLanguages[locale] = messages[locale] - }) - - console.debug(`Writing JSON file multistoreLanguages`) - fs.writeFileSync(path.join(__dirname, '../resource/i18n', `multistoreLanguages.json`), JSON.stringify(bundledLanguages)) - } else { - currentLocales.forEach((language) => { - if (language === fallbackLocale) return // it's already loaded - const filePath = path.join(__dirname, '../resource/i18n', `${language}.json`) - console.debug(`Writing JSON file: ${language}.json`) - fs.writeFileSync(filePath, JSON.stringify(messages[language])) - }) - fs.writeFileSync(path.join(__dirname, '../resource/i18n', `multistoreLanguages.json`), JSON.stringify({})) // fix for webpack compilation error in case of `bundleAllStoreviewLanguages` = `false` (#3188) - } -} diff --git a/core/lib/async-data-loader.ts b/core/lib/async-data-loader.ts deleted file mode 100644 index 0fbfad7397..0000000000 --- a/core/lib/async-data-loader.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** -* @deprecated This module has been created before Vue.js v2.6.0. From 2.6.x on please do preferably use the `serverPrefetch` hook to sync SSR Vuex data. More info: https://ssr.vuejs.org/api/#serverprefetch -* @description Please note: This module can be used not only for `asyncData` extensibility but also for simplyfying the data loaders in Vuex actions or other components - basicaly everywhere where we must `wait` for some async operations to complete. -*/ -import { isServer } from '@vue-storefront/core/helpers' -import { Logger } from '@vue-storefront/core/lib/logger' - -const DEFAULT_ACTION_CATEGORY = 'asyncData' -// Data loader queues all the data fetching operations and runs them at once - to be usedf for example in the `asyncData()` functions -export interface AsyncDataLoaderActionContext { - category?: string, - route: any, - store: any, - context: any -} - -// Data loader queues all the data fetching operations and runs them at once - to be usedf for example in the `asyncData()` functions -export interface AsyncDataLoaderAction { - execute: any, // this function must return a Promise - category?: string, - name?: string, - executedAt?: Date, - scheduledAt?: Date -} - -/** AsyncDataLoader helper for queueing the data fetching operations. The main purpose is to decentralize the `asyncData()` SSR method */ -export const AsyncDataLoader = { - - queue: new Array(), - - push: function (action: AsyncDataLoaderAction) { - if (!action.category) action.category = DEFAULT_ACTION_CATEGORY - action.scheduledAt = new Date() - this.queue.push(action) - }, - flush: function (actionContext: AsyncDataLoaderActionContext) { - if (!actionContext.category) actionContext.category = DEFAULT_ACTION_CATEGORY - const actionsToExecute = this.queue.filter(ac => (!ac.category || !actionContext.category) || (ac.category === actionContext.category && (!ac.executedAt))).map(ac => { - ac.executedAt = new Date() - return ac.execute(actionContext) // function must return Promise - }) - if (actionsToExecute.length > 0) { - Logger.info('Executing data loader actions(' + actionsToExecute.length + ')', 'dataloader')() - } - return Promise.all(actionsToExecute).then(results => { - return results - }) - } -} diff --git a/core/lib/hooks.ts b/core/lib/hooks.ts deleted file mode 100644 index 9ac954cd42..0000000000 --- a/core/lib/hooks.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - Listener hook just fires functions passed to hook function when executor is invoked. - e. g. We want to listen for onAppInit event in various places of the application. - Functions passed to this hook will be invoked only when executor function is executed. - Usually we want to use hook in app/modules and executor in core. - @return hook: a hook function to use in modules - @return executor: a function that will run all the collected hooks - */ -function createListenerHook () { - const functionsToRun: ((arg: T) => void)[] = [] - - function hook (fn: (arg?: T) => void) { - functionsToRun.push(fn) - } - - function executor (args: T = null): void { - functionsToRun.forEach(fn => fn(args)) - } - - return { - hook, - executor - } -} - -/** - Mutators work like listeners except they can modify passed value in hooks. - e.g we can apply the hook mutator to object order that is returned before placing order - now you can access and modify this value from hook returned by this function - @return hook: a hook function to use in modules - @return executor: a function that will apply all hooks on a given value - */ -function createMutatorHook () { - const mutators: ((arg: T) => R)[] = [] - - function hook (mutator: (arg: T) => R) { - mutators.push(mutator) - } - - function executor (rawOutput: T): T | R { - if (mutators.length > 0) { - let modifiedOutput: R = null - mutators.forEach(fn => { - modifiedOutput = fn(rawOutput) - }) - return modifiedOutput - } else { - return rawOutput - } - } - - return { - hook, - executor - } -} - -export { - createListenerHook, - createMutatorHook -} - -// TODO: Hooks for Client entry, replaceState (can be part of client entry), shopping cart loaded, user logged diff --git a/core/lib/logger.ts b/core/lib/logger.ts deleted file mode 100644 index 9c0e3e931c..0000000000 --- a/core/lib/logger.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers' -import { coreHooksExecutors } from '@vue-storefront/core/hooks' -import buildTimeConfig from 'config' -const bgColorStyle = (color) => `color: white; background: ${color}; padding: 4px; font-weight: bold; font-size: 0.8em'` - -/** VS message logger. By default works only on dev mode */ -class Logger { - /** - * Logger verbosity level - */ - public verbosityLevel: string; - - /** - * Is production environment - */ - public isProduction: boolean; - - /** - * Force to show error on production - */ - public showErrorOnProduction: boolean; - - /** - * Logger constructor - * - * @param verbosityLevel - * @param showErrorOnProduction - */ - public constructor (verbosityLevel: string = 'display-everything', showErrorOnProduction: boolean = false) { - this.verbosityLevel = verbosityLevel - this.showErrorOnProduction = showErrorOnProduction - this.isProduction = process.env.NODE_ENV === 'production' - } - - /** - * Convert message to string - as it may be object, array either primitive - * @param payload - */ - public convertToString (payload: any) { - if (typeof payload === 'string' || typeof payload === 'boolean' || typeof payload === 'number') return payload - if (payload && payload.message) return payload.message - return JSON.stringify(payload) - } - - /** - * Check if method can print into console - * - * @param {string} method - */ - public canPrint (method: string) { - const allowedMethods = [] - - if (this.verbosityLevel === 'display-everything' && this.isProduction === false) { - allowedMethods.push(...['info', 'warn', 'error', 'debug']) - } else if (this.verbosityLevel === 'only-errors' && (this.isProduction === false || this.showErrorOnProduction === true)) { - allowedMethods.push('error') - } - - return allowedMethods.indexOf(method) !== -1 - } - - /** - * Inform about debug events happening in the app - * Don't forget to invoke created function after passing arguments to keep context - * `Logger.debug(...args)()` - * @param message - * @param tag short tag specifying area where message was spawned (eg. cart, sync, module) - * @param context meaningful data related to this message - */ - public debug (message: any, tag: string = null, context: any = null): () => void { - if (!this.canPrint('debug')) { - return () => {} - } - - let noDefaultOutput - ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'debug', message, tag, context })) - if (noDefaultOutput === true) { - return () => {} - } - - if (isServer) { - return console.debug.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context) - } - - if (tag) { - return console.debug.bind(window.console, '%cVSF%c %c' + tag + '%c ' + this.convertToString(message), bgColorStyle('grey'), 'color: inherit', bgColorStyle('gray'), 'font-weight: normal', context) - } else { - return console.debug.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('grey'), 'font-weight: normal', context) - } - } - - /** - * Inform about log events happening in the app - * Don't forget to invoke created function after passing arguments to keep context - * `Logger.log(...args)()` - * @param message - * @param tag short tag specifying area where message was spawned (eg. cart, sync, module) - * @param context meaningful data related to this message - */ - public log (message: any, tag: string = null, context: any = null): () => void { - return this.info(message, tag, context) - } - - /** - * Inform about succesful events happening in the app - * Don't forget to invoke created function after passing arguments to keep context - * `Logger.info(...args)()` - * @param message - * @param tag short tag specifying area where message was spawned (eg. cart, sync, module) - * @param context meaningful data related to this message - */ - public info (message: any, tag: string = null, context: any = null): () => void { - if (!this.canPrint('info')) { - return () => {} - } - - let noDefaultOutput - ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'info', message, tag, context })) - if (noDefaultOutput === true) { - return () => {} - } - - if (isServer) { - return console.log.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context) - } - - if (tag) { - return console.log.bind(window.console, '%cVSF%c %c' + tag + '%c ' + this.convertToString(message), bgColorStyle('green'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context) - } else { - return console.log.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('green'), 'font-weight: bold', context) - } - } - - /** - * Inform about potential problems that may be a cause of app break - * Don't forget to invoke created function after passing arguments to keep context - * `Logger.warn(...args)()` - * @param message - * @param tag short tag specifying area where message was spawned (eg. cart, sync, module) - * @param context meaningful data related to this message - */ - public warn (message: any, tag: string = null, context: any = null): () => void { - if (!this.canPrint('warn')) { - return () => {} - } - - let noDefaultOutput - ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'warn', message, tag, context })) - if (noDefaultOutput === true) { - return () => {} - } - - if (isServer) { - return console.warn.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context) - } - - if (tag) { - return console.warn.bind(window.console, '%cVSF%c %c' + tag + '%c ' + this.convertToString(message), bgColorStyle('orange'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context) - } else { - return console.warn.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('orange'), 'font-weight: bold', context) - } - } - - /** - * Inform about errors that will break the app - * Don't forget to invoke created function after passing arguments to keep context - * `Logger.error(...args)()` - * @param message - * @param tag short tag specifying area where message was spawned (eg. cart, sync, module) - * @param context meaningful data related to this message - */ - public error (message: any, tag: string = null, context: any = null): () => void { - let noDefaultOutput - ({ message, tag, context, noDefaultOutput } = coreHooksExecutors.beforeLogRendered({ type: 'error', message, tag, context })) - if (noDefaultOutput === true) { - return () => {} - } - - if (isServer) { // always show errors in SSR - return console.error.bind(console, (tag ? `[${tag}] ` : '') + this.convertToString(message), context) - } - - if (this.canPrint('error')) { - if (tag) { - return console.error.bind(window.console, '%cVSF%c %c' + tag + '%c ' + this.convertToString(message), bgColorStyle('red'), 'color: inherit', bgColorStyle('gray'), 'font-weight: bold', context) - } else { - return console.error.bind(window.console, '%cVSF%c ' + this.convertToString(message), bgColorStyle('red'), 'font-weight: bold', context) - } - } - - return () => {} - } -} - -const logger = new Logger( - buildTimeConfig.console.verbosityLevel, - buildTimeConfig.console.showErrorOnProduction -) - -export { logger as Logger } diff --git a/core/lib/module/helpers.ts b/core/lib/module/helpers.ts deleted file mode 100644 index e25d7aeeb4..0000000000 --- a/core/lib/module/helpers.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { VueStorefrontModuleConfig } from './types' -import { Module } from 'vuex' -import merge from 'lodash-es/merge' -import some from 'lodash-es/some' -import find from 'lodash-es/find' - -function doesStoreAlreadyExists (key: string, registeredModules: VueStorefrontModuleConfig[]): boolean { - registeredModules.forEach(m => { - if (m.store) { - if (m.store.modules.some(m => m.key === key)) return true - } - }) - return false -} - -function mergeStores ( - originalStore: { modules?: { key: string, module: Module }[], plugin?: Function }, - extendedStore: { modules?: { key: string, module: Module }[], plugin?: Function } -) { - let mergedArray = [] - originalStore.modules.map(item => { - mergedArray.push(merge(item, find(extendedStore.modules, { 'key': item.key }))); - }) - extendedStore.modules.map(extendedStoreItem => { - if (some(originalStore.modules, null, { 'key': extendedStoreItem.key }) === false) { - mergedArray.push(extendedStoreItem) - } - }) - return mergedArray -} - -export { - doesStoreAlreadyExists, - mergeStores -} diff --git a/core/lib/module/index.ts b/core/lib/module/index.ts deleted file mode 100644 index 9ae5951e68..0000000000 --- a/core/lib/module/index.ts +++ /dev/null @@ -1,136 +0,0 @@ -// @deprecated from 2.0 -import { Module } from 'vuex' -import { RouteConfig, NavigationGuard } from 'vue-router' -import Vue from 'vue' -import merge from 'lodash-es/merge' -import rootStore from '@vue-storefront/core/store' -import { Logger } from '@vue-storefront/core/lib/logger' -import { setupMultistoreRoutes } from '../multistore' -import { router } from '@vue-storefront/core/app' -import { isServer } from '@vue-storefront/core/helpers' -import { VSF, VueStorefrontModuleConfig } from './types' -import { doesStoreAlreadyExists, mergeStores } from './helpers' -import config from 'config' - -const moduleExtendings: VueStorefrontModuleConfig[] = [] -const registeredModules: VueStorefrontModuleConfig[] = [] - -function registerModules (modules: VueStorefrontModule[], context): void { - modules.forEach(m => m.register()) - Logger.info('VS Modules registration finished.', 'module', { - succesfulyRegistered: registeredModules.length + ' / ' + modules.length, - registrationOrder: registeredModules - } - )() -} - -function extendModule (moduleConfig: VueStorefrontModuleConfig) { - moduleExtendings.push(moduleConfig) -} - -class VueStorefrontModule { - private _isRegistered = false - private _c: VueStorefrontModuleConfig - public constructor (_c: VueStorefrontModuleConfig) { - this._c = _c - } - - private static _extendStore (storeInstance: any, modules: { key: string, module: Module }[], plugin: any): void { - if (modules) modules.forEach(store => storeInstance.registerModule(store.key, store.module)) - if (plugin) storeInstance.subscribe(plugin) - } - - private static _extendRouter (routerInstance, routes?: RouteConfig[], beforeEach?: NavigationGuard, afterEach?: NavigationGuard): void { - if (routes) { - setupMultistoreRoutes(config, routerInstance, routes) - } - if (beforeEach) routerInstance.beforeEach(beforeEach) - if (afterEach) routerInstance.afterEach(afterEach) - } - - private _extendModule (extendedConfig: VueStorefrontModuleConfig): void { - const mergedStore = { modules: [], plugin: null } - const key = this._c.key - const originalStore = this._c.store - const extendedStore = extendedConfig.store - delete this._c.store - delete extendedConfig.store - this._c = merge(this._c, extendedConfig) - mergedStore.modules = mergeStores(originalStore, extendedStore) - mergedStore.plugin = extendedStore.plugin || originalStore.plugin || null - this._c.store = mergedStore - Logger.info('Module "' + key + '" has been succesfully extended.', 'module')() - } - - public get config () { - return this._c - } - - /** Use only if you want to explicitly modify module config. Otherwise it's much easier to use `extendModule` */ - public set config (config) { - this._c = config - } - - public register (): VueStorefrontModuleConfig | void { - if (!this._isRegistered) { - Logger.warn('The module you are registering is using outdated API that will soon be depreciated. Please check https://docs.vuestorefront.io to learn more.', 'module', this._c.key)() - let areStoresUnique = true - const VSF: VSF = { - Vue, - config: config, - store: rootStore, - isServer - } - - moduleExtendings.forEach(extending => { - if (extending.key === this._c.key) this._extendModule(extending) - }) - - if (this._c.store) { - this._c.store.modules.forEach(store => { - if (doesStoreAlreadyExists(store.key, registeredModules)) { - Logger.warn('Error during "' + this._c.key + '" module registration! Store with key "' + store.key + '" already exists!', 'module')() - areStoresUnique = false - } - }) - } - - if (areStoresUnique) { - if (this._c.beforeRegistration) { - if (this._c.beforeRegistration.length === 1) { - this._c.beforeRegistration(VSF) - } else { - Logger.warn('You are using outdated signature for beforeRegistration hook that soon will be deprecated and module will stop working properly. Please update to the new signature that can be found in our docs: https://docs.vuestorefront.io/guide/modules/introduction.html#beforeregistration', 'module', this._c.key)() - this._c.beforeRegistration(Vue, config, rootStore, isServer) - } - } - if (this._c.store) VueStorefrontModule._extendStore(rootStore, this._c.store.modules, this._c.store.plugin) - if (this._c.router) VueStorefrontModule._extendRouter(router, this._c.router.routes, this._c.router.beforeEach, this._c.router.afterEach) - registeredModules.push(this._c) - this._isRegistered = true - if (this._c.afterRegistration) { - if (this._c.afterRegistration.length === 1) { - this._c.afterRegistration(VSF) - } else { - Logger.warn('You are using outdated signature for afterRegistration hook that soon will be deprecated and module will stop working properly. Please update to the new signature that can be found in our docs: https://docs.vuestorefront.io/guide/modules/introduction.html#afterregistration', 'module', this._c.key)() - this._c.afterRegistration(Vue, config, rootStore, isServer) - } - } - return this._c - } - } - } -} - -function createModule (config: VueStorefrontModuleConfig): VueStorefrontModule { - return new VueStorefrontModule(config) -} - -export { - VSF, - VueStorefrontModuleConfig, - extendModule, - VueStorefrontModule, - registerModules, - createModule -} diff --git a/core/lib/module/types.ts b/core/lib/module/types.ts deleted file mode 100644 index 085b12dc73..0000000000 --- a/core/lib/module/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Module, Store } from 'vuex' -import { RouteConfig, NavigationGuard } from 'vue-router' -import { VueConstructor } from 'vue' -import RootState from '@vue-storefront/core/types/RootState' - -export interface VSF { - Vue?: VueConstructor, - config?: Record, - store?: Store, - isServer?: boolean -} - -export interface VueStorefrontModuleConfig { - key: string, - store?: { modules?: { key: string, module: Module }[], plugin?: Function }, - router?: { routes?: RouteConfig[], beforeEach?: NavigationGuard, afterEach?: NavigationGuard }, - beforeRegistration?: (VSF: VSF | VueConstructor, config?: Record, store?: Store, isServer?: boolean) => void, - afterRegistration?: (VSF: VSF | VueConstructor, config?: Record, store?: Store, isServer?: boolean) => void -} diff --git a/core/lib/modules.ts b/core/lib/modules.ts deleted file mode 100644 index e5b5e8001a..0000000000 --- a/core/lib/modules.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Store } from 'vuex' -import VueRouter from 'vue-router' -import Vue from 'vue' -import RootState from '@vue-storefront/core/types/RootState' - -export type StorefrontModule = ( - options: { - app: Vue, - store: Store, - router: VueRouter, - moduleConfig: any, - appConfig: any - } -) => void - -let refs: any = {} -let registeredModules: StorefrontModule[] = [] - -function injectReferences (app: any, store: Store, router: VueRouter, config: any): void { - refs.app = app - refs.store = store - refs.router = router - refs.config = config -} - -function registerModule (module: StorefrontModule, config?: any) { - if (!registeredModules.includes(module)) { - module({ - app: refs.app, - store: refs.store, - router: refs.router, - appConfig: refs.config, - moduleConfig: config - }) - registeredModules.push(module) - } -} - -function isModuleRegistered (name: string): boolean { - return registeredModules.some(m => m.name === name) -} - -export { refs, injectReferences, registerModule, isModuleRegistered } diff --git a/core/lib/multistore.ts b/core/lib/multistore.ts deleted file mode 100644 index aacca65c2b..0000000000 --- a/core/lib/multistore.ts +++ /dev/null @@ -1,239 +0,0 @@ -import rootStore from '../store' -import { loadLanguageAsync } from '@vue-storefront/i18n' -import { initializeSyncTaskStorage } from './sync/task' -import { Logger } from '@vue-storefront/core/lib/logger' -import Vue from 'vue' -import queryString from 'query-string' -import merge from 'lodash-es/merge' -import VueRouter, { RouteConfig, RawLocation } from 'vue-router' -import config from 'config' -import { coreHooksExecutors } from '@vue-storefront/core/hooks' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { LocalizedRoute, StoreView } from './types' -import storeCodeFromRoute from './storeCodeFromRoute' -import cloneDeep from 'lodash-es/cloneDeep' -import get from 'lodash-es/get' -import { isServer } from '@vue-storefront/core/helpers' - -function getExtendedStoreviewConfig (storeView: StoreView): StoreView { - if (storeView.extend) { - const originalParent = storeView.extend - - if (!config.storeViews[originalParent]) { - Logger.error(`Storeview "${storeView.extend}" doesn't exist!`)() - } else { - storeView = merge( - {}, - getExtendedStoreviewConfig(config.storeViews[originalParent]), - storeView - ) - } - } - - return storeView -} - -/** - * Returns base storeView object that can be created without storeCode - */ -function buildBaseStoreView (): StoreView { - return cloneDeep({ - tax: config.tax, - i18n: config.i18n, - elasticsearch: config.elasticsearch, - storeCode: null, - storeId: config.defaultStoreCode && config.defaultStoreCode !== '' ? config.storeViews[config.defaultStoreCode].storeId : 1, - seo: config.seo - }) -} - -export function currentStoreView (): StoreView { - const serverStoreView = get(global, 'process.storeView', undefined) - const clientStoreView = get(rootStore, 'state.storeView', undefined) - return (isServer ? serverStoreView : clientStoreView) || buildBaseStoreView() -} - -export async function prepareStoreView (storeCode: string): Promise { - let storeView: StoreView = buildBaseStoreView() // current, default store - if (config.storeViews.multistore === true) { - storeView.storeCode = storeCode || config.defaultStoreCode || '' - } else { - storeView.storeCode = storeCode || '' - } - - const storeViewHasChanged = !rootStore.state.storeView || rootStore.state.storeView.storeCode !== storeCode - - if (storeView.storeCode && config.storeViews.multistore === true && config.storeViews[storeView.storeCode]) { - storeView = merge(storeView, getExtendedStoreviewConfig(config.storeViews[storeView.storeCode])) - } - - if (rootStore.state.user) { - rootStore.state.user.current_storecode = storeView.storeCode - } - - if (storeViewHasChanged) { - storeView = coreHooksExecutors.beforeStoreViewChanged(storeView) - rootStore.state.storeView = storeView - - if (global && isServer) { - (global.process as any).storeView = storeView - } - - await loadLanguageAsync(storeView.i18n.defaultLocale) - } - if (storeViewHasChanged || StorageManager.currentStoreCode !== storeCode) { - initializeSyncTaskStorage() - StorageManager.currentStoreCode = storeView.storeCode - } - - coreHooksExecutors.afterStoreViewChanged(storeView) - - return storeView -} - -export function removeStoreCodeFromRoute (matchedRouteOrUrl: LocalizedRoute | string): LocalizedRoute | string { - const storeCodeInRoute = storeCodeFromRoute(matchedRouteOrUrl) - if (storeCodeInRoute !== '') { - let urlPath = typeof matchedRouteOrUrl === 'object' ? matchedRouteOrUrl.path : matchedRouteOrUrl - return urlPath.replace(storeCodeInRoute + '/', '') - } else { - return matchedRouteOrUrl - } -} - -function removeURLQueryParameter (url, parameter) { - // prefer to use l.search if you have a location/link object - var urlparts = url.split('?'); - if (urlparts.length >= 2) { - var prefix = encodeURIComponent(parameter) + '='; - var pars = urlparts[1].split(/[&;]/g); - - // reverse iteration as may be destructive - for (var i = pars.length; i-- > 0;) { - // idiom for string.startsWith - if (pars[i].lastIndexOf(prefix, 0) !== -1) { - pars.splice(i, 1); - } - } - - return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : ''); - } - return url; -} - -export function adjustMultistoreApiUrl (url: string): string { - const { storeCode } = currentStoreView() - if (storeCode) { - url = removeURLQueryParameter(url, 'storeCode') - const urlSep = (url.indexOf('?') > 0) ? '&' : '?' - url += `${urlSep}storeCode=${storeCode}` - } - return url -} - -export function localizedDispatcherRoute (routeObj: LocalizedRoute | string, storeCode?: string): LocalizedRoute | string { - const { storeCode: currentStoreCode, appendStoreCode } = currentStoreView() - if (!storeCode || !config.storeViews[storeCode]) { - storeCode = currentStoreCode - } - const appendStoreCodePrefix = storeCode && appendStoreCode - - if (typeof routeObj === 'string') { - if (routeObj[0] !== '/') routeObj = `/${routeObj}` - return appendStoreCodePrefix ? `/${storeCode}${routeObj}` : routeObj - } - - if (routeObj) { - if ((routeObj as LocalizedRoute).fullPath && !(routeObj as LocalizedRoute).path) { // support both path and fullPath - routeObj['path'] = (routeObj as LocalizedRoute).fullPath - } - - if (routeObj.path) { // case of using dispatcher - const routeCodePrefix = appendStoreCodePrefix ? `/${storeCode}` : '' - const qrStr = queryString.stringify(routeObj.params); - - const normalizedPath = routeObj.path[0] !== '/' ? `/${routeObj.path}` : routeObj.path - return `${routeCodePrefix}${normalizedPath}${qrStr ? `?${qrStr}` : ''}` - } - } - - return routeObj -} - -export function localizedDispatcherRouteName (routeName: string, storeCode: string, appendStoreCode: boolean = false): string { - if (appendStoreCode) { - return `${storeCode}-${routeName}` - } - return routeName -} - -/** - * Returns route path with proper language prefix - * @param path - route path - * @param storeCode - language prefix specified in global config - */ -export function localizedRoutePath (path: string, storeCode: string): string { - const _path = path.startsWith('/') ? path.slice(1) : path - - return `/${storeCode}/${_path}` -} - -/** - * Returns transformed route config with language - * @param route - route config object - * @param storeCode - language prefix specified in global config - * @param isChildRoute - determines if route config is for child route - */ -export function localizedRouteConfig (route: RouteConfig, storeCode: string, isChildRoute: boolean = false): RouteConfig { - // note: we need shallow copy to prevent modifications in provided route object - const _route = { ...route } - - if (_route.name && storeCode) { - _route.name = `${storeCode}-${_route.name}` - } - - if (_route.path && !isChildRoute) { - _route.path = localizedRoutePath(_route.path, storeCode) - } - - if (_route.children) { - _route.children = _route.children.map(childRoute => localizedRouteConfig(childRoute, storeCode, true)) - } - - return _route -} - -export function localizedRoute (routeObj: LocalizedRoute | string | RouteConfig | RawLocation, storeCode: string = null): any { - if (!storeCode) { - storeCode = currentStoreView().storeCode - } - if (!routeObj) { - return routeObj - } - - if ((typeof routeObj === 'object') && (routeObj as LocalizedRoute)) { - if ((routeObj as LocalizedRoute).fullPath && !(routeObj as LocalizedRoute).path) { // support both path and fullPath - routeObj['path'] = (routeObj as LocalizedRoute).fullPath - } - } - - if (storeCode && config.defaultStoreCode !== storeCode && config.storeViews[storeCode] && config.storeViews[storeCode].appendStoreCode) { - if (typeof routeObj !== 'object') { - return localizedRoutePath(routeObj, storeCode) - } - return localizedRouteConfig(routeObj as RouteConfig, storeCode) - } - - return routeObj -} - -export function setupMultistoreRoutes (config, router: VueRouter, routes: RouteConfig[], priority: number = 0): void { - const allRoutes: RouteConfig[] = [] - const { storeCode, appendStoreCode } = currentStoreView() - if (storeCode && appendStoreCode) { - allRoutes.push(...routes.map(route => localizedRouteConfig(route, storeCode))) - } else { - allRoutes.push(...routes) - } - router.addRoutes(allRoutes, true, priority) -} diff --git a/core/lib/passive-listeners.js b/core/lib/passive-listeners.js deleted file mode 100644 index 890eff2c31..0000000000 --- a/core/lib/passive-listeners.js +++ /dev/null @@ -1,59 +0,0 @@ -const eventListenerOptionsSupported = () => { - let supported = false - - try { - const opts = Object.defineProperty({}, 'passive', { - get () { - supported = true - } - }) - - window.addEventListener('test', null, opts) - window.removeEventListener('test', null, opts) - } catch (e) {} - - return supported -} - -const defaultOptions = { - passive: true, - capture: false -} -const supportedPassiveTypes = [ - 'scroll', 'wheel', - 'touchstart', 'touchmove', 'touchenter', 'touchend', 'touchleave', - 'mouseout', 'mouseleave', 'mouseup', 'mousedown', 'mousemove', 'mouseenter', 'mousewheel', 'mouseover' -] -const getDefaultPassiveOption = (passive, eventName) => { - if (passive !== undefined) return passive - - return supportedPassiveTypes.indexOf(eventName) === -1 ? false : defaultOptions.passive -} - -const getWritableOptions = (options) => { - const passiveDescriptor = Object.getOwnPropertyDescriptor(options, 'passive') - - return passiveDescriptor && passiveDescriptor.writable !== true && passiveDescriptor.set === undefined ? Object.assign({}, options) : options -} - -const overwriteAddEvent = (superMethod) => { - EventTarget.prototype.addEventListener = function (type, listener, options) { - const usesListenerOptions = typeof options === 'object' && options !== null - const useCapture = usesListenerOptions ? options.capture : options - - options = usesListenerOptions ? getWritableOptions(options) : {} - options.passive = getDefaultPassiveOption(options.passive, type) - options.capture = useCapture === undefined ? defaultOptions.capture : useCapture - - superMethod.call(this, type, listener, options) - } - - EventTarget.prototype.addEventListener._original = superMethod -} - -const supportsPassive = eventListenerOptionsSupported() - -if (supportsPassive) { - const addEvent = EventTarget.prototype.addEventListener - overwriteAddEvent(addEvent) -} diff --git a/core/lib/router-manager.ts b/core/lib/router-manager.ts deleted file mode 100644 index e20ad54c87..0000000000 --- a/core/lib/router-manager.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { baseRouter } from '@vue-storefront/core/app' -import { RouteConfig } from 'vue-router' - -const RouterManager = { - _registeredRoutes: new Array(), - _routeQueue: new Array(), - _routeQueueFlushed: false, - _routeLock: null, - _routeDispatched: false, - _callbacks: [], - addRoutes: function (routes: RouteConfig[], useRouteQueue: boolean = false, priority: number = 0): void { - if (useRouteQueue && !this._routeQueueFlushed) { - this._routeQueue.push(...routes.map(route => { return { route: route, priority: priority } })) - } else { - const uniqueRoutes = routes.filter((route) => { - return this._registeredRoutes.findIndex(registeredRoute => registeredRoute.route.name === route.name && registeredRoute.route.path === route.path) < 0 - }) - if (uniqueRoutes.length > 0) { - this._registeredRoutes.push(...uniqueRoutes.map(route => { return { route: route, priority: priority } })) - baseRouter.addRoutes(uniqueRoutes) - } - } - }, - flushRouteQueue: function (): void { - if (!this._routeQueueFlushed) { - this.addRoutesByPriority(this._routeQueue) - this._routeQueueFlushed = true - this._routeQueue = [] - } - }, - addRoutesByPriority: function (routesData) { - const routesToAdd = [] - for (const routeData of routesData) { - let exisitingIndex = routesToAdd.findIndex(r => r.route.name === routeData.route.name && r.route.path === routeData.route.path) - if ((exisitingIndex >= 0) && (routesToAdd[exisitingIndex].priority < routeData.priority)) { // same priority doesn't override exisiting - routesToAdd.splice(exisitingIndex, 1) - exisitingIndex = -1 - } - if (exisitingIndex < 0) { - routesToAdd.push(routeData) - } - } - this._registeredRoutes.push(...routesToAdd) - baseRouter.addRoutes(routesToAdd.map(r => r.route)) - }, - isRouteAdded: function (addedRoutes: any[], route: RouteConfig) { - return addedRoutes.findIndex((addedRoute) => addedRoute.route.name === route.name && addedRoute.route.path === route.path) >= 0 - }, - addDispatchCallback: function (callback: Function) { - this._callbacks.push(callback) - }, - findByName: function (name: string): RouteConfig { - return this.findByProperty('name', name) - }, - findByPath: function (path: string): RouteConfig { - return this.findByProperty('path', path) - }, - findByProperty: function (property: string, value: string): RouteConfig { - const registeredRoute = this._registeredRoutes.find(r => r.route[property] === value) - if (registeredRoute) return registeredRoute.route - if (this._routeQueueFlushed) return null - const queuedRoute = this._routeQueue.find(queueItem => queueItem.route[property] === value) - return queuedRoute ? queuedRoute.route : null - }, - lockRoute: function () { - let resolver - this._routeLock = { - lockPromise: new Promise(resolve => { resolver = resolve }), - resolver - } - }, - isRouteProcessing: function () { - return !!this._routeLock - }, - isRouteDispatched: function () { - return !!this._routeDispatched - }, - getRouteLockPromise: function () { - if (this._routeLock) return this._routeLock.lockPromise - return Promise.resolve() - }, - unlockRoute: function () { - if (this._routeLock) { - this._routeLock.resolver() - this._routeLock = null - } - this._routeDispatched = true - this._callbacks.forEach(callback => { - callback() - }); - } -} - -export { RouterManager } diff --git a/core/lib/search.ts b/core/lib/search.ts deleted file mode 100644 index 6dd5800762..0000000000 --- a/core/lib/search.ts +++ /dev/null @@ -1,127 +0,0 @@ -import Vue from 'vue' - -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import { sha3_224 } from 'js-sha3' -import rootStore from '@vue-storefront/core/store' -import { getSearchAdapter } from './search/adapter/searchAdapterFactory' -import { SearchRequest } from '@vue-storefront/core/types/search/SearchRequest' -import { SearchResponse } from '@vue-storefront/core/types/search/SearchResponse' -import { Logger } from '@vue-storefront/core/lib/logger' -import config from 'config' -import { isServer } from '@vue-storefront/core/helpers' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -// TODO - use one from helpers instead -export function isOnline (): boolean { - if (typeof navigator !== 'undefined') { - return navigator.onLine - } else { - return true // SSR - } -} - -/** - * Search ElasticSearch catalog of products using simple text query - * Use bodybuilder to build the query, aggregations etc: http://bodybuilder.js.org/ - * @param {Object} query is the object of searchQuery class - * @param {Int} start start index - * @param {Int} size page size - * @return {Promise} - */ -export const quickSearchByQuery = async ({ query = {}, start = 0, size = 50, entityType = 'product', sort = '', storeCode = null, excludeFields = null, includeFields = null } = {}): Promise => { - const searchAdapter = await getSearchAdapter() - if (size <= 0) size = 50 - if (start < 0) start = 0 - - return new Promise(async (resolve, reject) => { - const storeView = currentStoreView() - const Request: SearchRequest = { - store: storeCode || storeView.storeCode, // TODO: add grouped product and bundled product support - type: entityType, - searchQuery: query, - groupToken: null, - groupId: null, - size: size, - from: start, - sort: sort - } - - if (excludeFields) Request._sourceExclude = excludeFields - if (includeFields) Request._sourceInclude = includeFields - - if (config.usePriceTiers && (entityType === 'product') && rootStore.state.user.groupId) { - Request.groupId = rootStore.state.user.groupId - } - - const cache = StorageManager.get('elasticCache') // switch to appcache? - let servedFromCache = false - const cacheKey = sha3_224(JSON.stringify(Request)) - const benchmarkTime = new Date() - - try { - const res = await cache.getItem(cacheKey) - if (res !== null) { - res.cache = true - res.noresults = false - res.offline = !isOnline() // TODO: refactor it to checking ES heartbit - Logger.debug('Result from cache for ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))() - - servedFromCache = true - resolve(res) - return - } - } catch (err) { - Logger.error('Cannot read cache for ' + cacheKey + ', ' + err)() - } - - /* use only for cache */ - if (Request.groupId) { - delete Request.groupId - } - - if (rootStore.state.user.groupToken) { - Request.groupToken = rootStore.state.user.groupToken - } - - if (!searchAdapter.entities[Request.type]) { - throw new Error('No entity type registered for ' + Request.type) - } - - searchAdapter.search(Request).then((resp) => { // we're always trying to populate cache - when online - const res = searchAdapter.entities[Request.type].resultProcessor(resp, start, size) - - if (res) { // otherwise it can be just a offline mode - cache.setItem(cacheKey, res, null, config.elasticsearch.disablePersistentQueriesCache).catch((err) => { Logger.error('Cannot store cache for ' + cacheKey + ', ' + err)() }) - if (!servedFromCache) { // if navigator onLine == false means ES is unreachable and probably this will return false; sometimes returned false faster than indexedDb cache returns result ... - Logger.debug('Result from ES for ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))() - res.cache = false - res.noresults = false - res.offline = false - resolve(res) - } - } - }).catch(err => { - if (!servedFromCache) { - if (!isServer) { - Logger.debug('No results and offline ' + cacheKey + ' (' + entityType + '), ms=' + (new Date().getTime() - benchmarkTime.getTime()))() - const res = { - items: [], - total: 0, - start: 0, - perPage: 0, - aggregations: {}, - offline: true, - cache: true, - noresults: true, - suggestions: {} - } - resolve(res) - } else { - Logger.error('Can not connect the vue-storefront-api / ElasticSearch instance!', 'search', err)() - reject(err) - } - } - reject(err) - }) - }) -} diff --git a/core/lib/search/adapter/SeachAdapterInterface.ts b/core/lib/search/adapter/SeachAdapterInterface.ts deleted file mode 100644 index 8178f558b5..0000000000 --- a/core/lib/search/adapter/SeachAdapterInterface.ts +++ /dev/null @@ -1,4 +0,0 @@ -interface SearchAdapterInterface { - search(Request: any): void, - registerEntityType(entityType: string, options: any): void -} diff --git a/core/lib/search/adapter/api-search-query/searchAdapter.ts b/core/lib/search/adapter/api-search-query/searchAdapter.ts deleted file mode 100644 index 5357c2571d..0000000000 --- a/core/lib/search/adapter/api-search-query/searchAdapter.ts +++ /dev/null @@ -1,160 +0,0 @@ -import map from 'lodash-es/map' -import fetch from 'isomorphic-fetch' -import { slugify, processURLAddress } from '@vue-storefront/core/helpers' -import queryString from 'query-string' -import { currentStoreView, prepareStoreView } from '@vue-storefront/core/lib/multistore' -import { SearchQuery } from 'storefront-query-builder' -import HttpQuery from '@vue-storefront/core/types/search/HttpQuery' -import { SearchResponse } from '@vue-storefront/core/types/search/SearchResponse' -import config from 'config' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -export class SearchAdapter { - public entities: any - - public constructor () { - this.entities = [] - this.initBaseTypes() - } - - protected decompactItem (item, fieldsToCompact) { - for (let key in fieldsToCompact) { - const value = fieldsToCompact[key] - if (typeof item[value] !== 'undefined') { - item[key] = item[value] - delete item[value] - } - } - return item - } - - public async search (Request) { - const rawQueryObject = Request.searchQuery - if (!this.entities[Request.type]) { - throw new Error('No entity type registered for ' + Request.type) - } - if (!(Request.searchQuery instanceof SearchQuery)) { - throw new Error('The only supported type of the "Request.searchQuery" is "SearchQuery"') - } - if (Request.hasOwnProperty('groupId') && Request.groupId !== null) { - rawQueryObject['groupId'] = Request.groupId - } - if (Request.hasOwnProperty('groupToken') && Request.groupToken !== null) { - rawQueryObject['groupToken'] = Request.groupToken - } - if (Request.sort) { - const [ field, options ] = Request.sort.split(':') - rawQueryObject.applySort({ field, options }) - delete Request.sort - } - const storeView = (Request.store === null) ? currentStoreView() : await prepareStoreView(Request.store) - Request.index = storeView.elasticsearch.index - - let url = processURLAddress(getApiEndpointUrl(storeView.elasticsearch, 'host')) - - if (this.entities[Request.type].url) { - url = getApiEndpointUrl(this.entities[Request.type], 'url') - } - - const httpQuery: HttpQuery = { - size: Request.size, - from: Request.from, - sort: Request.sort, - request_format: 'search-query', - response_format: 'compact' - } - - if (Request._sourceExclude) { - httpQuery._source_exclude = Request._sourceExclude.join(',') - } - if (Request._sourceInclude) { - httpQuery._source_include = Request._sourceInclude.join(',') - } - if (Request.q) { - httpQuery.q = Request.q - } - - if (!Request.index || !Request.type) { - throw new Error('Query.index and Query.type are required arguments for executing ElasticSearch query') - } - if (config.elasticsearch.queryMethod === 'GET') { - httpQuery.request = JSON.stringify(rawQueryObject) - } - url = url + '/' + encodeURIComponent(Request.index) + '/' + encodeURIComponent(Request.type) + '/_search' - url = url + '?' + queryString.stringify(httpQuery) - return fetch(url, { method: config.elasticsearch.queryMethod, - mode: 'cors', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: config.elasticsearch.queryMethod === 'POST' ? JSON.stringify(rawQueryObject) : null - }) - .then(resp => { return resp.json() }) - .catch(error => { - throw new Error('FetchError in request to API: ' + error.toString()) - }) - } - - public handleResult (resp, type, start = 0, size = 50): SearchResponse { - if (resp === null) { - throw new Error('Invalid API result - null not exepcted') - } - if (resp.hasOwnProperty('hits')) { - return { - items: map(resp.hits, hit => { - if (type === 'product') { - hit = this.decompactItem(hit, config.products.fieldsToCompact) - if (hit.configurable_children) { - hit.configurable_children = hit.configurable_children.map(childItem => { - return this.decompactItem(childItem, config.products.fieldsToCompact) - }) - } - } - return Object.assign(hit, { slug: hit.slug ? hit.slug : ((hit.hasOwnProperty('url_key') && config.products.useMagentoUrlKeys) ? hit.url_key : (hit.hasOwnProperty('name') ? slugify(hit.name) + '-' + hit.id : '')) }) // TODO: assign slugs server side - }), // TODO: add scoring information - total: resp.total, - start: start, - perPage: size, - aggregations: resp.aggregations, - attributeMetadata: resp.attribute_metadata, - suggestions: resp.suggest - } - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with API catalog result in resultProcessor for entity type \'' + type + '\'') - } - } - } - - public registerEntityType (entityType, { url = '', url_ssr = '', queryProcessor, resultProcessor }) { - this.entities[entityType] = { - queryProcessor: queryProcessor, - resultProcessor: resultProcessor - } - if (url !== '') { - this.entities[entityType]['url'] = url - } - if (url_ssr !== '') { - this.entities[entityType]['url_ssr'] = url_ssr - } - return this - } - - public initBaseTypes () { - const baseTypes = ['product', 'attribute', 'category', 'taxrule', 'review', 'cms_page', 'cms_block', 'cms_hierarchy'] - baseTypes.forEach(type => { - this.registerEntityType(type, { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, type, start, size) - } - }) - }) - } -} diff --git a/core/lib/search/adapter/api/searchAdapter.ts b/core/lib/search/adapter/api/searchAdapter.ts deleted file mode 100644 index 48c29a7b3d..0000000000 --- a/core/lib/search/adapter/api/searchAdapter.ts +++ /dev/null @@ -1,211 +0,0 @@ -import map from 'lodash-es/map' -import { elasticsearch } from 'storefront-query-builder' -import fetch from 'isomorphic-fetch' -import { slugify, processURLAddress } from '@vue-storefront/core/helpers' -import queryString from 'query-string' -import { currentStoreView, prepareStoreView } from '@vue-storefront/core/lib/multistore' -import { SearchQuery } from 'storefront-query-builder' -import HttpQuery from '@vue-storefront/core/types/search/HttpQuery' -import { SearchResponse } from '@vue-storefront/core/types/search/SearchResponse' -import config from 'config' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -export class SearchAdapter { - public entities: any - - public constructor () { - this.entities = [] - this.initBaseTypes() - } - - public async search (Request) { - if (!this.entities[Request.type]) { - throw new Error('No entity type registered for ' + Request.type) - } - let ElasticsearchQueryBody = {} - if (Request.searchQuery instanceof SearchQuery) { - const bodybuilder = await import(/* webpackChunkName: "bodybuilder" */ 'bodybuilder') - ElasticsearchQueryBody = await elasticsearch.buildQueryBodyFromSearchQuery({ config, queryChain: bodybuilder.default(), searchQuery: Request.searchQuery }) - if (Request.searchQuery.getSearchText() !== '') { - ElasticsearchQueryBody['min_score'] = config.elasticsearch.min_score - } - } else { - // backward compatibility for old themes uses bodybuilder - ElasticsearchQueryBody = Request.searchQuery - } - if (Request.hasOwnProperty('groupId') && Request.groupId !== null) { - ElasticsearchQueryBody['groupId'] = Request.groupId - } - if (Request.hasOwnProperty('groupToken') && Request.groupToken !== null) { - ElasticsearchQueryBody['groupToken'] = Request.groupToken - } - - const storeView = (Request.store === null) ? currentStoreView() : await prepareStoreView(Request.store) - - Request.index = storeView.elasticsearch.index - - let url = processURLAddress(getApiEndpointUrl(storeView.elasticsearch, 'host')) - - if (this.entities[Request.type].url) { - url = getApiEndpointUrl(this.entities[Request.type], 'url') - } - - const httpQuery: HttpQuery = { - size: Request.size, - from: Request.from, - sort: Request.sort - } - - if (Request._sourceExclude) { - httpQuery._source_exclude = Request._sourceExclude.join(',') - } - if (Request._sourceInclude) { - httpQuery._source_include = Request._sourceInclude.join(',') - } - if (Request.q) { - httpQuery.q = Request.q - } - - if (!Request.index || !Request.type) { - throw new Error('Query.index and Query.type are required arguments for executing ElasticSearch query') - } - if (config.elasticsearch.queryMethod === 'GET') { - httpQuery.request = JSON.stringify(ElasticsearchQueryBody) - } - url = url + '/' + encodeURIComponent(Request.index) + '/' + encodeURIComponent(Request.type) + '/_search' - url = url + '?' + queryString.stringify(httpQuery) - - return fetch(url, { - method: config.elasticsearch.queryMethod, - mode: 'cors', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: config.elasticsearch.queryMethod === 'POST' ? JSON.stringify(ElasticsearchQueryBody) : null - }) - .then(resp => { return resp.json() }) - .catch(error => { - throw new Error('FetchError in request to ES: ' + error.toString()) - }) - } - - public handleResult (resp, type, start = 0, size = 50): SearchResponse { - if (resp === null) { - throw new Error('Invalid ES result - null not exepcted') - } - if (resp.hasOwnProperty('hits')) { - return { - items: map(resp.hits.hits, hit => { - return Object.assign(hit._source, { _score: hit._score, slug: hit._source.slug ? hit._source.slug : ((hit._source.hasOwnProperty('url_key') && config.products.useMagentoUrlKeys) ? hit._source.url_key : (hit._source.hasOwnProperty('name') ? slugify(hit._source.name) + '-' + hit._source.id : '')) }) // TODO: assign slugs server side - }), // TODO: add scoring information - total: resp.hits.total, - start: start, - perPage: size, - aggregations: resp.aggregations, - attributeMetadata: resp.attribute_metadata, - suggestions: resp.suggest - } - } else { - const isErrorObject = (resp && resp.code) >= 400 ? resp : null - if (resp.error || isErrorObject) { - throw new Error(JSON.stringify(resp.error || resp)) - } else { - throw new Error('Unknown error with elasticsearch result in resultProcessor for entity type \'' + type + '\'') - } - } - } - - public registerEntityType (entityType, { url = '', url_ssr = '', queryProcessor, resultProcessor }) { - this.entities[entityType] = { - queryProcessor: queryProcessor, - resultProcessor: resultProcessor - } - if (url !== '') { - this.entities[entityType]['url'] = url - } - if (url_ssr !== '') { - this.entities[entityType]['url_ssr'] = url_ssr - } - return this - } - - public initBaseTypes () { - this.registerEntityType('product', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'product', start, size) - } - }) - - this.registerEntityType('attribute', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'attribute', start, size) - } - }) - - this.registerEntityType('category', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'category', start, size) - } - }) - - this.registerEntityType('taxrule', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'taxrule', start, size) - } - }) - - this.registerEntityType('review', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'review', start, size) - } - }) - this.registerEntityType('cms_page', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'cms_page', start, size) - } - }) - this.registerEntityType('cms_block', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'cms_block', start, size) - } - }) - this.registerEntityType('cms_hierarchy', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'cms_hierarchy', start, size) - } - }) - } -} diff --git a/core/lib/search/adapter/searchAdapterFactory.js b/core/lib/search/adapter/searchAdapterFactory.js deleted file mode 100644 index 9895d2eb6f..0000000000 --- a/core/lib/search/adapter/searchAdapterFactory.js +++ /dev/null @@ -1,45 +0,0 @@ -import { server } from 'config' -let instances = {} - -const isImplementingSearchAdapterInterface = (obj) => { - return typeof obj.search === 'function' && typeof obj.registerEntityType === 'function' -} - -export const getSearchAdapter = async (adapterName = server.api) => { - let SearchAdapterModule - - try { - SearchAdapterModule = await import(/* webpackChunkName: "vsf-search-adapter-[request]" */ `src/search/adapter/${adapterName}/searchAdapter`) - } catch {} - - if (!SearchAdapterModule) { - try { - SearchAdapterModule = await import(/* webpackChunkName: "vsf-search-adapter-[request]" */ `./${adapterName}/searchAdapter`) - } catch {} - } - - if (!SearchAdapterModule) { - throw new Error('Search adapter module was not found in `src/search/adapter` neither in the `core/lib/search/adapter` folders') - } - - const SearchAdapter = SearchAdapterModule.SearchAdapter - - if (!SearchAdapter) { - throw new Error('Search adapter class is not provided') - } - - if (instances[adapterName]) { - return instances[adapterName] - } - - const searchAdapter = new SearchAdapter() - if (!isImplementingSearchAdapterInterface(searchAdapter)) { - throw new Error('Not valid search adapter class provided. Search Adapter must implements SearchAdapterInterfaces') - } - instances[adapterName] = searchAdapter; - return instances[adapterName]; -} - -export default { - getSearchAdapter -} diff --git a/core/lib/search/adapter/test/unit/searchAdapterFactory.spec.ts b/core/lib/search/adapter/test/unit/searchAdapterFactory.spec.ts deleted file mode 100644 index 9ffbb54f5b..0000000000 --- a/core/lib/search/adapter/test/unit/searchAdapterFactory.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { getSearchAdapter } from '@vue-storefront/core/lib/search/adapter/searchAdapterFactory' - -jest.mock('config', () => { - return { server: { api: 'api' } }; -}); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}) - } -})); - -const mockSearchAdapterModule = { - SearchAdapter: jest.fn().mockImplementation(() => { - return { - search: (Request: any): void => { - }, - registerEntityType: (entityType: string, options: any): void => { - } - } - }) -}; - -describe('Search adapter factory tests', () => { - it('Search adapter constructor called always only once', async () => { - jest.mock('../../api/searchAdapter', () => { - return mockSearchAdapterModule; - }) - - const apiSearchAdapter1 = await getSearchAdapter() - const apiSearchAdapter2 = await getSearchAdapter('api') - expect(mockSearchAdapterModule.SearchAdapter).toHaveBeenCalledTimes(1) - }) - - it('Search adapter class is not provided', async () => { - jest.mock( - '../../virtual/searchAdapter', - () => { - return {}; - }, - { virtual: true } - ) - - await expect(getSearchAdapter('virtual')).rejects.toThrowError(new Error('Search adapter class is not provided')) - }) - - it('Search adapter class has invalid search method', async () => { - jest.mock( - '../../invalidSearchMethod/searchAdapter', - () => { - return { - SearchAdapter: jest.fn().mockImplementation(() => { - return { - search: 1, - registerEntityType: (entityType: string, options: any): void => { - } - } - }) - } - }, - { virtual: true } - ) - - await expect(getSearchAdapter('invalidSearchMethod')) - .rejects.toThrowError( - new Error('Not valid search adapter class provided. Search Adapter must implements SearchAdapterInterfaces') - ) - }) - - it('Search adapter class has invalid registerEntityTypeMethod method', async () => { - jest.mock( - '../../invalidRegisterEntityTypeMethod/searchAdapter', - () => { - return { - SearchAdapter: jest.fn().mockImplementation(() => { - return { - search: (Request: any): void => { - }, - registerEntityType: 1 - } - }) - } - }, - { virtual: true } - ) - - await expect(getSearchAdapter('invalidRegisterEntityTypeMethod')) - .rejects.toThrowError( - new Error('Not valid search adapter class provided. Search Adapter must implements SearchAdapterInterfaces') - ) - }) -}) diff --git a/core/lib/storage-manager.ts b/core/lib/storage-manager.ts deleted file mode 100644 index 23b2c1e635..0000000000 --- a/core/lib/storage-manager.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Logger } from '@vue-storefront/core/lib/logger' -import * as localForage from 'localforage' -import UniversalStorage from '@vue-storefront/core/lib/store/storage' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import config from 'config' - -function _prepareCacheStorage (key, localized = !config.storeViews.commonCache, storageQuota = 0) { - const storeView = currentStoreView() - const dbNamePrefix = storeView && storeView.storeCode ? storeView.storeCode + '-' : '' - const cacheDriver = config.localForage && config.localForage.defaultDrivers[key] - ? config.localForage.defaultDrivers[key] - : 'LOCALSTORAGE' - - return new UniversalStorage(localForage.createInstance({ - name: localized ? `${dbNamePrefix}shop` : 'shop', - storeName: key, - driver: localForage[cacheDriver] - }), true, storageQuota) -} - -const StorageManager = { - currentStoreCode: '', - storageMap: {}, - /** - * Register the cache storage index that can be later accessed and modified - this is required prior to accessing the collection - * @param collectionName name of the cache collection to create - * @param isLocalized if set to `false` data will be shared between storeViews (default `true`) - * @param storageQuota max size of storage, 0 if unlimited (default `0`) - */ - init: function (collectionName: string, isLocalized = !config.storeViews.commonCache, storageQuota = 0) { - this.storageMap[collectionName] = _prepareCacheStorage(collectionName, isLocalized, storageQuota) - return this.storageMap[collectionName] - }, - /** - * Override or register the cache storage - this is required prior to accessing the collection - * @param collectionName { string} string name of the cache collection to register - * @param item UniversalStorage driver - */ - set: function (collectionName: string, collectionInstance: UniversalStorage): UniversalStorage { - this.storageMap[collectionName] = collectionInstance - return collectionInstance - }, - /** - * Check if the specified collection is already registered - * @param collectionName string collection name to check - */ - exists (collectionName): boolean { - return !!this.storageMap[collectionName] - }, - /** - * Returns the UniversalStorage driver for specific key. - * If it doesnt exist it creates it with defaults for `init` - * @returns UniversalStorage - */ - get: function (collectionName): UniversalStorage { - if (!this.exists(collectionName)) { - Logger.warn('Called cache collection ' + collectionName + ' does not exist. Initializing.', 'cache') - return this.set(collectionName, initCacheStorage(collectionName, true)) // eslint-disable-line @typescript-eslint/no-use-before-define - } else { - return this.storageMap[collectionName] - } - }, - clear (): Promise { - const promiseArray = Object.keys(this.storageMap).map((collectionName) => { - return (config.localForage.preserveCollections || []).every(collectionToKeep => collectionName !== collectionToKeep) && this.storageMap[collectionName].clear().then(() => { - Logger.warn(`storeManager cleared: ${collectionName}`, `storeManager cleared: ${collectionName}`, `storeManager cleared: ${collectionName}`)() - }) - }) - return Promise.all(promiseArray) - } -} - -/** - * @deprecated to be removed in 2.0 in favor to `StorageManager` - * */ -function initCacheStorage (key, localised = true, registerStorgeManager = true) { - if (registerStorgeManager) { - if (!StorageManager.exists(key)) { - return StorageManager.set(key, _prepareCacheStorage(key, localised)) - } else { - return StorageManager.get(key) - } - } else { - return _prepareCacheStorage(key, localised) - } -} - -export { StorageManager, initCacheStorage } diff --git a/core/lib/store/entities.ts b/core/lib/store/entities.ts deleted file mode 100644 index 76c6ebc881..0000000000 --- a/core/lib/store/entities.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @returns {string} - */ -export function guid () { - function s4 () { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1) - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4() -} -/** - * Return unique entity.id - * @param {Object} entity - */ -export function uniqueEntityId (entity) { - return new Date().getTime() + '-' + guid() -} - -/** - * Return unique entity key name for specified key value - * @param {String} key - * @param {String} value - */ -export function entityKeyName (...values) { - return values.join('$$') -} diff --git a/core/lib/store/filters.ts b/core/lib/store/filters.ts deleted file mode 100644 index 886b52f52d..0000000000 --- a/core/lib/store/filters.ts +++ /dev/null @@ -1,9 +0,0 @@ -import decode from 'lean-he/decode' - -/** - * Decodes any named and numerical character references in the text - * @param {String} value - */ -export function htmlDecode (value: any) { - return value ? decode(value) : '' -} diff --git a/core/lib/store/storage.ts b/core/lib/store/storage.ts deleted file mode 100644 index 792c5d7c6f..0000000000 --- a/core/lib/store/storage.ts +++ /dev/null @@ -1,376 +0,0 @@ -import * as localForage from 'localforage' -import { Logger } from '@vue-storefront/core/lib/logger' -import { isServer } from '@vue-storefront/core/helpers' -import cloneDeep from 'lodash-es/cloneDeep' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -const CACHE_TIMEOUT = 800 -const CACHE_TIMEOUT_ITERATE = 2000 -const DISABLE_PERSISTANCE_AFTER = 1 -const DISABLE_PERSISTANCE_AFTER_SAVE = 30 - -const _globalCache = {} - -function roughSizeOfObject (object) { - const objectList = [] - const stack = [ object ] - let bytes = 0 - while (stack.length) { - const value = stack.pop() - if (typeof value === 'boolean') { - bytes += 4 - } else if (typeof value === 'string') { - bytes += value.length * 2 - } else if (typeof value === 'number') { - bytes += 8 - } else if ( - typeof value === 'object' && - objectList.indexOf(value) === -1 - ) { - objectList.push(value) - for (var i in value) { - stack.push(value[ i ]) - } - } - } - return bytes -} - -interface CacheTimeouts { - getItem: any, - iterate: any, - setItem: any, - base: any -} - -class LocalForageCacheDriver { - private _collectionName: string; - private _dbName: string; - private _lastError: any; - private _localCache: any; - private _localForageCollection: any; - private _persistenceErrorNotified: boolean; - private _useLocalCacheByDefault: boolean; - private cacheErrorsCount: any; - private _storageQuota: number; - private _cacheTimeouts: CacheTimeouts = { - getItem: null, - iterate: null, - setItem: null, - base: null - } - - public constructor (collection, useLocalCacheByDefault = true, storageQuota = 0) { - const collectionName = collection._config.storeName - const dbName = collection._config.name - this._storageQuota = storageQuota - - if (this._storageQuota && !isServer) { - const storageQuota = this._storageQuota - const iterateFnc = this.iterate.bind(this) - const removeItemFnc = this.removeItem.bind(this) - clearInterval(this._cacheTimeouts.base) - this._cacheTimeouts.base = setInterval(() => { - let storageSize = 0 - this.iterate((item, id, number) => { - storageSize += roughSizeOfObject(item) - }, (err, result) => { // eslint-disable-line handle-callback-err - if ((storageSize / 1024) > storageQuota) { - Logger.info('Clearing out the storage ', 'cache', { storageSizeKB: Math.round(storageSize / 1024), storageQuotaKB: storageQuota })() - const howManyItemsToRemove = 100 - const keysPurged = [] - iterateFnc((item, id, number) => { - if (number < howManyItemsToRemove) { - removeItemFnc(id) - keysPurged.push(id) - } - }, (err, result) => { // eslint-disable-line handle-callback-err - Logger.info('Cache purged', 'cache', { keysPurged })() - }) - } else { - Logger.info('Storage size', 'cache', { storageSizeKB: Math.round(storageSize / 1024) })() - } - }) - }, 30000) - } - if (typeof this.cacheErrorsCount === 'undefined') { - this.cacheErrorsCount = {} - } - if (typeof this.cacheErrorsCount[collectionName] === 'undefined') { - this.cacheErrorsCount[collectionName] = 0 - } - if (isServer) { - this._localCache = {} - } else { - if (typeof _globalCache[dbName] === 'undefined') { - _globalCache[dbName] = {} - } - if (typeof _globalCache[dbName][collectionName] === 'undefined') { - _globalCache[dbName][collectionName] = {} - } - this._localCache = _globalCache[dbName][collectionName] - } - this._collectionName = collectionName - this._dbName = dbName - this._useLocalCacheByDefault = useLocalCacheByDefault - this._localForageCollection = collection - this._lastError = null - this._persistenceErrorNotified = false - } - - public getLastError () { - return this._lastError - } - - public getDbName () { - return this._dbName - } - - // Remove all keys from the datastore, effectively destroying all data in - // the app's key/value store! - public clear (callback?) { - return this._localForageCollection.clear(callback) - } - - // Increment the database version number and recreate the context - public recreateDb () { - if (this._localForageCollection._config) { - const existingConfig = Object.assign({}, this._localForageCollection._config) - if (existingConfig.storeName) { - // localForage.dropInstance(existingConfig) // drop the store and create the new one - const destVersionNumber = this._localForageCollection && this._localForageCollection._dbInfo ? this._localForageCollection._dbInfo.version + 1 : 0 - if (destVersionNumber > 0) { - this._localForageCollection = localForage.createInstance({ ...existingConfig, version: destVersionNumber }) - } else { - this._localForageCollection = localForage.createInstance(existingConfig) - } - Logger.log('DB recreated with', existingConfig, destVersionNumber)() - } - } - } - - public getLocalCache (key) { - return typeof this._localCache[key] !== 'undefined' ? cloneDeep(this._localCache[key]) : null - } - - // Retrieve an item from the store. Unlike the original async_storage - // library in Gaia, we don't modify return values at all. If a key's value - // is `undefined`, we pass that value to the callback function. - public getItem (key, callback?) { - const isCallbackCallable = (typeof callback !== 'undefined' && callback) - let isResolved = false - if (this._useLocalCacheByDefault && this._localCache[key]) { - // Logger.debug('Local cache fallback for GET', key)() - return new Promise((resolve, reject) => { - const value = this.getLocalCache(key) - if (isCallbackCallable) callback(null, value) - resolve(value) - }) - } - - if (!isServer) { - if (this.cacheErrorsCount[this._collectionName] >= DISABLE_PERSISTANCE_AFTER && this._useLocalCacheByDefault) { - if (!this._persistenceErrorNotified) { - Logger.error('Persistent cache disabled becasue of previous errors [get]', key)() - this._persistenceErrorNotified = true - } - return new Promise((resolve, reject) => { - if (isCallbackCallable) callback(null, null) - resolve(null) - }) - } else { - const startTime = new Date().getTime() - // Logger.debug('No local cache fallback for GET', key)() - const promise = this._localForageCollection.ready().then(() => this._localForageCollection.getItem(key).then(result => { - const endTime = new Date().getTime() - const clonedResult = cloneDeep(result) - if ((endTime - startTime) >= CACHE_TIMEOUT) { - Logger.error('Cache promise resolved after [ms]' + key + (endTime - startTime))() - } - if (!this._localCache[key] && clonedResult) { - this._localCache[key] = clonedResult // populate the local cache for the next call - } - if (!isResolved) { - if (isCallbackCallable) { - callback(null, clonedResult) - } - isResolved = true - } else { - Logger.debug('Skipping return value as it was previously resolved')() - } - return clonedResult - }).catch(err => { - this._lastError = err - if (!isResolved) { - const value = this.getLocalCache(key) - if (isCallbackCallable) callback(null, value) - } - Logger.error(err)() - isResolved = true - })) - clearTimeout(this._cacheTimeouts.getItem) - this._cacheTimeouts.getItem = setTimeout(() => { - if (!isResolved) { // this is cache time out check - if (!this._persistenceErrorNotified) { - Logger.error('Cache not responding for ' + key + '.', 'cache', { timeout: CACHE_TIMEOUT, errorsCount: this.cacheErrorsCount[this._collectionName] })() - this._persistenceErrorNotified = true - this.recreateDb() - } - this.cacheErrorsCount[this._collectionName] = this.cacheErrorsCount[this._collectionName] ? this.cacheErrorsCount[this._collectionName] + 1 : 1 - const value = this.getLocalCache(key) - if (isCallbackCallable) callback(null, value) - } - }, CACHE_TIMEOUT) - return promise - } - } else { - return new Promise((resolve, reject) => { - const value = this.getLocalCache(key) - if (isCallbackCallable) callback(null, value) - resolve(value) - }) - } - } - - // Iterate over all items in the store. - public iterate (iterator, callback?) { - const isIteratorCallable = (typeof iterator !== 'undefined' && iterator) - const isCallbackCallable = (typeof callback !== 'undefined' && callback) - let globalIterationNumber = 1 - if (this._useLocalCacheByDefault) { - // Logger.debug('Local cache iteration')() - for (const localKey in this._localCache) { - if (isIteratorCallable) { - iterator(this._localCache[localKey], localKey, globalIterationNumber) - globalIterationNumber++ - } - } - } - let isResolved = false - const promise = this._localForageCollection.ready().then(() => this._localForageCollection.iterate((value, key, iterationNumber) => { - isResolved = true - if (isIteratorCallable) { - if (this._useLocalCacheByDefault) { - if (typeof this._localCache[key] === 'undefined') { - iterator(value, key, globalIterationNumber) - globalIterationNumber++ - } else { - // Logger.debug('Skipping iteration key because local cache executed', key)() - } - } else { - iterator(value, key, iterationNumber) - } - } - }, (err, result) => { - if (isCallbackCallable) callback(err, result) - isResolved = true - })).catch(err => { - this._lastError = err - Logger.error(err)() - if (!isResolved) { - isResolved = true - if (isCallbackCallable) callback(err, null) - } - }) - clearTimeout(this._cacheTimeouts.iterate) - this._cacheTimeouts.iterate = setTimeout(() => { - if (!isResolved) { // this is cache time out check - if (!this._persistenceErrorNotified) { - Logger.error('Cache not responding. (iterate)', 'cache', { timeout: CACHE_TIMEOUT, errorsCount: this.cacheErrorsCount[this._collectionName] })() - this._persistenceErrorNotified = true - this.recreateDb() - } - this.cacheErrorsCount[this._collectionName] = this.cacheErrorsCount[this._collectionName] ? this.cacheErrorsCount[this._collectionName] + 1 : 1 - if (isCallbackCallable) callback(null, null) - } - }, CACHE_TIMEOUT_ITERATE) - return promise - } - - // Same as localStorage's key() method, except takes a callback. - public key (n, callback?) { - return this._localForageCollection.key(n, callback) - } - - public keys (callback?) { - return this._localForageCollection.keys(callback) - } - - // Supply the number of keys in the datastore to the callback function. - public length (callback?) { - return this._localForageCollection.length(callback) - } - - // Remove an item from the store, nice and simple. - public removeItem (key, callback?) { - if (typeof this._localCache[key] !== 'undefined') { - delete this._localCache[key] - } - return this._localForageCollection.removeItem(key, callback) - } - - // Set a key's value and run an optional callback once the value is set. - // Unlike Gaia's implementation, the callback function is passed the value, - // in case you want to operate on that value only after you're sure it - // saved, or something like that. - public setItem (key, value, callback?, memoryOnly = false) { - const isCallbackCallable = (typeof callback !== 'undefined' && callback) - const copiedValue = cloneDeep(value) - this._localCache[key] = copiedValue - if (memoryOnly) { - return new Promise((resolve, reject) => { - if (isCallbackCallable) callback(null, null) - resolve(null) - }) - } - if (!isServer) { - if (this.cacheErrorsCount[this._collectionName] >= DISABLE_PERSISTANCE_AFTER_SAVE && this._useLocalCacheByDefault) { - if (!this._persistenceErrorNotified) { - Logger.error('Persistent cache disabled becasue of previous errors [set]', key)() - this._persistenceErrorNotified = true - } - return new Promise((resolve, reject) => { - if (isCallbackCallable) callback(null, null) - resolve(null) - }) - } else { - let isResolved = false - const handleSetItem = () => this._localForageCollection.setItem(key, copiedValue) - .then(result => { - if (isCallbackCallable) { - callback(null, result) - } - isResolved = true - }) - .catch(async err => { - if (err.name === 'QuotaExceededError' || err.name === 'NS_ERROR_DOM_QUOTA_REACHED') { - await StorageManager.clear() - handleSetItem() - } - isResolved = true - this._lastError = err - throw err - }) - const promise = this._localForageCollection.ready().then(handleSetItem) - clearTimeout(this._cacheTimeouts.iterate) - this._cacheTimeouts.setItem = setTimeout(() => { - if (!isResolved) { // this is cache time out check - if (!this._persistenceErrorNotified) { - Logger.error('Cache not responding for ' + key + '.', 'cache', { timeout: CACHE_TIMEOUT, errorsCount: this.cacheErrorsCount[this._collectionName] })() - this._persistenceErrorNotified = true - this.recreateDb() - } - this.cacheErrorsCount[this._collectionName] = this.cacheErrorsCount[this._collectionName] ? this.cacheErrorsCount[this._collectionName] + 1 : 1 - if (isCallbackCallable) callback(null, null) - } - }, CACHE_TIMEOUT) - return promise - } - } else { - return new Promise((resolve, reject) => resolve()) - } - } -} - -// The actual localForage object that we expose as a module. It's extended by pulling in one of our other libraries. -export default LocalForageCacheDriver diff --git a/core/lib/storeCodeFromRoute.ts b/core/lib/storeCodeFromRoute.ts deleted file mode 100644 index 57b9567141..0000000000 --- a/core/lib/storeCodeFromRoute.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { RawLocation } from 'vue-router' -import config from 'config' -import { LocalizedRoute } from './types' - -const getNormalizedPath = (matchedRouteOrUrl) => { - const matchingPath = matchedRouteOrUrl && (matchedRouteOrUrl.path || matchedRouteOrUrl) - - return matchingPath && (matchingPath.length > 0 && matchingPath[0] !== '/') ? `/${matchingPath}` : matchingPath -} - -const getUrl = (matchedRouteOrUrl) => { - const normalizedPath = getNormalizedPath(matchedRouteOrUrl) - - if (matchedRouteOrUrl && typeof matchedRouteOrUrl === 'object') { - if (matchedRouteOrUrl['host']) { - return matchedRouteOrUrl['host'] + normalizedPath - } - - return '' - } - - return matchedRouteOrUrl -} - -const isMatchingByPath = (matchedRouteOrUrl, store) => { - const normalizedPath = getNormalizedPath(matchedRouteOrUrl) - return normalizedPath.startsWith(`${store.url}/`) || normalizedPath === store.url -} - -const isMatchingByDomainAndPath = (matchedRouteOrUrl, store) => { - const url = getUrl(matchedRouteOrUrl) - return url.startsWith(`${store.url}/`) || url === store.url -} - -const storeCodeFromRoute = (matchedRouteOrUrl: LocalizedRoute | RawLocation | string): string => { - if (!matchedRouteOrUrl) return '' - - for (let storeCode of config.storeViews.mapStoreUrlsFor) { - const store = config.storeViews[storeCode] - - if (isMatchingByPath(matchedRouteOrUrl, store) || isMatchingByDomainAndPath(matchedRouteOrUrl, store)) { - return storeCode - } - } - - return '' -} - -export default storeCodeFromRoute diff --git a/core/lib/sync/helpers/index.ts b/core/lib/sync/helpers/index.ts deleted file mode 100644 index 76bac21d1e..0000000000 --- a/core/lib/sync/helpers/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const hasResponseError = (jsonResponse): boolean => { - if (typeof jsonResponse.result === 'string') { - return true - } - - const hasMessage = jsonResponse.result.result || jsonResponse.result.message - - return Boolean(hasMessage) && jsonResponse.result.code !== 'ENOTFOUND' -} - -export const getResponseMessage = (jsonResponse): string => { - if (typeof jsonResponse.result === 'string') { - return jsonResponse.result - } - - if (typeof jsonResponse.result.result === 'string') { - return jsonResponse.result.result - } - - return jsonResponse.result.message -} diff --git a/core/lib/sync/index.ts b/core/lib/sync/index.ts deleted file mode 100644 index 999d37b7af..0000000000 --- a/core/lib/sync/index.ts +++ /dev/null @@ -1,65 +0,0 @@ - -import Vue from 'vue' -import rootStore from '@vue-storefront/core/store' -import { Logger } from '@vue-storefront/core/lib/logger' -import { execute as taskExecute, _prepareTask } from './task' -import { isServer } from '@vue-storefront/core/helpers' -import config from 'config' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -/** Syncs given task. If user is offline request will be sent to the server after restored connection */ -async function queue (task) { - const tasksCollection = StorageManager.get('syncTasks') - task = _prepareTask(task) - Logger.info('Sync task queued ' + task.url, 'sync', { task })() - return new Promise((resolve, reject) => { - tasksCollection.setItem(task.task_id.toString(), task, (err, resp) => { - if (err) Logger.error(err, 'sync')() - EventBus.$emit('sync/PROCESS_QUEUE', { config: config }) // process checkout queue - resolve(task) - }, config.syncTasks.disablePersistentTaskQueue).catch((reason) => { - Logger.error(reason, 'sync')() // it doesn't work on SSR - reject(reason) - }) - }) -} - -/** Runs given task. If user is offline request will fail */ -async function execute (task): Promise { // not offline task - task = _prepareTask(task) - return new Promise((resolve, reject) => { - if (isServer) { - taskExecute(task, null, null).then((result) => { - resolve(result) - }).catch(err => { - reject(err) - }) - } else { - const currentUserToken = rootStore.getters['user/getUserToken'] - const currentCartToken = rootStore.getters['cart/getCartToken'] - taskExecute(task, currentUserToken, currentCartToken).then((result) => { - resolve(result) - }).catch(err => { - reject(err) - }) - } - }) -} - -/** Clear sync tasks that were not transmitted yet */ -function clearNotTransmited () { - const syncTaskCollection = StorageManager.get('syncTasks') - syncTaskCollection.iterate((task, id, iterationNumber) => { - if (!task.transmited) { - syncTaskCollection.removeItem(id) - } - }) -} - -export const TaskQueue = { - queue, - execute, - clearNotTransmited -} diff --git a/core/lib/sync/task.ts b/core/lib/sync/task.ts deleted file mode 100644 index 30eba0353b..0000000000 --- a/core/lib/sync/task.ts +++ /dev/null @@ -1,243 +0,0 @@ -import i18n from '@vue-storefront/i18n' -import isNaN from 'lodash-es/isNaN' -import isUndefined from 'lodash-es/isUndefined' -import fetch from 'isomorphic-fetch' -import rootStore from '@vue-storefront/core/store' -import { adjustMultistoreApiUrl, currentStoreView } from '@vue-storefront/core/lib/multistore' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import { Logger } from '@vue-storefront/core/lib/logger' -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import * as entities from '@vue-storefront/core/lib/store/entities' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { processURLAddress } from '@vue-storefront/core/helpers' -import { serial } from '@vue-storefront/core/helpers' -import config from 'config' -import { onlineHelper } from '@vue-storefront/core/helpers' -import { hasResponseError, getResponseMessage } from '@vue-storefront/core/lib/sync/helpers' -import queryString from 'query-string' - -export function _prepareTask (task) { - const taskId = entities.uniqueEntityId(task) // timestamp as a order id is not the best we can do but it's enough - task.task_id = taskId.toString() - task.transmited = false - task.created_at = new Date() - task.updated_at = new Date() - return task -} - -function _sleep (time) { - return new Promise((resolve) => setTimeout(resolve, time)) -} - -function getUrl (task, currentToken, currentCartId) { - let url = task.url - .replace('{{token}}', (currentToken == null) ? '' : currentToken) - .replace('{{cartId}}', (currentCartId == null) ? '' : currentCartId) - - url = processURLAddress(url); // use relative url paths - if (config.storeViews.multistore) { - url = adjustMultistoreApiUrl(url) - } - - if (config.users.tokenInHeader) { - const parsedUrl = queryString.parseUrl(url) - delete parsedUrl['query']['token'] - url = queryString.stringifyUrl(parsedUrl) - } - - return url -} - -function getPayload (task, currentToken) { - const payload = { - ...task.payload, - headers: { - ...task.payload.headers, - ...(config.users.tokenInHeader ? { authorization: `Bearer ${currentToken}` } : {}) - } - } - return payload -} - -function _internalExecute (resolve, reject, task: Task, currentToken, currentCartId) { - if (currentToken && rootStore.state.userTokenInvalidateLock > 0) { // invalidate lock set - Logger.log('Waiting for rootStore.state.userTokenInvalidateLock to release for ' + task.url, 'sync')() - _sleep(1000).then(() => { - Logger.log('Another try for rootStore.state.userTokenInvalidateLock for ' + task.url, 'sync')() - _internalExecute(resolve, reject, task, currentToken, currentCartId) - }) - return // return but not resolve - } else if (rootStore.state.userTokenInvalidateLock < 0) { - Logger.error('Aborting the network task' + task.url + rootStore.state.userTokenInvalidateLock, 'sync')() - resolve({ code: 401, result: i18n.t('Error refreshing user token. User is not authorized to access the resource') })() - return - } else { - if (rootStore.state.userTokenInvalidated) { - Logger.log('Using new user token' + rootStore.state.userTokenInvalidated, 'sync')() - currentToken = rootStore.state.userTokenInvalidated - } - } - const isCartIdRequired = task.url.includes('{{cartId}}') // this is bypass for #2592 - if (isCartIdRequired && !currentCartId) { // by some reason we does't have the cart id yet - reject('Error executing sync task ' + task.url + ' the required cartId argument is null. Re-creating shopping cart synchro.') - return - } - const url = getUrl(task, currentToken, currentCartId) - const payload = getPayload(task, currentToken) - let silentMode = false - Logger.info('Executing sync task ' + url, 'sync', task)() - return fetch(url, payload).then((response) => { - const contentType = response.headers.get('content-type') - if (contentType && contentType.includes('application/json')) { - return response.json() - } else { - const msg = i18n.t('Error with response - bad content-type!') - Logger.error(msg.toString(), 'sync')() - reject(msg) - } - }).then((jsonResponse) => { - if (jsonResponse) { - const responseCode = parseInt(jsonResponse.code) - if (responseCode !== 200) { - if (responseCode === 401 /** unauthorized */ && currentToken) { // the token is no longer valid, try to invalidate it - Logger.error('Invalid token - need to be revalidated' + currentToken + task.url + rootStore.state.userTokenInvalidateLock, 'sync')() - silentMode = true - if (config.users.autoRefreshTokens) { - if (!rootStore.state.userTokenInvalidateLock) { - rootStore.state.userTokenInvalidateLock++ - if (rootStore.state.userTokenInvalidateAttemptsCount >= config.queues.maxNetworkTaskAttempts) { - Logger.error('Internal Application error while refreshing the tokens. Please clear the storage and refresh page.', 'sync')() - rootStore.state.userTokenInvalidateLock = -1 - rootStore.dispatch('user/logout', { silent: true }) - TaskQueue.clearNotTransmited() - EventBus.$emit('modal-show', 'modal-signup') - rootStore.dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t('Internal Application error while refreshing the tokens. Please clear the storage and refresh page.'), - action1: { label: i18n.t('OK') } - }) - rootStore.state.userTokenInvalidateAttemptsCount = 0 - } else { - Logger.info('Invalidation process in progress (autoRefreshTokens is set to true)' + rootStore.state.userTokenInvalidateAttemptsCount + rootStore.state.userTokenInvalidateLock, 'sync')() - rootStore.state.userTokenInvalidateAttemptsCount++ - rootStore.dispatch('user/refresh').then((token) => { - if (token) { - rootStore.state.userTokenInvalidateLock = 0 - rootStore.state.userTokenInvalidated = token - Logger.info('User token refreshed successfully' + token, 'sync')() - } else { - rootStore.state.userTokenInvalidateLock = -1 - rootStore.dispatch('user/logout', { silent: true }) - EventBus.$emit('modal-show', 'modal-signup') - TaskQueue.clearNotTransmited() - Logger.error('Error refreshing user token' + token, 'sync')() - } - }).catch((excp) => { - rootStore.state.userTokenInvalidateLock = -1 - rootStore.dispatch('user/logout', { silent: true }) - EventBus.$emit('modal-show', 'modal-signup') - TaskQueue.clearNotTransmited() - Logger.error('Error refreshing user token' + excp, 'sync')() - }) - } - } - if (rootStore.state.userTokenInvalidateAttemptsCount <= config.queues.maxNetworkTaskAttempts) _internalExecute(resolve, reject, task, currentToken, currentCartId) // retry - } else { - Logger.info('Invalidation process is disabled (autoRefreshTokens is set to false)', 'sync')() - rootStore.dispatch('user/logout', { silent: true }) - EventBus.$emit('modal-show', 'modal-signup') - } - } - - if (!task.silent && jsonResponse.result && hasResponseError(jsonResponse) && !silentMode) { - rootStore.dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t(getResponseMessage(jsonResponse)), - action1: { label: i18n.t('OK') } - }) - } - } - - Logger.debug('Response for: ' + task.task_id + ' = ' + JSON.stringify(jsonResponse.result), 'sync')() - task.transmited = true - task.transmited_at = new Date() - task.result = jsonResponse.result - task.resultCode = jsonResponse.code - task.code = jsonResponse.code // backward compatibility to fetch() - task.acknowledged = false - task.meta = jsonResponse.meta - - if (task.callback_event) { - if (task.callback_event.startsWith('store:')) { - rootStore.dispatch(task.callback_event.split(':')[1], task) - } else { - EventBus.$emit(task.callback_event, task) - } - } - if (!rootStore.state.userTokenInvalidateLock) { // in case we're revalidaing the token - user must wait for it - resolve(task) - } - } else { - const msg = i18n.t('Unhandled error, wrong response format!') - Logger.error(msg.toString(), 'sync')() - reject(msg) - } - }).catch((err) => { - Logger.error(err, 'sync')() - reject(err) - }) -} - -export function execute (task: Task, currentToken = null, currentCartId = null): Promise { - const taskId = task.task_id - - return new Promise((resolve, reject) => { - _internalExecute(resolve, reject, task, currentToken, currentCartId) - }) -} - -export function initializeSyncTaskStorage () { - const storeView = currentStoreView() - const dbNamePrefix = storeView.storeCode ? storeView.storeCode + '-' : '' - - StorageManager.init('syncTasks') -} - -export function registerSyncTaskProcessor () { - const mutex = {} - EventBus.$on('sync/PROCESS_QUEUE', async data => { - if (onlineHelper.isOnline) { - // event.data.config - configuration, endpoints etc - const syncTaskCollection = StorageManager.get('syncTasks') - const currentUserToken = rootStore.getters['user/getUserToken'] - const currentCartToken = rootStore.getters['cart/getCartToken'] - - const fetchQueue = [] - Logger.debug('Current User token = ' + currentUserToken)() - Logger.debug('Current Cart token = ' + currentCartToken)() - syncTaskCollection.iterate((task, id) => { - if (task && !task.transmited && !mutex[id]) { // not sent to the server yet - mutex[id] = true // mark this task as being processed - fetchQueue.push(execute(task, currentUserToken, currentCartToken).then(executedTask => { - if (!executedTask.is_result_cacheable) { - syncTaskCollection.removeItem(id) // remove successfully executed task from the queue - } else { - syncTaskCollection.setItem(id, executedTask) // update the 'transmitted' field - } - mutex[id] = false - }).catch(err => { - mutex[id] = false - Logger.error(err)() - })) - } - }, (err) => { - if (err) Logger.error(err)() - Logger.debug('Iteration has completed')() - // execute them serially - serial(fetchQueue) - Logger.debug('Processing sync tasks queue has finished')() - }) - } - }) -} diff --git a/core/lib/sync/types/Task.ts b/core/lib/sync/types/Task.ts deleted file mode 100644 index 1f723160b3..0000000000 --- a/core/lib/sync/types/Task.ts +++ /dev/null @@ -1,15 +0,0 @@ -export default interface Task { - acknowledged: boolean, - callback_event: string, - code: number, - payload: any, - result: any, - resultCode: number, - silent: boolean, - task_id: number, - transmited: boolean, - transmited_at: Date, - url: string, - is_result_cacheable?: boolean, - meta: any -} diff --git a/core/lib/test/unit/logger.spec.ts b/core/lib/test/unit/logger.spec.ts deleted file mode 100644 index 6713f7a1ad..0000000000 --- a/core/lib/test/unit/logger.spec.ts +++ /dev/null @@ -1,403 +0,0 @@ -import * as coreHelper from '@vue-storefront/core/helpers' -import config from 'config' - -jest.mock('config', () => ({})) -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true - } -})) - -describe('Logger', () => { - const isServerSpy = jest.spyOn((coreHelper as any).default, 'isServer', 'get') - const consoleDebugSpy = jest.spyOn(console, 'debug') - const consoleErrorSpy = jest.spyOn(console, 'error') - const consoleInfoSpy = jest.spyOn(console, 'log') - const consoleWarnSpy = jest.spyOn(console, 'warn') - const env = process.env - - consoleDebugSpy.mockImplementation(() => {}) - consoleErrorSpy.mockImplementation(() => {}) - consoleInfoSpy.mockImplementation(() => {}) - consoleWarnSpy.mockImplementation(() => {}) - - beforeEach(() => { - jest.clearAllMocks() - config.console = { - verbosityLevel: 'display-everything', - showErrorOnProduction: true - } - process.env = Object.assign({}, env) - }) - - it('can be initialized with default settings', () => { - jest.isolateModules(() => { - config.console = {} - - expect(require('../../logger').Logger).toBeTruthy() - }) - }) - - describe('convertToString', () => { - it('serializes objects to string', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - expect(Logger.convertToString({ foo: 'bar' })).toBe('{"foo":"bar"}') - }) - }) - - it('extracts message from complex objects (i.e. error)', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - expect(Logger.convertToString({ message: 'foo' })).toBe('foo') - }) - }) - it('returns primitive payloads unchanged', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - expect(Logger.convertToString('foo')).toBe('foo') - expect(Logger.convertToString(true)).toBe(true) - expect(Logger.convertToString(1337)).toBe(1337) - }) - }) - }) - - describe('canPrint', () => { - it('allows all types of logs when verbosity is set to display-everything in dev mode', () => { - expect.assertions(4) - - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - for (let method of ['info', 'warn', 'error', 'debug']) { - expect(Logger.canPrint(method)).toBeTruthy() - } - }) - }) - - it('allows showing errors when verbosity is set to only-errors in dev mode', () => { - jest.isolateModules(() => { - config.console.verbosityLevel = 'only-errors' - - const Logger = require('../../logger').Logger - - expect(Logger.canPrint('error')).toBeTruthy() - - for (let method of ['info', 'warn', 'debug']) { - expect(Logger.canPrint(method)).toBeFalsy() - } - }) - }) - - it('allows showing errors when verbosity is set to only-errors in prod mode but showErrorOnProduction is set', () => { - jest.isolateModules(() => { - config.console.verbosityLevel = 'only-errors' - process.env.NODE_ENV = 'production' - - const Logger = require('../../logger').Logger - - expect(Logger.canPrint('error')).toBeTruthy() - - for (let method of ['info', 'warn', 'debug']) { - expect(Logger.canPrint(method)).toBeFalsy() - } - }) - }) - - it('does not show any logs when verbosity is set to none', () => { - jest.isolateModules(() => { - config.console.verbosityLevel = 'none' - - const Logger = require('../../logger').Logger - - for (let method of ['info', 'warn', 'debug', 'error']) { - expect(Logger.canPrint(method)).toBeFalsy() - } - }) - }) - }) - - describe('debug', () => { - it('doesn\'t display message if logger is configured not to log it', () => { - jest.isolateModules(() => { - config.console.verbosityLevel = 'none' - - const Logger = require('../../logger').Logger - - Logger.debug('test', null, null)() - expect(consoleDebugSpy).not.toBeCalled() - }) - }) - - it('displays message without tag if none was given in SSR and logger is configured to log everything', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.debug('test')() - expect(consoleDebugSpy).toBeCalledWith('test', null) - }) - }) - - it('displays message with tag if one was given in SSR and logger is configured to log everything', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.debug('test', 'tag')() - expect(consoleDebugSpy).toBeCalledWith('[tag] test', null) - }) - }) - - it('displays message without tag given no tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.debug('test')() - expect(consoleDebugSpy).toBeCalledWith( - '%cVSF%c test', - expect.anything(), - expect.anything(), - null - ) - }) - }) - - it('displays message with tag given a tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.debug('test', 'tag')() - expect(consoleDebugSpy).toBeCalledWith( - '%cVSF%c %ctag%c test', - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - null - ) - }) - }) - }) - - describe('log', () => { - it('works as an alias of info', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.log('test')() - expect(consoleInfoSpy).toBeCalled() - }) - }) - - it('can be called with custom tag and context', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.log('test', 'tag', 'context')() - expect(consoleInfoSpy).toBeCalledWith( - '%cVSF%c %ctag%c test', - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - 'context' - ) - }) - }) - }) - - describe('info', () => { - it('doesn\'t display message if logger is configured not to log it', () => { - jest.isolateModules(() => { - config.console.verbosityLevel = 'none' - - const Logger = require('../../logger').Logger - - Logger.info('test', null, null)() - expect(consoleInfoSpy).not.toBeCalled() - }) - }) - - it('displays message without tag if none was given in SSR and logger is configured to log everything', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.info('test')() - expect(consoleInfoSpy).toBeCalledWith('test', null) - }) - }) - - it('displays message with tag if one was given in SSR and logger is configured to log everything', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.info('test', 'tag')() - expect(consoleInfoSpy).toBeCalledWith('[tag] test', null) - }) - }) - - it('displays message without tag given no tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.info('test')() - expect(consoleInfoSpy).toBeCalledWith( - '%cVSF%c test', - expect.anything(), - expect.anything(), - null - ) - }) - }) - - it('displays message with tag given a tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.info('test', 'tag')() - expect(consoleInfoSpy).toBeCalledWith( - '%cVSF%c %ctag%c test', - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - null - ) - }) - }) - }) - - describe('warn', () => { - it('doesn\'t display message if logger is configured not to log it', () => { - jest.isolateModules(() => { - config.console.verbosityLevel = 'none' - - const Logger = require('../../logger').Logger - - Logger.warn('test', null, null)() - expect(consoleWarnSpy).not.toBeCalled() - }) - }) - - it('displays message without tag if none was given in SSR and logger is configured to log everything', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.warn('test')() - expect(consoleWarnSpy).toBeCalledWith('test', null) - }) - }) - - it('displays message with tag if one was given in SSR and logger is configured to log everything', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.warn('test', 'tag')() - expect(consoleWarnSpy).toBeCalledWith('[tag] test', null) - }) - }) - - it('displays message without tag given no tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.warn('test')() - expect(consoleWarnSpy).toBeCalledWith( - '%cVSF%c test', - expect.anything(), - expect.anything(), - null - ) - }) - }) - - it('displays message with tag given a tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.warn('test', 'tag')() - expect(consoleWarnSpy).toBeCalledWith( - '%cVSF%c %ctag%c test', - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - null - ) - }) - }) - }) - - describe('error', () => { - it('always displays messages in SSR', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.error('test')() - expect(consoleErrorSpy).toBeCalled() - }) - }) - - it('displays message with tag if one was given in SSR and logger is configured to log everything', () => { - jest.isolateModules(() => { - const Logger = require('../../logger').Logger - - Logger.error('test', 'tag')() - expect(consoleErrorSpy).toBeCalledWith('[tag] test', null) - }) - }) - - it('doesn\'t display message if logger is configured not to log it and not in SSR mode', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - config.console.verbosityLevel = 'none' - - const Logger = require('../../logger').Logger - - Logger.error('test', null, null)() - expect(consoleErrorSpy).not.toBeCalled() - }) - }) - - it('displays message without tag given no tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.error('test')() - expect(consoleErrorSpy).toBeCalledWith( - '%cVSF%c test', - expect.anything(), - expect.anything(), - null - ) - }) - }) - - it('displays message with tag given a tag and logger is configured to log everything', () => { - jest.isolateModules(() => { - isServerSpy.mockReturnValueOnce(false) - const Logger = require('../../logger').Logger - - Logger.error('test', 'tag')() - expect(consoleErrorSpy).toBeCalledWith( - '%cVSF%c %ctag%c test', - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - null - ) - }) - }) - }) -}) diff --git a/core/lib/test/unit/multistore.spec.ts b/core/lib/test/unit/multistore.spec.ts deleted file mode 100644 index d2cf2b2f2b..0000000000 --- a/core/lib/test/unit/multistore.spec.ts +++ /dev/null @@ -1,737 +0,0 @@ -import storeCodeFromRoute from '@vue-storefront/core/lib/storeCodeFromRoute' -import { LocalizedRoute } from '@vue-storefront/core/lib/types' -import { - prepareStoreView, - adjustMultistoreApiUrl, - localizedDispatcherRoute, - setupMultistoreRoutes, - localizedRoutePath, - localizedRouteConfig -} from '@vue-storefront/core/lib/multistore' -import config from 'config' -import rootStore from '@vue-storefront/core/store'; -import { router } from '@vue-storefront/core/app'; -import { RouteConfig } from 'vue-router' - -jest.mock('@vue-storefront/core/app', () => ({ - createApp: jest.fn(), - router: { - addRoutes: jest.fn() - } -})) -jest.mock('../../../store', () => ({})) -jest.mock('@vue-storefront/i18n', () => ({ loadLanguageAsync: jest.fn() })) -jest.mock('../../sync/task', () => ({ initializeSyncTaskStorage: jest.fn() })) -jest.mock('@vue-storefront/core/hooks', () => ({ coreHooksExecutors: { - beforeStoreViewChanged: jest.fn(args => args), - afterStoreViewChanged: jest.fn(args => args) -} })) -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: {} -})) -jest.mock('config', () => ({})) - -describe('Multistore', () => { - beforeEach(() => { - jest.clearAllMocks(); - (rootStore as any).state = {}; - Object.keys(config).forEach((key) => { delete config[key]; }); - rootStore.state.storeView = { - appendStoreCode: true - } - }) - - describe('storeCodeFromRoute', () => { - it('returns store code given url matching a storeview by path', () => { - config.storeViews = { - mapStoreUrlsFor: ['us'], - us: { - url: '/us' - } - }; - - expect(storeCodeFromRoute('/us')).toBe('us') - }) - - it('returns store code given a route matching a storeview by path', () => { - config.storeViews = { - mapStoreUrlsFor: ['us'], - us: { - url: '/us' - } - }; - - const route = { path: 'us' } - - expect(storeCodeFromRoute(route)).toBe('us') - }) - - it('returns store code given a url matching a storeview by url', () => { - config.storeViews = { - mapStoreUrlsFor: ['us', 'gb'], - us: { - url: '/us' - }, - gb: { - url: 'domain.co.uk' - } - }; - - const route = 'domain.co.uk' - - expect(storeCodeFromRoute(route)).toBe('gb') - }) - - it('returns store code given a url matching a storeview by url with path', () => { - config.storeViews = { - mapStoreUrlsFor: ['us', 'gb'], - us: { - url: '/us' - }, - gb: { - url: 'domain.co.uk/gb' - } - }; - - const route = 'domain.co.uk/gb/foo' - - expect(storeCodeFromRoute(route)).toBe('gb') - }) - - it('returns store code given a route matching a storeview by url with path', () => { - config.storeViews = { - mapStoreUrlsFor: ['us', 'gb'], - us: { - url: '/us' - }, - gb: { - url: 'domain.co.uk/gb' - } - }; - - const route = { - host: 'domain.co.uk', - path: 'gb/foo' - } - - expect(storeCodeFromRoute(route)).toBe('gb') - }) - - it('returns empty string if given a route which doesn\'t have url and is not matched by path', () => { - config.storeViews = { - mapStoreUrlsFor: ['us', 'gb'], - us: { - url: '/us' - }, - gb: { - url: 'domain.co.uk' - } - }; - - const route = { path: 'gb' } - - expect(storeCodeFromRoute(route)).toBe('') - }) - - it('returns empty string if route was not matched to any store', () => { - config.storeViews = { - mapStoreUrlsFor: ['us'], - us: { - url: 'us.domain.tld' - } - }; - - expect(storeCodeFromRoute('unknown-domain.tld')).toBe('') - }) - - it('returns empty string if route is not given', () => { - expect(storeCodeFromRoute('')).toBe('') - }) - }) - - describe('prepareStoreView', () => { - it('returns default storeView given no storecode', async () => { - rootStore.state.storeView = {} - rootStore.state.user = {} - - config.storeViews = { - multistore: false - } - - config.tax = { - defaultCountry: 'US' - } - - config.i18n = { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - } - - config.elasticsearch = { - index: 'vue_storefront_catalog' - } - config.defaultStoreCode = '' - - config.seo = { - defaultTitle: 'Vue Storefront' - } - - expect(await prepareStoreView(null)).toStrictEqual({ - tax: { - defaultCountry: 'US' - }, - i18n: { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - }, - seo: { - defaultTitle: 'Vue Storefront' - }, - elasticsearch: { - index: 'vue_storefront_catalog' - }, - storeId: 1, - storeCode: '' - }) - }) - - it('returns default storeView without setting defaultStoreCode when multistore mode is disabled', async () => { - rootStore.state.storeView = {} - rootStore.state.user = {} - - config.storeViews = { - multistore: false, - de: { - storeId: 4 - } - } - - config.tax = { - defaultCountry: 'US' - } - - config.i18n = { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - } - - config.elasticsearch = { - index: 'vue_storefront_catalog' - } - config.defaultStoreCode = 'de' - - config.seo = { - defaultTitle: 'Vue Storefront' - } - - expect(await prepareStoreView(null)).toStrictEqual({ - tax: { - defaultCountry: 'US' - }, - i18n: { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - }, - seo: { - defaultTitle: 'Vue Storefront' - }, - elasticsearch: { - index: 'vue_storefront_catalog' - }, - storeId: 4, - storeCode: '' - }) - }) - - it('returns default storeView with defaultStoreCode set when multistore mode is enabled', async () => { - rootStore.state.storeView = {} - rootStore.state.user = {} - - config.storeViews = { - multistore: true, - de: { - storeId: 4 - } - } - - config.tax = { - defaultCountry: 'US' - } - - config.i18n = { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - } - - config.seo = { - defaultTitle: 'Vue Storefront' - } - - config.elasticsearch = { - index: 'vue_storefront_catalog' - } - config.defaultStoreCode = 'de' - - expect(await prepareStoreView(null)).toStrictEqual({ - tax: { - defaultCountry: 'US' - }, - i18n: { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - }, - seo: { - defaultTitle: 'Vue Storefront' - }, - elasticsearch: { - index: 'vue_storefront_catalog' - }, - storeId: 4, - storeCode: 'de' - }) - }) - - it('returns storeView overwritting default store config values when multistore mode is enabled', async () => { - rootStore.state.storeView = {} - rootStore.state.user = {} - - config.storeViews = { - multistore: true, - de: { - storeCode: 'de', - storeId: 3, - name: 'German Store', - elasticsearch: { - index: 'vue_storefront_catalog_de' - }, - tax: { - defaultCountry: 'DE' - }, - i18n: { - fullCountryName: 'Germany', - fullLanguageName: 'German', - defaultLocale: 'de-DE' - }, - seo: { - defaultTitle: 'Vue Storefront' - } - } - } - - config.tax = { - defaultCountry: 'US' - } - - config.i18n = { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - } - - config.seo = { - defaultTitle: 'Vue Storefront' - } - - config.elasticsearch = { - index: 'vue_storefront_catalog' - } - config.defaultStoreCode = 'de' - - expect(await prepareStoreView(null)).toStrictEqual({ - tax: { - defaultCountry: 'DE' - }, - i18n: { - fullCountryName: 'Germany', - fullLanguageName: 'German', - defaultLocale: 'de-DE' - }, - seo: { - defaultTitle: 'Vue Storefront' - }, - elasticsearch: { - index: 'vue_storefront_catalog_de' - }, - storeId: 3, - name: 'German Store', - storeCode: 'de' - }) - }) - - it('returns storeView extending other storeView in multistore mode', async () => { - rootStore.state.storeView = {} - rootStore.state.user = {} - - config.storeViews = { - multistore: true, - de: { - storeCode: 'de', - storeId: 3, - name: 'German Store', - elasticsearch: { - index: 'vue_storefront_catalog_de' - }, - tax: { - defaultCountry: 'DE' - }, - i18n: { - fullCountryName: 'Germany', - fullLanguageName: 'German', - defaultLocale: 'de-DE' - }, - seo: { - defaultTitle: 'Vue Storefront' - } - }, - it: { - extend: 'de', - storeCode: 'it', - storeId: 4, - name: 'Italian Store', - elasticsearch: { - index: 'vue_storefront_catalog_it' - }, - tax: { - defaultCountry: 'IT' - }, - i18n: { - fullCountryName: 'Italy', - fullLanguageName: 'Italian', - defaultLocale: 'it-IT' - }, - seo: { - defaultTitle: 'Vue Storefront' - } - } - } - - config.tax = { - defaultCountry: 'US' - } - - config.i18n = { - defaultLocale: 'en-US', - fullCountryName: 'United States', - fullLanguageName: 'English' - } - - config.seo = { - defaultTitle: 'Vue Storefront' - } - - config.elasticsearch = { - index: 'vue_storefront_catalog' - } - config.defaultStoreCode = 'it' - - expect(await prepareStoreView(null)).toStrictEqual({ - tax: { - defaultCountry: 'IT' - }, - i18n: { - fullCountryName: 'Italy', - fullLanguageName: 'Italian', - defaultLocale: 'it-IT' - }, - seo: { - defaultTitle: 'Vue Storefront' - }, - elasticsearch: { - index: 'vue_storefront_catalog_it' - }, - storeId: 4, - extend: 'de', - name: 'Italian Store', - storeCode: 'it' - }) - }) - }) - - describe('adjustMultistoreApiUrl', () => { - it('returns URL /test without storeCode as parameter', () => { - rootStore.state.storeView = { - storeCode: null - } - - expect(adjustMultistoreApiUrl('/test')).toStrictEqual('/test') - }) - - it('returns URL /test with storeCode de as parameter', () => { - rootStore.state.storeView = { - storeCode: 'de' - } - - expect(adjustMultistoreApiUrl('/test')).toStrictEqual('/test?storeCode=de') - }) - - it('returns URL /test?a=b with storeCode de as parameter and current parameters from the URL', () => { - rootStore.state.storeView = { - storeCode: 'de' - } - - expect(adjustMultistoreApiUrl('/test?a=b')).toStrictEqual('/test?a=b&storeCode=de') - }) - - it('returns URL /test?a=b&storeCode=de with added storeCode at as parameter and removes previous storeCode parameter', () => { - rootStore.state.storeView = { - storeCode: 'at' - } - - expect(adjustMultistoreApiUrl('/test?a=b&storeCode=de')).toStrictEqual('/test?a=b&storeCode=at') - }) - - it('returns URL /test?storeCode=de with changed storeCode at as parameter', () => { - rootStore.state.storeView = { - storeCode: 'at' - } - - expect(adjustMultistoreApiUrl('/test?storeCode=de')).toStrictEqual('/test?storeCode=at') - }) - - it('returns URL /test?storeCode=de with changed storeCode at as parameter', () => { - rootStore.state.storeView = { - storeCode: 'at' - } - - expect(adjustMultistoreApiUrl('/test?storeCode=de&storeCode=de')).toStrictEqual('/test?storeCode=at') - }) - }) - - describe('localizedDispatcherRoute', () => { - it('URL /test stays the same', () => { - config.storeViews = {} - - expect(localizedDispatcherRoute('/test', 'de')).toStrictEqual('/test') - }) - - it('URL /test starts with /de', () => { - config.storeViews = { - de: { - appendStoreCode: true - } - } - - expect(localizedDispatcherRoute('/test', 'de')).toStrictEqual('/de/test') - }) - - it('URL /test?a=b&b=a stays the same', () => { - config.storeViews = {} - - expect(localizedDispatcherRoute('/test?a=b&b=a', 'de')).toStrictEqual('/test?a=b&b=a') - }) - - it('URL /test?a=b&b=a starts with /de', () => { - config.storeViews = { - de: { - appendStoreCode: true - } - } - - expect(localizedDispatcherRoute('/test?a=b&b=a', 'de')).toStrictEqual('/de/test?a=b&b=a') - }) - - it('URL with LocalizedRoute object with fullPath test gets prefixed with /de', () => { - config.storeViews = {} - - const LocalizedRoute: LocalizedRoute = { - fullPath: 'test' - } - - expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/test') - }) - - it('URL with LocalizedRoute object with fullPath and parameter test stays the same', () => { - config.storeViews = {} - - const LocalizedRoute: LocalizedRoute = { - fullPath: 'test', - params: { - a: 'b', - b: 'a' - } - } - - expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/test?a=b&b=a') - }) - - it('URL with LocalizedRoute object with fullPath test gets prefixed with /de', () => { - config.storeViews = { - de: { - appendStoreCode: true - } - } - - const LocalizedRoute: LocalizedRoute = { - fullPath: 'test' - } - - expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/de/test') - }) - - it('URL with LocalizedRoute object with fullPath test and params gets prefixed with /de', () => { - config.storeViews = { - de: { - appendStoreCode: true - } - } - - const LocalizedRoute: LocalizedRoute = { - fullPath: 'test', - params: { - a: 'b', - b: 'a' - } - } - - expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/de/test?a=b&b=a') - }) - }) - - describe('setupMultistoreRoutes', () => { - it('Add new routes for each store in mapStoreUrlsFor', () => { - config.storeViews = { - 'de': { - appendStoreCode: true - }, - mapStoreUrlsFor: [ - 'de' - ], - multistore: true - } - config.seo = { - useUrlDispatcher: true - } - - const routeConfig: RouteConfig[] = [ - { - path: 'test' - }, - { - path: 'test2' - } - ] - - setupMultistoreRoutes(config, router, routeConfig) - - expect(router.addRoutes).toBeCalledTimes(1) - }) - - it('Do nothing as mapStoreUrlsFor is empty', () => { - config.storeViews = { - 'de': { - }, - mapStoreUrlsFor: [] - } - - const routeConfig: RouteConfig[] = [ - { - path: 'test' - }, - { - path: 'test2' - } - ] - - setupMultistoreRoutes(config, router, routeConfig) - - expect(router.addRoutes).toBeCalledTimes(1) - }) - }) - - describe('localizedRoutePath', () => { - it('add storeCode to route path with slash', () => { - const storeCode = 'de' - const path = '/test' - - expect(localizedRoutePath(path, storeCode)).toBe('/de/test') - }) - - it('add storeCode to route path without slash', () => { - const storeCode = 'de' - const path = 'test' - - expect(localizedRoutePath(path, storeCode)).toBe('/de/test') - }) - - it('add storeCode to route path with hash', () => { - const storeCode = 'de' - const path = '/test#test' - - expect(localizedRoutePath(path, storeCode)).toBe('/de/test#test') - }) - }) - - describe('localizedRouteConfig', () => { - it('create new route object with storeCode', () => { - const storeCode = 'de' - const route = { - path: '/test', - name: 'test' - } - const expectedRoute = { - path: '/de/test', - name: 'de-test' - } - - expect(localizedRouteConfig(route, storeCode)).toEqual(expectedRoute) - }) - - it('change only route name for child route', () => { - const storeCode = 'de' - const childRoute = { - path: '/test2', - name: 'test2' - } - const expectedRoute = { - path: '/test2', - name: 'de-test2' - } - - expect(localizedRouteConfig(childRoute, storeCode, true)).toEqual(expectedRoute) - }) - - it('add localization for nested routes', () => { - const storeCode = 'de' - const route = { - path: '/test', - name: 'test', - children: [ - { - path: 'test2', - name: 'test2', - children: [ - { - path: '/test3', - name: 'test3' - } - ] - } - ] - } - const expectedRoute = { - path: '/de/test', - name: 'de-test', - children: [ - { - path: 'test2', - name: 'de-test2', - children: [ - { - path: '/test3', - name: 'de-test3' - } - ] - } - ] - } - - expect(localizedRouteConfig(route, storeCode)).toEqual(expectedRoute) - }) - }) -}) diff --git a/core/lib/themes.ts b/core/lib/themes.ts deleted file mode 100644 index e33b119367..0000000000 --- a/core/lib/themes.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function registerTheme (themeName, app, routes, store, config, ssrContext) { - const themeEntryPoint = require('theme/index.js') - if (themeEntryPoint != null && themeEntryPoint.initTheme) { - themeEntryPoint.initTheme(app, routes, store, config, ssrContext) // register theme - } else { - throw new Error('Wrong theme name: ' + themeName) - } -} diff --git a/core/lib/types.ts b/core/lib/types.ts deleted file mode 100644 index 8a57fc78a8..0000000000 --- a/core/lib/types.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { PathToRegexpOptions } from 'vue-router/types/router'; - -export interface LocalizedRoute { - path?: string, - name?: string, - hash?: string, - params?: { [key: string]: unknown }, - fullPath?: string, - host?: string, - pathToRegexpOptions?: PathToRegexpOptions -} - -export interface StoreView { - storeCode: string, - extend?: string, - disabled?: boolean, - storeId: any, - name?: string, - url?: string, - appendStoreCode?: boolean, - elasticsearch: { - host: string, - index: string - }, - tax: { - sourcePriceIncludesTax?: boolean, - finalPriceIncludesTax?: boolean, - deprecatedPriceFieldsSupport?: boolean, - defaultCountry: string, - defaultRegion: null | string, - calculateServerSide: boolean, - userGroupId?: number, - useOnlyDefaultUserGroupId: boolean - }, - i18n: { - fullCountryName: string, - fullLanguageName: string, - defaultLanguage: string, - defaultCountry: string, - defaultLocale: string, - currencyCode: string, - currencySign: string, - currencyDecimal: string, - currencyGroup: string, - fractionDigits: number, - priceFormat: string, - dateFormat: string - }, - seo: { - defaultTitle: string - } -} diff --git a/core/mixins/composite.js b/core/mixins/composite.js deleted file mode 100644 index 79345e6ebe..0000000000 --- a/core/mixins/composite.js +++ /dev/null @@ -1,27 +0,0 @@ -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { Logger } from '@vue-storefront/core/lib/logger' - -// @deprecated from 2.0 -export default { - beforeCreated () { - const eventName = this.$options.name.toLowerCase() + '-before-created' - Logger.debug(eventName, 'event')() - EventBus.$emit(eventName, this) - }, - created () { - const eventName = this.$options.name.toLowerCase() + '-after-created' - Logger.debug(eventName, 'event')() - EventBus.$emit(eventName, this) - }, - beforeMount () { - const eventName = this.$options.name.toLowerCase() + '-before-mount' - Logger.debug(eventName, 'event')() - EventBus.$emit(eventName, this) - }, - mounted () { - const eventName = this.$options.name.toLowerCase() + '-after-mounted' - Logger.debug(eventName, 'event')() - EventBus.$emit(eventName, this) - } - -} diff --git a/core/mixins/index.js b/core/mixins/index.js deleted file mode 100644 index 990684de30..0000000000 --- a/core/mixins/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import { thumbnail } from './thumbnail.js' -import { multistore } from './multistore.js' - -export { - thumbnail, - multistore -} diff --git a/core/mixins/multistore.js b/core/mixins/multistore.js deleted file mode 100644 index 8964566055..0000000000 --- a/core/mixins/multistore.js +++ /dev/null @@ -1,29 +0,0 @@ -import { localizedRoute as localizedRouteHelper, localizedDispatcherRoute as localizedDispatcherRouteHelper, currentStoreView } from '@vue-storefront/core/lib/multistore' -import { isServer } from '../helpers'; - -export const multistore = { - methods: { - /** - * Return localized route params - * @param {String} relativeUrl - * @param {Int} width - * @param {Int} height - */ - localizedRoute (routeObj) { - const storeView = currentStoreView() - - return localizedRouteHelper(routeObj, storeView.storeCode) - }, - /** - * Return localized route params for URL Dispatcher - * @param {String} relativeUrl - * @param {Int} width - * @param {Int} height - */ - localizedDispatcherRoute (routeObj) { - const storeView = currentStoreView() - - return localizedDispatcherRouteHelper(routeObj, storeView.storeCode) - } - } -} diff --git a/core/mixins/onBottomScroll.js b/core/mixins/onBottomScroll.js deleted file mode 100644 index 5f8402791e..0000000000 --- a/core/mixins/onBottomScroll.js +++ /dev/null @@ -1,32 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers' - -const isBottomVisible = () => { - if (isServer) { - return false - } - const SAFETY_MARGIN = 20 - const scrollY = window.scrollY - const visible = window.innerHeight - const pageHeight = document.documentElement.scrollHeight - const bottomOfPage = scrollY + SAFETY_MARGIN >= pageHeight - visible - - return bottomOfPage || pageHeight < visible -} - -/** - * By implementing this mixin add "onBottomScroll" method in component. - * It will be invoked when view reach the bottom. - */ -export default { - mounted () { - const scrollHandler = () => { - if (isBottomVisible()) { - this.onBottomScroll() - } - } - document.addEventListener('scroll', scrollHandler) - this.$once('hook:destroyed', () => { - document.removeEventListener('scroll', scrollHandler) - }) - } -} diff --git a/core/mixins/onEscapePress.js b/core/mixins/onEscapePress.js deleted file mode 100644 index 0876333d89..0000000000 --- a/core/mixins/onEscapePress.js +++ /dev/null @@ -1,14 +0,0 @@ -export default { - mounted () { - const keydownHandler = (e) => { - // for old browser support as a fallback - if (e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) { - this.onEscapePress() - } - } - document.addEventListener('keydown', keydownHandler) - this.$once('hook:destroyed', () => { - document.removeEventListener('keydown', keydownHandler) - }) - } -} diff --git a/core/mixins/thumbnail.js b/core/mixins/thumbnail.js deleted file mode 100644 index 0ffff030fc..0000000000 --- a/core/mixins/thumbnail.js +++ /dev/null @@ -1,24 +0,0 @@ -import { getThumbnailPath } from '@vue-storefront/core/helpers' - -export const thumbnail = { - methods: { - /** - * Return thumbnail URL for specific base url and path - * @param {string} relativeUrl - * @param {number} width - * @param {number} height - * @param {string} pathType - * @returns {string} - */ - getThumbnail: (relativeUrl, width, height, pathType) => getThumbnailPath(relativeUrl, width, height, pathType), - - /** - * Return thumbnail URL for specific base url using media path - * @param {string} relativeUrl - * @param {number} width - * @param {number} height - * @returns {string} - */ - getMediaThumbnail: (relativeUrl, width, height) => getThumbnailPath(relativeUrl, width, height, 'media') - } -} diff --git a/core/modules-entry.ts b/core/modules-entry.ts deleted file mode 100644 index fd8f38bf66..0000000000 --- a/core/modules-entry.ts +++ /dev/null @@ -1,8 +0,0 @@ - -import { VueStorefrontModule } from '@vue-storefront/core/lib/module' -import { registerModules } from 'src/modules/client' - -// @deprecated from 2.0, use registerModule instead -export const enabledModules: VueStorefrontModule[] = [ - ...registerModules -] diff --git a/core/modules/breadcrumbs/README.md b/core/modules/breadcrumbs/README.md deleted file mode 100644 index fc428990f6..0000000000 --- a/core/modules/breadcrumbs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This module is not finished. - -So far it's save to use in: -- Product page \ No newline at end of file diff --git a/core/modules/breadcrumbs/components/Breadcrumbs.ts b/core/modules/breadcrumbs/components/Breadcrumbs.ts deleted file mode 100644 index b0c097acbf..0000000000 --- a/core/modules/breadcrumbs/components/Breadcrumbs.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { localizedRoute, currentStoreView } from '@vue-storefront/core/lib/multistore' -import i18n from '@vue-storefront/i18n' -import { mapGetters } from 'vuex' - -export const Breadcrumbs = { - computed: { - ...mapGetters({ - getBreadcrumbsRoutes: 'breadcrumbs/getBreadcrumbsRoutes', - getBreadcrumbsCurrent: 'breadcrumbs/getBreadcrumbsCurrent' - }), - paths () { - const routes = this.routes ? this.routes : this.getBreadcrumbsRoutes - - if (this.withHomepage) { - return [ - { name: i18n.t('Homepage'), route_link: localizedRoute('/', currentStoreView().storeCode) }, - ...routes - ] - } - - return routes - }, - current () { - return this.activeRoute || this.getBreadcrumbsCurrent - } - }, - props: { - routes: { - type: Array, - required: false, - default: null - }, - withHomepage: { - type: Boolean, - default: false - }, - activeRoute: { - type: String, - default: '' - } - } -} diff --git a/core/modules/breadcrumbs/helpers/index.ts b/core/modules/breadcrumbs/helpers/index.ts deleted file mode 100644 index 18f3e7d8b8..0000000000 --- a/core/modules/breadcrumbs/helpers/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { formatCategoryLink } from '@vue-storefront/core/modules/url/helpers' - -// Duplicate of breadCrumbRoutes, to repalce it soon. -/** Parse category path for product/category */ -export function parseCategoryPath (categoryPath) { - let routesArray = [] - for (let category of categoryPath) { - if (category.url_path === undefined || category.url_path === null) continue; - routesArray.push({ - name: category.name, - route_link: formatCategoryLink(category) - }) - } - - return routesArray -} diff --git a/core/modules/breadcrumbs/index.ts b/core/modules/breadcrumbs/index.ts deleted file mode 100644 index fbf2320cb0..0000000000 --- a/core/modules/breadcrumbs/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { breadcrumbsStore } from './store' -import { StorefrontModule } from '@vue-storefront/core/lib/modules' - -export const BreadcrumbsModule: StorefrontModule = function ({ store }) { - store.registerModule('breadcrumbs', breadcrumbsStore) -} diff --git a/core/modules/breadcrumbs/store/index.ts b/core/modules/breadcrumbs/store/index.ts deleted file mode 100644 index 02f195275d..0000000000 --- a/core/modules/breadcrumbs/store/index.ts +++ /dev/null @@ -1,23 +0,0 @@ - -export const breadcrumbsStore = { - namespaced: true, - state: { - routes: [], - current: null - }, - mutations: { - set (state, payload) { - state.routes = payload.routes - state.current = payload.current - } - }, - actions: { - set ({ commit }, payload) { - commit('set', payload) - } - }, - getters: { - getBreadcrumbsRoutes: (state) => state.routes, - getBreadcrumbsCurrent: (state) => state.current - } -} diff --git a/core/modules/breadcrumbs/test/unit/parseCategoryPath.spec.ts b/core/modules/breadcrumbs/test/unit/parseCategoryPath.spec.ts deleted file mode 100644 index 7e357c8e2c..0000000000 --- a/core/modules/breadcrumbs/test/unit/parseCategoryPath.spec.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/helpers'; -import { Category } from '@vue-storefront/core/modules/catalog-next/types/Category'; -import { currentStoreView } from '@vue-storefront/core/lib/multistore'; - -jest.mock('@vue-storefront/core/app', () => jest.fn()); -jest.mock('@vue-storefront/core/lib/router-manager', () => jest.fn()); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedDispatcherRoute: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) - -describe('parseCategoryPath method', () => { - describe('on category page', () => { - let categories: Category[]; - - beforeEach(() => { - jest.clearAllMocks(); - (currentStoreView as jest.Mock).mockImplementation(() => ({ storeCode: '' })); - categories = [ - { - path: '1/2', - is_active: true, - level: 1, - product_count: 1181, - children_count: '38', - parent_id: 1, - name: 'All', - id: 2, - url_key: 'all-2', - children_data: [], - url_path: 'all-2', - slug: 'all-2' - }, - { path: '1/2/20', - is_active: true, - level: 2, - product_count: 0, - children_count: '8', - parent_id: 2, - name: 'Women', - id: 20, - url_path: 'women/women-20', - url_key: 'women-20', - children_data: [], - slug: 'women-20' - }, - { - path: '1/2/20/21', - is_active: true, - level: 3, - product_count: 0, - children_count: '4', - parent_id: 20, - name: 'Tops', - id: 21, - url_path: 'women/tops-women/tops-21', - url_key: 'tops-21', - children_data: [], - slug: 'tops-21' - } - ]; - }); - - it('should return formatted category path for breadcrumbs', () => { - const result = parseCategoryPath(categories); - const expected = [ - { - name: 'All', - route_link: '/all-2' - }, - { - name: 'Women', - route_link: '/women/women-20' - }, - { - name: 'Tops', - route_link: '/women/tops-women/tops-21' - } - ]; - expect(result).toEqual(expected); - }); - }); - - describe('on product page', () => { - let categories; - - beforeEach(() => { - jest.clearAllMocks(); - (currentStoreView as jest.Mock).mockImplementation(() => ({ storeCode: '' })); - categories = [ - { - path: '1/2', - is_active: true, - level: 1, - product_count: 1181, - children_count: '38', - parent_id: 1, - name: 'All', - id: 2, - url_key: 'all-2', - children_data: [], - url_path: 'all-2', - slug: 'all-2' - }, - { path: '1/2/20', - is_active: true, - level: 2, - product_count: 0, - children_count: '8', - parent_id: 2, - name: 'Women', - id: 20, - url_path: 'women/women-20', - url_key: 'women-20', - children_data: [], - slug: 'women-20' - }, - { - path: '1/2/20/21', - is_active: true, - level: 3, - product_count: 0, - children_count: '4', - parent_id: 20, - name: 'Tops', - id: 21, - url_path: 'women/tops-women/tops-21', - url_key: 'tops-21', - children_data: [], - slug: 'tops-21' - }, - { - path: '1/2/20/21/23', - is_active: true, - level: 4, - product_count: 186, - children_count: '0', - parent_id: 21, - name: 'Jackets', - id: 23, - url_key: 'jackets-23', - url_path: 'women/tops-women/jackets-women/jackets-23', - slug: 'jackets-23' - } - ]; - }); - - it('should return formatted category path for breadcrumbs', () => { - const result = parseCategoryPath(categories); - const expected = [ - { - name: 'All', - route_link: '/all-2' - }, - { - name: 'Women', - route_link: '/women/women-20' - }, - { - name: 'Tops', - route_link: '/women/tops-women/tops-21' - }, - { - name: 'Jackets', - route_link: '/women/tops-women/jackets-women/jackets-23' - } - ]; - expect(result).toEqual(expected); - }); - }); -}); diff --git a/core/modules/cart/components/AddToCart.ts b/core/modules/cart/components/AddToCart.ts deleted file mode 100644 index 73f447159b..0000000000 --- a/core/modules/cart/components/AddToCart.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import { Logger } from '@vue-storefront/core/lib/logger'; - -// @deprecated moved to store -export const AddToCart = { - name: 'AddToCart', - data () { - return { - isAddingToCart: false - } - }, - props: { - product: { - required: true, - type: Object - }, - disabled: { - type: Boolean, - default: false - } - }, - methods: { - async addToCart (product: Product) { - this.isAddingToCart = true - try { - const diffLog = await this.$store.dispatch('cart/addItem', { productToAdd: product }) - - if (diffLog) { - if (diffLog.clientNotifications && diffLog.clientNotifications.length > 0) { - diffLog.clientNotifications.forEach(notificationData => { - this.notifyUser(notificationData) - }) - } - } else { - this.notifyUser({ - type: 'success', - message: this.$t('Product has been added to the cart!'), - action1: { label: this.$t('OK') }, - action2: null - }) - } - return diffLog - } catch (err) { - this.notifyUser({ - type: 'error', - message: err, - action1: { label: this.$t('OK') } - }) - return null - } finally { - this.isAddingToCart = false - } - } - } -} diff --git a/core/modules/cart/components/Microcart.ts b/core/modules/cart/components/Microcart.ts deleted file mode 100644 index 667c2c1aae..0000000000 --- a/core/modules/cart/components/Microcart.ts +++ /dev/null @@ -1,33 +0,0 @@ -import AppliedCoupon from '../types/AppliedCoupon' -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import CartTotalSegments from '../types/CartTotalSegments' - -// @deprecated moved to store -export const Microcart = { - name: 'Microcart', - computed: { - productsInCart (): Product[] { - return this.$store.state.cart.cartItems - }, - appliedCoupon (): AppliedCoupon | false { - return this.$store.getters['cart/getCoupon'] - }, - totals (): CartTotalSegments { - return this.$store.getters['cart/getTotals'] - }, - isOpen (): boolean { - return this.$store.state.cart.isMicrocartOpen - } - }, - methods: { - applyCoupon (code: string): Promise { - return this.$store.dispatch('cart/applyCoupon', code) - }, - removeCoupon (): Promise { - return this.$store.dispatch('cart/removeCoupon') - }, - toggleMicrocart (): void { - this.$store.dispatch('ui/toggleMicrocart') - } - } -} diff --git a/core/modules/cart/components/MicrocartButton.ts b/core/modules/cart/components/MicrocartButton.ts deleted file mode 100644 index 0a2b3e9cf6..0000000000 --- a/core/modules/cart/components/MicrocartButton.ts +++ /dev/null @@ -1,22 +0,0 @@ - -// @deprecated moved to theme -export const MicrocartButton = { - name: 'MicrocartButton', - mounted () { - document.addEventListener('visibilitychange', () => { - if (!document.hidden) { - this.$store.dispatch('cart/load') - } - }) - }, - methods: { - toggleMicrocart () { - this.$store.dispatch('cart/toggleMicrocart') - } - }, - computed: { - quantity () { - return this.$store.getters['cart/getItemsTotalQuantity'] - } - } -} diff --git a/core/modules/cart/components/Product.ts b/core/modules/cart/components/Product.ts deleted file mode 100644 index bd30e7617e..0000000000 --- a/core/modules/cart/components/Product.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { getThumbnailForProduct, getProductConfiguration } from '@vue-storefront/core/modules/cart/helpers' - -// @deprecated moved to theme -export const MicrocartProduct = { - name: 'MicrocartProduct', - props: { - product: { - type: Object, - required: true - } - }, - computed: { - thumbnail () { - return getThumbnailForProduct(this.product) - }, - configuration () { - return getProductConfiguration(this.product) - } - }, - methods: { - removeFromCart () { - this.$store.dispatch('cart/removeItem', { product: this.product }) - }, - updateQuantity (quantity) { - this.$store.dispatch('cart/updateQuantity', { product: this.product, qty: quantity }) - } - } -} diff --git a/core/modules/cart/helpers/calculateTotals.ts b/core/modules/cart/helpers/calculateTotals.ts deleted file mode 100644 index b7c54b4749..0000000000 --- a/core/modules/cart/helpers/calculateTotals.ts +++ /dev/null @@ -1,41 +0,0 @@ -import i18n from '@vue-storefront/i18n' -import sumBy from 'lodash-es/sumBy' -import ShippingMethod from '@vue-storefront/core/modules/cart/types/ShippingMethod' -import PaymentMethod from '@vue-storefront/core/modules/cart/types/PaymentMethod' -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' - -const calculateTotals = (shippingMethod: ShippingMethod, paymentMethod: PaymentMethod, cartItems: CartItem[]) => { - const shippingTax = shippingMethod ? shippingMethod.price_incl_tax : 0 - - const totalsArray = [ - { - code: 'subtotal_incl_tax', - title: i18n.t('Subtotal incl. tax'), - value: sumBy(cartItems, (p) => p.qty * p.price_incl_tax) - }, - { - code: 'grand_total', - title: i18n.t('Grand total'), - value: sumBy(cartItems, (p) => p.qty * p.price_incl_tax + shippingTax) - } - ] - - if (paymentMethod) { - totalsArray.push({ - code: 'payment', - title: i18n.t(paymentMethod.title), - value: paymentMethod.cost_incl_tax - }) - } - if (shippingMethod) { - totalsArray.push({ - code: 'shipping', - title: i18n.t(shippingMethod.method_title), - value: shippingMethod.price_incl_tax - }) - } - - return totalsArray -} - -export default calculateTotals diff --git a/core/modules/cart/helpers/cartCacheHandler.ts b/core/modules/cart/helpers/cartCacheHandler.ts deleted file mode 100644 index a870d1e072..0000000000 --- a/core/modules/cart/helpers/cartCacheHandler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as types from '../store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' - -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const cartCacheHandlerPlugin = (mutation, state) => { - const type = mutation.type; - - if ( - type.endsWith(types.CART_LOAD_CART) || - type.endsWith(types.CART_ADD_ITEM) || - type.endsWith(types.CART_DEL_ITEM) || - type.endsWith(types.CART_UPD_ITEM) || - type.endsWith(types.CART_DEL_NON_CONFIRMED_ITEM) || - type.endsWith(types.CART_UPD_ITEM_PROPS) - ) { - return StorageManager.get('cart').setItem('current-cart', state.cart.cartItems).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - } else if ( - type.endsWith(types.CART_LOAD_CART_SERVER_TOKEN) - ) { - return StorageManager.get('cart').setItem('current-cart-token', state.cart.cartServerToken).catch((reason) => { - Logger.error(reason)() - }) - } else if ( - type.endsWith(types.CART_SET_ITEMS_HASH) - ) { - return StorageManager.get('cart').setItem('current-cart-hash', state.cart.cartItemsHash).catch((reason) => { - Logger.error(reason)() - }) - } -} diff --git a/core/modules/cart/helpers/createCartItemForUpdate.ts b/core/modules/cart/helpers/createCartItemForUpdate.ts deleted file mode 100644 index 6b13be97ae..0000000000 --- a/core/modules/cart/helpers/createCartItemForUpdate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import config from 'config' -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem'; - -const createCartItemForUpdate = (clientItem: CartItem, serverItem: any, updateIds: boolean = false, mergeQty: boolean = false): CartItem => { - const sku = clientItem.parentSku && config.cart.setConfigurableProductOptions ? clientItem.parentSku : clientItem.sku - const cartItem = { - sku, - ...((serverItem && serverItem.item_id) ? { item_id: serverItem.item_id } : {}), - qty: mergeQty ? (clientItem.qty + serverItem.qty) : clientItem.qty, - product_option: clientItem.product_option - } as any as CartItem - - if (updateIds && serverItem.quote_id && serverItem.item_id) { - return { - ...cartItem, - quoteId: serverItem.quote_id, - item_id: serverItem.item_id - } - } - - return cartItem -} - -export default createCartItemForUpdate diff --git a/core/modules/cart/helpers/createDiffLog.ts b/core/modules/cart/helpers/createDiffLog.ts deleted file mode 100644 index 597aa159ea..0000000000 --- a/core/modules/cart/helpers/createDiffLog.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Notification, ServerResponse, Party } from '@vue-storefront/core/modules/cart/types/DiffLog' - -class DiffLog { - public items: Party[] - public serverResponses: ServerResponse[] - public clientNotifications: Notification[] - - public constructor () { - this.items = [] - this.serverResponses = [] - this.clientNotifications = [] - } - - public pushParty (party: Party): DiffLog { - this.items.push(party) - return this - } - - public pushClientParty (party: any): DiffLog { - this.pushParty({ party: 'client', ...party }) - return this - } - - public pushServerParty (party: any): DiffLog { - this.pushParty({ party: 'server', ...party }) - return this - } - - public pushServerResponse (response: ServerResponse): DiffLog { - this.serverResponses.push(response) - return this - } - - public pushNotification (notification: Notification): DiffLog { - this.clientNotifications.push(notification) - return this - } - - public pushNotifications (notifications: Notification[]): DiffLog { - this.clientNotifications = this.clientNotifications.concat(notifications) - return this - } - - public merge (diffLog: DiffLog): DiffLog { - this.items = this.items.concat(diffLog.items) - this.serverResponses = this.serverResponses.concat(diffLog.serverResponses) - this.clientNotifications = this.clientNotifications.concat(diffLog.clientNotifications) - return this - } - - public hasClientNotifications () { - return this.clientNotifications.length > 0 - } - - public hasServerResponses () { - return this.serverResponses.length > 0 - } - - public hasParties () { - return this.items.length > 0 - } - - public isEmpty (): boolean { - return !this.hasParties && - !this.hasClientNotifications() && - !this.hasServerResponses() - } -} - -const createDiffLog = (): DiffLog => new DiffLog() - -export default createDiffLog diff --git a/core/modules/cart/helpers/createOrderData.ts b/core/modules/cart/helpers/createOrderData.ts deleted file mode 100644 index 37c12e34ac..0000000000 --- a/core/modules/cart/helpers/createOrderData.ts +++ /dev/null @@ -1,54 +0,0 @@ -import OrderShippingDetails from '@vue-storefront/core/modules/cart/types/OrderShippingDetails' -import PaymentMethod from '@vue-storefront/core/modules/cart/types/PaymentMethod' -import ShippingMethod from '@vue-storefront/core/modules/cart/types/ShippingMethod' -import CheckoutData from '@vue-storefront/core/modules/cart/types/CheckoutData' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' - -const getDefaultShippingMethod = (shippingMethods: ShippingMethod[] = []): ShippingMethod => { - const onlineShippingMethods = shippingMethods.filter(shippingMethod => !shippingMethod.offline) - if (!onlineShippingMethods.length) return - - return onlineShippingMethods.find(shippingMethod => !!shippingMethod.default) || onlineShippingMethods[0] -} - -const getDefaultPaymentMethod = (paymentMethods: PaymentMethod[] = []): PaymentMethod => { - if (!paymentMethods || !paymentMethods.length) return - - return paymentMethods.find(item => item.default) || paymentMethods[0] -} - -const createOrderData = ({ - shippingDetails, - shippingMethods, - paymentMethods, - paymentDetails, - taxCountry = currentStoreView().tax.defaultCountry -}: CheckoutData): OrderShippingDetails => { - const country = shippingDetails.country ? shippingDetails.country : taxCountry - const shipping = getDefaultShippingMethod(shippingMethods) - const payment = getDefaultPaymentMethod(paymentMethods) - - return { - country, - shippingAddress: { - firstname: shippingDetails.firstName, - lastname: shippingDetails.lastName, - city: shippingDetails.city, - postcode: shippingDetails.zipCode, - street: [shippingDetails.streetAddress] - }, - billingAddress: { - firstname: paymentDetails.firstName, - lastname: paymentDetails.lastName, - city: paymentDetails.city, - postcode: paymentDetails.zipCode, - street: [paymentDetails.streetAddress], - countryId: paymentDetails.country - }, - method_code: shipping && shipping.method_code ? shipping.method_code : null, - carrier_code: shipping && shipping.carrier_code ? shipping.carrier_code : null, - payment_method: payment && payment.code ? payment.code : null - } -} - -export default createOrderData diff --git a/core/modules/cart/helpers/createShippingInfoData.ts b/core/modules/cart/helpers/createShippingInfoData.ts deleted file mode 100644 index 8395dcd816..0000000000 --- a/core/modules/cart/helpers/createShippingInfoData.ts +++ /dev/null @@ -1,13 +0,0 @@ -const createShippingInfoData = (methodsData) => ({ - shippingAddress: { - countryId: methodsData.country, - ...(methodsData.shippingAddress ? methodsData.shippingAddress : {}) - }, - billingAddress: { - ...(methodsData.billingAddress ? methodsData.billingAddress : {}) - }, - ...(methodsData.carrier_code ? { shippingCarrierCode: methodsData.carrier_code } : {}), - ...(methodsData.method_code ? { shippingMethodCode: methodsData.method_code } : {}) -}); - -export default createShippingInfoData diff --git a/core/modules/cart/helpers/getProductConfiguration.ts b/core/modules/cart/helpers/getProductConfiguration.ts deleted file mode 100644 index d45200279c..0000000000 --- a/core/modules/cart/helpers/getProductConfiguration.ts +++ /dev/null @@ -1,25 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import { ProductConfiguration } from '@vue-storefront/core/modules/catalog/types/ProductConfiguration' -import getProductOptions from './getProductOptions' - -const ATTRIBUTES = ['color', 'size'] - -const getProductConfiguration = (product: CartItem): ProductConfiguration => { - const options = getProductOptions(product) - const getAttributesFields = (attributeCode) => - (options[attributeCode] || []).find(c => String(c.id) === String(product[attributeCode])) - - if (!options) { - return null - } - - return ATTRIBUTES.reduce((prev, curr) => ({ - ...prev, - [curr]: { - attribute_code: curr, - ...getAttributesFields(curr) - } - }), {}) as any as ProductConfiguration -} - -export default getProductConfiguration diff --git a/core/modules/cart/helpers/getProductOptions.ts b/core/modules/cart/helpers/getProductOptions.ts deleted file mode 100644 index aa4a5ec36a..0000000000 --- a/core/modules/cart/helpers/getProductOptions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import { ProductOption } from '@vue-storefront/core/modules/catalog/types/ProductConfiguration' - -const mapValues = (current) => (val) => ({ - id: val.value_index, - label: val.label, - attribute_code: current.attribute_code, - type: current.attribute_code -}) - -const reduceOptions = (prev, curr) => ({ - ...prev, - [curr.attribute_code]: curr.values.map(mapValues(curr)) -}) - -const getProductOptions = (product: CartItem): ProductOption => { - if (!product.configurable_options) { - return null - } - - return product.configurable_options - .reduce(reduceOptions, {}) -} - -export default getProductOptions diff --git a/core/modules/cart/helpers/getThumbnailForProduct.ts b/core/modules/cart/helpers/getThumbnailForProduct.ts deleted file mode 100644 index aa5eafe06b..0000000000 --- a/core/modules/cart/helpers/getThumbnailForProduct.ts +++ /dev/null @@ -1,16 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import config from 'config' -import { getThumbnailPath } from '@vue-storefront/core/helpers' -import { productThumbnailPath } from '@vue-storefront/core/helpers' - -const getThumbnailForProduct = (product: CartItem): string => { - const thumbnail = productThumbnailPath(product) - - if (typeof navigator !== 'undefined' && !navigator.onLine) { - return getThumbnailPath(thumbnail, config.products.thumbnails.width, config.products.thumbnails.height) - } - - return getThumbnailPath(thumbnail, config.cart.thumbnails.width, config.cart.thumbnails.height) -} - -export default getThumbnailForProduct diff --git a/core/modules/cart/helpers/index.ts b/core/modules/cart/helpers/index.ts deleted file mode 100644 index e29ad43e58..0000000000 --- a/core/modules/cart/helpers/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { cartCacheHandlerPlugin } from './cartCacheHandler' -import { totalsCacheHandlerPlugin } from './totalsCacheHandler' -import optimizeProduct from './optimizeProduct' -import prepareProductsToAdd from './prepareProductsToAdd' -import productChecksum from './productChecksum' -import productsEquals from './productsEquals' -import calculateTotals from './calculateTotals' -import preparePaymentMethodsToSync from './preparePaymentMethodsToSync' -import validateProduct from './validateProduct' -import createDiffLog from './createDiffLog' -import * as notifications from './notifications' -import createCartItemForUpdate from './createCartItemForUpdate' -import prepareShippingInfoForUpdateTotals from './prepareShippingInfoForUpdateTotals' -import getThumbnailForProduct from './getThumbnailForProduct' -import getProductOptions from './getProductOptions' -import getProductConfiguration from './getProductConfiguration' -import createOrderData from './createOrderData' -import createShippingInfoData from './createShippingInfoData' -import * as syncCartWhenLocalStorageChange from './syncCartWhenLocalStorageChange' - -export { - cartCacheHandlerPlugin, - totalsCacheHandlerPlugin, - optimizeProduct, - prepareProductsToAdd, - productChecksum, - productsEquals, - calculateTotals, - preparePaymentMethodsToSync, - validateProduct, - notifications, - createDiffLog, - createCartItemForUpdate, - prepareShippingInfoForUpdateTotals, - getThumbnailForProduct, - getProductOptions, - getProductConfiguration, - createOrderData, - createShippingInfoData, - syncCartWhenLocalStorageChange -} diff --git a/core/modules/cart/helpers/notifications.ts b/core/modules/cart/helpers/notifications.ts deleted file mode 100644 index 06e5872d5d..0000000000 --- a/core/modules/cart/helpers/notifications.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { router } from '@vue-storefront/core/app'; -import { currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore'; -import i18n from '@vue-storefront/i18n'; -import config from 'config'; - -const proceedToCheckoutAction = () => ({ - label: i18n.t('Proceed to checkout'), - action: () => router.push(localizedRoute('/checkout', currentStoreView().storeCode)) -}); -const checkoutAction = () => !config.externalCheckout ? proceedToCheckoutAction() : null; - -const productAddedToCart = () => ({ - type: 'success', - message: i18n.t('Product has been added to the cart!'), - action1: { label: i18n.t('OK') }, - action2: checkoutAction() -}) - -const productQuantityUpdated = () => ({ - type: 'success', - message: i18n.t('Product quantity has been updated!'), - action1: { label: i18n.t('OK') }, - action2: checkoutAction() -}) - -const unsafeQuantity = () => ({ - type: 'warning', - message: i18n.t( - 'The system is not sure about the stock quantity (volatile). Product has been added to the cart for pre-reservation.' - ), - action1: { label: i18n.t('OK') } -}) - -const outOfStock = () => ({ - type: 'error', - message: i18n.t('The product is out of stock and cannot be added to the cart!'), - action1: { label: i18n.t('OK') } -}) - -const createNotification = ({ type, message }) => ({ type, message, action1: { label: i18n.t('OK') } }) -const createNotifications = ({ type, messages }) => - messages.map(message => createNotification({ type, message })); - -export { - createNotification, - createNotifications, - productAddedToCart, - productQuantityUpdated, - unsafeQuantity, - outOfStock -}; diff --git a/core/modules/cart/helpers/optimizeProduct.ts b/core/modules/cart/helpers/optimizeProduct.ts deleted file mode 100644 index 16370a516d..0000000000 --- a/core/modules/cart/helpers/optimizeProduct.ts +++ /dev/null @@ -1,20 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import config from 'config' -import omit from 'lodash-es/omit' -import pullAll from 'lodash-es/pullAll' - -const optimizeProduct = (product: CartItem): CartItem => { - if (!config.entities.optimizeShoppingCart) { - return product - } - - let fieldsToOmit = config.entities.optimizeShoppingCartOmitFields - - if (config.cart.productsAreReconfigurable) { - fieldsToOmit = pullAll(fieldsToOmit, ['configurable_children', 'configurable_options']) - } - - return omit(product, fieldsToOmit) -} - -export default optimizeProduct diff --git a/core/modules/cart/helpers/preparePaymentMethodsToSync.ts b/core/modules/cart/helpers/preparePaymentMethodsToSync.ts deleted file mode 100644 index 0f4d1d0717..0000000000 --- a/core/modules/cart/helpers/preparePaymentMethodsToSync.ts +++ /dev/null @@ -1,28 +0,0 @@ -import PaymentMethod from '@vue-storefront/core/modules/cart/types/PaymentMethod' - -const isPaymentMethodNotExist = (backendPaymentMethod: PaymentMethod, paymentMethods: PaymentMethod[]) => - typeof backendPaymentMethod === 'object' && !paymentMethods.find(item => item.code === backendPaymentMethod.code) - -const preparePaymentMethodsToSync = ( - backendPaymentMethods: PaymentMethod[], - currentPaymentMethods: PaymentMethod[] -): { uniqueBackendMethods: PaymentMethod[], paymentMethods: PaymentMethod[] } => { - const paymentMethods = [...currentPaymentMethods] - const uniqueBackendMethods = [] - - for (const backendPaymentMethod of backendPaymentMethods) { - if (isPaymentMethodNotExist(backendPaymentMethod, currentPaymentMethods)) { - const backendMethod = { - ...backendPaymentMethod, - is_server_method: true - } - - paymentMethods.push(backendMethod) - uniqueBackendMethods.push(backendMethod) - } - } - - return { uniqueBackendMethods, paymentMethods } -} - -export default preparePaymentMethodsToSync diff --git a/core/modules/cart/helpers/prepareProductsToAdd.ts b/core/modules/cart/helpers/prepareProductsToAdd.ts deleted file mode 100644 index d464694f34..0000000000 --- a/core/modules/cart/helpers/prepareProductsToAdd.ts +++ /dev/null @@ -1,27 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import productChecksum from './productChecksum' -import optimizeProduct from './optimizeProduct' - -const readAssociated = product => - product.product_links.filter(p => p.link_type === 'associated').map(p => p.product) - -const isDefined = product => typeof product !== 'undefined' || product !== null - -const applyQty = product => ({ - ...product, - qty: product.qty && typeof product.qty !== 'number' ? parseInt(product.qty) : product.qty -}); - -const applyChecksum = product => ({ ...product, checksum: productChecksum(product) }) - -const prepareProductsToAdd = (product: CartItem): CartItem[] => { - const products = product.type_id === 'grouped' ? readAssociated(product) : [product] - - return products - .filter(isDefined) - .map(applyQty) - .map(p => optimizeProduct(p)) - .map(applyChecksum); -}; - -export default prepareProductsToAdd; diff --git a/core/modules/cart/helpers/prepareShippingInfoForUpdateTotals.ts b/core/modules/cart/helpers/prepareShippingInfoForUpdateTotals.ts deleted file mode 100644 index 3004d40350..0000000000 --- a/core/modules/cart/helpers/prepareShippingInfoForUpdateTotals.ts +++ /dev/null @@ -1,22 +0,0 @@ -import isString from 'lodash-es/isString' -import Totals from '@vue-storefront/core/modules/cart/types/Totals' - -const applyOptions = (item: Totals) => { - if (item.options && isString(item.options)) { - return { ...item, options: JSON.parse(item.options) } - } - - return item -} - -const reduceToObject = (previousValue: any, currentValue: Totals) => ({ - ...previousValue, - [currentValue.item_id]: currentValue -}) - -const prepareShippingInfoForUpdateTotals = (totals: Totals[]) => - totals - .map(applyOptions) - .reduce(reduceToObject, {}) - -export default prepareShippingInfoForUpdateTotals diff --git a/core/modules/cart/helpers/productChecksum.ts b/core/modules/cart/helpers/productChecksum.ts deleted file mode 100644 index 4798da845f..0000000000 --- a/core/modules/cart/helpers/productChecksum.ts +++ /dev/null @@ -1,50 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import { sha3_224 } from 'js-sha3' -import get from 'lodash-es/get' -import flow from 'lodash-es/flow' -import cloneDeep from 'lodash-es/cloneDeep'; - -const replaceNumberToString = obj => { - Object.keys(obj).forEach(key => { - if (obj[key] !== null && typeof obj[key] === 'object') { - return replaceNumberToString(obj[key]); - } else if (typeof obj[key] === 'number') { - obj[key] = String(obj[key]); - } - }); - return obj; -} - -const transformToArray = value => Array.isArray(value) ? value : Object.values(value) - -export const getProductOptions = (product, optionsName) => { - return flow([ - get, - cloneDeep, - transformToArray, - replaceNumberToString - ])(product, `product_option.extension_attributes.${optionsName}`, []) -} - -const getDataToHash = (product: CartItem): any => { - if (!product.product_option) { - return null - } - - const supportedProductOptions = ['bundle_options', 'custom_options', 'configurable_item_options'] - - // returns first options that has array with options - for (let optionName of supportedProductOptions) { - const options = getProductOptions(product, optionName) - if (options.length) { - return options - } - } - - // if there are options that are not supported then just return all options - return product.product_option -} - -const productChecksum = (product: CartItem): string => sha3_224(JSON.stringify(getDataToHash(product))) - -export default productChecksum diff --git a/core/modules/cart/helpers/productsEquals.ts b/core/modules/cart/helpers/productsEquals.ts deleted file mode 100644 index 7af6cb287c..0000000000 --- a/core/modules/cart/helpers/productsEquals.ts +++ /dev/null @@ -1,96 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import productChecksum, { getProductOptions } from './productChecksum'; - -type ProductEqualCheckFn = (product1: CartItem, product2: CartItem) => boolean - -// 'id' check -const getServerItemId = (product: CartItem): string | number => - product.server_item_id || product.item_id -const isServerIdsEquals = (product1: CartItem, product2: CartItem): boolean => { - const product1ItemId = getServerItemId(product1) - const product2ItemId = getServerItemId(product2) - - const areItemIdsDefined = product1ItemId !== undefined && product2ItemId !== undefined - - return areItemIdsDefined && product1ItemId === product2ItemId -} - -// 'checksum' check -const getChecksum = (product: CartItem) => { - if (product.checksum) { - return product.checksum - } - return productChecksum(product) -} -const isChecksumEquals = (product1: CartItem, product2: CartItem): boolean => - getChecksum(product1) === getChecksum(product2) - -// 'sku' check -const isSkuEqual = (product1: CartItem, product2: CartItem): boolean => - String(product1.sku) === String(product2.sku) - -/** - * Returns product equality check function - * @param checkName - determines what type of check we want to do - */ -const getCheckFn = (checkName: string): ProductEqualCheckFn => { - switch (checkName) { - case 'id': { - return isServerIdsEquals - } - case 'checksum': { - return isChecksumEquals - } - case 'sku': { - return isSkuEqual - } - default: { - return isSkuEqual - } - } -} - -/** - * It passes all types of checks and returns the first passed. The order of checks matters! - */ -const makeCheck = (product1: CartItem, product2: CartItem, checks: string[]): boolean => { - for (let checkName of checks) { - const fn = getCheckFn(checkName) - if (fn(product1, product2)) { - return true - } - } - return false -} - -const productsEquals = (product1: CartItem, product2: CartItem): boolean => { - if (!product1 || !product2) { - return false - } - - const check = makeCheck.bind(null, product1, product2) - - if (getProductOptions(product1, 'bundle_options').length || getProductOptions(product2, 'bundle_options').length) { - // bundle options skus are merged into one sku so we can't rely on 'sku' - // by default we want to check server_item_id ('id'), we can also use 'checksum' - return check(['id', 'checksum']) - } - - if (getProductOptions(product1, 'custom_options').length || getProductOptions(product2, 'custom_options').length) { - // in admin panel we can add different sku for specific custom option so we can't rely on 'sku' - // by default we want to check server_item_id ('id'), we can also use 'checksum' - return check(['id', 'checksum']) - } - - if (getProductOptions(product1, 'configurable_item_options').length || getProductOptions(product2, 'configurable_item_options').length) { - // 'sku' should be uniq for configurable products - // we can't check 'id' because it is the same when user edit product in microcart, so it can give wrong result - return check(['sku']) - } - - // by default we want to check if server_item_id is equal and check sku as fallback - // this is for 'simple' and 'group' products - return check(['id', 'sku']) -} - -export default productsEquals diff --git a/core/modules/cart/helpers/syncCartWhenLocalStorageChange.ts b/core/modules/cart/helpers/syncCartWhenLocalStorageChange.ts deleted file mode 100644 index e3ed0bcd7c..0000000000 --- a/core/modules/cart/helpers/syncCartWhenLocalStorageChange.ts +++ /dev/null @@ -1,32 +0,0 @@ -import rootStore from '@vue-storefront/core/store' -import { storeViews } from 'config' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' - -function checkMultistoreKey (key: string, path: string): boolean { - const { multistore, commonCache } = storeViews - if (!multistore || (multistore && commonCache)) return key === path - return key === `${currentStoreView().storeCode}-${path}` -} - -function getItemsFromStorage ({ key }) { - if (checkMultistoreKey(key, 'shop/cart/current-cart')) { - const value = JSON.parse(localStorage[key]) - rootStore.dispatch('cart/updateCart', { items: value }) - } else if (checkMultistoreKey(key, 'shop/cart/current-totals')) { - const value = JSON.parse(localStorage[key]) - rootStore.dispatch('cart/updateTotals', value) - } -} - -function addEventListener () { - window.addEventListener('storage', getItemsFromStorage) -} - -function removeEventListener () { - window.removeEventListener('storage', getItemsFromStorage) -} - -export { - addEventListener, - removeEventListener -} diff --git a/core/modules/cart/helpers/totalsCacheHandler.ts b/core/modules/cart/helpers/totalsCacheHandler.ts deleted file mode 100644 index 515a347f27..0000000000 --- a/core/modules/cart/helpers/totalsCacheHandler.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as types from '../store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' - -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const totalsCacheHandlerPlugin = ({ type }, state) => { - if ( - type.endsWith(types.CART_UPD_TOTALS) - ) { - return StorageManager.get('cart').setItem('current-totals', { - platformTotalSegments: state.cart.platformTotalSegments, - platformTotals: state.cart.platformTotals - }).catch((reason) => { - Logger.error(reason)() - }) - } -} diff --git a/core/modules/cart/helpers/validateProduct.ts b/core/modules/cart/helpers/validateProduct.ts deleted file mode 100644 index c348d86b71..0000000000 --- a/core/modules/cart/helpers/validateProduct.ts +++ /dev/null @@ -1,23 +0,0 @@ -import config from 'config' -import i18n from '@vue-storefront/i18n' -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem'; - -const validateProduct = (product: CartItem): string[] => { - const errors = [] - - if (config.useZeroPriceProduct ? product.price_incl_tax < 0 : product.price_incl_tax <= 0) { - errors.push(i18n.t('Product price is unknown, product cannot be added to the cart!')) - } - - if (product.errors !== null && typeof product.errors !== 'undefined') { - for (const errKey in product.errors) { - if (product.errors[errKey]) { - errors.push(product.errors[errKey]) - } - } - } - - return errors -}; - -export default validateProduct; diff --git a/core/modules/cart/hooks/index.ts b/core/modules/cart/hooks/index.ts deleted file mode 100644 index 6d08ad598f..0000000000 --- a/core/modules/cart/hooks/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { createListenerHook, createMutatorHook } from '@vue-storefront/core/lib/hooks' -import CartItem from '../types/CartItem'; - -const { - hook: beforeSyncHook, - executor: beforeSyncExecutor -} = createMutatorHook<{ clientItems: CartItem[], serverItems: CartItem[] }, any>() - -const { - hook: afterSyncHook, - executor: afterSyncExecutor -} = createListenerHook() - -const { - hook: beforeAddToCartHook, - executor: beforeAddToCartExecutor -} = createMutatorHook<{ cartItem: CartItem }, any>() - -const { - hook: afterAddToCartHook, - executor: afterAddToCartExecutor -} = createListenerHook() - -const { - hook: beforeRemoveFromCartHook, - executor: beforeRemoveFromCartExecutor -} = createMutatorHook<{ cartItem: CartItem }, any>() - -const { - hook: afterRemoveFromCartHook, - executor: afterRemoveFromCartExecutor -} = createListenerHook() - -const { - hook: beforeMergeHook, - executor: beforeMergeExecutor -} = createMutatorHook<{ clientItems: CartItem[], serverItems: CartItem[] }, any>() - -const { - hook: afterLoadHook, - executor: afterLoadExecutor -} = createListenerHook() - -const cartHooksExecutors = { - beforeSync: beforeSyncExecutor, - afterSync: afterSyncExecutor, - beforeAddToCart: beforeAddToCartExecutor, - afterAddToCart: afterAddToCartExecutor, - beforeRemoveFromCart: beforeRemoveFromCartExecutor, - afterRemoveFromCart: afterRemoveFromCartExecutor, - beforeMerge: beforeMergeExecutor, - afterLoad: afterLoadExecutor -} - -const cartHooks = { - beforeSync: beforeSyncHook, - afterSync: afterSyncHook, - beforeAddToCart: beforeAddToCartHook, - afterAddToCart: afterAddToCartHook, - beforeRemoveFromCart: beforeRemoveFromCartHook, - afterRemoveFromCart: afterRemoveFromCartHook, - beforeMerge: beforeMergeHook, - afterLoad: afterLoadHook -} - -export { - cartHooks, - cartHooksExecutors -} diff --git a/core/modules/cart/index.ts b/core/modules/cart/index.ts deleted file mode 100644 index 6e2126d41e..0000000000 --- a/core/modules/cart/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { cartStore } from './store' -import { cartCacheHandlerPlugin, totalsCacheHandlerPlugin } from './helpers'; -import { isServer } from '@vue-storefront/core/helpers' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const CartModule: StorefrontModule = function ({ store }) { - StorageManager.init('cart') - - store.registerModule('cart', cartStore) - - if (!isServer) store.dispatch('cart/load') - store.subscribe(cartCacheHandlerPlugin); - store.subscribe(totalsCacheHandlerPlugin); -} diff --git a/core/modules/cart/store/actions/connectActions.ts b/core/modules/cart/store/actions/connectActions.ts deleted file mode 100644 index 9b0ce22df7..0000000000 --- a/core/modules/cart/store/actions/connectActions.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' -import config from 'config' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { CartService } from '@vue-storefront/core/data-resolver' -import { createDiffLog } from '@vue-storefront/core/modules/cart/helpers' - -const connectActions = { - toggleMicrocart ({ commit }) { - commit(types.CART_TOGGLE_MICROCART) - }, - /** - * It will always clear cart items on frontend. - * Options: - * sync - if you want to sync it with backend. - * disconnect - if you want to clear cart token. - */ - async clear ({ commit, dispatch }, { disconnect = true, sync = true } = {}) { - await commit(types.CART_LOAD_CART, []) - if (sync) { - await dispatch('sync', { forceClientState: true, forceSync: true }) - } - if (disconnect) { - await commit(types.CART_SET_ITEMS_HASH, null) - await dispatch('disconnect') - } - }, - async disconnect ({ commit }) { - commit(types.CART_LOAD_CART_SERVER_TOKEN, null) - }, - async authorize ({ dispatch, getters }) { - const coupon = getters.getCoupon.code - if (coupon) { - await dispatch('removeCoupon', { sync: false }) - } - - await dispatch('connect', { guestCart: false, mergeQty: true }) - - if (coupon) { - await dispatch('applyCoupon', coupon) - } - }, - async connect ({ getters, dispatch, commit }, { guestCart = false, forceClientState = false, mergeQty = false }) { - if (!getters.isCartSyncEnabled) return - const { result, resultCode } = await CartService.getCartToken(guestCart, forceClientState) - - if (resultCode === 200) { - Logger.info('Server cart token created.', 'cart', result)() - commit(types.CART_LOAD_CART_SERVER_TOKEN, result) - - return dispatch('sync', { forceClientState, dryRun: !config.cart.serverMergeByDefault, mergeQty }) - } - - if (resultCode === 401 && getters.bypassCounter < config.queues.maxCartBypassAttempts) { - Logger.log('Bypassing with guest cart' + getters.bypassCounter, 'cart')() - commit(types.CART_UPDATE_BYPASS_COUNTER, { counter: 1 }) - Logger.error(result, 'cart')() - return dispatch('connect', { guestCart: true }) - } - - Logger.warn('Cart sync is disabled by the config', 'cart')() - return createDiffLog() - }, - /** - * Create cart token when there are products in cart and we don't have token already - */ - async create ({ dispatch, getters }) { - const storedItems = getters['getCartItems'] || [] - const cartToken = getters['getCartToken'] - if (storedItems.length && !cartToken) { - Logger.info('Creating server cart token', 'cart')() - return dispatch('connect', { guestCart: false }) - } - } -} - -export default connectActions diff --git a/core/modules/cart/store/actions/couponActions.ts b/core/modules/cart/store/actions/couponActions.ts deleted file mode 100644 index cdb7005f09..0000000000 --- a/core/modules/cart/store/actions/couponActions.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CartService } from '@vue-storefront/core/data-resolver' -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' - -const couponActions = { - async removeCoupon ({ getters, dispatch, commit }, { sync = true } = {}) { - if (getters.canSyncTotals) { - const { result } = await CartService.removeCoupon() - - if (result && sync) { - await dispatch('syncTotals', { forceServerSync: true }) - - // 'getCurrentCartHash' has been changed (it's based on cart items data) - // so we need to update it in vuex and StorageManager - commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) - return result - } - } - }, - async applyCoupon ({ getters, dispatch, commit }, couponCode) { - if (couponCode && getters.canSyncTotals) { - const { result } = await CartService.applyCoupon(couponCode) - - if (result) { - await dispatch('syncTotals', { forceServerSync: true }) - - // 'getCurrentCartHash' has been changed (it's based on cart items data) - // so we need to update it in vuex and StorageManager - commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) - } - return result - } - } -} - -export default couponActions diff --git a/core/modules/cart/store/actions/index.ts b/core/modules/cart/store/actions/index.ts deleted file mode 100644 index 4f45902790..0000000000 --- a/core/modules/cart/store/actions/index.ts +++ /dev/null @@ -1,27 +0,0 @@ - -import { ActionTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import CartState from '@vue-storefront/core/modules/cart/types/CartState' -import connectActions from './connectActions' -import couponActions from './couponActions' -import itemActions from './itemActions' -import mergeActions from './mergeActions'; -import methodsActions from './methodsActions' -import productActions from './productActions' -import quantityActions from './quantityActions' -import synchronizeActions from './synchronizeActions' -import totalsActions from './totalsActions' - -const actions: ActionTree = { - ...connectActions, - ...itemActions, - ...couponActions, - ...mergeActions, - ...methodsActions, - ...productActions, - ...quantityActions, - ...synchronizeActions, - ...totalsActions -} - -export default actions diff --git a/core/modules/cart/store/actions/itemActions.ts b/core/modules/cart/store/actions/itemActions.ts deleted file mode 100644 index 02f0625fb3..0000000000 --- a/core/modules/cart/store/actions/itemActions.ts +++ /dev/null @@ -1,119 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' -import { - prepareProductsToAdd, - productsEquals, - validateProduct, - createDiffLog, - notifications -} from '@vue-storefront/core/modules/cart/helpers' -import { cartHooksExecutors } from './../../hooks' -import config from 'config' - -const itemActions = { - async configureItem (context, { product, configuration }) { - const { commit, dispatch, getters } = context - const variant = await dispatch('product/getProductVariant', { - product, - configuration - }, { root: true }) - - const itemWithSameSku = getters.getCartItems.find(item => item.sku === variant.sku) - - if (itemWithSameSku && product.sku !== variant.sku) { - Logger.debug('Item with the same sku detected', 'cart', { sku: itemWithSameSku.sku })() - commit(types.CART_DEL_ITEM, { product: itemWithSameSku }) - product.qty = parseInt(product.qty) + parseInt(itemWithSameSku.qty) - } - - commit(types.CART_UPD_ITEM_PROPS, { product: { ...product, ...variant } }) - - if (getters.isCartSyncEnabled && product.server_item_id) { - await dispatch('sync', { forceClientState: true }) - } - }, - updateItem ({ commit }, { product }) { - commit(types.CART_UPD_ITEM_PROPS, { product }) - }, - getItem ({ getters }, { product }) { - return getters.getCartItems.find(p => productsEquals(p, product)) - }, - async addItem ({ dispatch, commit }, { productToAdd, forceServerSilence = false }) { - const { cartItem } = cartHooksExecutors.beforeAddToCart({ cartItem: productToAdd }) - commit(types.CART_ADDING_ITEM, { isAdding: true }) - const result = await dispatch('addItems', { productsToAdd: prepareProductsToAdd(cartItem), forceServerSilence }) - commit(types.CART_ADDING_ITEM, { isAdding: false }) - cartHooksExecutors.afterAddToCart(result) - return result - }, - async checkProductStatus ({ dispatch, getters }, { product }) { - const record = getters.getCartItems.find(p => productsEquals(p, product)) - const qty = record ? record.qty + 1 : (product.qty ? product.qty : 1) - - return dispatch('stock/queueCheck', { product, qty }, { root: true }) - }, - async addItems ({ commit, dispatch, getters }, { productsToAdd, forceServerSilence = false }) { - let productIndex = 0 - const diffLog = createDiffLog() - for (let product of productsToAdd) { - const errors = validateProduct(product) - diffLog.pushNotifications(notifications.createNotifications({ type: 'error', messages: errors })) - - if (errors.length === 0) { - const { status, onlineCheckTaskId } = await dispatch('checkProductStatus', { product }) - - if (status === 'volatile' && !config.stock.allowOutOfStockInCart) { - diffLog.pushNotification(notifications.unsafeQuantity()) - } - if (status === 'out_of_stock') { - diffLog.pushNotification(notifications.outOfStock()) - } - - if (status === 'ok' || status === 'volatile') { - commit(types.CART_ADD_ITEM, { - product: { ...product, onlineStockCheckid: onlineCheckTaskId } - }) - } - if (productIndex === (productsToAdd.length - 1) && (!getters.isCartSyncEnabled || forceServerSilence)) { - diffLog.pushNotification(notifications.productAddedToCart()) - } - productIndex++ - } - } - - let newDiffLog = await dispatch('create') - if (newDiffLog !== undefined) { - diffLog.merge(newDiffLog) - } - - if (getters.isCartSyncEnabled && getters.isCartConnected && !forceServerSilence) { - const syncDiffLog = await dispatch('sync', { forceClientState: true }) - - if (!syncDiffLog.isEmpty()) { - diffLog.merge(syncDiffLog) - } - } - - return diffLog - }, - async removeItem ({ commit, dispatch, getters }, payload) { - const removeByParentSku = payload.product ? !!payload.removeByParentSku && payload.product.type_id !== 'bundle' : true - const product = payload.product || payload - const { cartItem } = cartHooksExecutors.beforeRemoveFromCart({ cartItem: product }) - - commit(types.CART_DEL_ITEM, { product: cartItem, removeByParentSku }) - - if (getters.isCartSyncEnabled && cartItem.server_item_id) { - const diffLog = await dispatch('sync', { forceClientState: true }) - cartHooksExecutors.afterRemoveFromCart(diffLog) - return diffLog - } - - const diffLog = createDiffLog() - .pushClientParty({ status: 'no-item', sku: product.sku }) - cartHooksExecutors.afterRemoveFromCart(diffLog) - return diffLog - } -} - -export default itemActions diff --git a/core/modules/cart/store/actions/mergeActions.ts b/core/modules/cart/store/actions/mergeActions.ts deleted file mode 100644 index b2fbf76ba0..0000000000 --- a/core/modules/cart/store/actions/mergeActions.ts +++ /dev/null @@ -1,217 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' -import config from 'config' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { CartService } from '@vue-storefront/core/data-resolver' -import { - productsEquals, - createDiffLog, - notifications, - createCartItemForUpdate -} from '@vue-storefront/core/modules/cart/helpers' -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem'; -import { cartHooksExecutors } from './../../hooks' - -const mergeActions = { - async updateClientItem ({ dispatch }, { clientItem, serverItem }) { - const cartItem = clientItem === null ? await dispatch('getItem', serverItem) : clientItem - - if (!cartItem || typeof serverItem.item_id === 'undefined') return - - const product = { - server_item_id: serverItem.item_id, - sku: cartItem.sku, - server_cart_id: serverItem.quote_id, - prev_qty: cartItem.qty, - product_option: serverItem.product_option, - type_id: serverItem.product_type - } - - await dispatch('updateItem', { product }) - EventBus.$emit('cart-after-itemchanged', { item: cartItem }) - }, - async updateServerItem ({ getters, rootGetters, commit, dispatch }, { clientItem, serverItem, updateIds, mergeQty }) { - const diffLog = createDiffLog() - const cartItem = createCartItemForUpdate(clientItem, serverItem, updateIds, mergeQty) - const event = await CartService.updateItem(getters.getCartToken, cartItem) - const wasUpdatedSuccessfully = event.resultCode === 200 - Logger.debug('Cart item server sync' + event, 'cart')() - diffLog.pushServerResponse({ status: event.resultCode, sku: clientItem.sku, result: event }) - - if (!wasUpdatedSuccessfully && !serverItem) { - commit(types.CART_DEL_ITEM, { product: clientItem, removeByParentSku: false }) - return diffLog - } - - if (!wasUpdatedSuccessfully && (clientItem.server_item_id || clientItem.item_id)) { - await dispatch('restoreQuantity', { product: clientItem }) - return diffLog - } - - if (!wasUpdatedSuccessfully) { - Logger.warn('Removing product from cart', 'cart', clientItem)() - commit(types.CART_DEL_NON_CONFIRMED_ITEM, { product: clientItem }) - return diffLog - } - - if (!rootGetters['checkout/isUserInCheckout']) { - const isThisNewItemAddedToTheCart = (!clientItem || !clientItem.server_item_id) - diffLog.pushNotification( - isThisNewItemAddedToTheCart ? notifications.productAddedToCart() : notifications.productQuantityUpdated() - ) - } - - await dispatch('updateClientItem', { clientItem, serverItem: event.result }) - - return diffLog - }, - async synchronizeServerItem ({ dispatch }, { serverItem, clientItem, forceClientState, dryRun, mergeQty }) { - const diffLog = createDiffLog() - - if (!serverItem) { - Logger.warn('No server item with sku ' + clientItem.sku + ' on stock.', 'cart')() - diffLog.pushServerParty({ sku: clientItem.sku, status: 'no-item' }) - - if (dryRun) return diffLog - if (forceClientState || !config.cart.serverSyncCanRemoveLocalItems) { - const updateServerItemDiffLog = await dispatch('updateServerItem', { clientItem, serverItem, updateIds: false }) - return diffLog.merge(updateServerItemDiffLog) - } - - await dispatch('removeItem', { product: clientItem }) - return diffLog - } - - if (serverItem.qty !== clientItem.qty || mergeQty) { - Logger.log('Wrong qty for ' + clientItem.sku, clientItem.qty, serverItem.qty)() - diffLog.pushServerParty({ sku: clientItem.sku, status: 'wrong-qty', 'client-qty': clientItem.qty, 'server-qty': serverItem.qty }) - if (dryRun) return diffLog - if (forceClientState || !config.cart.serverSyncCanModifyLocalItems) { - const updateServerItemDiffLog = await dispatch('updateServerItem', { clientItem, serverItem, updateIds: true, mergeQty }) - - return diffLog.merge(updateServerItemDiffLog) - } - - await dispatch('updateItem', { product: serverItem }) - } - - return diffLog - }, - async mergeClientItem ({ dispatch }, { clientItem, serverItems, forceClientState, dryRun, mergeQty }) { - const serverItem = serverItems.find(itm => productsEquals(itm, clientItem)) - const diffLog = await dispatch('synchronizeServerItem', { serverItem, clientItem, forceClientState, dryRun, mergeQty }) - - if (!diffLog.isEmpty()) return diffLog - - Logger.info('Server and client item with SKU ' + clientItem.sku + ' synced. Updating cart.', 'cart', 'cart')() - if (!dryRun) { - const product = { - sku: clientItem.sku, - server_cart_id: serverItem.quote_id, - server_item_id: serverItem.item_id, - product_option: serverItem.product_option, - type_id: serverItem.product_type - } - - await dispatch('updateItem', { product }) - } - - return diffLog - }, - async mergeClientItems ({ dispatch }, { clientItems, serverItems, forceClientState, dryRun, mergeQty }) { - const diffLog = createDiffLog() - - for (const clientItem of clientItems) { - try { - const mergeClientItemDiffLog = await dispatch('mergeClientItem', { clientItem, serverItems, forceClientState, dryRun, mergeQty }) - diffLog.merge(mergeClientItemDiffLog) - } catch (e) { - Logger.debug('Problem syncing clientItem', 'cart', clientItem)() - } - } - - return diffLog - }, - async mergeServerItem ({ dispatch, getters }, { clientItems, serverItem, forceClientState, dryRun }) { - const diffLog = createDiffLog() - const clientItem = clientItems.find(itm => productsEquals(itm, serverItem)) - if (clientItem) return diffLog - Logger.info('No client item for' + serverItem.sku, 'cart')() - diffLog.pushClientParty({ sku: serverItem.sku, status: 'no-item' }) - if (dryRun) return diffLog - - if (forceClientState) { - Logger.info('Removing product from cart', 'cart', serverItem)() - Logger.log('Removing item' + serverItem.sku + serverItem.item_id, 'cart')() - const cartItem = { - sku: serverItem.sku, - item_id: serverItem.item_id, - quoteId: serverItem.quote_id - } as any as CartItem - - const resp = await CartService.deleteItem(getters.getCartToken, cartItem) - return diffLog.pushServerResponse({ status: resp.resultCode, sku: serverItem.sku, result: resp }) - } - - const productToAdd = await dispatch('getProductVariant', { serverItem }) - - if (productToAdd) { - await dispatch('addItem', { productToAdd, forceServerSilence: true }) - Logger.debug('Product variant for given serverItem has not found', 'cart', serverItem)() - } - - return diffLog - }, - async mergeServerItems ({ dispatch }, { serverItems, clientItems, forceClientState, dryRun }) { - const diffLog = createDiffLog() - const definedServerItems = serverItems.filter(serverItem => serverItem) - - for (const serverItem of definedServerItems) { - try { - const mergeServerItemDiffLog = await dispatch('mergeServerItem', { clientItems, serverItem, forceClientState, dryRun }) - diffLog.merge(mergeServerItemDiffLog) - } catch (e) { - Logger.debug('Problem syncing serverItem', 'cart', serverItem)() - } - } - - return diffLog - }, - async updateTotalsAfterMerge ({ dispatch, getters, commit }, { clientItems, dryRun }) { - if (dryRun) return - - if (getters.isTotalsSyncRequired && clientItems.length > 0) { - await dispatch('syncTotals') - } - - commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) - }, - async merge ({ getters, dispatch }, { serverItems, clientItems, dryRun = false, forceClientState = false, mergeQty = false }) { - const hookResult = cartHooksExecutors.beforeSync({ clientItems, serverItems }) - - const diffLog = createDiffLog() - const mergeParameters = { - clientItems: hookResult.clientItems, - serverItems: hookResult.serverItems, - forceClientState, - dryRun, - mergeQty - } - const mergeClientItemsDiffLog = await dispatch('mergeClientItems', mergeParameters) - const mergeServerItemsDiffLog = await dispatch('mergeServerItems', mergeParameters) - await dispatch('updateTotalsAfterMerge', { clientItems, dryRun }) - - diffLog - .merge(mergeClientItemsDiffLog) - .merge(mergeServerItemsDiffLog) - .pushClientParty({ status: getters.isCartHashChanged ? 'update-required' : 'no-changes' }) - .pushServerParty({ status: getters.isTotalsSyncRequired ? 'update-required' : 'no-changes' }) - - EventBus.$emit('servercart-after-diff', { diffLog: diffLog, serverItems: hookResult.serverItems, clientItems: hookResult.clientItems, dryRun: dryRun }) - Logger.info('Client/Server cart synchronised ', 'cart', diffLog)() - - return diffLog - } -} - -export default mergeActions diff --git a/core/modules/cart/store/actions/methodsActions.ts b/core/modules/cart/store/actions/methodsActions.ts deleted file mode 100644 index e839481dd3..0000000000 --- a/core/modules/cart/store/actions/methodsActions.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import { Logger } from '@vue-storefront/core/lib/logger' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { CartService } from '@vue-storefront/core/data-resolver' -import { preparePaymentMethodsToSync, createOrderData, createShippingInfoData } from '@vue-storefront/core/modules/cart/helpers' -import PaymentMethod from '../../types/PaymentMethod' - -const methodsActions = { - async pullMethods ({ getters, dispatch }, { forceServerSync }) { - if (getters.isTotalsSyncRequired || forceServerSync) { - await dispatch('syncShippingMethods', { forceServerSync }) - await dispatch('syncPaymentMethods', { forceServerSync }) - } else { - Logger.debug('Skipping payment & shipping methods update as cart has not been changed', 'cart')() - } - }, - async setDefaultCheckoutMethods ({ getters, rootGetters, commit }) { - if (!getters.getShippingMethodCode) { - commit(types.CART_UPD_SHIPPING, rootGetters['checkout/getDefaultShippingMethod']) - } - - if (!getters.getPaymentMethodCode) { - commit(types.CART_UPD_PAYMENT, rootGetters['checkout/getDefaultPaymentMethod']) - } - }, - async syncPaymentMethods ({ getters, rootGetters, dispatch }, { forceServerSync = false }) { - if (getters.canUpdateMethods && (getters.isTotalsSyncRequired || forceServerSync)) { - Logger.debug('Refreshing payment methods', 'cart')() - let backendPaymentMethods: PaymentMethod[] - - const paymentDetails = rootGetters['checkout/getPaymentDetails'] - if (paymentDetails.country) { - // use shipping info endpoint to get payment methods using billing address - const shippingMethodsData = createOrderData({ - shippingDetails: rootGetters['checkout/getShippingDetails'], - shippingMethods: rootGetters['checkout/getShippingMethods'], - paymentMethods: rootGetters['checkout/getPaymentMethods'], - paymentDetails: paymentDetails - }) - - if (shippingMethodsData.country) { - const { result } = await CartService.setShippingInfo(createShippingInfoData(shippingMethodsData)) - backendPaymentMethods = result.payment_methods || [] - } - } - if (!backendPaymentMethods || backendPaymentMethods.length === 0) { - const { result } = await CartService.getPaymentMethods() - backendPaymentMethods = result - } - - const { uniqueBackendMethods, paymentMethods } = preparePaymentMethodsToSync( - backendPaymentMethods, - rootGetters['checkout/getNotServerPaymentMethods'] - ) - await dispatch('checkout/replacePaymentMethods', paymentMethods, { root: true }) - EventBus.$emit('set-unique-payment-methods', uniqueBackendMethods) - } else { - Logger.debug('Payment methods does not need to be updated', 'cart')() - } - }, - async updateShippingMethods ({ dispatch }, { shippingMethods }) { - const newShippingMethods = shippingMethods - .map(method => ({ ...method, is_server_method: true })) - .filter(method => !method.hasOwnProperty('available') || method.available) - await dispatch('checkout/replaceShippingMethods', newShippingMethods, { root: true }) - }, - async syncShippingMethods ({ getters, rootGetters, dispatch }, { forceServerSync = false }) { - if (getters.canUpdateMethods && (getters.isTotalsSyncRequired || forceServerSync)) { - const storeView = currentStoreView() - Logger.debug('Refreshing shipping methods', 'cart')() - const shippingDetails = rootGetters['checkout/getShippingDetails'] - - // build address data with what we have - const address = (shippingDetails) ? { - region: shippingDetails.state, - region_id: shippingDetails.region_id ? shippingDetails.region_id : 0, - country_id: shippingDetails.country, - street: [shippingDetails.streetAddress1, shippingDetails.streetAddress2], - postcode: shippingDetails.zipCode, - city: shippingDetails.city, - region_code: shippingDetails.region_code ? shippingDetails.region_code : '' - } : { country_id: storeView.tax.defaultCountry } - - const { result } = await CartService.getShippingMethods(address) - await dispatch('updateShippingMethods', { shippingMethods: result }) - } else { - Logger.debug('Shipping methods does not need to be updated', 'cart')() - } - } -} - -export default methodsActions diff --git a/core/modules/cart/store/actions/productActions.ts b/core/modules/cart/store/actions/productActions.ts deleted file mode 100644 index 1c138835b3..0000000000 --- a/core/modules/cart/store/actions/productActions.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const productActions = { - async findProductOption ({ dispatch }, { serverItem }) { - if (serverItem.product_type === 'configurable') { - let query = new SearchQuery() - query = query.applyFilter({ key: 'configurable_children.sku', value: { 'eq': serverItem.sku } }) - - const { items } = await dispatch('product/findProducts', { - query, - start: 0, - size: 1, - options: { - populateRequestCacheTags: false, - prefetchGroupProducts: false, - separateSelectedVariant: true - } - }, { root: true }) - - return items.length >= 1 ? { sku: items[0].sku, childSku: serverItem.sku } : null - } - - return { sku: serverItem.sku } - }, - async getProductVariant ({ dispatch }, { serverItem }) { - try { - const options = await dispatch('findProductOption', { serverItem }) - const singleProduct = await dispatch('product/single', { options }, { root: true }) - - return { - ...singleProduct, - server_item_id: serverItem.item_id, - qty: serverItem.qty, - server_cart_id: serverItem.quote_id, - product_option: serverItem.product_option || singleProduct.product_option - } - } catch (e) { - return null - } - } -} - -export default productActions diff --git a/core/modules/cart/store/actions/quantityActions.ts b/core/modules/cart/store/actions/quantityActions.ts deleted file mode 100644 index e217c48959..0000000000 --- a/core/modules/cart/store/actions/quantityActions.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { Logger } from '@vue-storefront/core/lib/logger' -import { createDiffLog } from '@vue-storefront/core/modules/cart/helpers' - -const quantityActions = { - async restoreQuantity ({ dispatch }, { product }) { - const currentCartItem = await dispatch('getItem', { product }) - if (currentCartItem) { - Logger.log('Restoring qty after error' + product.sku + currentCartItem.prev_qty, 'cart')() - if (currentCartItem.prev_qty > 0) { - await dispatch('updateItem', { - product: { - ...product, - qty: currentCartItem.prev_qty - } - }) - EventBus.$emit('cart-after-itemchanged', { item: currentCartItem }) - } else { - await dispatch('removeItem', { product: currentCartItem, removeByParentSku: false }) - } - } - }, - async updateQuantity ({ commit, dispatch, getters }, { product, qty, forceServerSilence = false }) { - commit(types.CART_UPD_ITEM, { product, qty }) - if (getters.isCartSyncEnabled && product.server_item_id && !forceServerSilence) { - return dispatch('sync', { forceClientState: true }) - } - - return createDiffLog() - .pushClientParty({ status: 'wrong-qty', sku: product.sku, 'client-qty': qty }) - } -} - -export default quantityActions diff --git a/core/modules/cart/store/actions/synchronizeActions.ts b/core/modules/cart/store/actions/synchronizeActions.ts deleted file mode 100644 index f332fabe2f..0000000000 --- a/core/modules/cart/store/actions/synchronizeActions.ts +++ /dev/null @@ -1,110 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' -import { isServer } from '@vue-storefront/core/helpers' -import config from 'config' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { CartService } from '@vue-storefront/core/data-resolver' -import { createDiffLog } from '@vue-storefront/core/modules/cart/helpers' -import i18n from '@vue-storefront/i18n' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { cartHooksExecutors } from '../../hooks' - -const synchronizeActions = { - async load ({ commit, dispatch }, { forceClientState = false }: {forceClientState?: boolean} = {}) { - if (isServer) return - - dispatch('setDefaultCheckoutMethods') - const storedItems = await StorageManager.get('cart').getItem('current-cart') - commit(types.CART_LOAD_CART, storedItems) - dispatch('synchronizeCart', { forceClientState }) - - cartHooksExecutors.afterLoad(storedItems) - }, - updateCart ({ commit }, { items }) { - commit(types.CART_LOAD_CART, items) - }, - async synchronizeCart ({ commit, dispatch }, { forceClientState }) { - const { synchronize, serverMergeByDefault } = config.cart - if (!synchronize) return - const cartStorage = StorageManager.get('cart') - const token = await cartStorage.getItem('current-cart-token') - const hash = await cartStorage.getItem('current-cart-hash') - - if (hash) { - commit(types.CART_SET_ITEMS_HASH, hash) - Logger.info('Cart hash received from cache.', 'cache', hash)() - } - if (token) { - commit(types.CART_LOAD_CART_SERVER_TOKEN, token) - Logger.info('Cart token received from cache.', 'cache', token)() - Logger.info('Syncing cart with the server.', 'cart')() - dispatch('sync', { forceClientState, dryRun: !serverMergeByDefault }) - } - await dispatch('create') - }, - /** @deprecated backward compatibility only */ - async serverPull ({ dispatch }, { forceClientState = false, dryRun = false }) { - Logger.warn('The "cart/serverPull" action is deprecated and will not be supported with the Vue Storefront 1.11', 'cart')() - return dispatch('sync', { forceClientState, dryRun }) - }, - async sync ({ getters, rootGetters, commit, dispatch, state }, { forceClientState = false, dryRun = false, mergeQty = false, forceSync = false }) { - const shouldUpdateClientState = rootGetters['checkout/isUserInCheckout'] || forceClientState - const { getCartItems, canUpdateMethods, isSyncRequired, bypassCounter } = getters - if ((!canUpdateMethods || !isSyncRequired) && !forceSync) return createDiffLog() - commit(types.CART_SET_SYNC) - const { result, resultCode } = await CartService.getItems() - const { serverItems, clientItems } = cartHooksExecutors.beforeSync({ clientItems: getCartItems, serverItems: result }) - - if (resultCode === 200) { - const diffLog = await dispatch('merge', { - dryRun, - serverItems, - clientItems, - forceClientState: shouldUpdateClientState, - mergeQty - }) - cartHooksExecutors.afterSync(diffLog) - return diffLog - } - - if (bypassCounter < config.queues.maxCartBypassAttempts) { - Logger.log('Bypassing with guest cart' + bypassCounter, 'cart')() - commit(types.CART_UPDATE_BYPASS_COUNTER, { counter: 1 }) - await dispatch('connect', { guestCart: true }) - } - - Logger.error(result, 'cart') - cartHooksExecutors.afterSync(result) - commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) - return createDiffLog() - }, - async stockSync ({ dispatch, commit, getters }, stockTask) { - const product = { sku: stockTask.product_sku } - - const cartItem = await dispatch('getItem', { product }) - - if (!cartItem || stockTask.result.code === 'ENOTFOUND') return - - if (!stockTask.result.is_in_stock) { - if (!config.stock.allowOutOfStockInCart && !config.cart.synchronize) { - Logger.log('Removing product from cart' + stockTask.product_sku, 'stock')() - commit(types.CART_DEL_ITEM, { product: { sku: stockTask.product_sku } }, { root: true }) - return - } - - dispatch('updateItem', { - product: { errors: { stock: i18n.t('Out of the stock!') }, sku: stockTask.product_sku, is_in_stock: false } - }) - - return - } - - dispatch('updateItem', { - product: { info: { stock: i18n.t('In stock!') }, sku: stockTask.product_sku, is_in_stock: true } - }) - EventBus.$emit('cart-after-itemchanged', { item: cartItem }) - commit(types.CART_SET_ITEMS_HASH, getters.getCurrentCartHash) - } -} - -export default synchronizeActions diff --git a/core/modules/cart/store/actions/totalsActions.ts b/core/modules/cart/store/actions/totalsActions.ts deleted file mode 100644 index cbaf541e34..0000000000 --- a/core/modules/cart/store/actions/totalsActions.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' -import { CartService } from '@vue-storefront/core/data-resolver' -import { - preparePaymentMethodsToSync, - prepareShippingInfoForUpdateTotals, - createOrderData, - createShippingInfoData -} from '@vue-storefront/core/modules/cart/helpers' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' - -const totalsActions = { - async updateTotals ({ commit }, payload) { - commit(types.CART_UPD_TOTALS, payload) - }, - async getTotals (context, { addressInformation, hasShippingInformation }) { - if (hasShippingInformation) { - return CartService.setShippingInfo(addressInformation) - } - - return CartService.getTotals() - }, - async overrideServerTotals ({ commit, getters, rootGetters, dispatch }, { addressInformation, hasShippingInformation }) { - const { resultCode, result } = await dispatch('getTotals', { addressInformation, hasShippingInformation }) - - if (resultCode === 200) { - const totals = result.totals || result - Logger.info('Overriding server totals. ', 'cart', totals)() - const itemsAfterTotal = prepareShippingInfoForUpdateTotals(totals.items) - - for (let key of Object.keys(itemsAfterTotal)) { - const item = itemsAfterTotal[key] - const product = { server_item_id: item.item_id, totals: item, qty: item.qty } - await dispatch('updateItem', { product }) - } - - commit(types.CART_UPD_TOTALS, { itemsAfterTotal, totals, platformTotalSegments: totals.total_segments }) - commit(types.CART_SET_TOTALS_SYNC) - - // we received payment methods as a result of this call, updating state - if (result.payment_methods && getters.canUpdateMethods) { - const { uniqueBackendMethods, paymentMethods } = preparePaymentMethodsToSync( - result.payment_methods.map(method => ({ ...method, is_server_method: true })), - rootGetters['checkout/getNotServerPaymentMethods'] - ) - dispatch('checkout/replacePaymentMethods', paymentMethods, { root: true }) - EventBus.$emit('set-unique-payment-methods', uniqueBackendMethods) - } - - return - } - - Logger.error(result, 'cart')() - }, - async syncTotals ({ dispatch, getters, rootGetters }, payload: { forceServerSync: boolean, methodsData?: any } = { forceServerSync: false, methodsData: null }) { - const methodsData = payload ? payload.methodsData : null - await dispatch('pullMethods', { forceServerSync: payload.forceServerSync }) - - if (getters.canSyncTotals && (getters.isTotalsSyncRequired || payload.forceServerSync)) { - const shippingMethodsData = methodsData || createOrderData({ - shippingDetails: rootGetters['checkout/getShippingDetails'], - shippingMethods: rootGetters['checkout/getShippingMethods'], - paymentMethods: rootGetters['checkout/getPaymentMethods'], - paymentDetails: rootGetters['checkout/getPaymentDetails'] - }) - - if (shippingMethodsData.country) { - await dispatch('overrideServerTotals', { - hasShippingInformation: shippingMethodsData.method_code || shippingMethodsData.carrier_code, - addressInformation: createShippingInfoData(shippingMethodsData) - }) - return - } - - Logger.error('Please do set the tax.defaultCountry in order to calculate totals', 'cart')() - } - }, - async refreshTotals ({ dispatch }, payload) { - Logger.warn('The "cart/refreshTotals" action is deprecated and will not be supported with the Vue Storefront 1.11', 'cart')() - await dispatch('syncTotals', payload) - } -} - -export default totalsActions diff --git a/core/modules/cart/store/getters.ts b/core/modules/cart/store/getters.ts deleted file mode 100644 index 523381646f..0000000000 --- a/core/modules/cart/store/getters.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { GetterTree } from 'vuex' -import sumBy from 'lodash-es/sumBy' -import CartState from '../types/CartState' -import RootState from '@vue-storefront/core/types/RootState' -import AppliedCoupon from '../types/AppliedCoupon' -import { onlineHelper, isServer, calcItemsHmac } from '@vue-storefront/core/helpers' -import { calculateTotals } from '@vue-storefront/core/modules/cart/helpers' -import config from 'config' - -const getters: GetterTree = { - getCartToken: state => state.cartServerToken, - getLastSyncDate: state => state.cartServerLastSyncDate, - getLastTotalsSyncDate: state => state.cartServerLastTotalsSyncDate, - getShippingMethod: state => state.shipping, - getPaymentMethod: state => state.payment, - getLastCartHash: state => state.cartItemsHash, - getCurrentCartHash: state => calcItemsHmac(state.cartItems, state.cartServerToken), - isCartHashChanged: (state, getters) => getters.getCurrentCartHash !== state.cartItemsHash, - isSyncRequired: (state, getters) => getters.isCartHashEmptyOrChanged || !state.cartServerLastSyncDate, - isTotalsSyncRequired: (state, getters) => getters.isCartHashEmptyOrChanged || !state.cartServerLastTotalsSyncDate, - isCartHashEmptyOrChanged: (state, getters) => !state.cartItemsHash || getters.isCartHashChanged, - getCartItems: state => state.cartItems, - isTotalsSyncEnabled: () => config.cart.synchronize_totals && onlineHelper.isOnline && !isServer, - isCartConnected: state => !!state.cartServerToken, - isCartSyncEnabled: () => config.cart.synchronize && onlineHelper.isOnline && !isServer, - getFirstShippingMethod: state => state.shipping instanceof Array ? state.shipping[0] : state.shipping, - getFirstPaymentMethod: state => state.payment instanceof Array ? state.payment[0] : state.payment, - getTotals: ({ cartItems, platformTotalSegments }, getters) => - (platformTotalSegments && onlineHelper.isOnline) ? platformTotalSegments : calculateTotals(getters.getFirstShippingMethod, getters.getFirstPaymentMethod, cartItems), - getItemsTotalQuantity: ({ cartItems }) => config.cart.minicartCountType === 'items' ? cartItems.length : sumBy(cartItems, p => p.qty), - getCoupon: ({ platformTotals }): AppliedCoupon | false => - !(platformTotals && platformTotals.hasOwnProperty('coupon_code')) ? false : { code: platformTotals.coupon_code, discount: platformTotals.discount_amount }, - isVirtualCart: ({ cartItems }) => cartItems.length ? cartItems.every(itm => itm.type_id === 'downloadable' || itm.type_id === 'virtual') : false, - canUpdateMethods: (state, getters) => getters.isCartSyncEnabled && getters.isCartConnected, - canSyncTotals: (state, getters) => getters.isTotalsSyncEnabled && getters.isCartConnected, - isCartEmpty: state => state.cartItems.length === 0, - bypassCounter: state => state.connectBypassCount, - getShippingMethodCode: state => state.shipping && state.shipping.method_code, - getPaymentMethodCode: state => state.payment && state.payment.code, - getIsAdding: state => state.isAddingToCart, - getIsMicroCartOpen: state => state.isMicrocartOpen - -} - -export default getters diff --git a/core/modules/cart/store/index.ts b/core/modules/cart/store/index.ts deleted file mode 100644 index 34ea8c3374..0000000000 --- a/core/modules/cart/store/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import CartState from '../types/CartState' - -export const cartStore: Module = { - namespaced: true, - state: { - isMicrocartOpen: false, - itemsAfterPlatformTotals: {}, - platformTotals: null, - platformTotalSegments: null, - cartIsLoaded: false, - cartServerToken: '', // server side ID to synchronize with Backend (for example Magento) - shipping: [], - payment: [], - cartItemsHash: '', - cartServerLastSyncDate: 0, - cartServerLastTotalsSyncDate: 0, - cartItems: [], // TODO: check if it's properly namespaced - connectBypassCount: 0, - isAddingToCart: false - }, - getters, - actions, - mutations -} diff --git a/core/modules/cart/store/mutation-types.ts b/core/modules/cart/store/mutation-types.ts deleted file mode 100644 index 5831ea5bb7..0000000000 --- a/core/modules/cart/store/mutation-types.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const SN_CART = 'cart' -export const CART_ADD_ITEM = SN_CART + '/ADD' -export const CART_DEL_ITEM = SN_CART + '/DEL' -export const CART_DEL_NON_CONFIRMED_ITEM = SN_CART + '/DEL_NONCONFIRMED' -export const CART_UPD_ITEM = SN_CART + '/UPD' -export const CART_LOAD_CART = SN_CART + '/LOAD' -export const CART_UPD_SHIPPING = SN_CART + '/SHIPPING' -export const CART_SAVE = SN_CART + '/SAVE' -export const CART_SET_ITEMS_HASH = SN_CART + '/SAVE_HASH' -export const CART_SET_SYNC = SN_CART + '/MARK_SYNC' -export const CART_SET_TOTALS_SYNC = SN_CART + '/MARK_TOTALS_SYNC' -export const CART_UPD_ITEM_PROPS = SN_CART + '/UPD_PROPS' -export const CART_UPD_TOTALS = SN_CART + '/UPD_TOTALS' -export const CART_LOAD_CART_SERVER_TOKEN = SN_CART + '/SRV_TOKEN' -export const CART_UPD_PAYMENT = SN_CART + '/UPD_PAYMENT' -export const CART_TOGGLE_MICROCART = SN_CART + '/TOGGLE_MICROCART' -export const CART_UPDATE_BYPASS_COUNTER = SN_CART + '/UPD_BYPASS_COUNTER' -export const CART_ADDING_ITEM = SN_CART + '/UPD_ADDING_ITEM' diff --git a/core/modules/cart/store/mutations.ts b/core/modules/cart/store/mutations.ts deleted file mode 100644 index a0bfce3a1b..0000000000 --- a/core/modules/cart/store/mutations.ts +++ /dev/null @@ -1,99 +0,0 @@ -import Vue from 'vue' -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import CartState from '../types/CartState' -import config from 'config' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import productsEquals from './../helpers/productsEquals' - -const mutations: MutationTree = { - /** - * Add product to cart - * @param {Object} product data format for products is described in /doc/ElasticSearch data formats.md - */ - [types.CART_ADD_ITEM] (state, { product }) { - const record = state.cartItems.find(p => productsEquals(p, product)) - if (!record) { - let item = { - ...product, - qty: parseInt(product.qty ? product.qty : 1) - } - EventBus.$emit('cart-before-add', { product: item }) - state.cartItems.push(item) - } else { - EventBus.$emit('cart-before-update', { product: record }) - record.qty += parseInt((product.qty ? product.qty : 1)) - } - }, - [types.CART_SET_ITEMS_HASH] (state, hash = null) { - state.cartItemsHash = hash - }, - [types.CART_SET_SYNC] (state) { - state.cartServerLastSyncDate = new Date().getTime() - }, - [types.CART_SET_TOTALS_SYNC] (state) { - state.cartServerLastTotalsSyncDate = new Date().getTime() - }, - [types.CART_DEL_ITEM] (state, { product, removeByParentSku = true }) { - EventBus.$emit('cart-before-delete', { items: state.cartItems }) - state.cartItems = state.cartItems.filter(p => !productsEquals(p, product) && (p.parentSku !== product.sku || removeByParentSku === false)) - EventBus.$emit('cart-after-delete', { items: state.cartItems }) - }, - [types.CART_DEL_NON_CONFIRMED_ITEM] (state, { product, removeByParentSku = true }) { - EventBus.$emit('cart-before-delete', { items: state.cartItems }) - state.cartItems = state.cartItems.filter(p => (!productsEquals(p, product) && (p.parentSku !== product.sku || removeByParentSku === false)) || p.server_item_id/* it's confirmed if server_item_id is set */) - EventBus.$emit('cart-after-delete', { items: state.cartItems }) - }, - [types.CART_UPD_ITEM] (state, { product, qty }) { - const record = state.cartItems.find(p => productsEquals(p, product)) - - if (record) { - EventBus.$emit('cart-before-update', { product: record }) - record.qty = parseInt(qty) - EventBus.$emit('cart-after-update', { product: record }) - } - }, - [types.CART_UPD_ITEM_PROPS] (state, { product }) { - let record = state.cartItems.find(p => (productsEquals(p, product) || (p.server_item_id && p.server_item_id === product.server_item_id))) - if (record) { - EventBus.$emit('cart-before-itemchanged', { item: record }) - Object.entries(product).forEach(([key, value]) => Vue.set(record, key, value)) - EventBus.$emit('cart-after-itemchanged', { item: record }) - } - }, - [types.CART_UPD_SHIPPING] (state, shippingMethod) { - state.shipping = shippingMethod - }, - [types.CART_LOAD_CART] (state, storedItems) { - state.cartItems = storedItems || [] - state.cartIsLoaded = true - - // EventBus.$emit('order/PROCESS_QUEUE', { config: config }) // process checkout queue - EventBus.$emit('sync/PROCESS_QUEUE', { config }) // process checkout queue - EventBus.$emit('application-after-loaded') - EventBus.$emit('cart-after-loaded') - }, - [types.CART_LOAD_CART_SERVER_TOKEN] (state, token) { - state.cartServerToken = token - }, - [types.CART_UPD_TOTALS] (state, { itemsAfterTotals, totals, platformTotalSegments }) { - state.itemsAfterPlatformTotals = itemsAfterTotals - state.platformTotals = totals - state.platformTotalSegments = platformTotalSegments - EventBus.$emit('cart-after-updatetotals', { platformTotals: totals, platformTotalSegments: platformTotalSegments }) - }, - [types.CART_UPD_PAYMENT] (state, paymentMethod) { - state.payment = paymentMethod - }, - [types.CART_TOGGLE_MICROCART] (state) { - state.isMicrocartOpen = !state.isMicrocartOpen - }, - [types.CART_UPDATE_BYPASS_COUNTER] (state, { counter }) { - state.connectBypassCount = state.connectBypassCount + counter - }, - [types.CART_ADDING_ITEM] (state, { isAdding }) { - state.isAddingToCart = isAdding - } -} - -export default mutations diff --git a/core/modules/cart/test/unit/components/AddToCart.spec.ts b/core/modules/cart/test/unit/components/AddToCart.spec.ts deleted file mode 100644 index a09a411320..0000000000 --- a/core/modules/cart/test/unit/components/AddToCart.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { AddToCart } from '../../../components/AddToCart' -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/app', () => ({ createApp: jest.fn() })) -jest.mock('@vue-storefront/i18n', () => ({ loadLanguageAsync: jest.fn() })) -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})); - -describe('AddToCart', () => { - it('addToCart dispatches addItem action', () => { - const product = {} as any as Product; - - const storeMock = { - modules: { - cart: { - actions: { - addItem: jest.fn(() => []) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(AddToCart, storeMock, { propsData: { product } }); - - (wrapper.vm as any).addToCart(product); - - expect(storeMock.modules.cart.actions.addItem).toBeCalledWith(expect.anything(), { productToAdd: product }); - }) -}); diff --git a/core/modules/cart/test/unit/components/Microcart.spec.ts b/core/modules/cart/test/unit/components/Microcart.spec.ts deleted file mode 100644 index 5c3c30ec86..0000000000 --- a/core/modules/cart/test/unit/components/Microcart.spec.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; - -import AppliedCoupon from '../../../types/AppliedCoupon' -import CartTotalSegments from '../../../types/CartTotalSegments' -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -import { Microcart } from '../../../components/Microcart' - -describe('Microcart', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('productsInCart returns products in cart', () => { - const storeMock = { - modules: { - cart: { - state: { - cartItems: [{} as any as Product] - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Microcart, storeMock); - - expect((wrapper.vm as any).productsInCart).toBe(storeMock.modules.cart.state.cartItems); - }); - - it('appliedCoupon returns currently set coupon', () => { - const storeMock = { - modules: { - cart: { - getters: { - getCoupon: () => ({} as any as AppliedCoupon) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Microcart, storeMock); - - expect((wrapper.vm as any).appliedCoupon).toEqual(storeMock.modules.cart.getters.getCoupon()); - }); - - it('totals returns cart totals', () => { - const storeMock = { - modules: { - cart: { - getters: { - getTotals: () => ({} as any as CartTotalSegments) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Microcart, storeMock); - - expect((wrapper.vm as any).totals).toEqual(storeMock.modules.cart.getters.getTotals()); - }); - - it('isOpen returns cart state if it is open', () => { - const storeMock = { - modules: { - cart: { - state: { - isMicrocartOpen: true - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Microcart, storeMock); - - expect((wrapper.vm as any).isOpen).toBe(storeMock.modules.cart.state.isMicrocartOpen); - }); - - it('applyCoupon dispatches applyCupon action to save it', () => { - const couponCode = 'foo'; - const storeMock = { - modules: { - cart: { - actions: { - applyCoupon: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Microcart, storeMock); - - (wrapper.vm as any).applyCoupon(couponCode); - - expect(storeMock.modules.cart.actions.applyCoupon).toBeCalledWith(expect.anything(), 'foo'); - }); - - it('removeCoupon dispatches removeCoupon action to delete it', () => { - const storeMock = { - modules: { - cart: { - actions: { - removeCoupon: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Microcart, storeMock); - - (wrapper.vm as any).removeCoupon(); - - expect(storeMock.modules.cart.actions.removeCoupon).toBeCalled(); - }); - - it('toggleMicrocart dispatches toggleMicrocart to change its state', () => { - const storeMock = { - modules: { - ui: { - actions: { - toggleMicrocart: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Microcart, storeMock); - - (wrapper.vm as any).toggleMicrocart(); - - expect(storeMock.modules.ui.actions.toggleMicrocart).toBeCalled(); - }); -}); diff --git a/core/modules/cart/test/unit/components/MicrocartButton.spec.ts b/core/modules/cart/test/unit/components/MicrocartButton.spec.ts deleted file mode 100644 index 012b3737dd..0000000000 --- a/core/modules/cart/test/unit/components/MicrocartButton.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; - -import { MicrocartButton } from '../../../components/MicrocartButton' - -describe('MicrocartButton', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('quantity returns total quantity of products in cart', () => { - const storeMock = { - modules: { - cart: { - getters: { - getItemsTotalQuantity: () => 123 - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(MicrocartButton, storeMock); - - expect((wrapper.vm as any).quantity).toEqual(storeMock.modules.cart.getters.getItemsTotalQuantity()); - }); - - it('toggleMicrocart dispatches toggleMicrocart to change its state', () => { - const storeMock = { - modules: { - cart: { - actions: { - toggleMicrocart: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(MicrocartButton, storeMock); - - (wrapper.vm as any).toggleMicrocart(); - - expect(storeMock.modules.cart.actions.toggleMicrocart).toBeCalled(); - }); -}); diff --git a/core/modules/cart/test/unit/components/Product.spec.ts b/core/modules/cart/test/unit/components/Product.spec.ts deleted file mode 100644 index 10dff6749d..0000000000 --- a/core/modules/cart/test/unit/components/Product.spec.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { productThumbnailPath, getThumbnailPath } from '@vue-storefront/core/helpers'; -import config from 'config' -import { MicrocartProduct } from '../../../components/Product'; -import Mock = jest.Mock; - -jest.mock('@vue-storefront/core/helpers', () => ({ - productThumbnailPath: jest.fn(), - getThumbnailPath: jest.fn() -})); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/lib/multistore', () => jest.fn()) -jest.mock('@vue-storefront/core/lib/storage-manager', () => jest.fn()) -jest.mock('@vue-storefront/core/store', () => ({})) - -describe('MicrocartProduct', () => { - beforeEach(() => { - jest.clearAllMocks(); - Object.keys(config).forEach((key) => { delete config[key]; }); - }); - - it('thumbnail in online mode returns thumbnail in lower size', () => { - config.products = { - thumbnails: { - width: 300, - height: 300 - } - }; - config.cart = { - thumbnails: { - width: 150, - height: 150 - } - }; - - (productThumbnailPath as Mock).mockReturnValueOnce('thumbnail-path'); - (getThumbnailPath as Mock).mockReturnValueOnce('resized-thumbnail-path'); - - Object.defineProperty(navigator, 'onLine', { value: true, configurable: true }); - - const product = {} as any as Product; - const wrapper = mountMixin(MicrocartProduct, { propsData: { product } }); - - expect((wrapper.vm as any).thumbnail).toEqual('resized-thumbnail-path'); - expect(getThumbnailPath).toBeCalledWith('thumbnail-path', 150, 150); - }); - - it('thumbnail in offline mode returns thumbnail in greater size', () => { - config.products = { - thumbnails: { - width: 300, - height: 300 - } - }; - config.cart = { - thumbnails: { - width: 150, - height: 150 - } - }; - - (productThumbnailPath as Mock).mockReturnValueOnce('thumbnail-path'); - (getThumbnailPath as Mock).mockReturnValueOnce('resized-thumbnail-path'); - - Object.defineProperty(navigator, 'onLine', { value: false, configurable: true }); - - const product = {} as any as Product; - const wrapper = mountMixin(MicrocartProduct, { propsData: { product } }); - - expect((wrapper.vm as any).thumbnail).toEqual('resized-thumbnail-path'); - expect(getThumbnailPath).toBeCalledWith('thumbnail-path', 300, 300); - }); - - it('removeFromCart dispatches removeItem to remove product from cart', () => { - const product = {} as any as Product; - const storeMock = { - modules: { - cart: { - actions: { - removeItem: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(MicrocartProduct, storeMock, { propsData: { product } }); - - (wrapper.vm as any).removeFromCart(); - - expect(storeMock.modules.cart.actions.removeItem).toBeCalledWith(expect.anything(), { product }); - }); - - it('updateQuantity dispatches updateQuantity update product quantity in cart', () => { - const product = {} as any as Product; - const qty = 123; - const storeMock = { - modules: { - cart: { - actions: { - updateQuantity: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(MicrocartProduct, storeMock, { propsData: { product } }); - - (wrapper.vm as any).updateQuantity(qty); - - expect(storeMock.modules.cart.actions.updateQuantity).toBeCalledWith( - expect.anything(), - { product, qty } - ); - }); -}); diff --git a/core/modules/cart/test/unit/helpers/cartCacheHandler.spec.ts b/core/modules/cart/test/unit/helpers/cartCacheHandler.spec.ts deleted file mode 100644 index 8f97923b46..0000000000 --- a/core/modules/cart/test/unit/helpers/cartCacheHandler.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import Vue from 'vue' -import Vuex from 'vuex' - -import * as types from '../../../store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' - -const StorageManager = { - cart: { - setItem: jest.fn() - }, - get (key) { - return this[key] - }, - clear () { - return new Promise((resolve, reject) => { - resolve() - }) - } -}; -const cartCacheHandlerPlugin = require('../../../helpers/cartCacheHandler').cartCacheHandlerPlugin - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ StorageManager })) -jest.mock('@vue-storefront/core/helpers', () => ({ - isServer: () => false -})); -jest.mock('@vue-storefront/core/app', () => ({ createApp: jest.fn() })) -jest.mock('@vue-storefront/i18n', () => ({ loadLanguageAsync: jest.fn() })) - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: () => () => {} - } -})) - -Vue.use(Vuex); - -describe('Cart afterRegistration', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it.each([ - types.CART_LOAD_CART, - types.CART_ADD_ITEM, - types.CART_DEL_ITEM, - types.CART_UPD_ITEM, - types.CART_UPD_ITEM_PROPS - ])('handler populates cart cache on mutation %s that modifies cart items', async (mutationType) => { - const stateMock = { - cart: { - cartItems: [{}] - } - }; - - StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.resolve('foo')); - - await cartCacheHandlerPlugin({ type: mutationType }, stateMock); - - expect(StorageManager.get('cart').setItem) - .toBeCalledWith('current-cart', stateMock.cart.cartItems); - }); - - it('handler logs error when populating cart cache with items fails', async () => { - const stateMock = { - cart: { - cartItems: [{}] - } - }; - - const consoleErrorSpy = jest.spyOn(Logger, 'error'); - - StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.reject('foo')); - - await cartCacheHandlerPlugin({ type: types.CART_LOAD_CART }, stateMock); - - expect(consoleErrorSpy).toBeCalled(); - }); - - it('hook updates cart token in cache on mutation changing cart token', async () => { - const stateMock = { - cart: { - cartServerToken: 'token' - } - }; - - StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.resolve('foo')); - - await cartCacheHandlerPlugin({ type: types.CART_LOAD_CART_SERVER_TOKEN }, stateMock); - - expect(StorageManager.get('cart').setItem) - .toBeCalledWith('current-cart-token', stateMock.cart.cartServerToken); - }); - - it('handler logs error when changing cached token fails', async () => { - const stateMock = { - cart: { - cartServerToken: 'token' - } - }; - - const consoleErrorSpy = jest.spyOn(Logger, 'error'); - - StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.reject('foo')); - - await cartCacheHandlerPlugin({ type: types.CART_LOAD_CART_SERVER_TOKEN }, stateMock); - - expect(consoleErrorSpy).toBeCalled(); - }); - - it('handler ignores mutation not related to cart cache', async () => { - const stateMock = { - cart: { - cartServerToken: 'token' - } - }; - - const consoleErrorSpy = jest.spyOn(Logger, 'error'); - - StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.reject('foo')); - - await cartCacheHandlerPlugin({ type: 'bar' }, stateMock); - - expect(consoleErrorSpy).not.toBeCalled(); - }); -}); diff --git a/core/modules/cart/test/unit/helpers/createOrderData.spec.ts b/core/modules/cart/test/unit/helpers/createOrderData.spec.ts deleted file mode 100644 index 411162be95..0000000000 --- a/core/modules/cart/test/unit/helpers/createOrderData.spec.ts +++ /dev/null @@ -1,171 +0,0 @@ -import createOrderData from '@vue-storefront/core/modules/cart/helpers/createOrderData'; - -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); - -const shippingDetails = { - country: 'UK', - firstName: 'John', - lastName: 'Doe', - city: 'London', - zipCode: 'EC123', - streetAddress: 'JohnDoe street', - region_id: 1, - apartmentNumber: '12', - state: 'xxxx', - phoneNumber: '123123123', - shippingMethod: 'method' -}; - -const paymentDetails = { - country: 'UK', - firstName: 'John', - lastName: 'Doe', - city: 'London', - zipCode: 'EC123', - streetAddress: 'JohnDoe street', - region_id: 1, - apartmentNumber: '12', - state: 'xxxx', - phoneNumber: '123123123', - company: '', - taxId: '', - paymentMethod: '', - paymentMethodAdditional: [] -}; - -describe('Cart createOrderData', () => { - it('returns data with default shipping and default payment', async () => { - const shippingMethods = [ - { - default: false, - offline: false, - method_code: 'CODE1', - carrier_code: 'CODE2' - }, - { - default: true, - offline: false, - method_code: 'CODE3', - carrier_code: 'CODE4' - } - ]; - - const paymentMethods = [ - { - default: false, - code: 'CODE3' - }, - { - default: true, - code: 'CODE4' - } - ]; - - const methodsData = createOrderData({ shippingDetails, shippingMethods, paymentMethods, paymentDetails, taxCountry: 'DE' }) - - expect(methodsData).toEqual({ - carrier_code: 'CODE4', - country: 'UK', - method_code: 'CODE3', - payment_method: 'CODE4', - shippingAddress: { - city: 'London', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - }, - billingAddress: { - city: 'London', - countryId: 'UK', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - } - }); - }); - - it('returns data with first online shipping and first payment', async () => { - const shippingMethods = [ - { - default: false, - offline: false, - method_code: 'CODE1-first', - carrier_code: 'CODE2-first' - }, - { - default: false, - offline: false, - method_code: 'CODE3', - carrier_code: 'CODE4' - } - ]; - - const paymentMethods = [ - { - default: false, - code: 'CODE3' - }, - { - default: false, - code: 'CODE4' - } - ]; - - const methodsData = createOrderData({ shippingDetails, shippingMethods, paymentMethods, paymentDetails, taxCountry: 'DE' }) - - expect(methodsData).toEqual({ - carrier_code: 'CODE2-first', - country: 'UK', - method_code: 'CODE1-first', - payment_method: 'CODE3', - shippingAddress: { - city: 'London', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - }, - billingAddress: { - city: 'London', - countryId: 'UK', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - } - }); - }); - - it('returns data without payment, carrier and method', async () => { - const shippingMethods = []; - const paymentMethods = []; - const methodsData = createOrderData({ shippingDetails, shippingMethods, paymentMethods, paymentDetails, taxCountry: 'DE' }); - - expect(methodsData).toEqual({ - carrier_code: null, - country: 'UK', - method_code: null, - payment_method: null, - shippingAddress: { - city: 'London', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - }, - billingAddress: { - city: 'London', - countryId: 'UK', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - } - }); - }); -}); diff --git a/core/modules/cart/test/unit/helpers/createShippingInfoData.spec.ts b/core/modules/cart/test/unit/helpers/createShippingInfoData.spec.ts deleted file mode 100644 index 3fbcf461f3..0000000000 --- a/core/modules/cart/test/unit/helpers/createShippingInfoData.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import createShippingInfoData from './../../../helpers/createShippingInfoData'; - -describe('Cart createShippingInfoData', () => { - it('returns methods data', async () => { - const methodsData = { - country: 'UK', - carrier_code: 'XX', - method_code: 'YY' - }; - const shippingInfoData = createShippingInfoData(methodsData); - expect(shippingInfoData).toEqual({ - billingAddress: {}, - shippingAddress: { - countryId: 'UK' - }, - shippingCarrierCode: 'XX', - shippingMethodCode: 'YY' - }); - }); - - it('returns methods data with shipping address', async () => { - const methodsData = { - country: 'UK', - carrier_code: 'XX', - method_code: 'YY', - shippingAddress: { - city: 'London', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - } - }; - const shippingInfoData = createShippingInfoData(methodsData); - expect(shippingInfoData).toEqual({ - billingAddress: {}, - shippingAddress: { - city: 'London', - countryId: 'UK', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - }, - shippingCarrierCode: 'XX', - shippingMethodCode: 'YY' - }); - }); - - it('returns methods data with billing address', async () => { - const methodsData = { - country: 'UK', - carrier_code: 'XX', - method_code: 'YY', - billingAddress: { - city: 'London', - countryId: 'UK', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - } - }; - const shippingInfoData = createShippingInfoData(methodsData); - expect(shippingInfoData).toEqual({ - shippingAddress: { countryId: 'UK' }, - billingAddress: { - city: 'London', - countryId: 'UK', - firstname: 'John', - lastname: 'Doe', - postcode: 'EC123', - street: ['JohnDoe street'] - }, - shippingCarrierCode: 'XX', - shippingMethodCode: 'YY' - }); - }); - - it('doesn\t add shippingCarrierCode or shippingMethodCode if missing carrier_code or method_code', async () => { - const methodsData = { - country: 'UK' - }; - const shippingInfoData = createShippingInfoData(methodsData); - expect(shippingInfoData).toEqual({ - billingAddress: {}, - shippingAddress: { - countryId: 'UK' - } - }); - }); -}); diff --git a/core/modules/cart/test/unit/helpers/prepareProductsToAdd.spec.ts b/core/modules/cart/test/unit/helpers/prepareProductsToAdd.spec.ts deleted file mode 100644 index 4af8c9556b..0000000000 --- a/core/modules/cart/test/unit/helpers/prepareProductsToAdd.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import prepareProductsToAdd from './../../../helpers/prepareProductsToAdd'; - -jest.mock('@vue-storefront/core/modules/cart/helpers/productChecksum', () => () => 'some checksum') - -const createProduct = ({ type_id }): CartItem => ({ - type_id, - qty: 1, - product_links: [ - { - link_type: 'associated', - product: { - sku: 'SK-001' - } - } - ] -} as any as CartItem) - -describe('Cart prepareProductsToAdd', () => { - it('returns associated products', async () => { - const product = createProduct({ type_id: 'grouped' }) - expect(prepareProductsToAdd(product)).toEqual([{ sku: 'SK-001', checksum: 'some checksum' }]) - }); - - it('returns products with checksum applied', async () => { - const product = createProduct({ type_id: 'bundle' }) - expect(prepareProductsToAdd(product)).toEqual([{ qty: 1, type_id: 'bundle', checksum: 'some checksum' }]) - }); -}); diff --git a/core/modules/cart/test/unit/helpers/prepareShippingInfoForUpdateTotals.spec.ts b/core/modules/cart/test/unit/helpers/prepareShippingInfoForUpdateTotals.spec.ts deleted file mode 100644 index c1c9b50ccf..0000000000 --- a/core/modules/cart/test/unit/helpers/prepareShippingInfoForUpdateTotals.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import prepareShippingInfoForUpdateTotals from '@vue-storefront/core/modules/cart/helpers/prepareShippingInfoForUpdateTotals' -import Totals from '@vue-storefront/core/modules/cart/types/Totals' - -describe('Cart prepareShippingInfoForUpdateTotals', () => { - it('returns shipping info', () => { - const shippingInfoItems = [ - { item_id: 1, key1: 1, key2: 2 }, - { item_id: 2, key1: 3, key2: 4 }, - { item_id: 3, key1: 5, key2: 6 } - ] as any as Totals[] - - expect(prepareShippingInfoForUpdateTotals(shippingInfoItems)).toEqual({ - 1: { item_id: 1, key1: 1, key2: 2 }, - 2: { item_id: 2, key1: 3, key2: 4 }, - 3: { item_id: 3, key1: 5, key2: 6 } - }) - }); - - it('returns shipping info with options', () => { - const shippingInfoItems = [ - { item_id: 1, key1: 1, key2: 2, options: JSON.stringify({ opt1: 1, opt2: 2 }) }, - { item_id: 2, key1: 3, key2: 4, options: JSON.stringify({ opt1: 3, opt2: 4 }) }, - { item_id: 3, key1: 5, key2: 6, options: JSON.stringify({ opt1: 5, opt2: 6 }) } - ] as any as Totals[] - - expect(prepareShippingInfoForUpdateTotals(shippingInfoItems)).toEqual({ - 1: { item_id: 1, key1: 1, key2: 2, options: { opt1: 1, opt2: 2 } }, - 2: { item_id: 2, key1: 3, key2: 4, options: { opt1: 3, opt2: 4 } }, - 3: { item_id: 3, key1: 5, key2: 6, options: { opt1: 5, opt2: 6 } } - }) - }); -}); diff --git a/core/modules/cart/test/unit/helpers/productChecksum.spec.ts b/core/modules/cart/test/unit/helpers/productChecksum.spec.ts deleted file mode 100644 index 3ed37bda09..0000000000 --- a/core/modules/cart/test/unit/helpers/productChecksum.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import productChecksum from './../../../helpers/productChecksum'; - -const configurableProduct: CartItem = { - product_option: { - extension_attributes: { - configurable_item_options: [ - { option_id: '93', option_value: '53' }, - { option_id: '142', option_value: '169' } - ] - } - } -} as any as CartItem; - -const bundleProduct: CartItem = { - product_option: { - extension_attributes: { - bundle_options: [ - { option_id: '1', option_qty: '1', option_selections: [ '2' ] }, - { option_id: '2', option_qty: '1', option_selections: [ '4' ] }, - { option_id: '3', option_qty: '1', option_selections: [ '5' ] }, - { option_id: '4', option_qty: '1', option_selections: [ '8' ] } - ] - } - } -} as any as CartItem; - -describe('Cart productChecksum', () => { - it('returns checksum for bundle product', async () => { - expect(productChecksum(bundleProduct)).toBe('3e183f026489207a9cd535d20f141e07ddfea729af58a9088b82612f'); - }); - - it('returns checksum for configurable product', async () => { - expect(productChecksum(configurableProduct)).toBe('357e8f9f8918873f12ed993c3073ddd3e8980c933034b5e2fdab10b6'); - }); -}); diff --git a/core/modules/cart/test/unit/helpers/productEquals.spec.ts b/core/modules/cart/test/unit/helpers/productEquals.spec.ts deleted file mode 100644 index 9e8e40bd14..0000000000 --- a/core/modules/cart/test/unit/helpers/productEquals.spec.ts +++ /dev/null @@ -1,142 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import productsEquals from './../../../helpers/productsEquals'; - -const createBundleOptions = (options) => { - if (!options) { - return [] - } - - return [ - { - option_id: 1, - option_qty: 1 - }, - { - option_id: 2, - option_qty: 1 - }, - { - option_id: 3, - option_qty: 1 - }, - { - option_id: 4, - option_qty: 1 - } - ].map((o, index) => ({ ...o, option_selections: [options[index]] })) -} - -const createBundleProduct = ({ id, sku, type_id, options }): CartItem => ({ - sku, - type_id, - server_item_id: id, - product_option: { - extension_attributes: { - bundle_options: createBundleOptions(options) - } - } -} as any as CartItem) - -const createCustomOptions = (options) => { - if (!options) { - return [] - } - - return options.map((option, index) => ({ - option_id: index + 1, - option_value: option - })) -} - -const createCustomOptionsProduct = ({ id, sku, options }): CartItem => ({ - sku, - server_item_id: id, - product_option: { - extension_attributes: { - custom_options: createCustomOptions(options) - } - } -} as any as CartItem) - -const createConfigurableProduct = ({ id, sku }): CartItem => ({ - sku, - type_id: 'configurable', - server_item_id: id, - product_option: { - extension_attributes: { - configurable_item_options: [ - { - option_id: '93', - option_value: 53 - }, - { - option_id: '142', - option_value: 169 - } - ] - } - } -} as any as CartItem) - -describe('Cart productEquals', () => { - describe('bundle product', () => { - it('returns true because products have the same options selected', async () => { - const product1 = createBundleProduct({ id: 1, sku: 'WG-001', type_id: 'bundle', options: [2, 4, 5, 8] }) - const product2 = createBundleProduct({ id: 2, sku: 'WG-001', type_id: 'bundle', options: [2, 4, 5, 8] }) - - expect(productsEquals(product1, product2)).toBeTruthy() - }); - - it('returns true because products have the same server id', async () => { - const product1 = createBundleProduct({ id: 1, sku: 'WG-001', type_id: 'bundle', options: null }) - const product2 = createBundleProduct({ id: 1, sku: 'WG-001', type_id: 'none', options: [2, 4, 5, 8] }) - - expect(productsEquals(product1, product2)).toBeTruthy() - }); - - it('returns false because products have not the same options selected', async () => { - const product1 = createBundleProduct({ id: 1, sku: 'WG-001', type_id: 'bundle', options: [2, 2, 5, 8] }) - const product2 = createBundleProduct({ id: 2, sku: 'WG-001', type_id: 'bundle', options: [2, 4, 5, 8] }) - - expect(productsEquals(product1, product2)).toBeFalsy() - }); - }) - - describe('custom options product', () => { - it('returns true because products have the same options selected', async () => { - const product1 = createCustomOptionsProduct({ id: 1, sku: 'WG-001', options: [2, 4, 5, 8] }) - const product2 = createCustomOptionsProduct({ id: 2, sku: 'WG-001', options: [2, 4, 5, 8] }) - - expect(productsEquals(product1, product2)).toBeTruthy() - }); - - it('returns false because bundle products have not the same options selected', async () => { - const product1 = createBundleProduct({ id: 1, sku: 'WG-001', type_id: 'bundle', options: [2, 2, 5, 8] }) - const product2 = createBundleProduct({ id: 2, sku: 'WG-001', type_id: 'bundle', options: [2, 4, 5, 8] }) - expect(productsEquals(product1, product2)).toBeFalsy() - }); - - it('returns true because products have the same server id', async () => { - const product1 = createCustomOptionsProduct({ id: 1, sku: 'WG-001', options: null }) - const product2 = createCustomOptionsProduct({ id: 1, sku: 'WG-001', options: [2, 4, 5, 8] }) - - expect(productsEquals(product1, product2)).toBeTruthy() - }); - - it('returns false because products have not the same options selected', async () => { - const product1 = createCustomOptionsProduct({ id: 1, sku: 'WG-001', options: [2, 2, 5, 8] }) - const product2 = createCustomOptionsProduct({ id: 2, sku: 'WG-001', options: [2, 4, 5, 8] }) - - expect(productsEquals(product1, product2)).toBeFalsy() - }); - }) - - describe('configurable product', () => { - it('returns true because products have the same sku', async () => { - const product1 = createConfigurableProduct({ id: 1, sku: 'WG-001' }) - const product2 = createConfigurableProduct({ id: 2, sku: 'WG-001' }) - - expect(productsEquals(product1, product2)).toBeTruthy() - }); - }) -}); diff --git a/core/modules/cart/test/unit/helpers/totalsCacheHandler.spec.ts b/core/modules/cart/test/unit/helpers/totalsCacheHandler.spec.ts deleted file mode 100644 index beb39fcae5..0000000000 --- a/core/modules/cart/test/unit/helpers/totalsCacheHandler.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import Vue from 'vue' -import Vuex from 'vuex' - -import * as types from '../../../store/mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' - -const StorageManager = { - cart: { - setItem: jest.fn() - }, - get (key) { - return this[key] - }, - clear () { - return new Promise((resolve, reject) => { - resolve() - }) - } -}; -const totalsCacheHandlerPlugin = require('../../../helpers/totalsCacheHandler').totalsCacheHandlerPlugin - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ StorageManager })) -jest.mock('@vue-storefront/core/helpers', () => ({ - isServer: () => false -})); -jest.mock('@vue-storefront/core/app', () => ({ createApp: jest.fn() })) -jest.mock('@vue-storefront/i18n', () => ({ loadLanguageAsync: jest.fn() })) - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: () => () => {} - } -})) - -Vue.use(Vuex); - -describe('Cart afterRegistration', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('handler populates cart cache on mutation CART_UPD_TOTALS that modifies totals', async () => { - const stateMock = { - cart: { - platformTotalSegments: 1, - platformTotals: 2 - } - }; - - StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.resolve('foo')); - - await totalsCacheHandlerPlugin({ type: types.CART_UPD_TOTALS }, stateMock); - - expect(StorageManager.get('cart').setItem) - .toBeCalledWith('current-totals', { - platformTotalSegments: 1, - platformTotals: 2 - }); - }); - - it('handler logs error when populating cart cache with items fails', async () => { - const stateMock = { - cart: { - cartItems: [{}] - } - }; - - const consoleErrorSpy = jest.spyOn(Logger, 'error'); - - StorageManager.get('cart').setItem.mockImplementationOnce(() => Promise.reject('foo')); - - await totalsCacheHandlerPlugin({ type: types.CART_UPD_TOTALS }, stateMock); - - expect(consoleErrorSpy).toBeCalled(); - }); - - it('nothing happens for mutation different than CART_UPD_TOTALS', async () => { - const stateMock = { - cart: { - cartItems: [{}] - } - }; - - const consoleErrorSpy = jest.spyOn(Logger, 'error'); - const storageManagerSpy = jest.spyOn(StorageManager.get('cart'), 'setItem'); - - await totalsCacheHandlerPlugin({ type: 'abc' }, stateMock); - - expect(consoleErrorSpy).not.toBeCalled(); - expect(storageManagerSpy).not.toBeCalled(); - }); -}); diff --git a/core/modules/cart/test/unit/helpers/validateProduct.spec.ts b/core/modules/cart/test/unit/helpers/validateProduct.spec.ts deleted file mode 100644 index f9180c6798..0000000000 --- a/core/modules/cart/test/unit/helpers/validateProduct.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import CartItem from '@vue-storefront/core/modules/cart/types/CartItem' -import validateProduct from '@vue-storefront/core/modules/cart/helpers/validateProduct' - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/multistore', () => jest.fn()) - -describe('Cart validateProduct', () => { - it('returns error about unknown price', () => { - const product = { - price_incl_tax: -1, - errors: {} - } as any as CartItem - - expect(validateProduct(product)).toEqual(['Product price is unknown, product cannot be added to the cart!']) - }); - - it('returns product errors', () => { - const product = { - price_incl_tax: 5, - errors: { - error1: 'error 1', - error2: 'error 2' - } - } as any as CartItem - - expect(validateProduct(product)).toEqual(['error 1', 'error 2']) - }); -}); diff --git a/core/modules/cart/test/unit/index.spec.ts b/core/modules/cart/test/unit/index.spec.ts deleted file mode 100644 index 0e405fcc6e..0000000000 --- a/core/modules/cart/test/unit/index.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CartModule } from '../../index' - -jest.mock('../../store', () => ({})); -jest.mock('@vue-storefront/core/lib/modules', () => ({ createModule: jest.fn(() => ({ module: 'cart' })) })); -jest.mock('../../helpers/cartCacheHandler', () => ({ cartCacheHandlerFactory: jest.fn() })) -jest.mock('@vue-storefront/core/helpers', () => ({ isServer: false })) -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ initCacheStorage: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/store', () => ({})) -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); - -describe('Cart Module', () => { - it('can be initialized', () => { - expect(CartModule).toBeTruthy() - }) -}); diff --git a/core/modules/cart/test/unit/store/connectActions.spec.ts b/core/modules/cart/test/unit/store/connectActions.spec.ts deleted file mode 100644 index 5c12c72559..0000000000 --- a/core/modules/cart/test/unit/store/connectActions.spec.ts +++ /dev/null @@ -1,205 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import config from 'config'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { CartService } from '@vue-storefront/core/data-resolver' -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ CartService: { - getCartToken: jest.fn() -} })); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true - }, - onlineHelper: { - get isOnline () { - return true - } - }, - processLocalizedURLAddress: (url) => url -})); - -describe('Cart connectActions', () => { - it('clear deletes all cart products and token', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isCartSyncEnabled: false } - }; - const wrapper = (actions: any) => actions.clear(contextMock); - config.cart = { synchronize: false }; - - await wrapper(cartActions); - - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true, forceSync: true }); - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_SET_ITEMS_HASH, null); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'disconnect'); - }); - - it('clear deletes all cart products but keep token', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isCartSyncEnabled: false } - }; - const wrapper = (actions: any) => actions.clear(contextMock, { disconnect: false }); - - config.cart = { synchronize: false }; - - await wrapper(cartActions); - - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true, forceSync: true }); - }); - - it('clear deletes all cart products and token, but not sync with backend', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isCartSyncEnabled: false } - }; - const wrapper = (actions: any) => actions.clear(contextMock, { sync: false }); - - config.cart = { synchronize: false }; - - await wrapper(cartActions); - - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_SET_ITEMS_HASH, null); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'disconnect'); - }); - - it('disconnects cart', async () => { - const contextMock = createContextMock() - await (cartActions as any).disconnect(contextMock) - expect(contextMock.commit).toBeCalledWith(types.CART_LOAD_CART_SERVER_TOKEN, null); - }) - - it('authorizes server cart token', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => 1 - })); - - const contextMock = createContextMock({ - getters: { - getCoupon: { - code: null - } - } - }) - - await (cartActions as any).authorize(contextMock) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'connect', { guestCart: false, mergeQty: true }); - }) - - it('creates cart token', async () => { - (CartService.getCartToken as jest.Mock).mockImplementation(async () => - ({ resultCode: 200, result: 'server-cart-token' }) - ); - - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true - } - }) - - config.cart = { - serverMergeByDefault: false - } - - await (cartActions as any).connect(contextMock, {}) - expect(contextMock.commit).toBeCalledWith(types.CART_LOAD_CART_SERVER_TOKEN, 'server-cart-token') - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: false, dryRun: true, mergeQty: false }) - }) - - it('attempts bypassing guest cart', async () => { - (CartService.getCartToken as jest.Mock).mockImplementation(async () => - ({ resultCode: 401, result: null }) - ); - - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true, - bypassCounter: 0 - } - }) - - config.cart = { - serverMergeByDefault: false - } - config.queues = { - maxCartBypassAttempts: 4 - } - - await (cartActions as any).connect(contextMock, {}) - expect(contextMock.commit).toBeCalledWith(types.CART_UPDATE_BYPASS_COUNTER, { counter: 1 }) - expect(contextMock.dispatch).toBeCalledWith('connect', { guestCart: true }) - }) - it('Create cart token when there are products in cart and we don\'t have token already', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { getCartItems: [{ id: 1 }], getCartToken: '' } - }; - - const wrapper = (actions: any) => actions.create(contextMock); - - await wrapper(cartActions); - - expect(contextMock.dispatch).toBeCalledWith('connect', { guestCart: false }); - }) - it('doesn\'t create cart token when there are NO products in cart', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { getCartItems: [], getCartToken: '' } - }; - - const wrapper = (actions: any) => actions.create(contextMock); - - await wrapper(cartActions); - - expect(contextMock.dispatch).toHaveBeenCalledTimes(0); - }) - it('doesn\'t create cart token when there are products in cart but we have token already', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { getCartItems: [{ id: 1 }], getCartToken: 'xyz' } - }; - - const wrapper = (actions: any) => actions.create(contextMock); - - await wrapper(cartActions); - - expect(contextMock.dispatch).toHaveBeenCalledTimes(0); - }) -}) diff --git a/core/modules/cart/test/unit/store/couponActions.spec.ts b/core/modules/cart/test/unit/store/couponActions.spec.ts deleted file mode 100644 index eced8f89cc..0000000000 --- a/core/modules/cart/test/unit/store/couponActions.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }) -} })); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true - }, - onlineHelper: { - get isOnline () { - return true - } - }, - processLocalizedURLAddress: (url) => url -})); - -describe('Cart couponActions', () => { - it('applies coupon', async () => { - const contextMock = createContextMock({ - getters: { - canSyncTotals: true - } - }) - await (cartActions as any).applyCoupon(contextMock, 'coupon-code') - - expect(contextMock.dispatch).toBeCalledWith('syncTotals', { forceServerSync: true }) - }) - - it('removes coupon', async () => { - const contextMock = createContextMock({ - getters: { - canSyncTotals: true - } - }) - await (cartActions as any).removeCoupon(contextMock) - - expect(contextMock.dispatch).toBeCalledWith('syncTotals', { forceServerSync: true }) - }) -}) diff --git a/core/modules/cart/test/unit/store/getters.spec.ts b/core/modules/cart/test/unit/store/getters.spec.ts deleted file mode 100644 index 5da051496a..0000000000 --- a/core/modules/cart/test/unit/store/getters.spec.ts +++ /dev/null @@ -1,245 +0,0 @@ -import cartGetters from '../../../store/getters'; -import { onlineHelper } from '@vue-storefront/core/helpers' -import config from 'config' - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/storage-manager', () => jest.fn()) -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/lib/multistore', () => jest.fn()) -jest.mock('@vue-storefront/core/store', () => ({})) -jest.mock('@vue-storefront/core/helpers', () => ({ - onlineHelper: { - get isOnline () { - return true - } - } -})); - -describe('Cart getters', () => { - const isOnlineSpy = jest.spyOn(onlineHelper, 'isOnline', 'get'); - - beforeEach(() => { - jest.clearAllMocks(); - Object.keys(config).forEach((key) => { delete config[key]; }); - }); - - it('totals returns platform total segments if they has been saved in store and client is online', () => { - const stateMock = { - platformTotalSegments: [ - { 'code': 'subtotal', 'title': 'Subtotal', 'value': 39.36 }, - { 'code': 'shipping', 'title': 'Shipping & Handling (Flat Rate - Fixed)', 'value': 5 }, - { 'code': 'discount', 'title': 'Discount', 'value': -4.8 }, - { 'code': 'tax', - 'title': 'Tax', - 'value': 6.26, - 'area': 'taxes', - 'extension_attributes': { - 'tax_grandtotal_details': [{ - 'amount': 6.26, - 'rates': [{ 'percent': '23', 'title': 'VAT23-PL' }], - 'group_id': 1 - }] - } }, - { 'code': 'grand_total', 'title': 'Grand Total', 'value': 38.46, 'area': 'footer' } - ] - }; - const wrapper = (getters: any) => getters.getTotals(stateMock, getters); - - expect(wrapper(cartGetters)).toEqual(stateMock.platformTotalSegments); - }); - - it(`totals returns totals without shipping and payment prices having neither platformTotalSegments - nor additional prices`, () => { - const stateMock = { - cartItems: [ - { qty: 1, price_incl_tax: 1 }, - { qty: 2, price_incl_tax: 2 } - ] - }; - const wrapper = (getters: any) => getters.getTotals(stateMock, { - ...getters, - getFirstShippingMethod: getters.getFirstShippingMethod(stateMock), - getFirstPaymentMethod: getters.getFirstPaymentMethod(stateMock) - }); - - expect(wrapper(cartGetters)).toEqual([ - { 'code': 'subtotal_incl_tax', 'title': 'Subtotal incl. tax', 'value': 5 }, - { 'code': 'grand_total', 'title': 'Grand total', 'value': 5 } - ]); - }); - - it(`totals returns totals even when there are platform total segments but client is offline`, () => { - isOnlineSpy.mockReturnValueOnce(false); - const stateMock = { - platformTotalSegments: [ - { 'code': 'subtotal', 'title': 'Subtotal', 'value': 39.36 }, - { 'code': 'shipping', 'title': 'Shipping & Handling (Flat Rate - Fixed)', 'value': 5 } - ], - cartItems: [ - { qty: 1, price_incl_tax: 1 }, - { qty: 2, price_incl_tax: 2 } - ] - }; - const wrapper = (getters: any) => getters.getTotals(stateMock, { - ...getters, - getFirstShippingMethod: getters.getFirstShippingMethod(stateMock), - getFirstPaymentMethod: getters.getFirstPaymentMethod(stateMock) - }); - - expect(wrapper(cartGetters)).toEqual([ - { 'code': 'subtotal_incl_tax', 'title': 'Subtotal incl. tax', 'value': 5 }, - { 'code': 'grand_total', 'title': 'Grand total', 'value': 5 } - ]); - }); - - it(`totals returns totals including shipping and payment prices having these prices in store - but no platformTotalSegments`, () => { - const stateMock = { - cartItems: [ - { qty: 1, price_incl_tax: 1 }, - { qty: 2, price_incl_tax: 2 } - ], - payment: { - title: 'payment', - cost_incl_tax: 4 - }, - shipping: { - method_title: 'shipping', - price_incl_tax: 8 - } - }; - const wrapper = (getters: any) => getters.getTotals(stateMock, { - ...getters, - getFirstShippingMethod: getters.getFirstShippingMethod(stateMock), - getFirstPaymentMethod: getters.getFirstPaymentMethod(stateMock) - }); - - expect(wrapper(cartGetters)).toEqual([ - { 'code': 'subtotal_incl_tax', 'title': 'Subtotal incl. tax', 'value': 5 }, - { 'code': 'grand_total', 'title': 'Grand total', 'value': 21 }, - { 'code': 'payment', 'title': 'payment', 'value': 4 }, - { 'code': 'shipping', 'title': 'shipping', 'value': 8 } - ]); - }); - - it(`totals returns totals including first shipping and first payment prices having multiple prices in store - but no platformTotalSegments`, () => { - const stateMock = { - cartItems: [ - { qty: 1, price_incl_tax: 1 }, - { qty: 2, price_incl_tax: 2 } - ], - payment: [ - { - title: 'payment', - cost_incl_tax: 4 - }, - { - title: 'another-payment', - cost_incl_tax: 16 - } - ], - shipping: [ - { - method_title: 'shipping', - price_incl_tax: 8 - }, - { - method_title: 'another-shipping', - price_incl_tax: 32 - } - ] - }; - const wrapper = (getters: any) => getters.getTotals(stateMock, { - ...getters, - getFirstShippingMethod: getters.getFirstShippingMethod(stateMock), - getFirstPaymentMethod: getters.getFirstPaymentMethod(stateMock) - }); - - expect(wrapper(cartGetters)).toEqual([ - { 'code': 'subtotal_incl_tax', 'title': 'Subtotal incl. tax', 'value': 5 }, - { 'code': 'grand_total', 'title': 'Grand total', 'value': 21 }, - { 'code': 'payment', 'title': 'payment', 'value': 4 }, - { 'code': 'shipping', 'title': 'shipping', 'value': 8 } - ]); - }); - - it('totalQuantity returns total quantity of all products in cart if minicart configuration is set to quantities', () => { - config.cart = { - minicartCountType: 'quantities' - } - const stateMock = { - cartItems: [ - { qty: 1 }, - { qty: 2 } - ] - }; - - const wrapper = (getters: any) => getters.getItemsTotalQuantity(stateMock, {}); - - expect(wrapper(cartGetters)).toBe(3); - }); - - it('totalQuantity returns number of different products instead of their sum if minicart configuration is set to items', () => { - config.cart = { - minicartCountType: 'items' - } - - const stateMock = { - cartItems: [ - { qty: 1 }, - { qty: 2 } - ] - }; - - const wrapper = (getters: any) => getters.getItemsTotalQuantity(stateMock, {}); - - expect(wrapper(cartGetters)).toBe(2); - }); - - it('coupon returns coupon information when coupon has been applied to the cart', () => { - const stateMock = { - platformTotals: { - coupon_code: 'foo', - discount_amount: 1.23 - } - }; - const wrapper = (getters: any) => getters.getCoupon(stateMock); - - expect(wrapper(cartGetters)).toEqual({ - code: stateMock.platformTotals.coupon_code, - discount: stateMock.platformTotals.discount_amount - }); - }); - - it('coupon returns false given no coupons applied to the cart', () => { - const stateMock = {}; - const wrapper = (getters: any) => getters.getCoupon(stateMock); - - expect(wrapper(cartGetters)).toBe(false); - }); - - it('isVirtualCart returns true given only virtual items in cart', () => { - const stateMock = { - cartItems: [ - { type_id: 'virtual' }, - { type_id: 'downloadable' } - ] - }; - const wrapper = (getters: any) => getters.isVirtualCart(stateMock); - - expect(wrapper(cartGetters)).toBe(true); - }); - - it('isVirtualCart returns false given any non virtual items in cart', () => { - const stateMock = { - cartItems: [ - { type_id: 'virtual' }, - { type_id: 'definitely-not-virtual' } - ] - }; - const wrapper = (getters: any) => getters.isVirtualCart(stateMock); - - expect(wrapper(cartGetters)).toBe(false); - }); -}); diff --git a/core/modules/cart/test/unit/store/index.spec.ts b/core/modules/cart/test/unit/store/index.spec.ts deleted file mode 100644 index 4f56009798..0000000000 --- a/core/modules/cart/test/unit/store/index.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('../../../store/actions', () => ({})); -jest.mock('../../../store/getters', () => ({})); -jest.mock('../../../store/mutations', () => ({})); - -describe('Cart Module', () => { - it('can be loaded', () => { - expect(module).toBeTruthy() - }) -}); diff --git a/core/modules/cart/test/unit/store/itemActions.spec.ts b/core/modules/cart/test/unit/store/itemActions.spec.ts deleted file mode 100644 index 89b1517b88..0000000000 --- a/core/modules/cart/test/unit/store/itemActions.spec.ts +++ /dev/null @@ -1,180 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types' -import { configureProductAsync } from '@vue-storefront/core/modules/catalog/helpers' -import { prepareProductsToAdd, productsEquals, validateProduct } from '@vue-storefront/core/modules/cart/helpers' -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; -import config from 'config'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }) -} })); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })) -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - configureProductAsync: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/cart/helpers', () => ({ - prepareProductsToAdd: jest.fn(), - productsEquals: jest.fn(), - validateProduct: jest.fn(), - notifications: { - createNotifications: jest.fn() - }, - createDiffLog: () => ({ - pushNotifications: jest.fn(), - merge: jest.fn() - }) -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true - }, - onlineHelper: { - get isOnline () { - return true - } - }, - processLocalizedURLAddress: (url) => url -})); -jest.mock('config', () => ({})); - -describe('Cart itemActions', () => { - it('configures item and deletes when there is same sku', async () => { - const product1 = { sku: 1, name: 'product1', server_item_id: 1 } - const product2 = { sku: 2, name: 'product2', server_item_id: 2 } - - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true, - getCartItems: [product2] - }, - dispatch: jest.fn((actionName) => { - switch (actionName) { - case 'product/getProductVariant': { - return product2 - } - } - }) - }) - - await (cartActions as any).configureItem(contextMock, { product: product1, configuration: {} }) - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_DEL_ITEM, { product: product2 }) - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_UPD_ITEM_PROPS, { product: { ...product1, ...product2 } }) - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: true }) - }) - - it('configures item', async () => { - const product1 = { sku: 1, name: 'product1', server_item_id: 1 } - const product2 = { sku: 2, name: 'product2', server_item_id: 2 } - - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true, - getCartItems: [product1] - }, - dispatch: jest.fn((actionName) => { - switch (actionName) { - case 'product/getProductVariant': { - return product2 - } - } - }) - }) - - await (cartActions as any).configureItem(contextMock, { product: product1, configuration: {} }) - expect(contextMock.commit).not.toHaveBeenNthCalledWith(1, types.CART_DEL_ITEM, { product: product2 }) - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_UPD_ITEM_PROPS, { product: { ...product1, ...product2 } }) - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: true }) - }) - - it('adds item to the cart', async () => { - const product1 = { sku: 1, name: 'product1', server_item_id: 1 } - const prepareProductsToAddMock = prepareProductsToAdd as jest.Mock - prepareProductsToAddMock.mockImplementation(() => [product1]) - const contextMock = createContextMock() - - await (cartActions as any).addItem(contextMock, { productToAdd: product1 }) - - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_ADDING_ITEM, { isAdding: true }) - expect(contextMock.dispatch).toBeCalledWith('addItems', { productsToAdd: [product1], forceServerSilence: false }) - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_ADDING_ITEM, { isAdding: false }) - }) - - it('checks product status', async () => { - (productsEquals as jest.Mock).mockImplementation(() => true) - - const product1 = { sku: 1, name: 'product1', server_item_id: 1, qty: 1 } - const contextMock = createContextMock({ - getters: { - getCartItems: [product1] - } - }) - - await (cartActions as any).checkProductStatus(contextMock, { product: product1 }) - expect(contextMock.dispatch).toBeCalledWith('stock/queueCheck', { product: product1, qty: 2 }, { root: true }) - }) - - it('adds items to the cart', async () => { - (validateProduct as jest.Mock).mockImplementation(() => []) - const product = { sku: 1, name: 'product1', server_item_id: 1, qty: 1 } - - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true, - isCartConnected: true - } - }) - - // The third 'dispatch' call gets an instance of DiffLog class, which has the isEmpty() method. - // The return value of the second 'dispatch' call is not used at all, so it can be left empty. - contextMock.dispatch - .mockImplementationOnce(() => Promise.resolve({ status: 'ok', onlineCheckTaskId: 1 })) - .mockImplementationOnce(() => Promise.resolve({})) - .mockImplementationOnce(() => Promise.resolve({ isEmpty: () => { return true } })) - - await (cartActions as any).addItems(contextMock, { productsToAdd: [product] }) - expect(contextMock.commit).toBeCalledWith(types.CART_ADD_ITEM, { product: { ...product, onlineStockCheckid: 1 } }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'checkProductStatus', { product }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'create') - expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'sync', { forceClientState: true }) - }) - - it('removes item from the cart', async () => { - const product = { sku: 1, name: 'product1', server_item_id: 1, qty: 1 } - - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true - } - }) - - await (cartActions as any).removeItem(contextMock, { product }) - expect(contextMock.commit).toBeCalledWith(types.CART_DEL_ITEM, { product, removeByParentSku: false }) - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: true }) - }) -}) diff --git a/core/modules/cart/test/unit/store/mergeActions.spec.ts b/core/modules/cart/test/unit/store/mergeActions.spec.ts deleted file mode 100644 index fed23f94e4..0000000000 --- a/core/modules/cart/test/unit/store/mergeActions.spec.ts +++ /dev/null @@ -1,357 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types'; -import config from 'config'; -import { CartService } from '@vue-storefront/core/data-resolver'; -import { - productsEquals, - createCartItemForUpdate, - createDiffLog -} from '@vue-storefront/core/modules/cart/helpers'; -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }), - updateItem: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - configureProductAsync: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/cart/helpers', () => ({ - prepareProductsToAdd: jest.fn(), - productsEquals: jest.fn(), - validateProduct: jest.fn(), - notifications: { - createNotifications: jest.fn() - }, - createCartItemForUpdate: jest.fn(), - createDiffLog: jest.fn(() => ({ - pushNotifications: jest.fn(), - pushServerResponse: jest.fn(), - pushServerParty: jest.fn(), - pushClientParty: jest.fn(), - merge: jest.fn(), - isEmpty: jest.fn() - })) -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true; - }, - onlineHelper: { - get isOnline () { - return true; - } - }, - processLocalizedURLAddress: url => url -})); - -describe('Cart mergeActions', () => { - it('updates client item', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 2, - item_id: 1 - }; - - const contextMock = createContextMock(); - - await (cartActions as any).updateClientItem(contextMock, { clientItem, serverItem }); - - expect(contextMock.dispatch).toBeCalledWith('updateItem', { - product: { - prev_qty: 2, - product_option: 'a', - server_cart_id: undefined, - server_item_id: 1, - sku: '1', - type_id: 'b' - } - }); - }); - - it('updates server item - removes when updating was not successful', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1, item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 2, - item_id: 1 - }; - - (createCartItemForUpdate as jest.Mock).mockImplementation(() => clientItem); - (CartService.updateItem as jest.Mock).mockImplementation(() => Promise.resolve({ resultCode: 500 })); - - const contextMock = createContextMock({ - getters: { - getCartToken: 'cart-token' - } - }); - - await (cartActions as any).updateServerItem(contextMock, { clientItem, serverItem: null }); - expect(contextMock.commit).toBeCalledWith(types.CART_DEL_ITEM, { product: clientItem, removeByParentSku: false }) - }) - - it('updates server item - restoring quantity', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1, item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 2, - item_id: 1 - }; - - (createCartItemForUpdate as jest.Mock).mockImplementation(() => clientItem); - (CartService.updateItem as jest.Mock).mockImplementation(() => Promise.resolve({ resultCode: 500 })); - - const contextMock = createContextMock({ - getters: { - getCartToken: 'cart-token' - } - }); - - await (cartActions as any).updateServerItem(contextMock, { clientItem, serverItem }); - expect(contextMock.commit).not.toBeCalledWith(types.CART_DEL_ITEM, { product: clientItem, removeByParentSku: false }) - expect(contextMock.dispatch).toBeCalledWith('restoreQuantity', { product: clientItem }) - }) - - it('updates server item - deletes non confirmed item', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 2, - item_id: 1 - }; - - (createCartItemForUpdate as jest.Mock).mockImplementation(() => clientItem); - (CartService.updateItem as jest.Mock).mockImplementation(() => Promise.resolve({ resultCode: 500 })); - - const contextMock = createContextMock({ - getters: { - getCartToken: 'cart-token' - } - }); - - await (cartActions as any).updateServerItem(contextMock, { clientItem, serverItem }); - expect(contextMock.commit).not.toBeCalledWith(types.CART_DEL_ITEM, { product: clientItem, removeByParentSku: false }) - expect(contextMock.dispatch).not.toBeCalledWith('restoreQuantity', { product: clientItem }) - expect(contextMock.commit).toBeCalledWith(types.CART_DEL_NON_CONFIRMED_ITEM, { product: clientItem }) - }) - - it('updates server item - apply changes for client item', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 2, - item_id: 1 - }; - - (createCartItemForUpdate as jest.Mock).mockImplementation(() => clientItem); - (CartService.updateItem as jest.Mock).mockImplementation(() => Promise.resolve({ resultCode: 200, result: serverItem })); - - const contextMock = createContextMock({ - getters: { - getCartToken: 'cart-token' - }, - rootGetters: { - 'checkout/isUserInCheckout': true - } - }); - - await (cartActions as any).updateServerItem(contextMock, { clientItem, serverItem }); - expect(contextMock.commit).not.toBeCalledWith(types.CART_DEL_ITEM, { product: clientItem, removeByParentSku: false }) - expect(contextMock.dispatch).not.toBeCalledWith('restoreQuantity', { product: clientItem }) - expect(contextMock.commit).not.toBeCalledWith(types.CART_DEL_NON_CONFIRMED_ITEM, { product: clientItem }) - expect(contextMock.dispatch).toBeCalledWith('updateClientItem', { clientItem, serverItem: serverItem }) - }) - - it('synchronizes item with server when there is no given server item', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 2, - item_id: 1 - }; - - config.cart = { - serverSyncCanRemoveLocalItems: false - } - - const contextMock = createContextMock() - - await (cartActions as any).synchronizeServerItem(contextMock, { clientItem, serverItem: null }); - expect(contextMock.dispatch).toBeCalledWith('updateServerItem', { clientItem, serverItem: null, updateIds: false }) - }) - - it('synchronizes item with server when there quantities are different', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 1, - item_id: 1 - }; - - config.cart = { - serverSyncCanRemoveLocalItems: false - } - - const contextMock = createContextMock() - - await (cartActions as any).synchronizeServerItem(contextMock, { clientItem, serverItem }); - expect(contextMock.dispatch).not.toBeCalledWith('updateServerItem', { clientItem, serverItem: null, updateIds: false }) - expect(contextMock.dispatch).toBeCalledWith('updateServerItem', { clientItem, serverItem, updateIds: true }) - }) - - it('merges client item', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 1, - item_id: 1 - }; - - const contextMock = createContextMock(); - (productsEquals as jest.Mock).mockImplementation(() => true); - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => Promise.resolve({ isEmpty: () => true })); - - await (cartActions as any).mergeClientItem(contextMock, { clientItem, serverItems: [serverItem], forceClientState: false, dryRun: false }); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'synchronizeServerItem', { serverItem, clientItem, forceClientState: false, dryRun: false }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'updateItem', { product: { product_option: 'a', server_cart_id: undefined, server_item_id: 1, sku: '1', type_id: 'b' } }) - }) - - it('merges server item', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 1, - item_id: 1 - }; - - const contextMock = createContextMock(); - - (productsEquals as jest.Mock).mockImplementation(() => false); - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => Promise.resolve(serverItem)); - - await (cartActions as any).mergeServerItem(contextMock, { clientItems: [clientItem], serverItem, forceClientState: false, dryRun: false }); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'getProductVariant', { serverItem }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'addItem', { productToAdd: serverItem, forceServerSilence: true }) - }) - - it('updates totals after merge', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - - const contextMock = createContextMock({ - getters: { - isTotalsSyncRequired: true, - getCurrentCartHash: 'cart-hash' - } - }); - await (cartActions as any).updateTotalsAfterMerge(contextMock, { clientItems: [clientItem], dryRun: false }); - expect(contextMock.dispatch).toBeCalledWith('syncTotals') - expect(contextMock.commit).toBeCalledWith(types.CART_SET_ITEMS_HASH, 'cart-hash') - }) - - it('merges client and server cart', async () => { - const clientItem = { sku: '1', name: 'product1', qty: 2, server_item_id: 1 }; - const serverItem = { - sku: '1', - name: 'product1', - server_item_id: 1, - server_cart_id: 12, - product_option: 'a', - product_type: 'b', - qty: 1, - item_id: 1 - }; - const contextMock = createContextMock({ - getters: { - isCartHashChanged: false - } - }); - - const diffLog = { - pushServerParty: () => diffLog, - pushClientParty: () => diffLog, - merge: () => diffLog - }; - - (createDiffLog as jest.Mock).mockImplementation(() => diffLog) - - await (cartActions as any).merge(contextMock, { clientItems: [clientItem], serverItems: [serverItem] }); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'mergeClientItems', { clientItems: [clientItem], serverItems: [serverItem], dryRun: false, forceClientState: false, mergeQty: false }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'mergeServerItems', { clientItems: [clientItem], serverItems: [serverItem], dryRun: false, forceClientState: false, mergeQty: false }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'updateTotalsAfterMerge', { clientItems: [clientItem], dryRun: false }) - }) -}); diff --git a/core/modules/cart/test/unit/store/methodsActions.spec.ts b/core/modules/cart/test/unit/store/methodsActions.spec.ts deleted file mode 100644 index 8fe7dc93ad..0000000000 --- a/core/modules/cart/test/unit/store/methodsActions.spec.ts +++ /dev/null @@ -1,168 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types'; -import { CartService } from '@vue-storefront/core/data-resolver'; -import { preparePaymentMethodsToSync, createOrderData } from '@vue-storefront/core/modules/cart/helpers'; -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }), - getPaymentMethods: jest.fn(), - updateItem: jest.fn(), - getShippingMethods: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('storefront-query-builder', () => jest.fn()); -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - configureProductAsync: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/cart/helpers', () => ({ - prepareProductsToAdd: jest.fn(), - productsEquals: jest.fn(), - validateProduct: jest.fn(), - notifications: { - createNotifications: jest.fn() - }, - createCartItemForUpdate: jest.fn(), - createDiffLog: jest.fn(() => ({ - pushNotifications: jest.fn(), - pushServerResponse: jest.fn(), - pushServerParty: jest.fn(), - pushClientParty: jest.fn(), - merge: jest.fn(), - isEmpty: jest.fn() - })), - preparePaymentMethodsToSync: jest.fn(), - createOrderData: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true; - }, - onlineHelper: { - get isOnline () { - return true; - } - }, - processLocalizedURLAddress: url => url -})); - -describe('Cart methodsActions', () => { - it('fetches payment and shipping methods', async () => { - const contextMock = createContextMock({ - getters: { - isTotalsSyncRequired: true - } - }) - - await (cartActions as any).pullMethods(contextMock, { forceServerSync: false }); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'syncShippingMethods', { forceServerSync: false }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'syncPaymentMethods', { forceServerSync: false }) - }); - - it('sets default shipping methods', async () => { - const contextMock = createContextMock({ - rootGetters: { - 'checkout/getDefaultShippingMethod': { shipping: 1 } - }, - getters: { - getShippingMethodCode: false, - getPaymentMethodCode: true - } - }) - - await (cartActions as any).setDefaultCheckoutMethods(contextMock); - expect(contextMock.commit).toBeCalledWith(types.CART_UPD_SHIPPING, { shipping: 1 }) - }) - - it('sets default payment methods', async () => { - const contextMock = createContextMock({ - rootGetters: { - 'checkout/getDefaultPaymentMethod': { payment: 1 } - }, - getters: { - getShippingMethodCode: true, - getPaymentMethodCode: false - } - }) - - await (cartActions as any).setDefaultCheckoutMethods(contextMock); - expect(contextMock.commit).toBeCalledWith(types.CART_UPD_PAYMENT, { payment: 1 }) - }) - - it('synchronizes payment methods', async () => { - const contextMock = createContextMock({ - rootGetters: { - 'checkout/getNotServerPaymentMethods': [], - 'checkout/getPaymentDetails': { country: 'US' } - }, - getters: { - canUpdateMethods: true, - isTotalsSyncRequired: true - } - }); - - (CartService.getPaymentMethods as jest.Mock).mockImplementation(() => Promise.resolve({ result: {} })); - (createOrderData as jest.Mock).mockImplementation(() => ({ shippingMethodsData: {} })); - (preparePaymentMethodsToSync as jest.Mock).mockImplementation(() => ({ uniqueBackendMethods: [], paymentMethods: [] })); - - await (cartActions as any).syncPaymentMethods(contextMock, {}); - expect(contextMock.dispatch).toBeCalledWith('checkout/replacePaymentMethods', [], { root: true }) - }) - - it('synchronizes shipping methods', async () => { - const contextMock = createContextMock({ - rootGetters: { - 'checkout/getShippingDetails': { country: 'US' } - }, - getters: { - canUpdateMethods: true, - isTotalsSyncRequired: true - } - }); - - (CartService.getShippingMethods as jest.Mock).mockImplementation(() => Promise.resolve({ result: [] })); - - await (cartActions as any).syncShippingMethods(contextMock, {}); - expect(contextMock.dispatch).toBeCalledWith('updateShippingMethods', { shippingMethods: [] }) - }) - - it('updates shipping methods', async () => { - const contextMock = createContextMock() - await (cartActions as any).updateShippingMethods(contextMock, { shippingMethods: [{ method: 1 }] }); - expect(contextMock.dispatch).toBeCalledWith('checkout/replaceShippingMethods', [{ is_server_method: true, method: 1 }], { root: true }) - }) - - it('doesn\'t add not available method', async () => { - const contextMock = createContextMock() - await (cartActions as any).updateShippingMethods(contextMock, { shippingMethods: [{ method: 1, available: false }] }); - expect(contextMock.dispatch).toBeCalledWith('checkout/replaceShippingMethods', [], { root: true }) - }) -}); diff --git a/core/modules/cart/test/unit/store/mutations.spec.ts b/core/modules/cart/test/unit/store/mutations.spec.ts deleted file mode 100644 index fa21430147..0000000000 --- a/core/modules/cart/test/unit/store/mutations.spec.ts +++ /dev/null @@ -1,585 +0,0 @@ -import * as types from '../../../store/mutation-types' -import cartMutations from '../../../store/mutations' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) - -EventBus.$emit = jest.fn() - -jest.mock('@vue-storefront/core/store', () => ({ - state: { - config: {} - } -})) - -describe('Cart mutations', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - describe('CART_ADD_ITEM', () => { - it('adds a product to cart if none of its sku is there yet', () => { - const stateMock = { - cartItems: [] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - cartItems: [ - { - qty: 123, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_ADD_ITEM](stateMock, { product }) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-before-add', { product }) - expect(stateMock).toEqual(expectedState) - }) - - it('adds a product to cart with quantity of 1 if none of its sku is there yet and product qty is not provided', () => { - const stateMock = { - cartItems: [] - } - const product = { - sku: 'foo' - } - const expectedState = { - cartItems: [ - { - qty: 1, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_ADD_ITEM](stateMock, { product }) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-before-add', { product: { ...product, qty: 1 } }) - expect(stateMock).toEqual(expectedState) - }) - - it('increases quantity of a a product in cart if one of its sku is already there', () => { - const stateMock = { - cartItems: [ - { - qty: 10, - sku: 'foo' - } - ] - } - const product = { - qty: 10, - sku: 'foo' - } - const expectedState = { - cartItems: [ - { - qty: 20, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_ADD_ITEM](stateMock, { product }) - - wrapper(cartMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('increases quantity of a a product in cart by 1 if quantity was not provided', () => { - const stateMock = { - cartItems: [ - { - qty: 10, - sku: 'foo' - } - ] - } - const product = { - sku: 'foo' - } - const expectedState = { - cartItems: [ - { - qty: 11, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_ADD_ITEM](stateMock, { product }) - - wrapper(cartMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('CART_DEL_ITEM', () => { - it('removes product from cart by sku', () => { - const stateMock = { - cartItems: [ - { - qty: 10, - sku: 'foo' - } - ] - } - const expectedState = { - cartItems: [] - } - const wrapper = (mutations: any) => mutations[types.CART_DEL_ITEM]( - stateMock, - { - product: { sku: 'foo' }, - removeByParentSku: false - } - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-before-delete', { - items: [{ - qty: 10, - sku: 'foo' - }] - }) - expect(EventBus.$emit).toBeCalledWith('cart-after-delete', { items: expectedState.cartItems }) - expect(stateMock).toEqual(expectedState) - }) - - it('removes product from cart by parent sku', () => { - const stateMock = { - cartItems: [ - { - qty: 10, - sku: 'foo' - } - ] - } - const expectedState = { - cartItems: [] - } - const wrapper = (mutations: any) => mutations[types.CART_DEL_ITEM]( - stateMock, - { - product: { parentSku: 'foo' } - } - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-before-delete', { - items: [{ - qty: 10, - sku: 'foo' - }] - }) - expect(EventBus.$emit).toBeCalledWith('cart-after-delete', { items: expectedState.cartItems }) - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('CART_DEL_NON_CONFIRMED_ITEM', () => { - it('removes product from cart by sku', () => { - const stateMock = { - cartItems: [ - { - qty: 10, - sku: 'foo' - } - ] - } - const expectedState = { - cartItems: [] } - const wrapper = (mutations: any) => mutations[types.CART_DEL_NON_CONFIRMED_ITEM]( - stateMock, - { - product: { sku: 'foo' }, - removeByParentSku: false - } - ) - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-before-delete', { - items: [{ - qty: 10, - sku: 'foo' - }] - }) - expect(EventBus.$emit).toBeCalledWith('cart-after-delete', { items: expectedState.cartItems }) - expect(stateMock).toEqual(expectedState) - }) - - it('removes product from cart by parent sku', () => { - const stateMock = { - cartItems: [ - { - qty: 10, - sku: 'foo' - } - ] - } - const expectedState = { - cartItems: [] - } - const wrapper = (mutations: any) => mutations[types.CART_DEL_NON_CONFIRMED_ITEM]( - stateMock, - { - product: { parentSku: 'foo' } - } - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-before-delete', { - items: [{ - qty: 10, - sku: 'foo' - }] - }) - expect(EventBus.$emit).toBeCalledWith('cart-after-delete', { items: expectedState.cartItems }) - expect(stateMock).toEqual(expectedState) - }) - - it('does not remove a product that has server_item_id set, so it surely exists in the backend', () => { - const stateMock = { - cartItems: [ - { - qty: 10, - sku: 'foo', - server_item_id: 123 - } - ] - } - const expectedState = { - cartItems: [ - { - qty: 10, - sku: 'foo', - server_item_id: 123 - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_DEL_NON_CONFIRMED_ITEM]( - stateMock, - { - product: { sku: 'foo' }, - removeByParentSku: false - } - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-before-delete', { items: stateMock.cartItems }) - expect(EventBus.$emit).toBeCalledWith('cart-after-delete', { items: expectedState.cartItems }) - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('CART_UPD_ITEM', () => { - it('updates product quantity by sku', () => { - const stateMock = { - cartItems: [ - { - sku: 'foo', - qty: 10 - } - ] - } - const expectedState = { - cartItems: [ - { - qty: 20, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_UPD_ITEM]( - stateMock, - { - product: { sku: 'foo' }, - qty: 20 - } - ) - wrapper(cartMutations) - - // unfortunately before and after events return a reference to the same object, therefore - // after performing this mutation after event return same object with same, updated value as before event - expect(EventBus.$emit).toBeCalledWith('cart-before-update', { product: expectedState.cartItems[0] }) - expect(EventBus.$emit).toBeCalledWith('cart-after-update', { product: expectedState.cartItems[0] }) - expect(stateMock).toEqual(expectedState) - }) - - it('doesn\'t update anything if product is not found in cart', () => { - const stateMock = { - cartItems: [ - { - sku: 'foo', - qty: 10 - } - ] - } - const expectedState = { ...stateMock } - const wrapper = (mutations: any) => mutations[types.CART_UPD_ITEM]( - stateMock, - { - product: { sku: 'qux' }, - qty: 20 - } - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).not.toBeCalled() - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('CART_UPD_ITEM_PROPS', () => { - it('updates product properties by sku', () => { - const stateMock = { - cartItems: [ - { - sku: 'foo', - someProp: 'bar', - qty: 10 - } - ] - } - const expectedState = { - cartItems: [ - { - qty: 20, - sku: 'foo', - someProp: 'baz' - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_UPD_ITEM_PROPS]( - stateMock, - { - product: { sku: 'foo', someProp: 'baz', qty: 20 } - } - ) - let firstEmitCall = [] - - EventBus.$emit.mockImplementationOnce((eventName, args) => { - firstEmitCall.push(eventName) - firstEmitCall.push(args) - }) - wrapper(cartMutations) - - expect(firstEmitCall).toEqual(['cart-before-itemchanged', { item: expectedState.cartItems[0] }]) - expect(EventBus.$emit).toBeCalledWith('cart-after-itemchanged', { item: expectedState.cartItems[0] }) - expect(stateMock).toEqual(expectedState) - }) - - it('updates product properties by server_item_id', () => { - const stateMock = { - cartItems: [ - { - sku: 'foo', - server_item_id: 123, - someProp: 'bar', - qty: 10 - } - ] - } - const expectedState = { - cartItems: [ - { - server_item_id: 123, - qty: 20, - sku: 'bar', - someProp: 'baz' - } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_UPD_ITEM_PROPS]( - stateMock, - { - product: { server_item_id: 123, sku: 'bar', someProp: 'baz', qty: 20 } - } - ) - let firstEmitCall = [] - - EventBus.$emit.mockImplementationOnce((eventName, args) => { - firstEmitCall.push(eventName) - firstEmitCall.push(args) - }) - wrapper(cartMutations) - - expect(firstEmitCall).toEqual(['cart-before-itemchanged', { item: expectedState.cartItems[0] }]) - expect(EventBus.$emit).toBeCalledWith('cart-after-itemchanged', { item: expectedState.cartItems[0] }) - expect(stateMock).toEqual(expectedState) - }) - - it('doesn\'t update anything if product is not found in cart', () => { - const stateMock = { - cartItems: [ - { - sku: 'foo', - someProp: 'bar', - qty: 10 - } - ] - } - const expectedState = { ...stateMock } - const wrapper = (mutations: any) => mutations[types.CART_UPD_ITEM_PROPS]( - stateMock, - { - product: { sku: 'qux', someProp: 'baz', qty: 20 } - } - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).not.toBeCalled() - expect(stateMock).toEqual(expectedState) - }) - }) - - it('CART_UPD_SHIPPING sets given shipping method', () => { - const stateMock = { - shipping: 'foo' - } - const expectedState = { - shipping: 'bar' - } - const wrapper = (mutations: any) => mutations[types.CART_UPD_SHIPPING]( - stateMock, - expectedState.shipping - ) - - wrapper(cartMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('CART_LOAD_CART initializes cart with given products', () => { - const stateMock = {} - const expectedState = { - cartItems: [ - { - sku: 'foo', - qty: 10 - } - ], - cartIsLoaded: true - } - const wrapper = (mutations: any) => mutations[types.CART_LOAD_CART]( - stateMock, - expectedState.cartItems - ) - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('sync/PROCESS_QUEUE', expect.anything()) - expect(EventBus.$emit).toBeCalledWith('application-after-loaded') - expect(EventBus.$emit).toBeCalledWith('cart-after-loaded') - expect(stateMock).toEqual(expectedState) - }) - - it('CART_LOAD_CART initializes an empty cart when no products are given', () => { - const stateMock = {} - const expectedState = { - cartItems: [], - cartIsLoaded: true - } - const wrapper = (mutations: any) => mutations[types.CART_LOAD_CART]( - stateMock - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('sync/PROCESS_QUEUE', expect.anything()) - expect(EventBus.$emit).toBeCalledWith('application-after-loaded') - expect(EventBus.$emit).toBeCalledWith('cart-after-loaded') - expect(stateMock).toEqual(expectedState) - }) - - it('CART_LOAD_CART_SERVER_TOKEN saves given cart token in cart data', () => { - const stateMock = {} - const expectedState = { - cartServerToken: 'foo' - } - const wrapper = (mutations: any) => mutations[types.CART_LOAD_CART_SERVER_TOKEN]( - stateMock, - expectedState.cartServerToken - ) - - wrapper(cartMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('CART_UPD_TOTALS updates totals related data', () => { - const stateMock = {} - const expectedState = { - itemsAfterPlatformTotals: ['foo'], - platformTotals: { - bar: 1 /** @todo replace with real alike data to show what it can be filled with */ - }, - platformTotalSegments: [ - { 'code': 'subtotal', 'title': 'Subtotal', 'value': 39.36 }, - { 'code': 'grand_total', 'title': 'Grand Total', 'value': 39.36, 'area': 'footer' } - ] - } - const wrapper = (mutations: any) => mutations[types.CART_UPD_TOTALS]( - stateMock, - { - itemsAfterTotals: expectedState.itemsAfterPlatformTotals, - totals: expectedState.platformTotals, - platformTotalSegments: expectedState.platformTotalSegments - } - ) - - wrapper(cartMutations) - - expect(EventBus.$emit).toBeCalledWith('cart-after-updatetotals', { - platformTotals: expectedState.platformTotals, - platformTotalSegments: expectedState.platformTotalSegments - }) - expect(stateMock).toEqual(expectedState) - }) - - it('CART_UPD_PAYMENT sets given payment method', () => { - const stateMock = { - payment: 'foo' - } - const expectedState = { - payment: 'bar' - } - const wrapper = (mutations: any) => mutations[types.CART_UPD_PAYMENT]( - stateMock, - expectedState.payment - ) - - wrapper(cartMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('CART_TOGGLE_MICROCART changes microcart open status to the opposite one', () => { - const stateMock = { - isMicrocartOpen: true - } - const expectedState = { - isMicrocartOpen: false - } - const wrapper = (mutations: any) => mutations[types.CART_TOGGLE_MICROCART]( - stateMock - ) - - wrapper(cartMutations) - - expect(stateMock).toEqual(expectedState) - }) -}) diff --git a/core/modules/cart/test/unit/store/productActions.spec.ts b/core/modules/cart/test/unit/store/productActions.spec.ts deleted file mode 100644 index 753b502d33..0000000000 --- a/core/modules/cart/test/unit/store/productActions.spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }), - getPaymentMethods: jest.fn(), - updateItem: jest.fn(), - getShippingMethods: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - configureProductAsync: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/cart/helpers', () => ({ - prepareProductsToAdd: jest.fn(), - productsEquals: jest.fn(), - validateProduct: jest.fn(), - notifications: { - createNotifications: jest.fn() - }, - createCartItemForUpdate: jest.fn(), - createDiffLog: jest.fn(() => ({ - pushNotifications: jest.fn(), - pushServerResponse: jest.fn(), - pushServerParty: jest.fn(), - pushClientParty: jest.fn(), - merge: jest.fn(), - isEmpty: jest.fn() - })), - preparePaymentMethodsToSync: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true; - }, - onlineHelper: { - get isOnline () { - return true; - } - }, - processLocalizedURLAddress: url => url -})); - -describe('Cart productActions', () => { - it('finds configurable children', async () => { - const serverItem = { sku: 1, name: 'product1', product_type: 'configurable' } - const contextMock = createContextMock(); - - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => ({ items: [serverItem] })); - const result = await (cartActions as any).findProductOption(contextMock, { serverItem }); - expect(contextMock.dispatch).toBeCalledWith('product/findProducts', { query: { _appliedFilters: [{ attribute: 'configurable_children.sku', options: Object, scope: 'default', value: { eq: 1 } }], _availableFilters: [], _appliedSort: [], _searchText: '' }, size: 1, start: 0, options: { populateRequestCacheTags: false, prefetchGroupProducts: false, separateSelectedVariant: true } }, { root: true }) - expect(result).toEqual({ childSku: 1, sku: 1 }) - }); - - it('finds product variant', async () => { - const serverItem = { sku: 1, name: 'product1', product_type: 'configurable', qty: 1, quote_id: 1, item_id: 1, product_option: 'opt1' } - const contextMock = createContextMock(); - - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => ({})); - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => ({ sku: 1, name: 'product1', product_type: 'configurable', opt1: 1 })); - - const result = await (cartActions as any).getProductVariant(contextMock, { serverItem }); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProductOption', { serverItem }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'product/single', { options: {} }, { root: true }) - expect(result).toEqual({ - name: 'product1', - opt1: 1, - product_option: 'opt1', - product_type: 'configurable', - qty: 1, - server_cart_id: 1, - server_item_id: 1, - sku: 1 - }) - }); -}); diff --git a/core/modules/cart/test/unit/store/quantityActions.spec.ts b/core/modules/cart/test/unit/store/quantityActions.spec.ts deleted file mode 100644 index 15f3bba5d8..0000000000 --- a/core/modules/cart/test/unit/store/quantityActions.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types'; -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }), - getPaymentMethods: jest.fn(), - updateItem: jest.fn(), - getShippingMethods: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - configureProductAsync: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/cart/helpers', () => ({ - prepareProductsToAdd: jest.fn(), - productsEquals: jest.fn(), - validateProduct: jest.fn(), - notifications: { - createNotifications: jest.fn() - }, - createCartItemForUpdate: jest.fn(), - createDiffLog: jest.fn(() => ({ - pushNotifications: jest.fn(), - pushServerResponse: jest.fn(), - pushServerParty: jest.fn(), - pushClientParty: jest.fn(), - merge: jest.fn(), - isEmpty: jest.fn() - })), - preparePaymentMethodsToSync: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return true; - }, - onlineHelper: { - get isOnline () { - return true; - } - }, - processLocalizedURLAddress: url => url -})); - -describe('Cart quantityActions', () => { - it('restores original quantity', async () => { - const cartItem = { sku: 1, name: 'product1', prev_qty: 2 } - const clientItem = { sku: 1, name: 'product1' } - - const contextMock = createContextMock(); - - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => cartItem); - await (cartActions as any).restoreQuantity(contextMock, { product: clientItem }) - expect(contextMock.dispatch).toBeCalledWith('getItem', { product: clientItem }) - expect(contextMock.dispatch).toBeCalledWith('updateItem', { product: { ...clientItem, qty: 2 } }) - }); - - it('removes item that has not quantity', async () => { - const cartItem = { sku: 1, name: 'product1' } - const clientItem = { sku: 1, name: 'product1' } - - const contextMock = createContextMock(); - - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => cartItem); - await (cartActions as any).restoreQuantity(contextMock, { product: clientItem }); - expect(contextMock.dispatch).toBeCalledWith('getItem', { product: clientItem }) - expect(contextMock.dispatch).toBeCalledWith('removeItem', { product: cartItem, removeByParentSku: false }) - }); - - it('updates quantity', async () => { - const product = { sku: 1, name: 'product1', qty: 2, server_item_id: 1 } - - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true - } - }); - - await (cartActions as any).updateQuantity(contextMock, { product, qty: 3 }); - expect(contextMock.commit).toBeCalledWith(types.CART_UPD_ITEM, { product, qty: 3 }) - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: true }) - }) -}); diff --git a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts b/core/modules/cart/test/unit/store/synchronizeActions.spec.ts deleted file mode 100644 index 27c1840794..0000000000 --- a/core/modules/cart/test/unit/store/synchronizeActions.spec.ts +++ /dev/null @@ -1,244 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types'; -import config from 'config'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; -import { CartService } from '@vue-storefront/core/data-resolver'; -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }), - getPaymentMethods: jest.fn(), - updateItem: jest.fn(), - getShippingMethods: jest.fn(), - getItems: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - configureProductAsync: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/cart/helpers', () => ({ - prepareProductsToAdd: jest.fn(), - productsEquals: jest.fn(), - validateProduct: jest.fn(), - notifications: { - createNotifications: jest.fn() - }, - createCartItemForUpdate: jest.fn(), - createDiffLog: jest.fn(() => ({ - pushNotifications: jest.fn(), - pushServerResponse: jest.fn(), - pushServerParty: jest.fn(), - pushClientParty: jest.fn(), - merge: jest.fn(), - isEmpty: jest.fn() - })), - preparePaymentMethodsToSync: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return false; - }, - onlineHelper: { - get isOnline () { - return true; - } - }, - processLocalizedURLAddress: url => url -})); - -describe('Cart synchronizeActions', () => { - it('loads current cart', async () => { - const contextMock = createContextMock(); - - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => ({}) - })) - await (cartActions as any).load(contextMock, {}); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'setDefaultCheckoutMethods') - expect(contextMock.commit).toBeCalledWith(types.CART_LOAD_CART, {}) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'synchronizeCart', { forceClientState: false }) - }); - - it('synchronizes cart', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => 'hash-token' - })) - - config.cart = { - synchronize: true, - serverMergeByDefault: false - } - const contextMock = createContextMock(); - - await (cartActions as any).synchronizeCart(contextMock, { forceClientState: false }); - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_SET_ITEMS_HASH, 'hash-token') - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_LOAD_CART_SERVER_TOKEN, 'hash-token') - expect(contextMock.dispatch).toBeCalledWith('sync', { forceClientState: false, dryRun: true }) - }) - - it('creates a new cart token', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => null - })) - - config.cart = { - synchronize: true, - serverMergeByDefault: false - } - const contextMock = createContextMock(); - - await (cartActions as any).synchronizeCart(contextMock, { forceClientState: false }); - expect(contextMock.commit).not.toHaveBeenNthCalledWith(1, types.CART_SET_ITEMS_HASH, 'hash-token') - expect(contextMock.commit).not.toHaveBeenNthCalledWith(2, types.CART_LOAD_CART_SERVER_TOKEN, 'hash-token') - expect(contextMock.dispatch).toBeCalledWith('create') - }) - - it('merges current cart', async () => { - (CartService.getItems as jest.Mock).mockImplementation(async () => ({ - resultCode: 200, - result: [] - })) - - const contextMock = createContextMock({ - rootGetters: { - 'checkout/isUserInCheckout': true - }, - getters: { - getCartItems: [], - canUpdateMethods: true, - isSyncRequired: true, - bypassCounter: 0 - } - }); - await (cartActions as any).sync(contextMock, {}); - expect(contextMock.dispatch).toBeCalledWith('merge', { - clientItems: [], - dryRun: false, - forceClientState: true, - serverItems: [], - mergeQty: false - }) - }) - - it('attempts to bypass guest cart', async () => { - (CartService.getItems as jest.Mock).mockImplementation(async () => ({ - resultCode: 500, - result: null - })) - - config.queues = { - maxCartBypassAttempts: 4 - } - - const contextMock = createContextMock({ - rootGetters: { - 'checkout/isUserInCheckout': true - }, - getters: { - getCartItems: [], - canUpdateMethods: true, - isSyncRequired: true, - bypassCounter: 0 - } - }); - - await (cartActions as any).sync(contextMock, {}); - expect(contextMock.dispatch).toBeCalledWith('connect', { guestCart: true }) - }) - - it('removes product when there is out of stock', async () => { - const product = { sku: 1, name: 'product1' } - const stockTask = { sku: 1, product_sku: 1, result: { is_in_stock: false, code: 'ok' } } - config.stock = { - allowOutOfStockInCart: false - } - config.cart = { - synchronize: false - } - const contextMock = createContextMock(); - - (contextMock.dispatch as jest.Mock).mockImplementation(() => product) - - await (cartActions as any).stockSync(contextMock, stockTask); - expect(contextMock.dispatch).toBeCalledWith('getItem', { product: { sku: 1 } }) - expect(contextMock.commit).toBeCalledWith(types.CART_DEL_ITEM, { product: { sku: 1 } }, { root: true }) - }) - it('triggers an error when there is out of stock', async () => { - const product = { sku: 1, name: 'product1' } - const stockTask = { sku: 1, product_sku: 1, result: { is_in_stock: false, code: 'ok' } } - config.stock = { - allowOutOfStockInCart: true - } - config.cart = { - synchronize: false - } - const contextMock = createContextMock(); - - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => product) - - await (cartActions as any).stockSync(contextMock, stockTask); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'getItem', { product: { sku: 1 } }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'updateItem', { - product: { - errors: { stock: 'Out of the stock!' }, - is_in_stock: false, - sku: 1 - } - }) - }) - it('shows that product is in stock', async () => { - const product = { sku: 1, name: 'product1' } - const stockTask = { sku: 1, product_sku: 1, result: { is_in_stock: true, code: 'ok' } } - config.stock = { - allowOutOfStockInCart: true - } - config.cart = { - synchronize: false - } - const contextMock = createContextMock({ - getters: { - getCurrentCartHash: 'zyx' - } - }); - - (contextMock.dispatch as jest.Mock).mockImplementationOnce(() => product) - - await (cartActions as any).stockSync(contextMock, stockTask); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'getItem', { product: { sku: 1 } }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'updateItem', { - product: { - info: { stock: 'In stock!' }, - is_in_stock: true, - sku: 1 - } - }) - }) -}); diff --git a/core/modules/cart/test/unit/store/totalsActions.spec.ts b/core/modules/cart/test/unit/store/totalsActions.spec.ts deleted file mode 100644 index 3319703948..0000000000 --- a/core/modules/cart/test/unit/store/totalsActions.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -import * as types from '@vue-storefront/core/modules/cart/store/mutation-types'; -import { - prepareShippingInfoForUpdateTotals, - createOrderData, - createShippingInfoData -} from '@vue-storefront/core/modules/cart/helpers'; -import cartActions from '@vue-storefront/core/modules/cart/store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('js-sha3', () => ({ sha3_224: jest.fn() })); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - CartService: { - applyCoupon: async () => ({ result: true }), - removeCoupon: async () => ({ result: true }), - getPaymentMethods: jest.fn(), - updateItem: jest.fn(), - getShippingMethods: jest.fn(), - getItems: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ router: jest.fn() })); -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - configureProductAsync: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/cart/helpers', () => ({ - prepareProductsToAdd: jest.fn(), - productsEquals: jest.fn(), - validateProduct: jest.fn(), - notifications: { - createNotifications: jest.fn() - }, - createCartItemForUpdate: jest.fn(), - createDiffLog: jest.fn(() => ({ - pushNotifications: jest.fn(), - pushServerResponse: jest.fn(), - pushServerParty: jest.fn(), - pushClientParty: jest.fn(), - merge: jest.fn(), - isEmpty: jest.fn() - })), - preparePaymentMethodsToSync: jest.fn(), - prepareShippingInfoForUpdateTotals: jest.fn(), - createOrderData: jest.fn(), - createShippingInfoData: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return false; - }, - onlineHelper: { - get isOnline () { - return true; - } - }, - processLocalizedURLAddress: url => url -})); - -describe('Cart totalsActions', () => { - it('replaces server totals', async () => { - const itemsAfterTotal = { - key1: { qty: 1, param1: 1, param2: 2, item_id: 1 }, - key2: { qty: 3, param1: 3, param2: 5, item_id: 2 }, - key3: { qty: 5, param1: 1, param2: 6, item_id: 3 } - } - const totals = { total_segments: {} } - const contextMock = createContextMock(); - - (contextMock.dispatch as jest.Mock).mockImplementationOnce(async () => ({ - resultCode: 200, - result: { totals } - })); - (prepareShippingInfoForUpdateTotals as jest.Mock).mockImplementation(() => itemsAfterTotal); - - await (cartActions as any).overrideServerTotals(contextMock, { addressInformation: {}, hasShippingInformation: true }); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'updateItem', { - product: { qty: 1, server_item_id: 1, totals: { item_id: 1, param1: 1, param2: 2, qty: 1 } } - }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'updateItem', { - product: { qty: 3, server_item_id: 2, totals: { item_id: 2, param1: 3, param2: 5, qty: 3 } } - }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(4, 'updateItem', { - product: { qty: 5, server_item_id: 3, totals: { item_id: 3, param1: 1, param2: 6, qty: 5 } } - }) - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_UPD_TOTALS, { itemsAfterTotal, totals, platformTotalSegments: totals.total_segments }) - }); - - it('synchronizes totals', async () => { - (createOrderData as jest.Mock).mockImplementation(() => ({ country: 'US', method_code: 'XXX' })); - (createShippingInfoData as jest.Mock).mockImplementation(() => 'address information'); - const contextMock = createContextMock({ - rootGetters: { - 'checkout/getShippingDetails': {}, - 'checkout/getShippingMethods': {}, - 'checkout/getPaymentMethods': {} - }, - getters: { - canSyncTotals: true, - isTotalsSyncRequired: true - } - }); - await (cartActions as any).syncTotals(contextMock, {}); - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'pullMethods', {}) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'overrideServerTotals', { hasShippingInformation: 'XXX', addressInformation: 'address information' }) - }) -}); diff --git a/core/modules/cart/types/AppliedCoupon.ts b/core/modules/cart/types/AppliedCoupon.ts deleted file mode 100644 index 7fdd6811e4..0000000000 --- a/core/modules/cart/types/AppliedCoupon.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface AppliedCoupon { - code: string, - discount: number -} diff --git a/core/modules/cart/types/BillingAddress.ts b/core/modules/cart/types/BillingAddress.ts deleted file mode 100644 index be0bae3f4b..0000000000 --- a/core/modules/cart/types/BillingAddress.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default interface BillingAddress { - firstname: string, - lastname: string, - city: string, - postcode: string, - street: string[], - countryId: string -} diff --git a/core/modules/cart/types/CartItem.ts b/core/modules/cart/types/CartItem.ts deleted file mode 100644 index 395cf363dd..0000000000 --- a/core/modules/cart/types/CartItem.ts +++ /dev/null @@ -1,16 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' - -import CartItemOption from './CartItemOption' -import CartItemTotals from './CartItemTotals' - -export default interface CartItem extends Product { - qty: number, - options: CartItemOption[], - totals: CartItemTotals, - server_item_id: number | string, - server_cart_id: any, - product_type?: string, - item_id?: number | string, - checksum?: string, - quoteId?: string -} diff --git a/core/modules/cart/types/CartItemOption.ts b/core/modules/cart/types/CartItemOption.ts deleted file mode 100644 index 3a40d3f48d..0000000000 --- a/core/modules/cart/types/CartItemOption.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface CartItemOption { - label: string, - value: string -} diff --git a/core/modules/cart/types/CartItemTotals.ts b/core/modules/cart/types/CartItemTotals.ts deleted file mode 100644 index cec1756d35..0000000000 --- a/core/modules/cart/types/CartItemTotals.ts +++ /dev/null @@ -1,24 +0,0 @@ - -import CartItemOption from './CartItemOption' - -export default interface CartItemTotals { - base_discount_amount: number, - base_price: number, - base_price_incl_tax: number, - base_row_total: number, - base_row_total_incl_tax: number, - base_tax_amount: number, - discount_amount: number, - discount_percent: number, - item_id: number | string, - name: string, - options: CartItemOption[], - price: number, - price_incl_tax: number, - qty: number, - row_total: number, - row_total_incl_tax: number, - row_total_with_discount: number, - tax_amount: number, - tax_percent: number -} diff --git a/core/modules/cart/types/CartState.ts b/core/modules/cart/types/CartState.ts deleted file mode 100644 index 5db0c3435b..0000000000 --- a/core/modules/cart/types/CartState.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default interface CartState { - isMicrocartOpen: boolean, - itemsAfterPlatformTotals: any, - platformTotals: any, - platformTotalSegments: any, - cartIsLoaded: boolean, - cartServerToken: string, - shipping: any, - payment: any, - cartItemsHash: string, - cartServerLastSyncDate: number, - cartServerLastTotalsSyncDate: number, - cartItems: any[], - connectBypassCount: number, - isAddingToCart: boolean -} diff --git a/core/modules/cart/types/CartTotalSegments.ts b/core/modules/cart/types/CartTotalSegments.ts deleted file mode 100644 index 6ed65a223b..0000000000 --- a/core/modules/cart/types/CartTotalSegments.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CartTotalSegmentsItem from './CartTotalSegmentsItem' -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export default interface CartTotalSegments extends Array{} diff --git a/core/modules/cart/types/CartTotalSegmentsItem.ts b/core/modules/cart/types/CartTotalSegmentsItem.ts deleted file mode 100644 index cb818b885a..0000000000 --- a/core/modules/cart/types/CartTotalSegmentsItem.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface CartTotalSegmentsItem { - code: string, - title: string, - value: number, - area?: string, - extension_attributes?: object -} diff --git a/core/modules/cart/types/CheckoutData.ts b/core/modules/cart/types/CheckoutData.ts deleted file mode 100644 index 4e2c5c676e..0000000000 --- a/core/modules/cart/types/CheckoutData.ts +++ /dev/null @@ -1,12 +0,0 @@ -import ShippingDetails from '@vue-storefront/core/modules/checkout/types/ShippingDetails' -import ShippingMethod from './ShippingMethod' -import PaymentMethod from './PaymentMethod' -import PaymentDetails from '@vue-storefront/core/modules/checkout/types/PaymentDetails' - -export default interface CheckoutData { - shippingDetails: ShippingDetails, - shippingMethods: ShippingMethod[], - paymentMethods: PaymentMethod[], - paymentDetails: PaymentDetails, - taxCountry?: string -} diff --git a/core/modules/cart/types/DiffLog.ts b/core/modules/cart/types/DiffLog.ts deleted file mode 100644 index 0e29914953..0000000000 --- a/core/modules/cart/types/DiffLog.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface Notification { - type: string, - message: any, - action1: any, - action2?: any -} - -export interface ServerResponse { - status: string | number, - sku: string, - result: any -} - -export interface Party { - party: string, - status: string, - sku: string -} diff --git a/core/modules/cart/types/OrderShippingDetails.ts b/core/modules/cart/types/OrderShippingDetails.ts deleted file mode 100644 index ebfc2005c5..0000000000 --- a/core/modules/cart/types/OrderShippingDetails.ts +++ /dev/null @@ -1,11 +0,0 @@ -import ShippingAddress from './ShippingAddress' -import BillingAddress from './BillingAddress' - -export default interface OrderShippingDetails { - country?: string, - method_code?: string, - carrier_code?: string, - payment_method?: string, - shippingAddress?: ShippingAddress, - billingAddress?: BillingAddress -} diff --git a/core/modules/cart/types/PaymentMethod.ts b/core/modules/cart/types/PaymentMethod.ts deleted file mode 100644 index cb86869fe7..0000000000 --- a/core/modules/cart/types/PaymentMethod.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface PaymentMethod { - default: boolean, - code?: string, - cost_incl_tax?: number, - title?: string -} diff --git a/core/modules/cart/types/ShippingAddress.ts b/core/modules/cart/types/ShippingAddress.ts deleted file mode 100644 index a0e200c0f4..0000000000 --- a/core/modules/cart/types/ShippingAddress.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface ShippingAddress { - firstname: string, - lastname: string, - city: string, - postcode: string, - street: string[] -} diff --git a/core/modules/cart/types/ShippingMethod.ts b/core/modules/cart/types/ShippingMethod.ts deleted file mode 100644 index 1620052bbd..0000000000 --- a/core/modules/cart/types/ShippingMethod.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default interface ShippingMethod { - method_code?: string, - carrier_code?: string, - offline: boolean, - default: boolean, - price_incl_tax?: number, - method_title?: string -} diff --git a/core/modules/cart/types/Totals.ts b/core/modules/cart/types/Totals.ts deleted file mode 100644 index 68c99e731f..0000000000 --- a/core/modules/cart/types/Totals.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default interface Totals { - item_id?: number | string, - options?: string, - name: string, - qty: number, - row_total: number, - row_total_incl_tax: number, - tax_amount: number, - tax_percent: number -} diff --git a/core/modules/catalog-next/helpers/cacheProductsHelper.ts b/core/modules/catalog-next/helpers/cacheProductsHelper.ts deleted file mode 100644 index d5fe9a83d8..0000000000 --- a/core/modules/catalog-next/helpers/cacheProductsHelper.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { products } from 'config' - -export const prefetchStockItems = (cachedProductsResponse, cache = {}) => { - const skus = [] - let prefetchIndex = 0 - cachedProductsResponse.items.map(i => { - if (products.configurableChildrenStockPrefetchStatic && - products.configurableChildrenStockPrefetchStaticPrefetchCount > 0) { - if (prefetchIndex > products.configurableChildrenStockPrefetchStaticPrefetchCount) return - } - skus.push(i.sku) // main product sku to be checked anyway - if (i.type_id === 'configurable' && i.configurable_children && i.configurable_children.length > 0) { - for (const confChild of i.configurable_children) { - const cachedItem = cache[confChild.id] - if (typeof cachedItem === 'undefined' || cachedItem === null) { - skus.push(confChild.sku) - } - } - prefetchIndex++ - } - }) - - return skus -} diff --git a/core/modules/catalog-next/helpers/categoryHelpers.ts b/core/modules/catalog-next/helpers/categoryHelpers.ts deleted file mode 100644 index 98a3b72521..0000000000 --- a/core/modules/catalog-next/helpers/categoryHelpers.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { entities } from 'config' -import { Category, ChildrenData } from '../types/Category' - -export const compareByLabel = (a, b) => { - if (a.label < b.label) { - return -1 - } - if (a.label > b.label) { - return 1 - } - return 0 -} - -export const _prepareCategoryPathIds = (category: Category): string[] => { - if (!category || !category.path) return [] - return category.path.split('/') -} - -export const getSearchOptionsFromRouteParams = (params: { [key: string]: string } = {}): Record => { - const filterableKeys = entities.category.validSearchOptionsFromRouteParams - let filters: { [key: string]: string } = {} - - Object.keys(params) - .filter(key => filterableKeys.includes(key)) - .forEach(key => { filters[key] = params[key] }) - - return filters -} diff --git a/core/modules/catalog-next/helpers/filterHelpers.ts b/core/modules/catalog-next/helpers/filterHelpers.ts deleted file mode 100644 index 5b29e3dd4d..0000000000 --- a/core/modules/catalog-next/helpers/filterHelpers.ts +++ /dev/null @@ -1,58 +0,0 @@ -import config from 'config' -import FilterVariant from 'core/modules/catalog-next/types/FilterVariant' -import { Filters } from '../types/Category' - -export const getSystemFilterNames: string[] = config.products.systemFilterNames - -/** - * Creates new filtersQuery (based on currentQuery) by modifying specific filter variant. - */ -export const changeFilterQuery = ({ currentQuery = {}, filterVariant }: {currentQuery?: any, filterVariant?: FilterVariant} = {}) => { - const newQuery = JSON.parse(JSON.stringify(currentQuery)) - if (!filterVariant) return newQuery - if (getSystemFilterNames.includes(filterVariant.type)) { - if (newQuery[filterVariant.type] && newQuery[filterVariant.type] === filterVariant.id) { - delete newQuery[filterVariant.type] - } else { - newQuery[filterVariant.type] = filterVariant.id - } - } else { - let queryFilter = newQuery[filterVariant.type] || [] - if (!Array.isArray(queryFilter)) queryFilter = [queryFilter] - if (queryFilter.includes(filterVariant.id)) { - queryFilter = queryFilter.filter(value => value !== filterVariant.id) - } else if (filterVariant.single) { - queryFilter = [filterVariant.id] - } else { - queryFilter.push(filterVariant.id) - } - // delete or add filter variant to query - if (!queryFilter.length) delete newQuery[filterVariant.type] - else newQuery[filterVariant.type] = queryFilter - } - return newQuery -} - -export const getFiltersFromQuery = ({ filtersQuery = {}, availableFilters = {} } = {}): { filters: Filters } => { - const searchQuery = { - filters: {} - } - Object.keys(filtersQuery).forEach(filterKey => { - const filter = availableFilters[filterKey] - let queryValue = filtersQuery[filterKey] - if (!filter) return - // keep original value for system filters - for example sort - if (getSystemFilterNames.includes(filterKey)) { - searchQuery[filterKey] = queryValue - } else { - queryValue = [].concat(filtersQuery[filterKey]) - queryValue.map(singleValue => { - const variant = filter.find(filterVariant => filterVariant.id === singleValue) - if (!variant) return - if (!Array.isArray(searchQuery.filters[filterKey])) searchQuery.filters[filterKey] = [] - searchQuery.filters[filterKey].push({ ...variant, attribute_code: filterKey }) - }) - } - }) - return searchQuery -} diff --git a/core/modules/catalog-next/helpers/optionLabel.ts b/core/modules/catalog-next/helpers/optionLabel.ts deleted file mode 100644 index 49d3ed76fc..0000000000 --- a/core/modules/catalog-next/helpers/optionLabel.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { optionLabel } from '@vue-storefront/core/modules/catalog/helpers/optionLabel' - -// TODO in future move old helper here, add tests and refactor -export { optionLabel } diff --git a/core/modules/catalog-next/hooks.ts b/core/modules/catalog-next/hooks.ts deleted file mode 100644 index 87f6f71d37..0000000000 --- a/core/modules/catalog-next/hooks.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createListenerHook } from '@vue-storefront/core/lib/hooks' -import { Category } from './types/Category'; -import Product from 'core/modules/catalog/types/Product'; - -const { - hook: categoryPageVisitedHook, - executor: categoryPageVisitedExecutor -} = createListenerHook() - -const { - hook: productPageVisitedHook, - executor: productPageVisitedExecutor -} = createListenerHook() - -/** Only for internal usage */ -const catalogHooksExecutors = { - categoryPageVisited: categoryPageVisitedExecutor, - productPageVisited: productPageVisitedExecutor -} - -const catalogHooks = { - /** - * Hook is fired right after category page is visited. - * @param category visited category - */ - categoryPageVisited: categoryPageVisitedHook, - /** - * Hook is fired right after product page is visited. - * @param product visited product - */ - productPageVisited: productPageVisitedHook -} - -export { - catalogHooks, - catalogHooksExecutors -} diff --git a/core/modules/catalog-next/index.ts b/core/modules/catalog-next/index.ts deleted file mode 100644 index 5ea5cd1222..0000000000 --- a/core/modules/catalog-next/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { categoryModule } from './store/category' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -export const CatalogNextModule: StorefrontModule = function ({ store }) { - store.registerModule('category-next', categoryModule) -} diff --git a/core/modules/catalog-next/store/category/CategoryState.ts b/core/modules/catalog-next/store/category/CategoryState.ts deleted file mode 100644 index d3af9e7a3b..0000000000 --- a/core/modules/catalog-next/store/category/CategoryState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Category } from '../../types/Category'; -import Product from 'core/modules/catalog/types/Product'; - -export default interface CategoryState { - categoriesMap: { [id: string]: Category }, - notFoundCategoryIds: string[], - filtersMap: { [id: string]: any }, - products: Product[], - searchProductsStats: any, - menuCategories: Category[] -} diff --git a/core/modules/catalog-next/store/category/actions.ts b/core/modules/catalog-next/store/category/actions.ts deleted file mode 100644 index 8b8a06bde3..0000000000 --- a/core/modules/catalog-next/store/category/actions.ts +++ /dev/null @@ -1,269 +0,0 @@ -import Vue from 'vue' -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState' -import CategoryState from './CategoryState' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import { buildFilterProductsQuery } from '@vue-storefront/core/helpers' -import { router } from '@vue-storefront/core/app' -import { localizedDispatcherRoute } from '@vue-storefront/core/lib/multistore' -import FilterVariant from '../../types/FilterVariant' -import { CategoryService } from '@vue-storefront/core/data-resolver' -import { changeFilterQuery } from '../../helpers/filterHelpers' -import { products, entities } from 'config' -import { DataResolver } from 'core/data-resolver/types/DataResolver'; -import { Category } from '../../types/Category'; -import { _prepareCategoryPathIds } from '../../helpers/categoryHelpers'; -import { prefetchStockItems } from '../../helpers/cacheProductsHelper'; -import chunk from 'lodash-es/chunk' -import omit from 'lodash-es/omit' -import cloneDeep from 'lodash-es/cloneDeep' -import config from 'config' -import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/helpers' -import createCategoryListQuery from '@vue-storefront/core/modules/catalog/helpers/createCategoryListQuery' -import { transformCategoryUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl'; - -const actions: ActionTree = { - async loadCategoryProducts ({ commit, getters, dispatch, rootState }, { route, category, pageSize = 50 } = {}) { - const searchCategory = category || getters.getCategoryFrom(route.path) || {} - const areFiltersInQuery = !!Object.keys(route[products.routerFiltersSource]).length - let categoryMappedFilters = getters.getFiltersMap[searchCategory.id] - if (!categoryMappedFilters && areFiltersInQuery) { // loading all filters only when some filters are currently chosen and category has no available filters yet - await dispatch('loadCategoryFilters', searchCategory) - categoryMappedFilters = getters.getFiltersMap[searchCategory.id] - } - const searchQuery = getters.getCurrentFiltersFrom(route[products.routerFiltersSource], categoryMappedFilters) - let filterQr = buildFilterProductsQuery(searchCategory, searchQuery.filters) - const { items, perPage, start, total, aggregations, attributeMetadata } = await dispatch('product/findProducts', { - query: filterQr, - sort: searchQuery.sort || `${products.defaultSortBy.attribute}:${products.defaultSortBy.order}`, - includeFields: entities.productList.includeFields, - excludeFields: entities.productList.excludeFields, - size: pageSize, - configuration: searchQuery.filters, - options: { - populateRequestCacheTags: true, - prefetchGroupProducts: false, - setProductErrors: false, - fallbackToDefaultWhenNoAvailable: true, - assignProductConfiguration: false, - separateSelectedVariant: false - } - }, { root: true }) - await dispatch('loadAvailableFiltersFrom', { - aggregations, - attributeMetadata, - category: searchCategory, - filters: searchQuery.filters - }) - commit(types.CATEGORY_SET_SEARCH_PRODUCTS_STATS, { perPage, start, total }) - commit(types.CATEGORY_SET_PRODUCTS, items) - - return items - }, - async loadMoreCategoryProducts ({ commit, getters, rootState, dispatch }) { - const { perPage, start, total } = getters.getCategorySearchProductsStats - const totalValue = typeof total === 'object' ? total.value : total - if (start >= totalValue || totalValue < perPage) return - - const searchQuery = getters.getCurrentSearchQuery - let filterQr = buildFilterProductsQuery(getters.getCurrentCategory, searchQuery.filters) - const searchResult = await dispatch('product/findProducts', { - query: filterQr, - sort: searchQuery.sort || `${products.defaultSortBy.attribute}:${products.defaultSortBy.order}`, - start: start + perPage, - size: perPage, - includeFields: entities.productList.includeFields, - excludeFields: entities.productList.excludeFields, - configuration: searchQuery.filters, - options: { - populateRequestCacheTags: true, - prefetchGroupProducts: false, - setProductErrors: false, - fallbackToDefaultWhenNoAvailable: true, - assignProductConfiguration: false, - separateSelectedVariant: false - } - }, { root: true }) - commit(types.CATEGORY_SET_SEARCH_PRODUCTS_STATS, { - perPage: searchResult.perPage, - start: searchResult.start, - total: searchResult.total - }) - - commit(types.CATEGORY_ADD_PRODUCTS, searchResult.items) - - return searchResult.items - }, - async cacheProducts ({ commit, getters, dispatch, rootState }, { route } = {}) { - if (config.api.saveBandwidthOverCache) { - return - } - - const searchCategory = getters.getCategoryFrom(route.path) || {} - const searchQuery = getters.getCurrentFiltersFrom(route[products.routerFiltersSource]) - let filterQr = buildFilterProductsQuery(searchCategory, searchQuery.filters) - - const cachedProductsResponse = await dispatch('product/findProducts', { - query: filterQr, - sort: searchQuery.sort, - options: { - populateRequestCacheTags: false, - prefetchGroupProducts: false - } - }, { root: true }) - if (products.filterUnavailableVariants) { // prefetch the stock items - const skus = prefetchStockItems(cachedProductsResponse, rootState.stock.cache) - - for (const chunkItem of chunk(skus, 15)) { - dispatch('stock/list', { skus: chunkItem }, { root: true }) // store it in the cache - } - } - }, - async findCategories (context, categorySearchOptions: DataResolver.CategorySearchOptions): Promise { - return CategoryService.getCategories(categorySearchOptions) - }, - async loadCategories ({ commit, getters }, categorySearchOptions: DataResolver.CategorySearchOptions): Promise { - const searchingByIds = !(!categorySearchOptions || !categorySearchOptions.filters || !categorySearchOptions.filters.id) - const searchedIds: string[] = searchingByIds ? [...categorySearchOptions.filters.id].map(String) : [] - const loadedCategories: Category[] = [] - if (searchingByIds && !categorySearchOptions.reloadAll) { // removing from search query already loaded categories, they are added to returned results - for (const [categoryId, category] of Object.entries(getters.getCategoriesMap)) { - if (searchedIds.includes(categoryId)) { - loadedCategories.push(category as Category) - } - } - categorySearchOptions.filters.id = searchedIds.filter(categoryId => !getters.getCategoriesMap[categoryId] && !getters.getNotFoundCategoryIds.includes(categoryId)) - } - if (!searchingByIds || categorySearchOptions.filters.id.length) { - categorySearchOptions.filters = Object.assign(cloneDeep(config.entities.category.filterFields), categorySearchOptions.filters ? cloneDeep(categorySearchOptions.filters) : {}) - const categories = await CategoryService.getCategories(categorySearchOptions) - if (Vue.prototype.$cacheTags) { - categories.forEach(category => { - Vue.prototype.$cacheTags.add(`C${category.id}`) - }) - } - const notFoundCategories = searchedIds.filter(categoryId => !categories.some(cat => cat.id === parseInt(categoryId) || cat.id === categoryId)) - - commit(types.CATEGORY_ADD_CATEGORIES, categories) - commit(types.CATEGORY_ADD_NOT_FOUND_CATEGORY_IDS, notFoundCategories) - return [...loadedCategories, ...categories] - } - return loadedCategories - }, - async loadCategory ({ commit }, categorySearchOptions: DataResolver.CategorySearchOptions): Promise { - const categories: Category[] = await CategoryService.getCategories(categorySearchOptions) - const category: Category = categories && categories.length ? categories[0] : null - if (Vue.prototype.$cacheTags) { - Vue.prototype.$cacheTags.add(`C${category.id}`) - } - commit(types.CATEGORY_ADD_CATEGORY, category) - return category - }, - /** - * Fetch and process filters from current category and sets them in available filters. - */ - async loadCategoryFilters ({ dispatch, getters }, category) { - const searchCategory = category || getters.getCurrentCategory - let filterQr = buildFilterProductsQuery(searchCategory) - const { aggregations, attributeMetadata } = await quickSearchByQuery({ - query: filterQr, - size: config.products.maxFiltersQuerySize, - excludeFields: ['*'] - }) - await dispatch('loadAvailableFiltersFrom', { aggregations, attributeMetadata: attributeMetadata, category }) - }, - async loadAvailableFiltersFrom ({ commit, getters, dispatch }, { aggregations, attributeMetadata, category, filters = {} }) { - if (config.entities.attribute.loadByAttributeMetadata) { - await dispatch('attribute/loadCategoryAttributes', { attributeMetadata }, { root: true }) - } - const aggregationFilters = getters.getAvailableFiltersFrom(aggregations) - const currentCategory = category || getters.getCurrentCategory - const categoryMappedFilters = getters.getFiltersMap[currentCategory.id] - let resultFilters = aggregationFilters - const filtersKeys = Object.keys(filters) - if (categoryMappedFilters && filtersKeys.length) { - resultFilters = Object.assign(cloneDeep(categoryMappedFilters), cloneDeep(omit(aggregationFilters, filtersKeys))) - } - commit(types.CATEGORY_SET_CATEGORY_FILTERS, { category, filters: resultFilters }) - }, - - async switchSearchFilters ({ dispatch }, filterVariants: FilterVariant[] = []) { - let currentQuery = router.currentRoute[products.routerFiltersSource] - filterVariants.forEach(filterVariant => { - currentQuery = changeFilterQuery({ currentQuery, filterVariant }) - }) - await dispatch('changeRouterFilterParameters', currentQuery) - }, - async resetSearchFilters ({ dispatch }) { - await dispatch('changeRouterFilterParameters', {}) - }, - async changeRouterFilterParameters (context, query) { - router.push({ [products.routerFiltersSource]: query }) - }, - async loadCategoryBreadcrumbs ({ dispatch, getters }, { category, currentRouteName, omitCurrent = false }) { - if (!category) return - const categoryHierarchyIds = category.parent_ids ? [...category.parent_ids, category.id] : _prepareCategoryPathIds(category) // getters.getCategoriesHierarchyMap.find(categoryMapping => categoryMapping.includes(category.id)) - const categoryFilters = Object.assign({ 'id': categoryHierarchyIds }, cloneDeep(config.entities.category.breadcrumbFilterFields)) - const categories = await dispatch('loadCategories', { filters: categoryFilters, reloadAll: Object.keys(config.entities.category.breadcrumbFilterFields).length > 0 }) - const sorted = [] - for (const id of categoryHierarchyIds) { - const index = categories.findIndex(cat => cat.id.toString() === id) - if (index >= 0 && (!omitCurrent || categories[index].id !== category.id)) { - sorted.push(categories[index]) - } - } - await dispatch('breadcrumbs/set', { current: currentRouteName, routes: parseCategoryPath(sorted) }, { root: true }) - return sorted - }, - /** - * Load categories within specified parent - * @param {Object} commit promise - * @param {Object} parent parent category - */ - async fetchMenuCategories ({ commit, getters, dispatch }, { - parent = null, - key = null, - value = null, - level = null, - onlyActive = true, - onlyNotEmpty = false, - size = 4000, - start = 0, - sort = 'position:asc', - includeFields = (config.entities.optimize ? config.entities.category.includeFields : null), - excludeFields = (config.entities.optimize ? config.entities.category.excludeFields : null), - skipCache = false - }) { - const { searchQuery, isCustomizedQuery } = createCategoryListQuery({ parent, level, key, value, onlyActive, onlyNotEmpty }) - const shouldLoadCategories = skipCache || isCustomizedQuery - - if (shouldLoadCategories) { - const resp = await quickSearchByQuery({ entityType: 'category', query: searchQuery, sort, size, start, includeFields, excludeFields }) - - await dispatch('registerCategoryMapping', { categories: resp.items }) - - commit(types.CATEGORY_UPD_MENU_CATEGORIES, { items: resp.items }) - - return resp - } - - const list = { items: getters.getMenuCategories, total: getters.getMenuCategories.length } - - return list - }, - async registerCategoryMapping ({ dispatch }, { categories }) { - for (let category of categories) { - if (category.url_path) { - await dispatch('url/registerMapping', { - url: localizedDispatcherRoute(category.url_path), - routeData: transformCategoryUrl(category) - }, { root: true }) - } - } - }, - /** Below actions are not used from 1.12 and can be removed to reduce bundle */ - ...require('./deprecatedActions').default -} - -export default actions diff --git a/core/modules/catalog-next/store/category/deprecatedActions.ts b/core/modules/catalog-next/store/category/deprecatedActions.ts deleted file mode 100644 index f6701570ed..0000000000 --- a/core/modules/catalog-next/store/category/deprecatedActions.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { currentStoreView, localizedDispatcherRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore' -import { preConfigureProduct } from '@vue-storefront/core/modules/catalog/helpers/search' -import omit from 'lodash-es/omit' -import config from 'config' -const { configureProductAsync } = require('@vue-storefront/core/modules/catalog/helpers') - -const actions = { - /** - * Calculates products taxes - * Registers URLs - * Configures products - */ - async processCategoryProducts ({ dispatch, rootState }, { products = [], filters = {} } = {}) { - dispatch('registerCategoryProductsMapping', products) // we don't need to wait for this - const configuredProducts = await dispatch('configureProducts', { products, filters }) - return dispatch('tax/calculateTaxes', { products: configuredProducts }, { root: true }) - }, - /** - * Configure configurable products to have first available options selected - * so they can be added to cart/wishlist/compare without manual configuring - */ - async configureProducts ({ rootState }, { products = [], filters = {}, populateRequestCacheTags = config.server.useOutputCacheTagging } = {}) { - return products.map(product => { - product = Object.assign({}, preConfigureProduct({ product, populateRequestCacheTags })) - const configuredProductVariant = configureProductAsync({ rootState, state: { current_configuration: {} } }, { product, configuration: filters, selectDefaultVariant: false, fallbackToDefaultWhenNoAvailable: true, setProductErorrs: false }) - return Object.assign(product, omit(configuredProductVariant, ['visibility'])) - }) - }, - async registerCategoryProductsMapping ({ dispatch }, products = []) { - const { storeCode, appendStoreCode } = currentStoreView() - await Promise.all(products.map(product => { - const { url_path, sku, slug, type_id } = product - return dispatch('url/registerMapping', { - url: localizedDispatcherRoute(url_path, storeCode), - routeData: { - params: { - parentSku: product.parentSku || product.sku, - slug - }, - 'name': localizedDispatcherRouteName(type_id + '-product', storeCode, appendStoreCode) - } - }, { root: true }) - })) - } -} - -export default actions diff --git a/core/modules/catalog-next/store/category/getters.ts b/core/modules/catalog-next/store/category/getters.ts deleted file mode 100644 index 70a5e89531..0000000000 --- a/core/modules/catalog-next/store/category/getters.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { nonReactiveState } from './index'; -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import CategoryState from './CategoryState' -import { compareByLabel } from '../../helpers/categoryHelpers' -import { products } from 'config' -import FilterVariant from '../../types/FilterVariant' -import { optionLabel } from '@vue-storefront/core/modules/catalog/helpers' -import trim from 'lodash-es/trim' -import toString from 'lodash-es/toString' -import forEach from 'lodash-es/forEach' -import get from 'lodash-es/get' -import { getFiltersFromQuery } from '../../helpers/filterHelpers' -import { Category } from '../../types/Category' -import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/helpers' -import { _prepareCategoryPathIds, getSearchOptionsFromRouteParams } from '../../helpers/categoryHelpers'; -import { currentStoreView, removeStoreCodeFromRoute } from '@vue-storefront/core/lib/multistore' -import cloneDeep from 'lodash-es/cloneDeep' -import config from 'config'; - -function mapCategoryProducts (productsFromState, productsData) { - return productsFromState.map(prodState => { - if (typeof prodState === 'string') { - const product = productsData.find(prodData => prodData.sku === prodState) - return cloneDeep(product) - } - return prodState - }) -} - -const getters: GetterTree = { - getCategories: (state): Category[] => Object.values(state.categoriesMap), - getCategoriesMap: (state): { [id: string]: Category} => state.categoriesMap, - getNotFoundCategoryIds: (state): string[] => state.notFoundCategoryIds, - getCategoryProducts: (state) => mapCategoryProducts(state.products, nonReactiveState.products), - getCategoryFrom: (state, getters) => (path: string = '') => { - return getters.getCategories.find(category => (removeStoreCodeFromRoute(path) as string).replace(/^(\/)/gm, '') === category.url_path) - }, - getCategoryByParams: (state, getters, rootState) => (params: { [key: string]: string } = {}) => { - return getters.getCategories.find(category => { - let valueCheck = [] - const searchOptions = getSearchOptionsFromRouteParams(params) - forEach(searchOptions, (value, key) => valueCheck.push(category[key] && category[key] === (category[key].constructor)(value))) - return valueCheck.filter(check => check === true).length === Object.keys(searchOptions).length - }) || {} - }, - getCurrentCategory: (state, getters, rootState, rootGetters) => { - return getters.getCategoryByParams(rootState.route.params) - }, - getAvailableFiltersFrom: (state, getters, rootState) => (aggregations) => { - const filters = {} - if (aggregations) { // populate filter aggregates - for (let attrToFilter of products.defaultFilters) { // fill out the filter options - let filterOptions: FilterVariant[] = [] - - let uniqueFilterValues = new Set() - if (attrToFilter !== 'price') { - if (aggregations['agg_terms_' + attrToFilter]) { - let buckets = aggregations['agg_terms_' + attrToFilter].buckets - if (aggregations['agg_terms_' + attrToFilter + '_options']) { - buckets = buckets.concat(aggregations['agg_terms_' + attrToFilter + '_options'].buckets) - } - - for (let option of buckets) { - uniqueFilterValues.add(toString(option.key)) - } - } - - uniqueFilterValues.forEach(key => { - const label = optionLabel(rootState.attribute, { attributeKey: attrToFilter, optionId: key }) - if (trim(label) !== '') { // is there any situation when label could be empty and we should still support it? - filterOptions.push({ - id: key, - label: label, - type: attrToFilter - }) - } - }); - filters[attrToFilter] = filterOptions.sort(compareByLabel) - } else { // special case is range filter for prices - const currencySign = currentStoreView().i18n.currencySign - - if (aggregations['agg_range_' + attrToFilter]) { - let index = 0 - let count = aggregations['agg_range_' + attrToFilter].buckets.length - for (let option of aggregations['agg_range_' + attrToFilter].buckets) { - filterOptions.push({ - id: option.key, - type: attrToFilter, - from: option.from, - to: option.to, - label: (index === 0 || (index === count - 1)) ? (option.to ? '< ' + currencySign + option.to : '> ' + currencySign + option.from) : currencySign + option.from + (option.to ? ' - ' + option.to : ''), // TODO: add better way for formatting, extract currency sign - single: true - }) - index++ - } - filters[attrToFilter] = filterOptions - } - } - } - // Add sort to available filters - let variants = [] - Object.keys(products.sortByAttributes).map(label => { - variants.push({ - label: label, - id: products.sortByAttributes[label], - type: 'sort' - }) - }) - filters['sort'] = variants - } - return filters - }, - getFiltersMap: state => state.filtersMap, - getAvailableFilters: (state, getters) => { - const categoryId = get(getters.getCurrentCategory, 'id', null) - return state.filtersMap[categoryId] || {} - }, - getCurrentFiltersFrom: (state, getters, rootState) => (filters, categoryFilters) => { - const currentQuery = filters || rootState.route[products.routerFiltersSource] - const availableFilters = categoryFilters || getters.getAvailableFilters - return getFiltersFromQuery({ availableFilters, filtersQuery: currentQuery }) - }, - getCurrentSearchQuery: (state, getters, rootState) => getters.getCurrentFiltersFrom(rootState.route[products.routerFiltersSource]), - getCurrentFilters: (state, getters) => getters.getCurrentSearchQuery.filters, - hasActiveFilters: (state, getters) => !!Object.keys(getters.getCurrentFilters).length, - getSystemFilterNames: () => products.systemFilterNames, - getBreadcrumbs: (state, getters) => getters.getBreadcrumbsFor(getters.getCurrentCategory), - getBreadcrumbsFor: (state, getters) => category => { - if (!category) return [] - const categoryHierarchyIds = _prepareCategoryPathIds(category) - let resultCategoryList = categoryHierarchyIds.map(categoryId => { - return getters.getCategoriesMap[categoryId] - }).filter(c => !!c) - return parseCategoryPath(resultCategoryList) - }, - getCategorySearchProductsStats: state => state.searchProductsStats || {}, - getCategoryProductsTotal: (state, getters) => { - const { total } = getters.getCategorySearchProductsStats - const totalValue = typeof total === 'object' ? total.value : total - - return totalValue || 0 - }, - getMenuCategories (state, getters, rootState, rootGetters) { - return state.menuCategories || rootGetters['category/getCategories'] - } -} - -export default getters diff --git a/core/modules/catalog-next/store/category/index.ts b/core/modules/catalog-next/store/category/index.ts deleted file mode 100644 index 6d2f9f7eaa..0000000000 --- a/core/modules/catalog-next/store/category/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import CategoryState from './CategoryState' - -export const categoryModule: Module = { - namespaced: true, - state: { - categoriesMap: {}, - notFoundCategoryIds: [], - filtersMap: {}, - products: [], - searchProductsStats: {}, - menuCategories: [] - }, - getters, - actions, - mutations -} - -export const nonReactiveState = { - products: [] -} diff --git a/core/modules/catalog-next/store/category/mutation-types.ts b/core/modules/catalog-next/store/category/mutation-types.ts deleted file mode 100644 index 3234f97a9b..0000000000 --- a/core/modules/catalog-next/store/category/mutation-types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const SN_CATEGORY = 'category' -export const CATEGORY_SET_PRODUCTS = `${SN_CATEGORY}/SET_PRODUCTS` -export const CATEGORY_ADD_PRODUCTS = `${SN_CATEGORY}/ADD_PRODUCTS` -export const CATEGORY_SET_SEARCH_PRODUCTS_STATS = `${SN_CATEGORY}/SET_SEARCH_PRODUCTS_STATS` -export const CATEGORY_ADD_CATEGORIES = `${SN_CATEGORY}/ADD_CATEGORIES` -export const CATEGORY_ADD_CATEGORY = `${SN_CATEGORY}/ADD_CATEGORY` -export const CATEGORY_SET_CATEGORY_FILTERS = `${SN_CATEGORY}/SET_CATEGORY_FILTERS` -export const CATEGORY_ADD_NOT_FOUND_CATEGORY_IDS = `${SN_CATEGORY}/ADD_NOT_FOUND_CATEGORY_IDS` -export const CATEGORY_UPD_MENU_CATEGORIES = `${SN_CATEGORY}/UPD_MENU_CATEGORIES` diff --git a/core/modules/catalog-next/store/category/mutations.ts b/core/modules/catalog-next/store/category/mutations.ts deleted file mode 100644 index fcee774545..0000000000 --- a/core/modules/catalog-next/store/category/mutations.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers'; -import { nonReactiveState } from './index'; -import Vue from 'vue' -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import CategoryState from './CategoryState' -import { Category } from '../../types/Category' -import cloneDeep from 'lodash-es/cloneDeep' -import slugifyCategories from '@vue-storefront/core/modules/catalog/helpers/slugifyCategories' - -const mutations: MutationTree = { - [types.CATEGORY_SET_PRODUCTS] (state, products = []) { - nonReactiveState.products = cloneDeep(products) - state.products = isServer ? products : products.map(prod => prod.sku) - }, - [types.CATEGORY_ADD_PRODUCTS] (state, products = []) { - nonReactiveState.products.push(...cloneDeep(products)) - state.products.push(...(isServer ? products : products.map(prod => prod.sku))) - }, - [types.CATEGORY_ADD_CATEGORY] (state, category: Category) { - if (category) { - Vue.set(state.categoriesMap, category.id, category) - } - }, - [types.CATEGORY_ADD_CATEGORIES] (state, categories: Category[] = []) { - if (categories.length) { - let newCategoriesEntry = {} - categories.forEach(category => { - newCategoriesEntry[category.id] = category - }) - state.categoriesMap = Object.assign({}, state.categoriesMap, newCategoriesEntry) - } - }, - [types.CATEGORY_ADD_NOT_FOUND_CATEGORY_IDS] (state, categoryIds: string[] = []) { - state.notFoundCategoryIds = [...state.notFoundCategoryIds, ...categoryIds] - }, - [types.CATEGORY_SET_CATEGORY_FILTERS] (state, { category, filters }) { - Vue.set(state.filtersMap, category.id, filters) - }, - [types.CATEGORY_SET_SEARCH_PRODUCTS_STATS] (state, stats = {}) { - state.searchProductsStats = stats - }, - [types.CATEGORY_UPD_MENU_CATEGORIES] (state, categories) { - for (let category of categories.items) { - category = slugifyCategories(category) - const catExist = state.menuCategories.find(existingCat => existingCat.id === category.id) - - if (!catExist) { - state.menuCategories.push(category) - } - } - - state.menuCategories.sort((catA, catB) => { - if (catA.position && catB.position) { - if (catA.position < catB.position) return -1 - if (catA.position > catB.position) return 1 - } - return 0 - }) - } -} - -export default mutations diff --git a/core/modules/catalog-next/test/unit/_prepareCategoryPathIds.spec.ts b/core/modules/catalog-next/test/unit/_prepareCategoryPathIds.spec.ts deleted file mode 100644 index 00fac236a8..0000000000 --- a/core/modules/catalog-next/test/unit/_prepareCategoryPathIds.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { _prepareCategoryPathIds } from '@vue-storefront/core/modules/catalog-next/helpers/categoryHelpers'; -import { Category } from '../../types/Category'; - -jest.mock('@vue-storefront/core/modules/breadcrumbs/helpers', () => jest.fn()); - -describe('_prepareCategoryPathIds method', () => { - let parentCategory: Category - - beforeEach(() => { - parentCategory = { - 'path': '1/2', - 'is_active': true, - 'level': 1, - 'product_count': 1181, - 'children_count': '38', - 'parent_id': 1, - 'name': 'All', - 'id': 2, - 'url_key': 'all-2', - 'children_data': [], - 'url_path': 'all-2', - 'slug': 'all-2' - } - }) - - it('should return empty array when no category provided', () => { - const result = _prepareCategoryPathIds(null) - expect(result).toBeDefined() - expect(result.length).toEqual(0) - }); - - it('should return an array from category path', () => { - const result = _prepareCategoryPathIds(parentCategory) - expect(result).toBeDefined() - expect(result).toEqual(['1', '2']) - }); - - it('should return array with single value', () => { - parentCategory.path = '2' - const result = _prepareCategoryPathIds(parentCategory) - expect(result).toBeDefined() - expect(result).toEqual(['2']) - }) - - it('should return array deep connection', () => { - parentCategory.path = '1/2/20/21/252' - const result = _prepareCategoryPathIds(parentCategory) - expect(result).toBeDefined() - expect(result).toEqual(['1', '2', '20', '21', '252']) - }) -}) diff --git a/core/modules/catalog-next/test/unit/changeFilterQuery.spec.ts b/core/modules/catalog-next/test/unit/changeFilterQuery.spec.ts deleted file mode 100644 index d0145e9329..0000000000 --- a/core/modules/catalog-next/test/unit/changeFilterQuery.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { changeFilterQuery } from '@vue-storefront/core/modules/catalog-next/helpers/filterHelpers'; -import FilterVariant from '@vue-storefront/core/modules/catalog-next/types/FilterVariant'; - -describe('changeFilterQuery method', () => { - it('should not change query when no filter variant provided', () => { - const currentQuery = { - color: 1 - }; - const result = changeFilterQuery({ currentQuery }); - expect(result).toEqual(currentQuery); - }); - - it('should not return same query object instance', () => { - const currentQuery = { - color: 1 - }; - const result = changeFilterQuery({ currentQuery }); - expect(result).not.toBe(currentQuery); - }); - - it('should add filter to array', () => { - const currentQuery = {}; - const filterVariant: FilterVariant = { - id: '33', - label: 'Red', - type: 'color' - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({ color: ['33'] }); - }); - - it('should remove filter if exist in query', () => { - const currentQuery = { - color: ['23', '33'] - }; - const filterVariant: FilterVariant = { - id: '33', - label: 'Red', - type: 'color' - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({ color: ['23'] }); - }); - - it('should add sort filter', () => { - const currentQuery = {}; - const filterVariant: FilterVariant = { - id: 'final_price', - label: 'Price: Low to high', - type: 'sort' - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({ sort: 'final_price' }); - }); - - it('should remove sort filter', () => { - const currentQuery = { sort: 'final_price' }; - const filterVariant: FilterVariant = { - id: 'final_price', - label: 'Price: Low to high', - type: 'sort' - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({}); - }); - - it('should change sort filter', () => { - const currentQuery = { sort: 'final_price' }; - const filterVariant: FilterVariant = { - id: 'updated_at', - label: 'Latest', - type: 'sort' - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({ sort: 'updated_at' }); - }); - - it('should add single filter when there is none', () => { - const currentQuery = { - }; - const filterVariant: FilterVariant = { - id: '50.0-100.0', - type: 'price', - from: '50', - to: '100', - label: '$50 - 100', - single: true - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({ price: ['50.0-100.0'] }); - }); - - it('should remove single filter when adding is the same', () => { - const currentQuery = { - price: ['50.0-100.0'] - }; - const filterVariant: FilterVariant = { - id: '50.0-100.0', - type: 'price', - from: '50', - to: '100', - label: '$50 - 100', - single: true - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({}); - }); - - it('should change single filter when adding another single', () => { - const currentQuery = { - price: ['100.0-150.0'] - }; - const filterVariant: FilterVariant = { - id: '50.0-100.0', - type: 'price', - from: '50', - to: '100', - label: '$50 - 100', - single: true - }; - const result = changeFilterQuery({ currentQuery, filterVariant }); - expect(result).toEqual({ price: ['50.0-100.0'] }); - }); -}); diff --git a/core/modules/catalog-next/test/unit/getFiltersFromQuery.spec.ts b/core/modules/catalog-next/test/unit/getFiltersFromQuery.spec.ts deleted file mode 100644 index 6f0b607a9c..0000000000 --- a/core/modules/catalog-next/test/unit/getFiltersFromQuery.spec.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { getFiltersFromQuery } from '../../helpers/filterHelpers'; - -describe('getFiltersFromQuery method', () => { - let availableFilters - beforeEach(() => { - availableFilters = { - 'color': [ - { - 'id': '49', - 'label': 'Black', - 'type': 'color' - }, - { - 'id': '50', - 'label': 'Blue', - 'type': 'color' - } - ], - 'size': [ - { - 'id': '172', - 'label': '28', - 'type': 'size' - }, - { - 'id': '170', - 'label': 'L', - 'type': 'size' - }, - { - 'id': '169', - 'label': 'M', - 'type': 'size' - } - ], - 'price': [ - { - 'id': '0.0-50.0', - 'type': 'price', - 'from': 0, - 'to': 50, - 'label': '< $50' - }, - { - 'id': '50.0-100.0', - 'type': 'price', - 'from': 50, - 'to': 100, - 'label': '$50 - 100' - }, - { - 'id': '150.0-*', - 'type': 'price', - 'from': 150, - 'label': '> $150' - } - ], - 'erin_recommends': [ - { - 'id': '0', - 'label': 'No', - 'type': 'erin_recommends' - }, - { - 'id': '1', - 'label': 'Yes', - 'type': 'erin_recommends' - } - ], - 'sort': [ - { - 'label': 'Latest', - 'id': 'updated_at', - 'type': 'sort' - }, - { - 'label': 'Price: Low to high', - 'id': 'final_price', - 'type': 'sort' - }, - { - 'label': 'Price: High to low', - 'id': 'final_price:desc', - 'type': 'sort' - } - ] - } - }); - - it('should return color filter', () => { - const filtersQuery = { - color: '49' - } - const result = getFiltersFromQuery({ availableFilters, filtersQuery }) - expect(result).toEqual({ - filters: { - color: [ - { - 'id': '49', - 'label': 'Black', - 'type': 'color', - 'attribute_code': 'color' - } - ] - } - }) - }); - - it('should return color filters', () => { - const filtersQuery = { - color: ['49', '50'] - } - const result = getFiltersFromQuery({ availableFilters, filtersQuery }) - expect(result).toEqual({ - filters: { - color: [ - { - 'id': '49', - 'label': 'Black', - 'type': 'color', - 'attribute_code': 'color' - }, - { - 'id': '50', - 'label': 'Blue', - 'type': 'color', - 'attribute_code': 'color' - } - ] - } - }) - }); - - it('should not return not existing filter', () => { - const filtersQuery = { - color: '111' - } - const result = getFiltersFromQuery({ availableFilters, filtersQuery }) - expect(result).toEqual({ - filters: {} - }) - }); - - it('should return size filter', () => { - const filtersQuery = { - sort: 'updated_at' - } - const result = getFiltersFromQuery({ availableFilters, filtersQuery }) - expect(result).toEqual({ - filters: {}, - sort: 'updated_at' - }) - }); -}); diff --git a/core/modules/catalog-next/test/unit/prefetchStockItems.spec.ts b/core/modules/catalog-next/test/unit/prefetchStockItems.spec.ts deleted file mode 100644 index 12b2ed2b66..0000000000 --- a/core/modules/catalog-next/test/unit/prefetchStockItems.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { prefetchStockItems } from '../../helpers/cacheProductsHelper'; -import config from 'config'; - -describe('prefetchStockItems method', () => { - describe('default configurableChildrenStockPrefetchStaticPrefetchCount', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.mock('config', () => ({})); - }) - - it('returns an empty array when no items are provided', () => { - const cachedProductsResponse = { - items: [] - } - const result = prefetchStockItems(cachedProductsResponse) - expect(result).toEqual([]); - }) - - it('returns the skus of the children of a configurable', () => { - const cachedProductsResponse = { - items: [ - { sku: 'foo' }, - { - sku: 'bar', - type_id: 'configurable', - configurable_children: [ - { sku: 'bar.foo' }, - { sku: 'bar.bar' }, - { sku: 'bar.baz' } - ] - }, - { sku: 'baz' } - ] - } - const result = prefetchStockItems(cachedProductsResponse) - expect(result).toEqual(['foo', 'bar', 'bar.foo', 'bar.bar', 'bar.baz', 'baz']); - }) - - it('returns the same skus of the provided simple products', () => { - const cachedProductsResponse = { - items: [ - { sku: 'foo' }, - { sku: 'bar' }, - { sku: 'baz' } - ] - } - const result = prefetchStockItems(cachedProductsResponse) - expect(result).toEqual(['foo', 'bar', 'baz']); - }) - - it('ignores the pre-cached skus of children of a configurable', () => { - const cachedProductsResponse = { - items: [ - { sku: 'foo' }, - { - sku: 'bar', - type_id: 'configurable', - configurable_children: [ - { sku: 'bar.foo', id: 1337 }, - { sku: 'bar.bar' }, - { sku: 'bar.baz', id: 4711 } - ] - }, - { sku: 'baz' } - ] - } - const result = prefetchStockItems(cachedProductsResponse, { 1337: {}, 4711: {} }) - expect(result).toEqual(['foo', 'bar', 'bar.bar', 'baz']); - }) - }) -}) diff --git a/core/modules/catalog-next/types/Category.d.ts b/core/modules/catalog-next/types/Category.d.ts deleted file mode 100644 index a5bc993eb7..0000000000 --- a/core/modules/catalog-next/types/Category.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -import FilterVariant from './FilterVariant' - -export interface ChildrenData { - id: number | string, - children_data?: ChildrenData[], - name?: string, - slug?: string, - url_key?: string -} - -export interface Category { - path: string, - is_active: boolean, - level: number, - product_count: number, - children_count: string, - parent_id: number | string, - name: string, - id: number | string, - url_path: string, - url_key: string, - children_data: ChildrenData[], - slug: string, - position?: number -} - -export interface Filters { - [key: string]: FilterVariant[] -} diff --git a/core/modules/catalog-next/types/FilterVariant.ts b/core/modules/catalog-next/types/FilterVariant.ts deleted file mode 100644 index 8f8f2d8d75..0000000000 --- a/core/modules/catalog-next/types/FilterVariant.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default interface FilterVariant { - id: string, - label: string, - type: string, - from?: string, - to?: string, - single?: boolean -} diff --git a/core/modules/catalog/components/CategoryFilters.ts b/core/modules/catalog/components/CategoryFilters.ts deleted file mode 100644 index 75b9baf952..0000000000 --- a/core/modules/catalog/components/CategoryFilters.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { buildFilterProductsQuery } from '@vue-storefront/core/helpers' -import { mapGetters } from 'vuex'; - -export default { - name: 'CategoryFilters', - computed: { - ...mapGetters('category', ['getActiveCategoryFilters', 'getCurrentCategoryProductQuery', 'getAllCategoryFilters']), - filters () { - return this.getAllCategoryFilters - }, - activeFilters () { - return this.getActiveCategoryFilters - } - }, - methods: { - /** used to sort filters descending by id */ - sortById (filters) { - return [...filters].sort((a, b) => { return a.id - b.id }) - }, - resetAllFilters () { - // todo: get rid of this one - this.$bus.$emit('filter-reset') - this.$store.dispatch('category/resetFilters') - this.$store.dispatch('category/searchProductQuery', {}) - this.$store.dispatch('category/mergeSearchOptions', { - searchProductQuery: buildFilterProductsQuery(this.category, this.activeFilters) - }) - this.$store.dispatch('category/products', this.getCurrentCategoryProductQuery) - } - } -} diff --git a/core/modules/catalog/components/CategorySort.ts b/core/modules/catalog/components/CategorySort.ts deleted file mode 100644 index 10214890ef..0000000000 --- a/core/modules/catalog/components/CategorySort.ts +++ /dev/null @@ -1,44 +0,0 @@ -import config from 'config' -import { mapGetters } from 'vuex' -export const CategorySort = { - name: 'SortBy', - data () { - return { - sortby: '' - } - }, - mounted () { - const sort = this.getCurrentCategoryProductQuery && this.getCurrentCategoryProductQuery.sort ? this.getCurrentCategoryProductQuery.sort : null - if (sort) { - const sortingOptionValues = Object.values(this.sortingOptions) - const sortOptionExist = sortingOptionValues.includes(sort) - if (sortOptionExist) { - this.sortby = sort - } - } - }, - methods: { - // emit to category, todo: move all logic inside - sort () { - this.$emit('change', this.sortby) - // this.$bus.$emit('list-change-sort', { attribute: this.sortby }) - } - }, - computed: { - ...mapGetters('category', ['getCurrentCategoryProductQuery']), - sortingOptions () { - return config.products.sortByAttributes - }, - sortingVariants () { - let variants = [] - Object.keys(this.sortingOptions).map(label => { - variants.push({ - label: label, - id: this.sortingOptions[label], - type: 'sort' - }) - }) - return variants - } - } -} diff --git a/core/modules/catalog/components/ProductAttribute.ts b/core/modules/catalog/components/ProductAttribute.ts deleted file mode 100644 index c0a855ecdd..0000000000 --- a/core/modules/catalog/components/ProductAttribute.ts +++ /dev/null @@ -1,55 +0,0 @@ -export const ProductAttribute = { - name: 'ProductAttribute', - props: { - product: { - type: Object, - required: true - }, - attribute: { - type: null, - required: true - }, - emptyPlaceholder: { - type: String, - default: '' - } - }, - computed: { - label () { - return (this.attribute && this.attribute.frontend_label) ? this.attribute.frontend_label : ((this.attribute && this.attribute.default_frontend_label) ? this.attribute.default_frontend_label : '') - }, - value () { - let parsedValues = this.product[this.attribute.attribute_code] - - if (!parsedValues) { - return this.emptyPlaceholder - } else if (this.attribute.frontend_input !== 'multiselect' && this.attribute.frontend_input !== 'select') { - return parsedValues.toString() - } else { - parsedValues = typeof parsedValues === 'string' ? parsedValues.split(',') : parsedValues - - if (!Array.isArray(parsedValues)) { - parsedValues = [parsedValues] - } - - let results = [] - for (let parsedVal of parsedValues) { - if (this.attribute.options) { - let option = this.attribute.options.find(av => { - /* eslint eqeqeq: "off" */ - return av.value == parsedVal - }) - if (option) { - results.push(option.label) - } else { - results.push(parsedVal) - } - } else { - results.push(parsedVal) - } - } - return results.join(', ') - } - } - } -} diff --git a/core/modules/catalog/components/ProductBundleOption.ts b/core/modules/catalog/components/ProductBundleOption.ts deleted file mode 100644 index d902cbead3..0000000000 --- a/core/modules/catalog/components/ProductBundleOption.ts +++ /dev/null @@ -1,83 +0,0 @@ -import config from 'config' - -export const ProductBundleOption = { - name: 'ProductBundleOption', - props: { - option: { - type: Object, - required: true - }, - errorMessages: { - type: Object, - default: null - } - }, - data () { - return { - productOptionId: null, - quantity: 1 - } - }, - computed: { - productBundleOption () { - return `bundleOption_${this.option.option_id}` - }, - bundleOptionName () { - return `bundleOption_${this._uid}_${this.option.option_id}_` - }, - quantityName () { - return `bundleOptionQty_${this.option.option_id}` - }, - value () { - const { product_links } = this.option - if (Array.isArray(product_links)) { - return product_links.find(product => product.id === this.productOptionId) - } - return product_links - }, - errorMessage () { - return this.errorMessages ? this.errorMessages[this.quantityName] : '' - } - }, - mounted () { - this.setDefaultValues() - if (config.usePriceTiers) { - this.$bus.$on('product-after-setup-associated', this.setDefaultValues) - } - }, - beforeDestroy () { - if (config.usePriceTiers) { - this.$bus.$off('product-after-setup-associated', this.setDefaultValues) - } - }, - watch: { - productOptionId (value) { - this.bundleOptionChanged() - }, - quantity (value) { - this.bundleOptionChanged() - } - }, - methods: { - setDefaultValues () { - const { product_links } = this.option - - if (product_links) { - const defaultOption = Array.isArray(product_links) - ? product_links.find(pl => pl.is_default) - : product_links - - this.productOptionId = defaultOption ? defaultOption.id : product_links[0].id - this.quantity = defaultOption ? defaultOption.qty : 1 - } - }, - bundleOptionChanged () { - this.$emit('option-changed', { - option: this.option, - fieldName: this.productBundleOption, - qty: this.quantity, - value: this.value - }) - } - } -} diff --git a/core/modules/catalog/components/ProductBundleOptions.ts b/core/modules/catalog/components/ProductBundleOptions.ts deleted file mode 100644 index 240a3a1ecb..0000000000 --- a/core/modules/catalog/components/ProductBundleOptions.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { mapMutations } from 'vuex' -import * as types from '../store/product/mutation-types' -import rootStore from '@vue-storefront/core/store' -import i18n from '@vue-storefront/i18n' -import { Logger } from '@vue-storefront/core/lib/logger' - -function _fieldName (co) { - return ['bundleOption_' + co.option_id, 'bundleOptionQty_' + co.option_id] -} - -export const ProductBundleOptions = { - name: 'ProductBundleOptions', - props: { - product: { - type: Object, - required: true - } - }, - data () { - return { - selectedOptions: {}, - validationRules: {}, - validationResults: {} - } - }, - computed: { - /** - * Error messages map for validation options - */ - errorMessages () { - let messages = {} - Object.keys(this.validationResults).map(optionKey => { - const validationResult = this.validationResults[optionKey] - if (validationResult.error) { - messages[optionKey] = validationResult.message - } - }) - return messages - } - }, - beforeMount () { - this.setupValidationRules() - }, - methods: { - ...mapMutations('product', { - setBundleOptionValue: types.PRODUCT_SET_BUNDLE_OPTION // map `this.add()` to `this.$store.commit('increment')` - }), - setupValidationRules () { - rootStore.dispatch('product/addCustomOptionValidator', { - validationRule: 'gtzero', // You may add your own custom fields validators elsewhere in the theme - validatorFunction: (value) => { - return { error: (value === null || value === '') || (value === false) || (value <= 0), message: i18n.t('Must be greater than 0') } - } - }) - - for (let co of this.product.bundle_options) { - for (let fieldName of _fieldName(co)) { - if (co.required) { // validation rules are very basic - this.validationRules[fieldName] = 'gtzero' // TODO: add custom validators for the custom options - } - } - } - }, - optionChanged ({ fieldName, option, qty, value }) { - if (!fieldName) return - this.setBundleOptionValue({ optionId: option.option_id, optionQty: parseInt(qty), optionSelections: [parseInt(value.id)] }) - this.$store.dispatch('product/setBundleOptions', { product: this.product, bundleOptions: this.$store.state.product.current_bundle_options }) // TODO: move it to "AddToCart" - this.selectedOptions[fieldName] = { qty, value } - const valueId = value ? value.id : null - if (this.validateField(option, qty, valueId)) { - this.$bus.$emit('product-after-bundleoptions', { product: this.product, option: option, optionValues: this.selectedOptions }) - } - }, - isValid () { - let isValid = true - this.validationResults.map((res) => { if (res.error) isValid = false }) - return isValid - }, - validateField (option, qty, optionId) { - let result = true - let validationResult = { error: false, message: '' } - for (let fieldName of _fieldName(option)) { - const validationRule = this.validationRules[fieldName] - this.product.errors.custom_options = null - if (validationRule) { - const validator = this.$store.state.product.custom_options_validators[validationRule] - if (typeof validator === 'function') { - const quantityValidationResult = validator(qty) - if (quantityValidationResult.error) validationResult = quantityValidationResult - const optionValidationResult = validator(optionId) - if (optionValidationResult.error) validationResult = optionValidationResult - this.$set(this.validationResults, fieldName, validationResult) - if (validationResult.error) { - this.product.errors['bundle_options_' + fieldName] = i18n.t('Please configure product bundle options and fix the validation errors') - result = false - } else { - this.product.errors['bundle_options_' + fieldName] = null - } - } else { - Logger.error('No validation rule found for ' + validationRule, 'components-product-bundle-options')() - this.$set(this.validationResults, fieldName, validationResult) - } - } else { - this.$set(this.validationResults, fieldName, validationResult) - } - } - return result - } - } -} diff --git a/core/modules/catalog/components/ProductCustomOption.ts b/core/modules/catalog/components/ProductCustomOption.ts deleted file mode 100644 index e3752ecb13..0000000000 --- a/core/modules/catalog/components/ProductCustomOption.ts +++ /dev/null @@ -1,51 +0,0 @@ -export const ProductCustomOption = { - name: 'ProductCustomOption', - props: { - label: { - type: String, - required: false, - default: '' - }, - id: { - type: null, - required: false, - default: () => false - }, - code: { - type: null, - required: false, - default: () => false - }, - context: { - type: null, - required: false, - default: () => false - } - }, - data () { - return { - active: false - } - }, - beforeMount () { - this.$bus.$on('filter-reset', this.filterReset) - this.$bus.$on('filter-changed-' + this.context, this.filterChanged) - }, - beforeDestroy () { - this.$bus.$off('filter-reset', this.filterReset) - this.$bus.$off('filter-changed-' + this.context, this.filterChanged) - }, - methods: { - filterChanged (filterOption) { - if (filterOption.attribute_code === this.code) { - this.active = filterOption.id === this.id ? !this.active : false - } - }, - filterReset (filterOption) { - this.active = false - }, - switchFilter (id, label) { - this.$bus.$emit('filter-changed-' + this.context, { attribute_code: this.code, id: id, label: label }) - } - } -} diff --git a/core/modules/catalog/components/ProductCustomOptions.ts b/core/modules/catalog/components/ProductCustomOptions.ts deleted file mode 100644 index 412e06fcb2..0000000000 --- a/core/modules/catalog/components/ProductCustomOptions.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { customOptionFieldName, selectedCustomOptionValue, defaultCustomOptionValue } from '@vue-storefront/core/modules/catalog/helpers/customOption'; -import { mapMutations } from 'vuex' -import * as types from '../store/product/mutation-types' -import rootStore from '@vue-storefront/core/store' -import i18n from '@vue-storefront/i18n' -import { Logger } from '@vue-storefront/core/lib/logger' - -export const ProductCustomOptions = { - name: 'ProductCustomOptions', - props: { - product: { - type: Object, - required: true - } - }, - data () { - return { - inputValues: {}, - validation: { - rules: {}, - results: {} - } - } - }, - computed: { - selectedOptions () { - const customOptions = this.product.custom_options - if (!customOptions) { - return {} - } - - return customOptions.reduce((selectedOptions, option) => { - const fieldName = customOptionFieldName(option) - selectedOptions[fieldName] = selectedCustomOptionValue(option.type, option.values, this.inputValues[fieldName]) - return selectedOptions - }, {}) - } - }, - created () { - rootStore.dispatch('product/addCustomOptionValidator', { - validationRule: 'required', // You may add your own custom fields validators elsewhere in the theme - validatorFunction: (value) => { - const error = Array.isArray(value) ? !value.length : !value - const message = i18n.t('Field is required.') - return { error, message } - } - }) - this.setupInputFields() - }, - methods: { - ...mapMutations('product', { - setCustomOptionValue: types.PRODUCT_SET_CUSTOM_OPTION // map `this.add()` to `this.$store.commit('increment')` - }), - setupInputFields () { - for (const customOption of this.product.custom_options) { - const fieldName = customOptionFieldName(customOption) - this.$set(this.inputValues, fieldName, defaultCustomOptionValue(customOption)) - if (customOption.is_require) { // validation rules are very basic - this.$set(this.validation.rules, fieldName, 'required') // TODO: add custom validators for the custom options - } - this.optionChanged(customOption) - } - }, - optionChanged (option) { - const fieldName = customOptionFieldName(option) - this.validateField(option) - this.setCustomOptionValue({ optionId: option.option_id, optionValue: this.selectedOptions[fieldName] }) - this.$store.dispatch('product/setCustomOptions', { product: this.product, customOptions: this.$store.state.product.current_custom_options }) // TODO: move it to "AddToCart" - this.$bus.$emit('product-after-customoptions', { product: this.product, option: option, optionValues: this.selectedOptions }) - }, - validateField (option) { - const fieldName = customOptionFieldName(option) - const validationRule = this.validation.rules[fieldName] - this.product.errors.custom_options = null - if (validationRule) { - const validator = this.$store.state.product.custom_options_validators[validationRule] - if (typeof validator === 'function') { - const validationResult = validator(this['inputValues'][fieldName]) - this.validation.results[fieldName] = validationResult - if (validationResult.error) { - this.product.errors['custom_options_' + fieldName] = i18n.t('Please configure product custom options and fix the validation errors') - } else { - this.product.errors['custom_options_' + fieldName] = null - } - } else { - Logger.error('No validation rule found for ' + validationRule, 'components-product-custom-options')() - this.validation.results[fieldName] = { error: false, message: '' } - } - } else { - this.validation.results[fieldName] = { error: false, message: '' } - } - }, - isValid () { - let isValid = true - this.validation.results.map((res) => { if (res.error) isValid = false }) - return isValid - } - } -} diff --git a/core/modules/catalog/components/ProductGallery.ts b/core/modules/catalog/components/ProductGallery.ts deleted file mode 100644 index dc85cfcb6e..0000000000 --- a/core/modules/catalog/components/ProductGallery.ts +++ /dev/null @@ -1,28 +0,0 @@ -import VueOffline from 'vue-offline' - -export const ProductGallery = { - name: 'ProductGallery', - components: { - VueOffline - }, - props: { - gallery: { - type: Array, - required: true - }, - configuration: { - type: Object, - required: true - }, - offline: { - type: Object, - required: false - }, - product: { - type: Object, - required: true - } - }, - computed: { - } -} diff --git a/core/modules/catalog/components/ProductOption.ts b/core/modules/catalog/components/ProductOption.ts deleted file mode 100644 index 4aa4c44523..0000000000 --- a/core/modules/catalog/components/ProductOption.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isOptionAvailableAsync } from '@vue-storefront/core/modules/catalog/helpers/index' -import { getAvailableFiltersByProduct, getSelectedFiltersByProduct } from '@vue-storefront/core/modules/catalog/helpers/filters' - -export const ProductOption = { - computed: { - getAvailableFilters () { - return getAvailableFiltersByProduct(this.product) - }, - getSelectedFilters () { - return getSelectedFiltersByProduct(this.product, this.configuration) - } - }, - methods: { - isOptionAvailable (option) { // check if the option is available - let currentConfig = Object.assign({}, this.configuration) - currentConfig[option.type] = option - return isOptionAvailableAsync(this.$store, { product: this.product, configuration: currentConfig }) - } - } -} diff --git a/core/modules/catalog/components/ProductTile.ts b/core/modules/catalog/components/ProductTile.ts deleted file mode 100644 index 8dcdad0356..0000000000 --- a/core/modules/catalog/components/ProductTile.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { productThumbnailPath } from '@vue-storefront/core/helpers' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import { formatProductLink } from '@vue-storefront/core/modules/url/helpers' -import config from 'config' - -export const ProductTile = { - name: 'ProductTile', - props: { - product: { - type: Object, - required: true - } - }, - data () { - return { - clicks: 0, - placeholder: '/assets/placeholder.jpg' - } - }, - computed: { - productLink () { - return formatProductLink(this.product, currentStoreView().storeCode) - }, - thumbnail () { - // todo: play with the image based on category page filters - eg. when 'red' color is chosen, the image is going to be 'red' - let thumbnail = productThumbnailPath(this.product) - return this.getThumbnail(thumbnail, config.products.thumbnails.width, config.products.thumbnails.height) - }, - thumbnailObj () { - return { - src: this.thumbnail, - loading: this.placeholder, - error: this.placeholder - } - }, - isOnSale () { - return [true, '1'].includes(this.product.sale) ? 'sale' : '' - }, - isNew () { - return [true, '1'].includes(this.product.new) ? 'new' : '' - } - } -} diff --git a/core/modules/catalog/components/ProductVideo.ts b/core/modules/catalog/components/ProductVideo.ts deleted file mode 100644 index 9c66809bfb..0000000000 --- a/core/modules/catalog/components/ProductVideo.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const ProductVideo = { - name: 'ProductVideo', - props: { - url: { - type: String, - required: true - }, - id: { - type: String, - required: true - }, - type: { - type: String, - required: true - }, - index: { - type: Number, - required: false, - default: 0 - } - }, - data () { - return { - videoStarted: false, - iframeLoaded: false - } - }, - methods: { - initVideo () { - this.videoStarted = true - this.$emit('video-started', this.index) - }, - iframeIsLoaded () { - this.iframeLoaded = true - } - }, - computed: { - embedUrl () { - switch (this.type) { - case 'youtube': - return `https://www.youtube.com/embed/${this.id}?autoplay=1` - case 'vimeo': - return `https://player.vimeo.com/video/${this.id}?autoplay=1` - default: - } - } - } -} diff --git a/core/modules/catalog/components/Search.ts b/core/modules/catalog/components/Search.ts deleted file mode 100644 index f22343331a..0000000000 --- a/core/modules/catalog/components/Search.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { mapState } from 'vuex' -import i18n from '@vue-storefront/i18n' -import onEscapePress from '@vue-storefront/core/mixins/onEscapePress' -import { prepareQuickSearchQuery } from '@vue-storefront/core/modules/catalog/queries/searchPanel' -import RootState from '@vue-storefront/core/types/RootState' -import { Logger } from '@vue-storefront/core/lib/logger' - -export const Search = { - name: 'SearchPanel', - data () { - return { - products: [], - search: '', - size: 18, - start: 0, - placeholder: i18n.t('Type what you are looking for...'), - emptyResults: false, - readMore: true, - componentLoaded: false - } - }, - mounted () { - this.search = localStorage.getItem(`shop/user/searchQuery`) || '' - - if (this.search) { - this.makeSearch(); - } - }, - beforeDestroy () { - localStorage.setItem(`shop/user/searchQuery`, this.search ? this.search : ''); - }, - methods: { - onEscapePress () { - this.closeSearchpanel() - }, - closeSearchpanel () { - this.$store.commit('ui/setSidebar', false) - this.$store.commit('ui/setMicrocart', false) - this.$store.commit('ui/setSearchpanel', false) - }, - buildSearchQuery (queryText) { - let searchQuery = prepareQuickSearchQuery(queryText) - return searchQuery - }, - async makeSearch () { - if (this.search !== '' && this.search !== undefined) { - let query = this.buildSearchQuery(this.search) - let startValue = 0; - this.start = startValue - this.readMore = true - try { - const { items } = await this.$store.dispatch('product/findProducts', { - query, - start: this.start, - size: this.size, - options: { - populateRequestCacheTags: false, - prefetchGroupProducts: false - } - }) - this.products = items - this.start = startValue + this.size - this.emptyResults = items.length < 1 - } catch (err) { - Logger.error(err, 'components-search')() - } - } else { - this.products = [] - this.emptyResults = 0 - } - }, - async seeMore () { - if (this.search !== '' && this.search !== undefined) { - let query = this.buildSearchQuery(this.search) - let startValue = this.start; - try { - const { items, total, start } = await this.$store.dispatch('product/findProducts', { - query, - start: startValue, - size: this.size, - options: { - populateRequestCacheTags: false, - prefetchGroupProducts: false - } - }) - let page = Math.floor(total / this.size) - let exceeed = total - this.size * page - if (start === total - exceeed) { - this.readMore = false - } - this.products = this.products.concat(items) - this.start = startValue + this.size - this.emptyResults = this.products.length < 1 - } catch (err) { - Logger.error(err, 'components-search')() - } - } else { - this.products = [] - this.emptyResults = 0 - } - } - }, - computed: { - items () { - return this.$store.state.search - }, - ...mapState({ - isOpen: (state: RootState) => state.ui.searchpanel - }) - }, - mixins: [onEscapePress] -} diff --git a/core/modules/catalog/events.ts b/core/modules/catalog/events.ts deleted file mode 100644 index 8c63658bf6..0000000000 --- a/core/modules/catalog/events.ts +++ /dev/null @@ -1,160 +0,0 @@ -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { PRODUCT_SET_CURRENT } from './store/product/mutation-types' -import omit from 'lodash-es/omit' -import config from 'config' -import i18n from '@vue-storefront/core/i18n'; -import { SearchQuery } from 'storefront-query-builder' -import { AsyncDataLoader } from '@vue-storefront/core/lib/async-data-loader'; -import { currentStoreView } from '@vue-storefront/core/lib/multistore'; -import { formatProductLink } from '@vue-storefront/core/modules/url/helpers'; -import { Logger } from '@vue-storefront/core/lib/logger'; -import { isServer } from '@vue-storefront/core/helpers'; -import { router } from '@vue-storefront/core/app' - -// Listeners moved from Product.js - -const prefixMutation = (mutationKey) => `product/${mutationKey}` - -export const productAfterPriceupdate = async (product, store) => { - if (store.getters['product/getCurrentProduct'] && product.sku === store.getters['product/getCurrentProduct'].sku) { - // join selected variant object to the store - await store.dispatch('product/setCurrent', omit(product, ['name'])) - } -} - -export const filterChangedProduct = async (filterOption, store, router) => { - EventBus.$emit('product-before-configure', { filterOption: filterOption, configuration: store.getters['product/getCurrentProductConfiguration'] }) - const currentProductConfiguration = store.getters['product/getCurrentProductConfiguration'] - const changedConfig = Object.assign({}, currentProductConfiguration, { [filterOption.attribute_code]: filterOption }) - let searchQuery = new SearchQuery() - searchQuery = searchQuery.applyFilter({ key: 'sku', value: { 'eq': store.getters['product/getCurrentProduct'].parentSku } }) - const { items: [newProductVariant] } = await store.dispatch('product/findProducts', { - query: searchQuery, - size: 1, - configuration: changedConfig, - options: { - fallbackToDefaultWhenNoAvailable: false, - setProductErrors: true, - assignProductConfiguration: true, - separateSelectedVariant: true - } - }, { root: true }) - const { configuration, selectedVariant, options, product_option } = newProductVariant - const routeProp = config.seo.useUrlDispatcher ? 'query' : 'params'; - - if (config.products.setFirstVarianAsDefaultInURL && selectedVariant) { - router.push({ [routeProp]: { childSku: selectedVariant.sku } }) - } - if (selectedVariant) { - const newProductConfiguration = Object.assign( - {}, - store.getters['product/getCurrentProduct'], - selectedVariant, - { configuration, options, product_option } - ) - await store.dispatch('product/setCurrent', newProductConfiguration) - EventBus.$emit('product-after-configure', { product: newProductConfiguration, configuration: configuration, selectedVariant: selectedVariant }) - - if (router?.currentRoute?.[routeProp]?.childSku) { - router.push( - { - [routeProp]: { - ...router.currentRoute[routeProp], - childSku: selectedVariant.sku - } - } - ) - } - return selectedVariant - } else { - store.dispatch('notification/spawnNotification', { - type: 'warning', - message: i18n.t( - 'No such configuration for the product. Please do choose another combination of attributes.' - ), - action1: { label: i18n.t('OK') } - }) - } -} - -export const productAfterCustomoptions = async (payload, store) => { - let priceDelta = 0 - let priceDeltaInclTax = 0 - const optionValues: any[] = Object.values(payload.optionValues) - optionValues.forEach(optionValue => { - if (optionValue && parseInt(optionValue.option_type_id) > 0) { - if (optionValue.price_type === 'fixed' && optionValue.price !== 0) { - priceDelta += optionValue.price - priceDeltaInclTax += optionValue.price - } - if (optionValue.price_type === 'percent' && optionValue.price !== 0) { - priceDelta += ((optionValue.price / 100) * store.getters['product/getCurrentProduct'].price) - priceDeltaInclTax += ((optionValue.price / 100) * store.getters['product/getCurrentProduct'].price_incl_tax) - } - } - }) - - store.commit(prefixMutation(PRODUCT_SET_CURRENT), Object.assign( - {}, - store.getters['product/getCurrentProduct'], - { - price: store.getters['product/getCurrentProduct'].price + priceDelta, - price_incl_tax: store.getters['product/getCurrentProduct'].price_incl_tax + priceDeltaInclTax - } - ), { root: true }) -} - -export const productAfterBundleoptions = async (payload, store) => { - let priceDelta = 0 - let priceDeltaInclTax = 0 - const optionValues: any[] = Object.values(payload.optionValues) - optionValues.forEach(optionValue => { - if (optionValue && optionValue.value && optionValue.product && parseInt(optionValue.qty) >= 0) { - priceDelta += optionValue.value.product.price * parseInt(optionValue.qty) - priceDeltaInclTax += optionValue.value.product.price_incl_tax * parseInt(optionValue.qty) - } - }) - if (priceDelta > 0) { - store.commit(prefixMutation(PRODUCT_SET_CURRENT), Object.assign( - {}, - store.getters['product/getCurrentProduct'], - { - price: priceDelta, - price_incl_tax: priceDeltaInclTax - } - ), { root: true }) - } -} - -export const onUserPricesRefreshed = async (store, router) => { - if (router.currentRoute.params.parentSku) { - await store.dispatch('product/reset', {}, { root: true }) - await store.dispatch('product/single', { - options: { - sku: router.currentRoute.params.parentSku, - childSku: router && router.currentRoute.params && router.currentRoute.params.childSku ? router.currentRoute.params.childSku : null - }, - skipCache: true - }, { root: true }) - } -} - -export const checkParentRedirection = (currentProduct, parentProduct) => { - if (parentProduct && parentProduct.id !== currentProduct.id && config.products.preventConfigurableChildrenDirectAccess) { - Logger.log('Redirecting to parent, configurable product', parentProduct.sku)() - parentProduct.parentSku = parentProduct.sku - parentProduct.sku = currentProduct.sku - const parentUrl = formatProductLink(parentProduct, currentStoreView().storeCode) - if (isServer) { - AsyncDataLoader.push({ - execute: async ({ context }) => { - if (context && !context.url.includes(parentUrl)) { - context.server.response.redirect(301, parentUrl) - } - } - }) - } else { - router.replace(parentUrl as string) - } - } -} diff --git a/core/modules/catalog/helpers/associatedProducts/buildQuery.ts b/core/modules/catalog/helpers/associatedProducts/buildQuery.ts deleted file mode 100644 index 5a409f6553..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/buildQuery.ts +++ /dev/null @@ -1,16 +0,0 @@ -import config from 'config' -import { SearchQuery } from 'storefront-query-builder' - -/** - * Creates simple query that will search product by skus list - */ -export default function buildQuery (skus: string[]): SearchQuery { - let productsQuery = new SearchQuery() - productsQuery = productsQuery - .applyFilter({ key: 'sku', value: { 'in': skus } }) - .applyFilter({ key: 'status', value: { 'in': [1] } }) - if (config.products.listOutOfStockProducts === false) { - productsQuery = productsQuery.applyFilter({ key: 'stock.is_in_stock', value: { 'eq': true } }) - } - return productsQuery -} diff --git a/core/modules/catalog/helpers/associatedProducts/getAttributesFromMetadata.ts b/core/modules/catalog/helpers/associatedProducts/getAttributesFromMetadata.ts deleted file mode 100644 index e44a424e12..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/getAttributesFromMetadata.ts +++ /dev/null @@ -1,11 +0,0 @@ -import config from 'config' -import Product from '@vue-storefront/core/modules/catalog/types/Product' - -/** - * Set associated attributes by meta data of product links - */ -export default async function getAttributesFromMetadata (context: any, products: Product[]) { - if (config.entities.attribute.loadByAttributeMetadata) { - context.dispatch('attribute/loadProductAttributes', { products, merge: true }, { root: true }) - } -} diff --git a/core/modules/catalog/helpers/associatedProducts/getBundleProductPrice.ts b/core/modules/catalog/helpers/associatedProducts/getBundleProductPrice.ts deleted file mode 100644 index 5ceb6b7386..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/getBundleProductPrice.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - getBundleOptionsValues, - getBundleOptionPrice, - getSelectedBundleOptions -} from '@vue-storefront/core/modules/catalog/helpers/bundleOptions' -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -export default function getBundleProductPrice (product: Product) { - const selectedBundleOptions = getSelectedBundleOptions(product) - const { price, priceInclTax } = getBundleOptionPrice( - getBundleOptionsValues(selectedBundleOptions, product.bundle_options) - ) - - return { price, priceInclTax } -} diff --git a/core/modules/catalog/helpers/associatedProducts/getGroupedProductPrice.ts b/core/modules/catalog/helpers/associatedProducts/getGroupedProductPrice.ts deleted file mode 100644 index ac50a373f3..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/getGroupedProductPrice.ts +++ /dev/null @@ -1,8 +0,0 @@ -import Product, { ProductLink } from '@vue-storefront/core/modules/catalog/types/Product'; -import { getProductLinkPrice } from './getProductLinkPrice'; - -export default function getGroupedProductPrice (product: Product) { - const productLinks: ProductLink[] = (product.product_links || []) - - return getProductLinkPrice(productLinks) -} diff --git a/core/modules/catalog/helpers/associatedProducts/getProductLinkPrice.ts b/core/modules/catalog/helpers/associatedProducts/getProductLinkPrice.ts deleted file mode 100644 index 34339f6908..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/getProductLinkPrice.ts +++ /dev/null @@ -1,36 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -interface BaseProductLink { - product?: Product, - qty?: number -} - -export const calculateProductLinkPrice = ({ price = 1, priceInclTax = 1, qty = 1 }) => { - const product = { - price: 0, - priceInclTax: 0 - } - const qtyNum = typeof qty === 'string' ? parseInt(qty) : qty - if (qtyNum >= 0) { - product.price += price * qtyNum - product.priceInclTax += priceInclTax * qtyNum - } - return product -} - -export const getProductLinkPrice = (productLinks: BaseProductLink[]) => productLinks - .map((productLink) => { - const product = productLink.product || { price: 1, price_incl_tax: 1, priceInclTax: 1 } - return calculateProductLinkPrice({ - price: product.price, - priceInclTax: product.price_incl_tax || product.priceInclTax, - qty: productLink.qty - }) - }) - .reduce( - (priceDelta, currentPriceDelta) => ({ - price: currentPriceDelta.price + priceDelta.price, - priceInclTax: currentPriceDelta.priceInclTax + priceDelta.priceInclTax - }), - { price: 0, priceInclTax: 0 } - ) diff --git a/core/modules/catalog/helpers/associatedProducts/index.ts b/core/modules/catalog/helpers/associatedProducts/index.ts deleted file mode 100644 index 5918ed1dfa..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import setGroupedProduct from './setGroupedProduct' -import setBundleProducts from './setBundleProducts' -import getAttributesFromMetadata from './getAttributesFromMetadata' - -export { - setGroupedProduct, - setBundleProducts, - getAttributesFromMetadata -} diff --git a/core/modules/catalog/helpers/associatedProducts/setBundleProducts.ts b/core/modules/catalog/helpers/associatedProducts/setBundleProducts.ts deleted file mode 100644 index aa836cf228..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/setBundleProducts.ts +++ /dev/null @@ -1,51 +0,0 @@ -import config from 'config' -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import buildQuery from './buildQuery' -import setProductLink from './setProductLink' -import getBundleProductPrice from './getBundleProductPrice' -import { isBundleProduct } from './..' -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' -import { catalogHooksExecutors } from './../../hooks' - -/** - * This function prepare all product_links for bundle products. - * It fetches products by sku. - */ -export default async function setBundleProducts (product: Product, { includeFields = null, excludeFields = null } = {}) { - if (isBundleProduct(product) && product.bundle_options) { - const skus = product.bundle_options - .map((bundleOption) => bundleOption.product_links.map((productLink) => productLink.sku)) - .reduce((acc, next) => acc.concat(next), []) - - const query = buildQuery(skus) - const { items } = await ProductService.getProducts({ - query, - excludeFields, - includeFields, - options: { - prefetchGroupProducts: false, - fallbackToDefaultWhenNoAvailable: false, - setProductErrors: false, - setConfigurableProductOptions: false, - assignProductConfiguration: false, - separateSelectedVariant: false - } - }) - - catalogHooksExecutors.afterSetBundleProducts(items) - - for (const bundleOption of product.bundle_options) { - for (const productLink of bundleOption.product_links) { - const associatedProduct = items.find((associatedProduct) => associatedProduct.sku === productLink.sku) - setProductLink(productLink, associatedProduct) - } - } - - if (config.products.calculateBundlePriceByOptions) { - const { price, priceInclTax } = getBundleProductPrice(product) - product.price = price - product.priceInclTax = priceInclTax - product.price_incl_tax = priceInclTax - } - } -} diff --git a/core/modules/catalog/helpers/associatedProducts/setGroupedProduct.ts b/core/modules/catalog/helpers/associatedProducts/setGroupedProduct.ts deleted file mode 100644 index c27e04edcd..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/setGroupedProduct.ts +++ /dev/null @@ -1,45 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import buildQuery from './buildQuery' -import setProductLink from './setProductLink' -import getGroupedProductPrice from './getGroupedProductPrice' -import { isGroupedProduct } from './..' -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' -import { catalogHooksExecutors } from './../../hooks' - -/** - * This function prepare all product_links for grouped products. - * It fetches products by sku. - */ -export default async function setGroupedProduct (product: Product, { includeFields = null, excludeFields = null } = {}) { - if (isGroupedProduct(product) && product.product_links) { - const productLinks = product.product_links.filter((productLink) => productLink.link_type === 'associated' && productLink.linked_product_type === 'simple') - const skus = productLinks.map((productLink) => productLink.linked_product_sku) - - const query = buildQuery(skus) - const { items } = await ProductService.getProducts({ - query, - excludeFields, - includeFields, - options: { - prefetchGroupProducts: false, - fallbackToDefaultWhenNoAvailable: false, - setProductErrors: false, - setConfigurableProductOptions: false, - assignProductConfiguration: false, - separateSelectedVariant: false - } - }) - - catalogHooksExecutors.afterSetGroupedProduct(items) - - for (const productLink of productLinks) { - const associatedProduct = items.find((associatedProduct) => associatedProduct.sku === productLink.linked_product_sku) - setProductLink(productLink, associatedProduct) - } - - const { price, priceInclTax } = getGroupedProductPrice(product) - product.price = price - product.priceInclTax = priceInclTax - product.price_incl_tax = priceInclTax - } -} diff --git a/core/modules/catalog/helpers/associatedProducts/setProductLink.ts b/core/modules/catalog/helpers/associatedProducts/setProductLink.ts deleted file mode 100644 index bb9b0da7f5..0000000000 --- a/core/modules/catalog/helpers/associatedProducts/setProductLink.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Product, { ProductLink } from '@vue-storefront/core/modules/catalog/types/Product'; -import { preConfigureProduct } from '../prepare'; -import { Logger } from '@vue-storefront/core/lib/logger'; -import { BundleOptionsProductLink } from '@vue-storefront/core/modules/catalog/types/BundleOption'; - -/** - * Set associated product to product link object - */ - -export default async function setProductLink (productLink: BundleOptionsProductLink | ProductLink, associatedProduct: Product) { - if (associatedProduct) { - productLink.product = preConfigureProduct(associatedProduct) - productLink.product.qty = Number((productLink as BundleOptionsProductLink).qty || '1') - } else { - Logger.error('Product not found', (productLink as ProductLink).linked_product_sku || productLink.sku)() - } -} diff --git a/core/modules/catalog/helpers/bundleOptions.ts b/core/modules/catalog/helpers/bundleOptions.ts deleted file mode 100644 index b54561390a..0000000000 --- a/core/modules/catalog/helpers/bundleOptions.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getProductLinkPrice } from './associatedProducts/getProductLinkPrice'; -import get from 'lodash-es/get' -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { BundleOption, BundleOptionsProductLink, SelectedBundleOption } from '@vue-storefront/core/modules/catalog/types/BundleOption'; - -export const getBundleOptionPrice = (bundleOptionValues: BundleOptionsProductLink[]) => getProductLinkPrice(bundleOptionValues) - -export const getBundleOptionsValues = (selectedBundleOptions: SelectedBundleOption[], allBundeOptions: BundleOption[]): BundleOptionsProductLink[] => selectedBundleOptions - .map(selectedBundleOption => { - const { - product_links: productLinks = [] - } = allBundeOptions.find(bundleOption => bundleOption.option_id === selectedBundleOption.option_id) || {} - const value = productLinks.find(productLink => String(productLink.id) === String(selectedBundleOption.option_selections[0])) - return { ...value, qty: selectedBundleOption.option_qty } - }) - -export const getSelectedBundleOptions = (product: Product): SelectedBundleOption[] => { - const selectedBundleOptions = Object.values(get(product, 'product_option.extension_attributes.bundle_options', {})) - if (selectedBundleOptions.length) { - return selectedBundleOptions as any as SelectedBundleOption[] - } - - // get default options - const allBundeOptions = product.bundle_options || [] - return allBundeOptions.map((bundleOption) => { - const productLinks = bundleOption.product_links || [] - const defaultLink = productLinks.find((productLink) => productLink.is_default) || productLinks[0] - const qty = (typeof defaultLink.qty === 'string' ? parseInt(defaultLink.qty) : defaultLink.qty) || 1 - return { - option_id: bundleOption.option_id, - option_qty: qty, - option_selections: [Number(defaultLink.id)] - } - }) -} diff --git a/core/modules/catalog/helpers/configure/configureProductAsync.ts b/core/modules/catalog/helpers/configure/configureProductAsync.ts deleted file mode 100644 index a1417bd204..0000000000 --- a/core/modules/catalog/helpers/configure/configureProductAsync.ts +++ /dev/null @@ -1,93 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import cloneDeep from 'lodash-es/cloneDeep' -import { getSelectedVariant, omitSelectedVariantFields } from '../variant'; -import { getProductConfiguration, setProductConfigurableOptions } from '../productOptions'; -import { filterOutUnavailableVariants } from '../stock'; -import { setGroupedProduct, setBundleProducts } from '../associatedProducts'; -import { hasConfigurableChildren } from './..' - -interface ConfigureProductAsyncParams { - product: Product, - configuration: any, - attribute: any, - options?: { - fallbackToDefaultWhenNoAvailable?: boolean, - setProductErrors?: boolean, - setConfigurableProductOptions?: boolean, - filterUnavailableVariants?: boolean, - assignProductConfiguration?: boolean, - separateSelectedVariant?: boolean, - prefetchGroupProducts?: boolean - }, - stockItems: any[], - excludeFields?: string[], - includeFields?: string[] -} - -/** - * This function configure product for 'configurable', 'bundle' or 'group' product. - */ -export default async function configureProductAsync ({ - product, - configuration, - attribute, - options: { - fallbackToDefaultWhenNoAvailable = true, - setProductErrors = true, - setConfigurableProductOptions = true, - filterUnavailableVariants = false, - assignProductConfiguration = false, - separateSelectedVariant = false, - prefetchGroupProducts = false - } = {}, - stockItems = [], - excludeFields, - includeFields -}: ConfigureProductAsyncParams) { - // it not only filter variants but also it apply stock object - if (filterUnavailableVariants) { - filterOutUnavailableVariants(product, stockItems) - } - - // setup bundle or group product. Product is filled with productLinks - if (prefetchGroupProducts) { - await setGroupedProduct(product, { includeFields, excludeFields }) - await setBundleProducts(product, { includeFields, excludeFields }) - } - - // setup configurable product - if (hasConfigurableChildren(product)) { - // we don't want to modify configuration object - let _configuration = cloneDeep(configuration) - - // find selected variant by configuration - let selectedVariant = getSelectedVariant(product, _configuration, { fallbackToDefaultWhenNoAvailable }) - - if (selectedVariant) { - // if there is selectedVariant we want to get configuration based on that variant - _configuration = getProductConfiguration({ product, selectedVariant, attribute }) - - // here we add product_options with selected configuration. It only applies to configurable product - setProductConfigurableOptions({ product, configuration: _configuration, setConfigurableProductOptions }) // set the custom options - - product.is_configured = true - - // remove props from variant that we don't want need to override in base product - selectedVariant = omitSelectedVariantFields(selectedVariant) - } - if (!selectedVariant && setProductErrors) { // can not find variant anyway, even the default one - product.errors.variants = 'No available product variants' - } - - const configuredProduct = { - ...product, - ...(assignProductConfiguration ? { configuration: _configuration } : {}) // we can need configuration as separate object - } - return { - ...configuredProduct, - ...(separateSelectedVariant ? { selectedVariant } : selectedVariant) // we can need selected variant as separate object - } - } - - return product -} diff --git a/core/modules/catalog/helpers/configure/configureProducts.ts b/core/modules/catalog/helpers/configure/configureProducts.ts deleted file mode 100644 index f8d0589a4a..0000000000 --- a/core/modules/catalog/helpers/configure/configureProducts.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { AttributesMetadata } from './../../types/Attribute'; -import { getStockItems } from '../stock'; -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import transformMetadataToAttributes from '../transformMetadataToAttributes'; -import configureProductAsync from './configureProductAsync'; - -interface ConfigureProductsParams { - products: Product[], - attributes_metadata: AttributesMetadata[], - configuration: any, - options?: { - fallbackToDefaultWhenNoAvailable?: boolean, - setProductErrors?: boolean, - setConfigurableProductOptions?: boolean, - filterUnavailableVariants?: boolean, - assignProductConfiguration?: boolean, - separateSelectedVariant?: boolean, - prefetchGroupProducts?: boolean - }, - excludeFields?: string[], - includeFields?: string[] -} - -/** - * Prepare all data needed to make product configuration. - * After common data is setup this function map through every product and configure it based on 'configuration' object - */ -export default async function configureProducts ({ - products, - attributes_metadata = [], - configuration = {}, - options = {}, - excludeFields = null, - includeFields = null -}: ConfigureProductsParams) { - const productAttributesMetadata = products.map((product) => product.attributes_metadata || []) - const attribute = transformMetadataToAttributes([attributes_metadata, ...productAttributesMetadata]) - const attributeStateFormat = { list_by_code: attribute.attrHashByCode, list_by_id: attribute.attrHashById } - - let stockItems = [] - if (options.filterUnavailableVariants) { - stockItems = await getStockItems(products) - } - - const configuredProducts = await Promise.all((products as Product[]).map(async (product) => { - const configuredProduct = await configureProductAsync({ - product, - configuration, - attribute: attributeStateFormat, - options: options, - stockItems, - excludeFields, - includeFields - }) - return configuredProduct as Product - })) - - return configuredProducts -} diff --git a/core/modules/catalog/helpers/configure/index.ts b/core/modules/catalog/helpers/configure/index.ts deleted file mode 100644 index 258ce96379..0000000000 --- a/core/modules/catalog/helpers/configure/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import configureProducts from './configureProducts' - -export { - configureProducts -} diff --git a/core/modules/catalog/helpers/createAttributesListQuery.ts b/core/modules/catalog/helpers/createAttributesListQuery.ts deleted file mode 100644 index e5fa9de967..0000000000 --- a/core/modules/catalog/helpers/createAttributesListQuery.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const createAttributesListQuery = ({ - filterValues, - filterField, - onlyDefinedByUser, - onlyVisible -}: { - filterValues: string[], - filterField: string, - onlyDefinedByUser: boolean, - onlyVisible: boolean -}): SearchQuery => { - let searchQuery = new SearchQuery() - - if (filterValues) { - searchQuery = searchQuery.applyFilter({ key: filterField, value: { 'in': filterValues } }) - } - if (onlyDefinedByUser) { - searchQuery = searchQuery.applyFilter({ key: 'is_user_defined', value: { 'in': [true] } }) - } - if (onlyVisible) { - searchQuery = searchQuery.applyFilter({ key: 'is_visible', value: { 'in': [true] } }) - } - - return searchQuery -} - -export default createAttributesListQuery diff --git a/core/modules/catalog/helpers/createCategoryListQuery.ts b/core/modules/catalog/helpers/createCategoryListQuery.ts deleted file mode 100644 index 5e6db47db2..0000000000 --- a/core/modules/catalog/helpers/createCategoryListQuery.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' -import { isServer } from '@vue-storefront/core/helpers' -import config from 'config' - -const createCategoryListQuery = ({ parent, level, key, value, onlyActive, onlyNotEmpty }) => { - let isCustomizedQuery = false - let searchQuery = new SearchQuery() - - if (parent) { - searchQuery = searchQuery.applyFilter({ key: 'parent_id', value: { 'eq': typeof parent === 'object' ? parent.id : parent } }) - isCustomizedQuery = true - } - - if (level !== null) { - searchQuery = searchQuery.applyFilter({ key: 'level', value: { 'eq': level } }) - if (level !== config.entities.category.categoriesDynamicPrefetchLevel && !isServer) { - isCustomizedQuery = true - } - } - - if (key !== null) { - if (Array.isArray(value)) { - searchQuery = searchQuery.applyFilter({ key: key, value: { 'in': value } }) - } else { - searchQuery = searchQuery.applyFilter({ key: key, value: { 'eq': value } }) - } - isCustomizedQuery = true - } - - if (onlyActive === true) { - searchQuery = searchQuery.applyFilter({ key: 'is_active', value: { 'eq': true } }) - } - - if (onlyNotEmpty === true) { - searchQuery = searchQuery.applyFilter({ key: 'product_count', value: { 'gt': 0 } }) - isCustomizedQuery = true - } - - return { searchQuery, isCustomizedQuery } -} - -export default createCategoryListQuery diff --git a/core/modules/catalog/helpers/customOption.ts b/core/modules/catalog/helpers/customOption.ts deleted file mode 100644 index 2ff7a837bb..0000000000 --- a/core/modules/catalog/helpers/customOption.ts +++ /dev/null @@ -1,77 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { CustomOption, OptionValue, InputValue, SelectedCustomOption } from '@vue-storefront/core/modules/catalog/types/CustomOption'; - -export const defaultCustomOptionValue = (customOption: CustomOption): InputValue => { - switch (customOption.type) { - case 'radio': { - return customOption.values && customOption.values.length ? customOption.values[0].option_type_id : 0 - } - case 'checkbox': { - return [] - } - default: { - return '' - } - } -} - -export const customOptionFieldName = (customOption: CustomOption): string => { - return 'customOption_' + customOption.option_id -} - -export const selectedCustomOptionValue = (optionType: string, optionValues: OptionValue[] = [], inputValue: InputValue): string => { - switch (optionType) { - case 'field': { - return inputValue as string - } - case 'radio': - case 'select': - case 'drop_down': { - const selectedValue = optionValues.find((value) => value.option_type_id === inputValue as number) - const optionTypeId = selectedValue && selectedValue.option_type_id - - return optionTypeId ? String(optionTypeId) : null - } - case 'checkbox': { - const checkboxOptionValues = inputValue as number[] || [] - - return optionValues.filter((value) => checkboxOptionValues.includes(value.option_type_id)) - .map((value) => value.option_type_id) - .join(',') || null - } - default: { - return null - } - } -} - -export const getCustomOptionValues = (selectedCustomOptions: SelectedCustomOption[], allCustomOptions: CustomOption[]): OptionValue[] => selectedCustomOptions - .filter(customOptionIds => customOptionIds.option_value) // remove null | undefined values - .map(customOptionIds => { - const { values = [] } = allCustomOptions.find( - customOption => String(customOption.option_id) === String(customOptionIds.option_id) // get all custom option values based on 'option_id' - ) - const customOptionValues = customOptionIds.option_value - .split(',') // split ids, because there can be '1,2' for checkbox - .map(optionValueId => values.find(value => String(value.option_type_id) === optionValueId)) // get custom option value based on selected option value id - .filter(Boolean) // remove falsy results - - return customOptionValues - }) - .reduce((allCustomOptionValues, customOptionValue) => allCustomOptionValues.concat(customOptionValue), []) // merge all values in one array - -export const getCustomOptionPriceDelta = (customOptionValues: OptionValue[], { price, priceInclTax, price_incl_tax }: Pick) => customOptionValues - .reduce((delta, customOptionValue) => { - if (customOptionValue.price_type === 'fixed' && customOptionValue.price !== 0) { - delta.price += customOptionValue.price - delta.priceInclTax += customOptionValue.price - } - if (customOptionValue.price_type === 'percent' && customOptionValue.price !== 0) { - delta.price += ((customOptionValue.price / 100) * price) - delta.priceInclTax += ((customOptionValue.price / 100) * (priceInclTax || price_incl_tax)) - } - return delta - }, { - price: 0, - priceInclTax: 0 - }) diff --git a/core/modules/catalog/helpers/deprecatedHelpers.ts b/core/modules/catalog/helpers/deprecatedHelpers.ts deleted file mode 100644 index 2917769e9e..0000000000 --- a/core/modules/catalog/helpers/deprecatedHelpers.ts +++ /dev/null @@ -1,145 +0,0 @@ -import Vue from 'vue' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import omit from 'lodash-es/omit' -import i18n from '@vue-storefront/i18n' -import { Logger } from '@vue-storefront/core/lib/logger' -import { optionLabel } from './optionLabel' -import { setProductConfigurableOptions } from './productOptions' -import config from 'config' -import { findConfigurableVariant } from './variant' -import { hasImage } from './' - -export function populateProductConfigurationAsync (context, { product, selectedVariant }) { - Logger.warn('deprecated, will be not used from 1.12')() - if (product.configurable_options) { - for (let option of product.configurable_options) { - let attribute_code - let attribute_label - if (option.attribute_code) { - attribute_code = option.attribute_code - attribute_label = option.label ? option.label : (option.frontend_label ? option.frontend_label : option.default_frontend_label) - } else { - if (option.attribute_id) { - let attr = context.rootState.attribute.list_by_id[option.attribute_id] - if (!attr) { - Logger.error('Wrong attribute given in configurable_options - can not find by attribute_id', option)() - continue - } else { - attribute_code = attr.attribute_code - attribute_label = attr.frontend_label ? attr.frontend_label : attr.default_frontend_label - } - } else { - Logger.error('Wrong attribute given in configurable_options - no attribute_code / attribute_id', option)() - } - } - let selectedOption = null - if (selectedVariant.custom_attributes) { - selectedOption = selectedVariant.custom_attributes.find((a) => { // this is without the "label" - return (a.attribute_code === attribute_code) - }) - } else { - selectedOption = { - attribute_code: attribute_code, - value: selectedVariant[attribute_code] - } - } - if (option.values && option.values.length) { - const selectedOptionMeta = option.values.find(ov => { return ov.value_index === selectedOption.value }) - if (selectedOptionMeta) { - selectedOption.label = selectedOptionMeta.label ? selectedOptionMeta.label : selectedOptionMeta.default_label - selectedOption.value_data = selectedOptionMeta.value_data - } - } - - const confVal = { - attribute_code: attribute_code, - id: selectedOption.value, - label: selectedOption.label ? selectedOption.label : /* if not set - find by attribute */optionLabel(context.rootState.attribute, { attributeKey: selectedOption.attribute_code, searchBy: 'code', optionId: selectedOption.value }) - } - Vue.set(context.state.current_configuration, attribute_code, confVal) - } - setProductConfigurableOptions({ - product, - configuration: context.state.current_configuration, - setConfigurableProductOptions: config.cart.setConfigurableProductOptions - }) // set the custom options - } - return selectedVariant -} - -export function configureProductAsync (context, { product, configuration, selectDefaultVariant = true, fallbackToDefaultWhenNoAvailable = true, setProductErorrs = false }) { - Logger.warn('deprecated, will be not used from 1.12')() - // use current product if product wasn't passed - if (product === null) product = context.getters.getCurrentProduct - const hasConfigurableChildren = (product.configurable_children && product.configurable_children.length > 0) - - if (hasConfigurableChildren) { - // handle custom_attributes for easier comparing in the future - product.configurable_children.forEach((child) => { - let customAttributesAsObject = {} - if (child.custom_attributes) { - child.custom_attributes.forEach((attr) => { - customAttributesAsObject[attr.attribute_code] = attr.value - }) - // add values from custom_attributes in a different form - Object.assign(child, customAttributesAsObject) - } - }) - // find selected variant - let desiredProductFound = false - let selectedVariant = findConfigurableVariant({ product, configuration, availabilityCheck: true }) - if (!selectedVariant) { - if (fallbackToDefaultWhenNoAvailable) { - selectedVariant = findConfigurableVariant({ product, selectDefaultChildren: true, availabilityCheck: true }) // return first available child - desiredProductFound = false - } else { - desiredProductFound = false - } - } else { - desiredProductFound = true - } - - if (selectedVariant) { - if (!desiredProductFound && selectDefaultVariant /** don't change the state when no selectDefaultVariant is set */) { // update the configuration - populateProductConfigurationAsync(context, { product: product, selectedVariant: selectedVariant }) - configuration = context.state.current_configuration - } - if (setProductErorrs) { - product.errors = {} // clear the product errors - } - product.is_configured = true - - if (config.cart.setConfigurableProductOptions && !selectDefaultVariant && !(Object.keys(configuration).length === 1 && configuration.sku)) { - // the condition above: if selectDefaultVariant - then "setCurrent" is seeting the configurable options; if configuration = { sku: '' } -> this is a special case when not configuring the product but just searching by sku - setProductConfigurableOptions({ - product, - configuration, - setConfigurableProductOptions: config.cart.setConfigurableProductOptions - }) // set the custom options - }/* else { - Logger.debug('Skipping configurable options setup', configuration)() - } */ - const fieldsToOmit = ['name'] - if (!hasImage(selectedVariant)) fieldsToOmit.push('image') - selectedVariant = omit(selectedVariant, fieldsToOmit) // We need to send the parent SKU to the Magento cart sync but use the child SKU internally in this case - // use chosen variant for the current product - if (selectDefaultVariant) { - context.dispatch('setCurrent', selectedVariant) - } - EventBus.$emit('product-after-configure', { product: product, configuration: configuration, selectedVariant: selectedVariant }) - } - if (!selectedVariant && setProductErorrs) { // can not find variant anyway, even the default one - product.errors.variants = i18n.t('No available product variants') - if (selectDefaultVariant) { - context.dispatch('setCurrent', product) // without the configuration - } - } - return selectedVariant - } else { - if (fallbackToDefaultWhenNoAvailable) { - return product - } else { - return null - } - } -} diff --git a/core/modules/catalog/helpers/filterAttributes.ts b/core/modules/catalog/helpers/filterAttributes.ts deleted file mode 100644 index ff3b26013b..0000000000 --- a/core/modules/catalog/helpers/filterAttributes.ts +++ /dev/null @@ -1,30 +0,0 @@ -import config from 'config' - -export default function filterAttributes ({ - filterValues, - filterField, - blacklist, - idsList, - codesList -}: { - filterValues: string[], - filterField: string, - blacklist: string[], - idsList: any, - codesList: any -}) { - return filterValues.filter(fv => { - if (fv.indexOf('.') >= 0) { - return false - } - if (blacklist !== null && blacklist.includes(fv)) { - return false - } - if (filterField === 'attribute_id') { - return (typeof idsList[fv] === 'undefined' || idsList[fv] === null) - } - if (filterField === 'attribute_code') { - return (typeof codesList[fv] === 'undefined' || codesList[fv] === null) - } - }) -} diff --git a/core/modules/catalog/helpers/filters.ts b/core/modules/catalog/helpers/filters.ts deleted file mode 100644 index 000157ec72..0000000000 --- a/core/modules/catalog/helpers/filters.ts +++ /dev/null @@ -1,37 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import { ProductConfiguration } from '@vue-storefront/core/modules/catalog/types/ProductConfiguration' - -const getAvailableFiltersByProduct = (product: Product) => { - let filtersMap = {} - if (product && product.configurable_options) { - product.configurable_options.forEach(configurableOption => { - const type = configurableOption.attribute_code - const filterVariants = configurableOption.values.map(({ value_index, label }) => { - return { id: value_index, label, type } - }) - filtersMap[type] = filterVariants - }) - } - return filtersMap -} - -const getSelectedFiltersByProduct = (product: Product, configuration: ProductConfiguration) => { - if (!configuration) { - return null - } - - let selectedFilters = {} - if (configuration && product) { - Object.keys(configuration).map(filterType => { - const filter = configuration[filterType] - selectedFilters[filterType] = { - id: filter.id, - label: filter.label, - type: filterType - } - }) - } - return selectedFilters -} - -export { getAvailableFiltersByProduct, getSelectedFiltersByProduct } diff --git a/core/modules/catalog/helpers/getProductGallery.ts b/core/modules/catalog/helpers/getProductGallery.ts deleted file mode 100644 index 87709f7f23..0000000000 --- a/core/modules/catalog/helpers/getProductGallery.ts +++ /dev/null @@ -1,21 +0,0 @@ -import config from 'config' -import { - getMediaGallery, - configurableChildrenImages, - attributeImages -} from './' -import uniqBy from 'lodash-es/uniqBy' -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -export default function getProductGallery (product: Product) { - if (product.type_id === 'configurable' && product.hasOwnProperty('configurable_children')) { - if (!config.products.gallery.mergeConfigurableChildren && product.is_configured) { - return attributeImages(product) - } - } - - const productGallery = uniqBy(configurableChildrenImages(product).concat(getMediaGallery(product)), 'src') - .filter(f => f.src && f.src !== config.images.productPlaceholder) - - return productGallery -} diff --git a/core/modules/catalog/helpers/index.ts b/core/modules/catalog/helpers/index.ts deleted file mode 100644 index 5eeedb7c20..0000000000 --- a/core/modules/catalog/helpers/index.ts +++ /dev/null @@ -1,148 +0,0 @@ -import Vue from 'vue' -// TODO: Remove this dependency -import { optionLabel } from './optionLabel' -import { getThumbnailPath } from '@vue-storefront/core/helpers' -import config from 'config' -import registerProductsMapping from './registerProductsMapping' -import getProductGallery from './getProductGallery' -import { findConfigurableVariant, isOptionAvailable } from './variant' -import { filterOutUnavailableVariants } from './stock' -import { doPlatformPricesSync } from './price' -import { setProductConfigurableOptions } from './productOptions' - -/** Below helpers are not used from 1.12 and can be removed to reduce bundle */ -import { populateProductConfigurationAsync, configureProductAsync } from './deprecatedHelpers' -export { - populateProductConfigurationAsync, - configureProductAsync -} -/***/ - -export { - registerProductsMapping, - getProductGallery, - optionLabel, - findConfigurableVariant as findConfigurableChildAsync, - isOptionAvailable as isOptionAvailableAsync, - filterOutUnavailableVariants, - doPlatformPricesSync, - setProductConfigurableOptions as setConfigurableProductOptionsAsync -} - -export const hasConfigurableChildren = (product) => product && product.configurable_children && product.configurable_children.length -export const isGroupedProduct = (product) => product.type_id === 'grouped' -export const isBundleProduct = (product) => product.type_id === 'bundle' - -/** - * check if object have an image - */ -export const hasImage = (product) => product && product.image && product.image !== 'no_selection' -/** - * check if one of the configuableChildren has an image - */ -export const childHasImage = (children = []) => children.some(hasImage) - -function _prepareProductOption (product) { - let product_option = { - extension_attributes: { - custom_options: [], - configurable_item_options: [], - bundle_options: [] - } - } - /* if (product.product_option) { - product_option = product.product_option - } */ - return product_option -} - -export function setCustomProductOptionsAsync (context, { product, customOptions }) { - const productOption = _prepareProductOption(product) - productOption.extension_attributes.custom_options = customOptions - return productOption -} - -export function setBundleProductOptionsAsync (context, { product, bundleOptions }) { - const productOption = _prepareProductOption(product) - productOption.extension_attributes.bundle_options = bundleOptions - return productOption -} - -/** - * Get media Gallery images from product - */ - -export function getMediaGallery (product) { - let mediaGallery = [] - if (product.media_gallery) { - for (let mediaItem of product.media_gallery) { - if (mediaItem.image) { - let video = mediaItem.vid - - if (video && video.video_id) { - video.id = video.video_id - delete video.video_id - } - - mediaGallery.push({ - 'src': getThumbnailPath(mediaItem.image, config.products.gallery.width, config.products.gallery.height), - 'loading': getThumbnailPath(mediaItem.image, config.products.thumbnails.width, config.products.thumbnails.height), - 'error': getThumbnailPath(mediaItem.image, config.products.thumbnails.width, config.products.thumbnails.height), - 'video': video - }) - } - } - } - return mediaGallery -} - -/** - * Get images from configured attribute images - */ -export function attributeImages (product) { - let attributeImages = [] - if (config.products.gallery.imageAttributes) { - for (let attribute of config.products.gallery.imageAttributes) { - if (product[attribute] && product[attribute] !== 'no_selection') { - attributeImages.push({ - 'src': getThumbnailPath(product[attribute], config.products.gallery.width, config.products.gallery.height), - 'loading': getThumbnailPath(product[attribute], 310, 300), - 'error': getThumbnailPath(product[attribute], 310, 300) - }) - } - } - } - return attributeImages -} -/** - * Get configurable_children images from product if any - * otherwise get attribute images - */ - -export function configurableChildrenImages (product) { - let configurableChildrenImages = [] - if (childHasImage(product.configurable_children)) { - let configurableAttributes = product.configurable_options.map(option => option.attribute_code) - configurableChildrenImages = product.configurable_children.map(child => - ({ - 'src': getThumbnailPath((!hasImage(child) ? product.image : child.image), config.products.gallery.width, config.products.gallery.height), - 'loading': getThumbnailPath((!hasImage(child) ? product.image : child.image), config.products.thumbnails.width, config.products.thumbnails.height), - 'id': configurableAttributes.reduce((result, attribute) => { - result[attribute] = child[attribute] - return result - }, {}) - }) - ) - } else { - configurableChildrenImages = attributeImages(product) - } - return configurableChildrenImages -} - -export const setRequestCacheTags = ({ products = [] }) => { - if (Vue.prototype.$cacheTags) { - products.forEach((product) => { - Vue.prototype.$cacheTags.add(`P${product.id}`); - }) - } -} diff --git a/core/modules/catalog/helpers/optionLabel.ts b/core/modules/catalog/helpers/optionLabel.ts deleted file mode 100644 index d169855152..0000000000 --- a/core/modules/catalog/helpers/optionLabel.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Helper method for getting attribute name - TODO: to be moved to external/shared helper - * - * @param {String} attributeCode - * @param {String} optionId - value to get label for - */ -import toString from 'lodash-es/toString' -import get from 'lodash-es/get' - -export function optionLabel (state, { attributeKey, searchBy = 'code', optionId }) { - if (!state.labels) { - state.labels = {} - } - - // check cached attribute - const attrCache = get(state, `labels.${attributeKey}.${optionId}`, null) - if (attrCache) { - return attrCache - } - - let attr = state['list_by_' + searchBy][attributeKey] - if (attr) { - let opt = attr.options.find((op) => toString(op.value) === toString(optionId)) - - if (opt) { - if (!state.labels[attributeKey]) { - state.labels[attributeKey] = {} - } - state.labels[attributeKey][optionId] = opt.label - return opt ? opt.label : optionId - } else { - return optionId - } - } else { - return optionId - } -} diff --git a/core/modules/catalog/helpers/prefetchCachedAttributes.ts b/core/modules/catalog/helpers/prefetchCachedAttributes.ts deleted file mode 100644 index 6a40b10cf7..0000000000 --- a/core/modules/catalog/helpers/prefetchCachedAttributes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { entityKeyName } from '@vue-storefront/core/lib/store/entities' -import config from 'config' - -async function prefetchCachedAttributes (filterField, filterValues) { - if (!config.attributes || !config.attributes.disablePersistentAttributesCache) { - const attrCollection = StorageManager.get('attributes') - const cachedAttributes = filterValues.map( - async filterValue => attrCollection.getItem(entityKeyName(filterField, String(filterValue).toLowerCase())) - ) - return Promise.all(cachedAttributes) - } -} - -export { prefetchCachedAttributes } diff --git a/core/modules/catalog/helpers/prepare/index.ts b/core/modules/catalog/helpers/prepare/index.ts deleted file mode 100644 index 62417fd11e..0000000000 --- a/core/modules/catalog/helpers/prepare/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import setDefaultQty from './setDefaultQty'; -import setDefaultObjects from './setDefaultObjects'; -import setParentSku from './setParentSku'; -import setParentId from './setParentId'; -import setCustomAttributesForChild from './setCustomAttributesForChild'; -import setDefaultProductOptions from './setDefaultProductOptions'; - -/** - * Apply base modification to product, after those modification we can store product in cache. - */ -function preConfigureProduct (product) { - // base product modifications - setDefaultQty(product) - setDefaultObjects(product) - setParentSku(product) - setParentId(product) - setCustomAttributesForChild(product) - setDefaultProductOptions(product) - - return product; -} - -/** - * Apply base modification to product list, after those modification we can store product in cache. - */ -function prepareProducts (products) { - const preparedProducts = products.map(preConfigureProduct) - - return preparedProducts -} - -export { - prepareProducts, - preConfigureProduct -} diff --git a/core/modules/catalog/helpers/prepare/setCustomAttributesForChild.ts b/core/modules/catalog/helpers/prepare/setCustomAttributesForChild.ts deleted file mode 100644 index 73ba3a7be6..0000000000 --- a/core/modules/catalog/helpers/prepare/setCustomAttributesForChild.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { hasConfigurableChildren } from '../'; -/** - * Fill custom attributes for every configurable child - */ -export default function setCustomAttributesForChild (product: Product) { - if (!hasConfigurableChildren(product)) return - // handle custom_attributes for easier comparing in the future - product.configurable_children.forEach((child) => { - let customAttributesAsObject = {} - if (child.custom_attributes) { - child.custom_attributes.forEach((attr) => { - customAttributesAsObject[attr.attribute_code] = attr.value - }) - // add values from custom_attributes in a different form - Object.assign(child, customAttributesAsObject) - } - }) -} diff --git a/core/modules/catalog/helpers/prepare/setDefaultObjects.ts b/core/modules/catalog/helpers/prepare/setDefaultObjects.ts deleted file mode 100644 index 00fc289ca1..0000000000 --- a/core/modules/catalog/helpers/prepare/setDefaultObjects.ts +++ /dev/null @@ -1,8 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -/** - * Default object that are used in vsf - */ -export default function setDefaultObjects (product: Product) { - product.errors = {}; // this is an object to store validation result for custom options and others - product.info = {}; -} diff --git a/core/modules/catalog/helpers/prepare/setDefaultProductOptions.ts b/core/modules/catalog/helpers/prepare/setDefaultProductOptions.ts deleted file mode 100644 index 22ef357177..0000000000 --- a/core/modules/catalog/helpers/prepare/setDefaultProductOptions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -/** - * Init product_option, needed to next configuration step - */ -export default function setDefaultProductOptions (product: Product) { - if (product.product_option) return - product.product_option = { - extension_attributes: { - custom_options: [], - configurable_item_options: [], - bundle_options: [] - } - } -} diff --git a/core/modules/catalog/helpers/prepare/setDefaultQty.ts b/core/modules/catalog/helpers/prepare/setDefaultQty.ts deleted file mode 100644 index bd3d27321e..0000000000 --- a/core/modules/catalog/helpers/prepare/setDefaultQty.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -/** - * set product quantity to 1 - */ -export default function setDefaultQty (product: Product) { - if (!product.qty) { - product.qty = 1 - } -} diff --git a/core/modules/catalog/helpers/prepare/setParentId.ts b/core/modules/catalog/helpers/prepare/setParentId.ts deleted file mode 100644 index 169fea5844..0000000000 --- a/core/modules/catalog/helpers/prepare/setParentId.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -/** - * set parent id, this is needed, because in configuration process we will override id by configurable_children.id - */ -export default function setParentId (product: Product) { - if (!product.parentId) { - product.parentId = product.id - } -} diff --git a/core/modules/catalog/helpers/prepare/setParentSku.ts b/core/modules/catalog/helpers/prepare/setParentSku.ts deleted file mode 100644 index d0c0038c0f..0000000000 --- a/core/modules/catalog/helpers/prepare/setParentSku.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -/** - * set parent sku, this is needed, because in configuration process we will override sku by configurable_children.sku - */ -export default function setParentSku (product: Product) { - if (!product.parentSku) { - product.parentSku = product.sku - } -} diff --git a/core/modules/catalog/helpers/price/doPlatformPricesSync.ts b/core/modules/catalog/helpers/price/doPlatformPricesSync.ts deleted file mode 100644 index 2c9556ad45..0000000000 --- a/core/modules/catalog/helpers/price/doPlatformPricesSync.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers' -import config from 'config' -import flattenDeep from 'lodash-es/flattenDeep' -import union from 'lodash-es/union' -import { Logger } from '@vue-storefront/core/lib/logger' -import rootStore from '@vue-storefront/core/store' -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' -import syncProductPrice from './syncProductPrice' - -/** - * Synchronize / override prices got from ElasticSearch with current one's from Magento2 or other platform backend - * @param {Array} products - */ -export default function doPlatformPricesSync (products) { - return new Promise(async (resolve, reject) => { - if (config.products.alwaysSyncPlatformPricesOver) { - if (config.products.clearPricesBeforePlatformSync) { - for (let product of products) { // clear out the prices as we need to sync them with Magento - product.price_incl_tax = null - product.original_price_incl_tax = null - product.special_price_incl_tax = null - - product.special_price = null - product.price = null - product.original_price = null - - product.price_tax = null - product.special_price_tax = null - product.original_price_tax = null - - /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */ - product.priceInclTax = product.price_incl_tax - product.priceTax = product.price_tax - product.originalPrice = product.original_price - product.originalPriceInclTax = product.original_price_incl_tax - product.originalPriceTax = product.original_price_tax - product.specialPriceInclTax = product.special_price_incl_tax - product.specialPriceTax = product.special_price_tax - /** END */ - - if (product.configurable_children) { - for (let sc of product.configurable_children) { - sc.price_incl_tax = null - sc.original_price_incl_tax = null - sc.special_price_incl_tax = null - - sc.special_price = null - sc.price = null - sc.original_price = null - - sc.price_tax = null - sc.special_price_tax = null - sc.original_price_tax = null - - /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */ - sc.priceInclTax = sc.price_incl_tax - sc.priceTax = sc.price_tax - sc.originalPrice = sc.original_price - sc.originalPriceInclTax = sc.original_price_incl_tax - sc.originalPriceTax = sc.original_price_tax - sc.specialPriceInclTax = sc.special_price_incl_tax - sc.specialPriceTax = sc.special_price_tax - /** END */ - } - } - } - } - - let skus = products.map((p) => { return p.sku }) - - if (products.length === 1) { // single product - download child data - const childSkus = flattenDeep(products.map((p) => { return (p.configurable_children) ? p.configurable_children.map((cc) => { return cc.sku }) : null })) - skus = union(skus, childSkus) - } - if (skus && skus.length > 0) { - Logger.log('Starting platform prices sync for', skus) // TODO: add option for syncro and non syncro return() - const { items } = await ProductService.getProductRenderList({ - skus, - isUserGroupedTaxActive: rootStore.getters['tax/getIsUserGroupedTaxActive'], - userGroupId: rootStore.getters['tax/getUserTaxGroupId'], - token: rootStore.getters['user/getToken'] - }) - if (items) { - for (let product of products) { - const backProduct = items.find((itm) => itm.id === product.id) - if (backProduct) { - product.price_is_current = true // in case we're syncing up the prices we should mark if we do have current or not - product.price_refreshed_at = new Date() - product = syncProductPrice(product, backProduct) - - if (product.configurable_children) { - for (let configurableChild of product.configurable_children) { - const backProductChild = items.find((itm) => itm.id === configurableChild.id) - if (backProductChild) { - configurableChild = syncProductPrice(configurableChild, backProductChild) - } - } - } - // TODO: shall we update local storage here for the main product? - } - } - } - resolve(products) - } else { // empty list of products - resolve(products) - } - if (!config.products.waitForPlatformSync && !isServer) { - Logger.log('Returning products, the prices yet to come from backend!')() - for (let product of products) { - product.price_is_current = false // in case we're syncing up the prices we should mark if we do have current or not - product.price_refreshed_at = null - } - resolve(products) - } - } else { - resolve(products) - } - }) -} diff --git a/core/modules/catalog/helpers/price/index.ts b/core/modules/catalog/helpers/price/index.ts deleted file mode 100644 index 93dc819cc5..0000000000 --- a/core/modules/catalog/helpers/price/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import doPlatformPricesSync from './doPlatformPricesSync' - -export { - doPlatformPricesSync -} diff --git a/core/modules/catalog/helpers/price/syncProductPrice.ts b/core/modules/catalog/helpers/price/syncProductPrice.ts deleted file mode 100644 index c36d516311..0000000000 --- a/core/modules/catalog/helpers/price/syncProductPrice.ts +++ /dev/null @@ -1,34 +0,0 @@ -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' - -export default function syncProductPrice (product, backProduct) { // TODO: we probably need to update the Net prices here as well - product.sgn = backProduct.sgn // copy the signature for the modified price - product.price_incl_tax = backProduct.price_info.final_price - product.original_price_incl_tax = backProduct.price_info.regular_price - product.special_price_incl_tax = backProduct.price_info.special_price - - product.special_price = backProduct.price_info.extension_attributes.tax_adjustments.special_price - product.price = backProduct.price_info.extension_attributes.tax_adjustments.final_price - product.original_price = backProduct.price_info.extension_attributes.tax_adjustments.regular_price - - product.price_tax = product.price_incl_tax - product.price - product.special_price_tax = product.special_price_incl_tax - product.special_price - product.original_price_tax = product.original_price_incl_tax - product.original_price - - if (product.price_incl_tax >= product.original_price_incl_tax) { - product.special_price_incl_tax = 0 - product.special_price = 0 - } - - /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */ - product.priceInclTax = product.price_incl_tax - product.priceTax = product.price_tax - product.originalPrice = product.original_price - product.originalPriceInclTax = product.original_price_incl_tax - product.originalPriceTax = product.original_price_tax - product.specialPriceInclTax = product.special_price_incl_tax - product.specialPriceTax = product.special_price_tax - /** END */ - EventBus.$emit('product-after-priceupdate', product) - // Logger.log(product.sku, product, backProduct)() - return product -} diff --git a/core/modules/catalog/helpers/productOptions/getAllProductConfigurations.ts b/core/modules/catalog/helpers/productOptions/getAllProductConfigurations.ts deleted file mode 100644 index 2f50e8e7ff..0000000000 --- a/core/modules/catalog/helpers/productOptions/getAllProductConfigurations.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ProductOptions } from '@vue-storefront/core/modules/catalog/types/Product'; - -/** - * It returns all available options for configurable product - */ -export default function getAllProductConfigurations ({ configurableOptions, configuration }): ProductOptions { - const product_option: ProductOptions = { - extension_attributes: { - custom_options: [], - configurable_item_options: [], - bundle_options: [] - } - } - /* eslint camelcase: "off" */ - product_option.extension_attributes.configurable_item_options = Object.keys(configuration) - .map((key) => configuration[key]) - .filter((configOption) => - configOption && - configOption.attribute_code && - configOption.attribute_code !== 'price' - ) - .map((configOption) => ({ - configOption, - productOption: configurableOptions.find((productConfigOption) => productConfigOption.attribute_code === configOption.attribute_code) - })) - .filter(({ productOption }) => productOption) - .map(({ configOption, productOption }) => ({ - option_id: String(productOption.attribute_id), - option_value: String(configOption.id), - label: productOption.label || configOption.attribute_code, - value: configOption.label - })) - - return product_option -} diff --git a/core/modules/catalog/helpers/productOptions/getAttributeCode.ts b/core/modules/catalog/helpers/productOptions/getAttributeCode.ts deleted file mode 100644 index 1362630954..0000000000 --- a/core/modules/catalog/helpers/productOptions/getAttributeCode.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Returns attribute_code for product option - */ -export default function getAttributeCode (option, attribute): string { - const attribute_code = option.attribute_code - ? option.attribute_code - : option.attribute_id && (attribute.list_by_id[option.attribute_id] || {}).attribute_code - return attribute_code || option.label.toLowerCase() -} diff --git a/core/modules/catalog/helpers/productOptions/getInternalOptionsFormat.ts b/core/modules/catalog/helpers/productOptions/getInternalOptionsFormat.ts deleted file mode 100644 index 407e0d0a50..0000000000 --- a/core/modules/catalog/helpers/productOptions/getInternalOptionsFormat.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Returns internal format for product options - */ -export default function getInternalOptionsFormat (productOption) { - return productOption.extension_attributes.configurable_item_options - .map(({ label, value }) => ({ label, value })) -} diff --git a/core/modules/catalog/helpers/productOptions/getProductConfiguration.ts b/core/modules/catalog/helpers/productOptions/getProductConfiguration.ts deleted file mode 100644 index 55c8e6ea91..0000000000 --- a/core/modules/catalog/helpers/productOptions/getProductConfiguration.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { optionLabel } from '@vue-storefront/core/modules/catalog/helpers'; -import getAttributeCode from './getAttributeCode' -import getSelectedOption from './getSelectedOption' - -/** - * Returns configuration based on selected variant. Only applies to configurable product - */ -export default function getProductConfiguration ({ product, selectedVariant, attribute }) { - const currentProductOption = {} - const configurableOptions = product.configurable_options || [] - for (const option of configurableOptions) { - const attributeCode = getAttributeCode(option, attribute) - const selectedOption = getSelectedOption(selectedVariant, attributeCode, option) - const label = selectedOption.label - ? selectedOption.label - : optionLabel(attribute, { - attributeKey: selectedOption.attribute_code, - searchBy: 'code', - optionId: selectedOption.value - }) - currentProductOption[attributeCode] = { - attribute_code: attributeCode, - id: String(selectedOption.value), - label: label - } - } - return currentProductOption -} diff --git a/core/modules/catalog/helpers/productOptions/getProductConfigurationOptions.ts b/core/modules/catalog/helpers/productOptions/getProductConfigurationOptions.ts deleted file mode 100644 index 93a0adf456..0000000000 --- a/core/modules/catalog/helpers/productOptions/getProductConfigurationOptions.ts +++ /dev/null @@ -1,27 +0,0 @@ -import getAttributeCode from './getAttributeCode' -import trim from 'lodash-es/trim' -import { optionLabel } from '@vue-storefront/core/modules/catalog/helpers'; - -export default function getProductConfigurationOptions ({ product, attribute }) { - const productOptions = {} - const configurableOptions = product.configurable_options || [] - for (let option of configurableOptions) { - const attributeCode = getAttributeCode(option, attribute) - const productOptionValues = option.values - .map((optionValue) => ({ - label: optionValue.label - ? optionValue.label - : optionLabel(attribute, { - attributeKey: option.attribute_id, - searchBy: 'id', - optionId: optionValue.value_index - }), - id: String(optionValue.value_index), - attribute_code: option.attribute_code - })) - .filter((optionValue) => trim(optionValue.label) !== '') - - productOptions[attributeCode] = productOptionValues - } - return productOptions -} diff --git a/core/modules/catalog/helpers/productOptions/getSelectedOption.ts b/core/modules/catalog/helpers/productOptions/getSelectedOption.ts deleted file mode 100644 index 8bf5bd12f2..0000000000 --- a/core/modules/catalog/helpers/productOptions/getSelectedOption.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Returns single option for configurable product based on attribute code - */ -export default function getSelectedOption (selectedVariant, attributeCode, option) { - let selectedOption = (selectedVariant.custom_attributes || []).find((a) => a.attribute_code === attributeCode) - selectedOption = selectedOption || { - attribute_code: attributeCode, - value: selectedVariant[attributeCode] - } - if (option.values && option.values.length) { - const selectedOptionMeta = option.values.find(ov => String(ov.value_index) === String(selectedOption.value)) - if (selectedOptionMeta) { - selectedOption.label = selectedOptionMeta.label - ? selectedOptionMeta.label - : selectedOptionMeta.default_label - } - } - return selectedOption -} diff --git a/core/modules/catalog/helpers/productOptions/index.ts b/core/modules/catalog/helpers/productOptions/index.ts deleted file mode 100644 index 6370d77c60..0000000000 --- a/core/modules/catalog/helpers/productOptions/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import setProductConfigurableOptions from './setProductConfigurableOptions' -import getProductConfiguration from './getProductConfiguration' -import getProductConfigurationOptions from './getProductConfigurationOptions' - -export { - setProductConfigurableOptions, - getProductConfiguration, - getProductConfigurationOptions -} diff --git a/core/modules/catalog/helpers/productOptions/omitInternalOptionsFormat.ts b/core/modules/catalog/helpers/productOptions/omitInternalOptionsFormat.ts deleted file mode 100644 index b2098e3b9c..0000000000 --- a/core/modules/catalog/helpers/productOptions/omitInternalOptionsFormat.ts +++ /dev/null @@ -1,9 +0,0 @@ -import omit from 'lodash-es/omit' - -/** - * Omit props that is not needed for product_option - */ -export default function omitInternalOptionsFormat (productOption) { - productOption.extension_attributes.configurable_item_options = productOption.extension_attributes.configurable_item_options - .map((option) => omit(option, ['label', 'value'])) -} diff --git a/core/modules/catalog/helpers/productOptions/setProductConfigurableOptions.ts b/core/modules/catalog/helpers/productOptions/setProductConfigurableOptions.ts deleted file mode 100644 index b71c226aba..0000000000 --- a/core/modules/catalog/helpers/productOptions/setProductConfigurableOptions.ts +++ /dev/null @@ -1,23 +0,0 @@ -import getAllProductConfigurations from './getAllProductConfigurations' -import getInternalOptionsFormat from './getInternalOptionsFormat' -import omitInternalOptionsFormat from './omitInternalOptionsFormat' - -/** - * set 'product_option' and 'options' based on selected configuration - */ -export default function setProductConfigurableOptions ({ product, configuration, setConfigurableProductOptions }) { - // return if there is no 'setConfigurableProductOptions' option - if (!setConfigurableProductOptions) return - - const configurableOptions = product.configurable_options - - if (!configurableOptions) return - - const productOptions = getAllProductConfigurations({ configurableOptions, configuration }) - - product.options = getInternalOptionsFormat(productOptions) - - omitInternalOptionsFormat(productOptions) - - product.product_option = productOptions -} diff --git a/core/modules/catalog/helpers/reduceAttributesLists.ts b/core/modules/catalog/helpers/reduceAttributesLists.ts deleted file mode 100644 index 3662224e62..0000000000 --- a/core/modules/catalog/helpers/reduceAttributesLists.ts +++ /dev/null @@ -1,26 +0,0 @@ -import Attribute from '@vue-storefront/core/modules/catalog/types/Attribute' - -const reduceAttributes = (prev, curr) => { - if (curr) { - prev.attrHashByCode[curr.attribute_code] = curr - prev.attrHashById[curr.attribute_id] = curr - } - - return prev -} - -const reduceAttributesLists = ({ - codesList, - idsList, - attributes -}: { - codesList: any, - idsList: any, - attributes: Attribute[] -}) => { - return attributes.reduce( - reduceAttributes, { attrHashByCode: codesList, attrHashById: idsList } - ) -} - -export default reduceAttributesLists diff --git a/core/modules/catalog/helpers/registerProductsMapping.ts b/core/modules/catalog/helpers/registerProductsMapping.ts deleted file mode 100644 index 13ebf84d25..0000000000 --- a/core/modules/catalog/helpers/registerProductsMapping.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { transformProductUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl'; -import { localizedDispatcherRoute } from '@vue-storefront/core/lib/multistore'; -import { ActionContext } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' - -export default async function registerProductsMapping ({ dispatch }: ActionContext, products = []): Promise { - await Promise.all(products.map(product => { - if (product.url_path) { - const { url_path, sku, slug, type_id, parentSku } = product - return dispatch('url/registerMapping', { - url: localizedDispatcherRoute(url_path), - routeData: transformProductUrl({ sku, parentSku, slug, type_id }) - }, { root: true }) - } - })) -} diff --git a/core/modules/catalog/helpers/search.ts b/core/modules/catalog/helpers/search.ts deleted file mode 100644 index 2be2cf5aac..0000000000 --- a/core/modules/catalog/helpers/search.ts +++ /dev/null @@ -1,87 +0,0 @@ -import Vue from 'vue'; -import { Logger } from '@vue-storefront/core/lib/logger'; -import config from 'config'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; -import { entityKeyName } from '@vue-storefront/core/lib/store/entities'; - -export const canCache = ({ includeFields, excludeFields }) => { - const isCacheable = includeFields === null && excludeFields === null; - - if (isCacheable) { - Logger.debug('Entity cache is enabled for productList')(); - } else { - Logger.debug('Entity cache is disabled for productList')(); - } - - return isCacheable; -}; - -const getCacheKey = (product, cacheByKey) => { - if (!product[cacheByKey]) { - cacheByKey = 'id'; - } - - return entityKeyName( - cacheByKey, - product[cacheByKey === 'sku' && product['parentSku'] ? 'parentSku' : cacheByKey] - ); // to avoid caching products by configurable_children.sku -}; - -export const configureChildren = product => { - if (product.configurable_children) { - for (let configurableChild of product.configurable_children) { - if (configurableChild.custom_attributes) { - for (let opt of configurableChild.custom_attributes) { - configurableChild[opt.attribute_code] = opt.value; - } - } - } - } - - return product; -}; - -export const storeProductToCache = (product, cacheByKey) => { - const cacheKey = getCacheKey(product, cacheByKey); - const cache = StorageManager.get('elasticCache'); - - cache.setItem(cacheKey, product, null, config.products.disablePersistentProductsCache) -}; - -export const preConfigureProduct = ({ product, populateRequestCacheTags }) => { - const shouldPopulateCacheTags = populateRequestCacheTags && Vue.prototype.$cacheTags; - const isFirstVariantAsDefaultInURL = - config.products.setFirstVarianAsDefaultInURL && - product.hasOwnProperty('configurable_children') && - product.configurable_children.length > 0; - product.errors = {}; // this is an object to store validation result for custom options and others - product.info = {}; - - if (shouldPopulateCacheTags) { - Vue.prototype.$cacheTags.add(`P${product.id}`); - } - - if (!product.parentSku) { - product.parentSku = product.sku; - } - - if (isFirstVariantAsDefaultInURL) { - product.sku = product.configurable_children[0].sku; - } - - return product; -}; - -export const getOptimizedFields = ({ excludeFields, includeFields }) => { - if (config.entities.optimize) { - return { - excluded: excludeFields || config.entities.product.excludeFields, - included: includeFields || config.entities.product.includeFields - }; - } - - return { excluded: excludeFields, included: includeFields }; -}; - -export const isGroupedOrBundle = product => - product.type_id === 'grouped' || product.type_id === 'bundle'; diff --git a/core/modules/catalog/helpers/slugifyCategories.ts b/core/modules/catalog/helpers/slugifyCategories.ts deleted file mode 100644 index 930cf18749..0000000000 --- a/core/modules/catalog/helpers/slugifyCategories.ts +++ /dev/null @@ -1,24 +0,0 @@ -import config from 'config' -import { slugify } from '@vue-storefront/core/helpers' -import { Category, ChildrenData } from '@vue-storefront/core/modules/catalog-next/types/Category' - -const createSlug = (category: ChildrenData): string => { - if (category.url_key && config.products.useMagentoUrlKeys) { - return category.url_key - } - - return `${slugify(category.name)}-${category.id}` -} - -const slugifyCategories = (category: Category | ChildrenData): Category | ChildrenData => { - if (category.children_data) { - category.children_data.forEach((subCat: ChildrenData): void => { - if (subCat.name && !subCat.slug) { - slugifyCategories({ ...subCat, slug: createSlug(subCat) } as any as ChildrenData) - } - }) - } - return category -} - -export default slugifyCategories diff --git a/core/modules/catalog/helpers/stock/filterChildrenByStockitem.ts b/core/modules/catalog/helpers/stock/filterChildrenByStockitem.ts deleted file mode 100644 index c41fca73a0..0000000000 --- a/core/modules/catalog/helpers/stock/filterChildrenByStockitem.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Add 'stock' object to 'configurable_children' and filter configurable child that is not available - */ -export default function filterChildrenByStockitem (product, stockItems = []) { - for (const stockItem of stockItems) { - const confChild = product.configurable_children.find((child) => child.id === stockItem.product_id) - if (!stockItem.is_in_stock || (confChild && confChild.status >= 2/* conf child is disabled */)) { - product.configurable_children = product.configurable_children.filter((child) => child.id !== stockItem.product_id) - } else { - if (confChild) { - confChild.stock = stockItem - } - } - } -} diff --git a/core/modules/catalog/helpers/stock/filterOutUnavailableVariants.ts b/core/modules/catalog/helpers/stock/filterOutUnavailableVariants.ts deleted file mode 100644 index cdb0778632..0000000000 --- a/core/modules/catalog/helpers/stock/filterOutUnavailableVariants.ts +++ /dev/null @@ -1,18 +0,0 @@ -import filterChildrenByStockitem from './filterChildrenByStockitem' -import { hasConfigurableChildren } from './..'; - -/** - * Add 'stock' object to product. If product is out of stock then add error to product. - * Also check all children for configurable products and remove if any children is out of stock. - */ -export default function filterOutUnavailableVariants (product, stockItems = []) { - const productStockItem = stockItems.find(p => p.product_id === product.id) - product.stock = productStockItem - if (productStockItem && !productStockItem.is_in_stock) { - product.errors.variants = 'No available product variants' - } - - if (product.type_id === 'configurable' && hasConfigurableChildren(product)) { - filterChildrenByStockitem(product, stockItems) - } -} diff --git a/core/modules/catalog/helpers/stock/getProductInfos.ts b/core/modules/catalog/helpers/stock/getProductInfos.ts deleted file mode 100644 index 66f646e908..0000000000 --- a/core/modules/catalog/helpers/stock/getProductInfos.ts +++ /dev/null @@ -1,7 +0,0 @@ -const getProductInfos = (products) => products.map(product => ({ - is_in_stock: product.is_in_stock, - qty: product.qty, - product_id: product.product_id -})) - -export default getProductInfos diff --git a/core/modules/catalog/helpers/stock/getStatus.ts b/core/modules/catalog/helpers/stock/getStatus.ts deleted file mode 100644 index 552ffddf4e..0000000000 --- a/core/modules/catalog/helpers/stock/getStatus.ts +++ /dev/null @@ -1,9 +0,0 @@ -const getStatus = (product, defaultStatus) => { - if (product.stock) { - return product.stock.is_in_stock ? 'ok' : 'out_of_stock' - } - - return defaultStatus -} - -export default getStatus diff --git a/core/modules/catalog/helpers/stock/getStockItems.ts b/core/modules/catalog/helpers/stock/getStockItems.ts deleted file mode 100644 index 76e5b98b95..0000000000 --- a/core/modules/catalog/helpers/stock/getStockItems.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { StockService } from '@vue-storefront/core/data-resolver'; -import config from 'config' - -/** - * Get products skus and products children skus. Based on that search for stock objects and return them. - */ -export default async function getStockItems (products) { - const skuArray = products.map(({ sku, configurable_children = [] }) => { - const childSkus = configurable_children.map((c) => c.sku) - return [sku, ...childSkus] - }).reduce((acc, curr) => acc.concat(curr), []) - if (!config.stock.synchronize) return - try { - const task = await StockService.list(skuArray) - - if (task.resultCode === 200) { - return task.result - } - return [] - } catch (err) { - console.error(err) - return [] - } -} diff --git a/core/modules/catalog/helpers/stock/index.ts b/core/modules/catalog/helpers/stock/index.ts deleted file mode 100644 index fa4c682999..0000000000 --- a/core/modules/catalog/helpers/stock/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import getStatus from './getStatus' -import getProductInfos from './getProductInfos' -import getStockItems from './getStockItems' -import filterOutUnavailableVariants from './filterOutUnavailableVariants' - -export { - getStatus, - getProductInfos, - getStockItems, - filterOutUnavailableVariants -} diff --git a/core/modules/catalog/helpers/taxCalc.ts b/core/modules/catalog/helpers/taxCalc.ts deleted file mode 100644 index 46bdd61c02..0000000000 --- a/core/modules/catalog/helpers/taxCalc.ts +++ /dev/null @@ -1,230 +0,0 @@ -import camelCase from 'lodash-es/camelCase' -import dayjs from 'dayjs' - -// this is the mirror copy of taxcalc.js from VSF API - -function isSpecialPriceActive (fromDate, toDate) { - if (!fromDate && !toDate) { - return true - } - - const now = dayjs() - fromDate = fromDate ? dayjs(fromDate) : false - toDate = toDate ? dayjs(toDate) : false - - if (fromDate && toDate) { - return fromDate.isBefore(now) && now.isBefore(toDate) - } - - if (fromDate && !toDate) { - return fromDate.isBefore(now) - } - - if (!fromDate && toDate) { - return now.isBefore(toDate) - } -} - -/** - * change object keys to camelCase - */ -function toCamelCase (obj: Record = {}): Record { - return Object.keys(obj).reduce((accObj, currKey) => { - accObj[camelCase(currKey)] = obj[currKey] - return accObj - }, {}) -} - -/** - * Create price object with base price and tax - * @param price - product price which is used to extract tax value - * @param rateFactor - tax % in decimal - * @param isPriceInclTax - determines if price already include tax - */ -function createSinglePrice (price: number, rateFactor: number, isPriceInclTax: boolean) { - const _price = isPriceInclTax ? price / (1 + rateFactor) : price - const tax = _price * rateFactor - - return { price: _price, tax } -} - -interface AssignPriceParams { - product: any, - target: string, - price: number, - tax?: number, - deprecatedPriceFieldsSupport?: boolean -} -/** - * assign price and tax to product with proper keys - * @param AssignPriceParams - */ -function assignPrice ({ product, target, price, tax = 0, deprecatedPriceFieldsSupport = true }: AssignPriceParams): void { - let priceUpdate = { - [target]: price, - [`${target}_tax`]: tax, - [`${target}_incl_tax`]: price + tax - } - - if (deprecatedPriceFieldsSupport) { - /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */ - priceUpdate = Object.assign(priceUpdate, toCamelCase(priceUpdate)) - /** END */ - } - - Object.assign(product, priceUpdate) -} - -export function updateProductPrices ({ product, rate, sourcePriceInclTax = false, deprecatedPriceFieldsSupport = false, finalPriceInclTax = true }) { - const rate_factor = parseFloat(rate.rate) / 100 - const hasOriginalPrices = ( - product.hasOwnProperty('original_price') && - product.hasOwnProperty('original_final_price') && - product.hasOwnProperty('original_special_price') - ) - - // build objects with original price and tax - // for first calculation use `price`, for next one use `original_price` - const priceWithTax = createSinglePrice(parseFloat(product.original_price || product.price), rate_factor, sourcePriceInclTax && !hasOriginalPrices) - const finalPriceWithTax = createSinglePrice(parseFloat(product.original_final_price || product.final_price), rate_factor, finalPriceInclTax && !hasOriginalPrices) - const specialPriceWithTax = createSinglePrice(parseFloat(product.original_special_price || product.special_price), rate_factor, sourcePriceInclTax && !hasOriginalPrices) - - // save original prices - if (!hasOriginalPrices) { - assignPrice({ product, target: 'original_price', ...priceWithTax, deprecatedPriceFieldsSupport }) - - if (specialPriceWithTax.price) { - product.original_special_price = specialPriceWithTax.price - } - - if (finalPriceWithTax.price) { - product.original_final_price = finalPriceWithTax.price - } - } - - // reset previous calculation - assignPrice({ product, target: 'price', ...priceWithTax, deprecatedPriceFieldsSupport }) - - if (specialPriceWithTax.price) { - assignPrice({ product, target: 'special_price', ...specialPriceWithTax, deprecatedPriceFieldsSupport }) - } - if (finalPriceWithTax.price) { - assignPrice({ product, target: 'final_price', ...finalPriceWithTax, deprecatedPriceFieldsSupport }) - } - - if (product.final_price) { - if (product.final_price < product.price) { // compare the prices with the product final price if provided; final prices is used in case of active catalog promo rules for example - assignPrice({ product, target: 'price', ...finalPriceWithTax, deprecatedPriceFieldsSupport }) - if (product.special_price && product.final_price < product.special_price) { // for VS - special_price is any price lowered than regular price (`price`); in Magento there is a separate mechanism for setting the `special_prices` - assignPrice({ product, target: 'price', ...specialPriceWithTax, deprecatedPriceFieldsSupport }) // if the `final_price` is lower than the original `special_price` - it means some catalog rules were applied over it - assignPrice({ product, target: 'special_price', ...finalPriceWithTax, deprecatedPriceFieldsSupport }) - } else { - assignPrice({ product, target: 'price', ...finalPriceWithTax, deprecatedPriceFieldsSupport }) - } - } - } - - if (product.special_price && (product.special_price < product.original_price)) { - if (!isSpecialPriceActive(product.special_from_date, product.special_to_date)) { - // out of the dates period - assignPrice({ product, target: 'special_price', price: 0, tax: 0, deprecatedPriceFieldsSupport }) - } else { - assignPrice({ product, target: 'price', ...specialPriceWithTax, deprecatedPriceFieldsSupport }) - } - } else { - // the same price as original; it's not a promotion - assignPrice({ product, target: 'special_price', price: 0, tax: 0, deprecatedPriceFieldsSupport }) - } - - if (product.configurable_children) { - for (let configurableChild of product.configurable_children) { - if (configurableChild.custom_attributes) { - for (let opt of configurableChild.custom_attributes) { - configurableChild[opt.attribute_code] = opt.value - } - } - - // update children prices - updateProductPrices({ product: configurableChild, rate, sourcePriceInclTax, deprecatedPriceFieldsSupport, finalPriceInclTax }) - - if ((configurableChild.price_incl_tax <= product.price_incl_tax) || product.price === 0) { // always show the lowest price - assignPrice({ - product, - target: 'price', - price: configurableChild.price, - tax: configurableChild.price_tax, - deprecatedPriceFieldsSupport - }) - assignPrice({ - product, - target: 'special_price', - price: configurableChild.special_price, - tax: configurableChild.special_price_tax, - deprecatedPriceFieldsSupport - }) - } - } - } -} - -export function calculateProductTax ({ product, taxClasses, taxCountry = 'PL', taxRegion = '', sourcePriceInclTax = false, deprecatedPriceFieldsSupport = false, finalPriceInclTax = true, userGroupId = null, isTaxWithUserGroupIsActive }) { - let rateFound = false - let product_tax_class_id = parseInt(product.tax_class_id) - if (product_tax_class_id > 0) { - let taxClass - if (isTaxWithUserGroupIsActive) { - taxClass = taxClasses.find((el) => - el.product_tax_class_ids.indexOf(product_tax_class_id) >= 0 && - el.customer_tax_class_ids.indexOf(userGroupId) >= 0 - ) - } else { - taxClass = taxClasses.find((el) => el.product_tax_class_ids.indexOf(product_tax_class_id) >= 0) - } - - if (taxClass) { - for (let rate of taxClass.rates) { // TODO: add check for zip code ranges (!) - if (rate.tax_country_id === taxCountry && (rate.region_name === taxRegion || rate.tax_region_id === 0 || !rate.region_name)) { - updateProductPrices({ product, rate, sourcePriceInclTax, deprecatedPriceFieldsSupport, finalPriceInclTax }) - rateFound = true - break - } - } - } - } - if (!rateFound) { - updateProductPrices({ product, rate: { rate: 0 }, sourcePriceInclTax, deprecatedPriceFieldsSupport, finalPriceInclTax }) - - product.price_incl_tax = product.price - product.price_tax = 0 - product.special_price_incl_tax = 0 - product.special_price_tax = 0 - - if (deprecatedPriceFieldsSupport) { - /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */ - product.priceInclTax = product.price - product.priceTax = 0 - product.specialPriceInclTax = 0 - product.specialPriceTax = 0 - /** END */ - } - - if (product.configurable_children) { - for (let configurableChildren of product.configurable_children) { - configurableChildren.price_incl_tax = configurableChildren.price - configurableChildren.price_tax = 0 - configurableChildren.special_price_incl_tax = 0 - configurableChildren.special_price_tax = 0 - - if (deprecatedPriceFieldsSupport) { - /** BEGIN @deprecated - inconsitent naming kept just for the backward compatibility */ - configurableChildren.priceInclTax = configurableChildren.price - configurableChildren.priceTax = 0 - configurableChildren.specialPriceInclTax = 0 - configurableChildren.specialPriceTax = 0 - /** END */ - } - } - } - } - return product -} diff --git a/core/modules/catalog/helpers/transformMetadataToAttributes.ts b/core/modules/catalog/helpers/transformMetadataToAttributes.ts deleted file mode 100644 index e857f10087..0000000000 --- a/core/modules/catalog/helpers/transformMetadataToAttributes.ts +++ /dev/null @@ -1,34 +0,0 @@ -import uniqBy from 'lodash-es/uniqBy' - -const transformMetadataToAttributes = (attributeMetadata) => attributeMetadata - .reduce((prev, curr) => ([ ...prev, ...curr ]), []) - .reduce((prev, curr) => { - const attribute = prev.find(a => a.attribute_id === curr.attribute_id && a.options) - - if (attribute) { - return prev.map(attr => { - if (attr.attribute_id === curr.attribute_id) { - return { - ...attr, - options: uniqBy([...(attr.options || []), ...(curr.options || [])], (obj) => `${obj.label}_${obj.value}`) - } - } - - return attr - }) - } - - return [...prev, curr] - }, []) - .reduce((prev, curr) => ({ - attrHashByCode: { - ...(prev.attrHashByCode || {}), - [curr.attribute_code]: curr - }, - attrHashById: { - ...(prev.attrHashById || {}), - [curr.attribute_id]: curr - } - }), { attrHashByCode: {}, attrHashById: {} }) - -export default transformMetadataToAttributes diff --git a/core/modules/catalog/helpers/variant/findConfigurableVariant.ts b/core/modules/catalog/helpers/variant/findConfigurableVariant.ts deleted file mode 100644 index 3587d57f54..0000000000 --- a/core/modules/catalog/helpers/variant/findConfigurableVariant.ts +++ /dev/null @@ -1,43 +0,0 @@ -import getConfigurationMatchLevel from './getConfigurationMatchLevel' -import getVariantWithLowestPrice from './getVariantWithLowestPrice' -import config from 'config' - -/** - * This function responsiblity is to find best matching variant for configurable product based on configuration object or stock availability. - */ -export default function findConfigurableVariant ({ product, configuration = null, selectDefaultChildren = false, availabilityCheck = true }) { - const selectedVariant = product.configurable_children.reduce((prevVariant, nextVariant) => { - if (availabilityCheck) { - if (nextVariant.stock && !config.products.listOutOfStockProducts) { - if (!nextVariant.stock.is_in_stock) { - return prevVariant - } - } - } - if (nextVariant.status >= 2/** disabled product */) { - return prevVariant - } - if (selectDefaultChildren) { - return prevVariant || nextVariant // return first - } - if ( - (configuration && configuration.sku) && - (nextVariant.sku === configuration.sku) - ) { // by sku or first one - return nextVariant - } else { - // get match level for each variant - const prevVariantMatch = getConfigurationMatchLevel(configuration, prevVariant) - const nextVariantMatch = getConfigurationMatchLevel(configuration, nextVariant) - - // if we have draw between prev variant and current variant then return one that has lowest price - if (prevVariantMatch === nextVariantMatch) { - return getVariantWithLowestPrice(prevVariant, nextVariant) - } - - // return variant with best matching level - return nextVariantMatch > prevVariantMatch ? nextVariant : prevVariant - } - }, undefined) - return selectedVariant -} diff --git a/core/modules/catalog/helpers/variant/getConfigurationMatchLevel.ts b/core/modules/catalog/helpers/variant/getConfigurationMatchLevel.ts deleted file mode 100644 index c49a71624b..0000000000 --- a/core/modules/catalog/helpers/variant/getConfigurationMatchLevel.ts +++ /dev/null @@ -1,23 +0,0 @@ -import toString from 'lodash-es/toString' -import omit from 'lodash-es/omit' - -/** - * Counts how much coniguration match for specific variant - */ -export default function getConfigurationMatchLevel (configuration, variant): number { - if (!variant || !configuration) return 0 - const configProperties = Object.keys(omit(configuration, ['price'])) - return configProperties - .map(configProperty => { - const variantPropertyId = variant[configProperty] - if (configuration[configProperty] === null) { - return false - } - - return [].concat(configuration[configProperty]) - .map(f => typeof f === 'object' ? toString(f.id) : f) - .includes(toString(variantPropertyId)) - }) - .filter(Boolean) - .length -} diff --git a/core/modules/catalog/helpers/variant/getSelectedVariant.ts b/core/modules/catalog/helpers/variant/getSelectedVariant.ts deleted file mode 100644 index 359705bfa4..0000000000 --- a/core/modules/catalog/helpers/variant/getSelectedVariant.ts +++ /dev/null @@ -1,15 +0,0 @@ -import findConfigurableVariant from './findConfigurableVariant' - -/** - * Returns product based on configuration or if there is no match then return first variant as default. - */ -export default function getSelectedVariant (product, configuration, { fallbackToDefaultWhenNoAvailable }) { - let selectedVariant = findConfigurableVariant({ product, configuration, availabilityCheck: true }) - if (!selectedVariant) { - if (fallbackToDefaultWhenNoAvailable) { - selectedVariant = findConfigurableVariant({ product, selectDefaultChildren: true, availabilityCheck: true }) // return first available child - } - } - - return selectedVariant -} diff --git a/core/modules/catalog/helpers/variant/getVariantWithLowestPrice.ts b/core/modules/catalog/helpers/variant/getVariantWithLowestPrice.ts deleted file mode 100644 index 1dbfa6879b..0000000000 --- a/core/modules/catalog/helpers/variant/getVariantWithLowestPrice.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Makes product variants comparission and returns variant with lowest price - */ -export default function getVariantWithLowestPrice (prevVariant, nextVariant) { - if (!prevVariant || !prevVariant.original_price_incl_tax) { - return nextVariant - } - - const prevPrice = prevVariant.price_incl_tax || prevVariant.original_price_incl_tax - const nextPrice = nextVariant.price_incl_tax || nextVariant.original_price_incl_tax - return nextPrice < prevPrice ? nextVariant : prevVariant -} diff --git a/core/modules/catalog/helpers/variant/index.ts b/core/modules/catalog/helpers/variant/index.ts deleted file mode 100644 index 6a9bdc17a1..0000000000 --- a/core/modules/catalog/helpers/variant/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import omitSelectedVariantFields from './omitSelectedVariantFields' -import getSelectedVariant from './getSelectedVariant' -import isOptionAvailable from './isOptionAvailable' -import findConfigurableVariant from './findConfigurableVariant' - -export { - omitSelectedVariantFields, - getSelectedVariant, - isOptionAvailable, - findConfigurableVariant -} diff --git a/core/modules/catalog/helpers/variant/isOptionAvailable.ts b/core/modules/catalog/helpers/variant/isOptionAvailable.ts deleted file mode 100644 index 1c5ee5542d..0000000000 --- a/core/modules/catalog/helpers/variant/isOptionAvailable.ts +++ /dev/null @@ -1,9 +0,0 @@ -import findConfigurableVariant from './findConfigurableVariant' - -/** - * Checks if variant with specific configuration exist - */ -export default function isOptionAvailable (context, { product, configuration }): boolean { - const variant = findConfigurableVariant({ product: product, configuration: configuration, availabilityCheck: true }) - return typeof variant !== 'undefined' && variant !== null -} diff --git a/core/modules/catalog/helpers/variant/omitSelectedVariantFields.ts b/core/modules/catalog/helpers/variant/omitSelectedVariantFields.ts deleted file mode 100644 index f59acc883c..0000000000 --- a/core/modules/catalog/helpers/variant/omitSelectedVariantFields.ts +++ /dev/null @@ -1,12 +0,0 @@ -import omit from 'lodash-es/omit' -import config from 'config' - -/** - * Omit some variant fields to prevent overriding same base product fields - */ -export default function omitSelectedVariantFields (selectedVariant): void { - const hasImage = selectedVariant && selectedVariant.image && selectedVariant.image !== 'no_selection' - const fieldsToOmit = config.products.omitVariantFields - if (!hasImage) fieldsToOmit.push('image') - return omit(selectedVariant, fieldsToOmit) -} diff --git a/core/modules/catalog/hooks/index.ts b/core/modules/catalog/hooks/index.ts deleted file mode 100644 index c6b04decb3..0000000000 --- a/core/modules/catalog/hooks/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createMutatorHook, createListenerHook } from '@vue-storefront/core/lib/hooks' -import Product from '../types/Product'; - -const { - hook: beforeTaxesCalculatedHook, - executor: beforeTaxesCalculatedExecutor -} = createMutatorHook() - -const { - hook: afterSetBundleProductsHook, - executor: afterSetBundleProductsExecutor -} = createListenerHook() - -const { - hook: afterSetGroupedProductHook, - executor: afterSetGroupedProductExecutor -} = createListenerHook() - -const catalogHooksExecutors = { - beforeTaxesCalculated: beforeTaxesCalculatedExecutor, - afterSetBundleProducts: afterSetBundleProductsExecutor, - afterSetGroupedProduct: afterSetGroupedProductExecutor -} - -const catalogHooks = { - beforeTaxesCalculated: beforeTaxesCalculatedHook, - afterSetBundleProducts: afterSetBundleProductsHook, - afterSetGroupedProduct: afterSetGroupedProductHook -} - -export { - catalogHooks, - catalogHooksExecutors -} diff --git a/core/modules/catalog/index.ts b/core/modules/catalog/index.ts deleted file mode 100644 index ce3cc6dd7a..0000000000 --- a/core/modules/catalog/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { productModule } from './store/product' -import { attributeModule } from './store/attribute' -import { stockModule } from './store/stock' -import { taxModule } from './store/tax' -import { categoryModule } from './store/category' -import { catalogHooks } from './hooks' -import { getAttributesFromMetadata } from './helpers/associatedProducts' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import config from 'config' -import { filterChangedProduct, productAfterCustomoptions, productAfterBundleoptions, productAfterPriceupdate, onUserPricesRefreshed } from './events' -import { isServer } from '@vue-storefront/core/helpers' -import uniq from 'lodash-es/uniq' - -export const CatalogModule: StorefrontModule = async function ({ store, router, appConfig }) { - StorageManager.init('categories') - StorageManager.init('attributes') - StorageManager.init('products') - StorageManager.init('elasticCache', true, appConfig.server.elasticCacheQuota) - - store.registerModule('product', productModule) - store.registerModule('attribute', attributeModule) - store.registerModule('stock', stockModule) - store.registerModule('tax', taxModule) - store.registerModule('category', categoryModule) - - catalogHooks.afterSetBundleProducts(products => getAttributesFromMetadata(store, products)) - catalogHooks.afterSetGroupedProduct(products => getAttributesFromMetadata(store, products)) - - if (!config.entities.attribute.loadByAttributeMetadata) { - await store.dispatch('attribute/list', { // loading attributes for application use - filterValues: uniq([...config.products.defaultFilters, ...config.entities.productListWithChildren.includeFields]) - }) - } - - if (!isServer) { - // Things moved from Product.js - EventBus.$on('product-after-priceupdate', product => productAfterPriceupdate(product, store)) - EventBus.$on('filter-changed-product', filterOptions => filterChangedProduct(filterOptions, store, router)) - EventBus.$on('product-after-customoptions', payload => productAfterCustomoptions(payload, store)) - EventBus.$on('product-after-bundleoptions', payload => productAfterBundleoptions(payload, store)) - - if (config.usePriceTiers || store.getters['tax/getIsUserGroupedTaxActive']) { - EventBus.$on('user-after-loggedin', onUserPricesRefreshed.bind(null, store, router)) - EventBus.$on('user-after-logout', onUserPricesRefreshed.bind(null, store, router)) - } - } -} diff --git a/core/modules/catalog/queries/common.js b/core/modules/catalog/queries/common.js deleted file mode 100644 index adcc18346d..0000000000 --- a/core/modules/catalog/queries/common.js +++ /dev/null @@ -1,38 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' -import config from 'config' - -export function prepareQuery ({ queryText = '', filters = [], queryConfig = '' }) { - let query = new SearchQuery() - // prepare filters and searchText - if (filters.length === 0 && queryConfig !== '') { - // try get filters from config - if (config.hasOwnProperty('query') && config.query.hasOwnProperty(queryConfig) && config.query[queryConfig].hasOwnProperty('filter')) { - filters = config.query[queryConfig].filter - } - } - - if (queryText === '') { - // try to get searchText from config - if (config.hasOwnProperty('query') && config.query.hasOwnProperty(queryConfig) && config.query[queryConfig].hasOwnProperty('searchText')) { - queryText = config.query[queryConfig].searchText - } - } - - // Process filters and searchText if exists - if (filters.length > 0) { - filters.forEach(filter => { - query = query.applyFilter({ key: filter.key, value: filter.value }) // Tees category - }) - } - - if (queryText !== '') { - query = query.setSearchText(queryText) - } - - // Add basic filters - query = query - .applyFilter({ key: 'visibility', value: { 'in': [2, 3, 4] } }) - .applyFilter({ key: 'status', value: { 'in': [0, 1] } }) - - return query -} diff --git a/core/modules/catalog/queries/related.js b/core/modules/catalog/queries/related.js deleted file mode 100644 index 12a0d46da4..0000000000 --- a/core/modules/catalog/queries/related.js +++ /dev/null @@ -1,18 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' -import config from 'config' - -export function prepareRelatedQuery (key, sku) { - let relatedProductsQuery = new SearchQuery() - - relatedProductsQuery = relatedProductsQuery.applyFilter({ key: key, value: { 'in': sku } }) - - relatedProductsQuery = relatedProductsQuery - .applyFilter({ key: 'visibility', value: { 'in': [2, 3, 4] } }) - .applyFilter({ key: 'status', value: { 'in': [1] } }) - - if (config.products.listOutOfStockProducts === false) { - relatedProductsQuery = relatedProductsQuery.applyFilter({ key: 'stock.is_in_stock', value: { 'eq': true } }) - } - - return relatedProductsQuery -} diff --git a/core/modules/catalog/queries/searchPanel.js b/core/modules/catalog/queries/searchPanel.js deleted file mode 100644 index 3543093f0b..0000000000 --- a/core/modules/catalog/queries/searchPanel.js +++ /dev/null @@ -1,17 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' -import config from 'config' - -export function prepareQuickSearchQuery (queryText) { - let searchQuery = new SearchQuery() - - searchQuery = searchQuery - .setSearchText(queryText) - .applyFilter({ key: 'visibility', value: { 'in': [3, 4] } }) - .applyFilter({ key: 'status', value: { 'in': [0, 1] } })/* 2 = disabled, 3 = out of stock */ - - if (config.products.listOutOfStockProducts === false) { - searchQuery = searchQuery.applyFilter({ key: 'stock.is_in_stock', value: { 'eq': true } }) - } - - return searchQuery -} diff --git a/core/modules/catalog/store/attribute/actions.ts b/core/modules/catalog/store/attribute/actions.ts deleted file mode 100644 index d89fa32463..0000000000 --- a/core/modules/catalog/store/attribute/actions.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as types from './mutation-types' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import AttributeState from '@vue-storefront/core/modules/catalog/types/AttributeState' -import RootState from '@vue-storefront/core/types/RootState' -import { ActionTree } from 'vuex' -import config from 'config' -import { Logger } from '@vue-storefront/core/lib/logger' -import { entityKeyName } from '@vue-storefront/core/lib/store/entities' -import { prefetchCachedAttributes } from '@vue-storefront/core/modules/catalog/helpers/prefetchCachedAttributes' -import createAttributesListQuery from '@vue-storefront/core/modules/catalog/helpers/createAttributesListQuery' -import reduceAttributesLists from '@vue-storefront/core/modules/catalog/helpers/reduceAttributesLists' -import transformMetadataToAttributes from '@vue-storefront/core/modules/catalog/helpers/transformMetadataToAttributes' -import filterAttributes from '@vue-storefront/core/modules/catalog/helpers/filterAttributes' - -const actions: ActionTree = { - async updateAttributes ({ commit, getters }, { attributes }) { - const idsList = getters.getAttributeListById - const codesList = getters.getAttributeListByCode - - for (let attr of attributes) { - if (attr && !config.attributes.disablePersistentAttributesCache) { - const attrCollection = StorageManager.get('attributes') - - try { - await attrCollection.setItem(entityKeyName('attribute_code', attr.attribute_code.toLowerCase()), attr) - await attrCollection.setItem(entityKeyName('attribute_id', attr.attribute_id.toString()), attr) - } catch (e) { - Logger.error(e, 'mutations')() - } - } - } - - commit(types.ATTRIBUTE_UPD_ATTRIBUTES, reduceAttributesLists({ codesList, idsList, attributes })) - }, - async loadCachedAttributes ({ dispatch }, { filterField, filterValues }) { - if (!filterValues) { - return - } - - const attributes = await prefetchCachedAttributes(filterField, filterValues) - - if (attributes) { - await dispatch('updateAttributes', { attributes }) - } - }, - updateBlacklist ({ commit, getters }, { filterValues, filterField, attributes }) { - if (attributes && filterValues.length > 0) { - const foundValues = attributes.map(attr => attr[filterField]) - const toBlackList = filterValues.filter(ofv => !foundValues.includes(ofv) && !getters.getBlacklist.includes(ofv)) - commit(types.ATTRIBUTE_UPD_BLACKLIST, toBlackList) - } - }, - /** - * Load attributes with specific codes - * @param {Object} context - * @param {Array} attrCodes attribute codes to load - */ - async list ({ getters, dispatch }, { filterValues = null, filterField = 'attribute_code', only_user_defined = false, only_visible = false, size = 150, start = 0, includeFields = config.entities.optimize ? config.entities.attribute.includeFields : null }) { - const blacklist = getters.getBlacklist - const idsList = getters.getAttributeListById - const codesList = getters.getAttributeListByCode - const orgFilterValues = filterValues || [] - - await dispatch('loadCachedAttributes', { filterField, filterValues }) - - filterValues = filterAttributes({ filterValues, filterField, blacklist, idsList, codesList }) - if (filterValues.length === 0) { - Logger.info('Skipping attribute load - attributes already loaded', 'attr', { orgFilterValues, filterField })() - return { items: Object.values(codesList) } - } - - const query = createAttributesListQuery({ - filterValues, - filterField, - onlyDefinedByUser: only_user_defined, - onlyVisible: only_visible - }) - const resp = await quickSearchByQuery({ entityType: 'attribute', query, includeFields, start, size }) - const attributes = resp && orgFilterValues.length > 0 ? resp.items : null - - dispatch('updateBlacklist', { filterValues, filterField, attributes }) - await dispatch('updateAttributes', { attributes }) - - return resp - }, - async loadProductAttributes (context, { products, merge = false }) { - const attributeMetadata = products - .filter(product => product.attributes_metadata) - .map(product => product.attributes_metadata) - - const attributes = transformMetadataToAttributes(attributeMetadata) - - if (merge) { - attributes.attrHashByCode = { ...attributes.attrHashByCode, ...context.state.list_by_code } - attributes.attrHashById = { ...attributes.attrHashById, ...context.state.list_by_id } - } - - context.commit(types.ATTRIBUTE_UPD_ATTRIBUTES, attributes) - }, - async loadCategoryAttributes (context, { attributeMetadata }) { - if (!attributeMetadata) return - const attributes = transformMetadataToAttributes([attributeMetadata]) - - context.commit(types.ATTRIBUTE_UPD_ATTRIBUTES, attributes) - } -} - -export default actions diff --git a/core/modules/catalog/store/attribute/getters.ts b/core/modules/catalog/store/attribute/getters.ts deleted file mode 100644 index 13a87bce0d..0000000000 --- a/core/modules/catalog/store/attribute/getters.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { GetterTree } from 'vuex' -import AttributeState from '../../types/AttributeState' -import RootState from '@vue-storefront/core/types/RootState' -import { Logger } from '@vue-storefront/core/lib/logger' - -const getters: GetterTree = { - getAttributeListByCode: (state) => state.list_by_code, - getAttributeListById: (state) => state.list_by_id, - // @deprecated - attributeListByCode: (state, getters) => getters.getAttributeListByCode, - // @deprecated - attributeListById: (state, getters) => getters.getAttributeListById, - getBlacklist: (state) => state.blacklist, - getAllComparableAttributes: (state, getters) => { - const attributesByCode = getters.getAttributeListByCode - return Object.values(attributesByCode).filter((a: any) => ['1', true].includes(a.is_comparable)) // In some cases we get boolean instead of "0"/"1" that why we support both options - } -} - -export default getters diff --git a/core/modules/catalog/store/attribute/index.ts b/core/modules/catalog/store/attribute/index.ts deleted file mode 100644 index 9a747e3aba..0000000000 --- a/core/modules/catalog/store/attribute/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import AttributeState from '../../types/AttributeState' - -export const attributeModule: Module = { - namespaced: true, - state: { - list_by_code: {}, - list_by_id: {}, - blacklist: [], - labels: {} - }, - getters, - actions, - mutations -} diff --git a/core/modules/catalog/store/attribute/mutation-types.ts b/core/modules/catalog/store/attribute/mutation-types.ts deleted file mode 100644 index 67254b063a..0000000000 --- a/core/modules/catalog/store/attribute/mutation-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SN_ATTRIBUTE = 'attribute' -export const ATTRIBUTE_UPD_ATTRIBUTES = SN_ATTRIBUTE + '/UPD_ATTRIBUTES' -export const ATTRIBUTE_UPD_BLACKLIST = SN_ATTRIBUTE + '/UPD_BLACKLIST_ATTRIBUTES' diff --git a/core/modules/catalog/store/attribute/mutations.ts b/core/modules/catalog/store/attribute/mutations.ts deleted file mode 100644 index 1a7f23a998..0000000000 --- a/core/modules/catalog/store/attribute/mutations.ts +++ /dev/null @@ -1,23 +0,0 @@ -import Vue from 'vue' -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import AttributeState from '../../types/AttributeState' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' - -const mutations: MutationTree = { - /** - * Store attributes by code in state and localForage - * @param {} state - * @param {Array} attributes - */ - async [types.ATTRIBUTE_UPD_ATTRIBUTES] (state, { attrHashByCode, attrHashById }) { - Vue.set(state, 'list_by_code', attrHashByCode) - Vue.set(state, 'list_by_id', attrHashById) - EventBus.$emit('product-after-attributes-loaded') - }, - [types.ATTRIBUTE_UPD_BLACKLIST] (state, blacklist) { - state.blacklist = state.blacklist.concat(blacklist) - } -} - -export default mutations diff --git a/core/modules/catalog/store/category/actions.ts b/core/modules/catalog/store/category/actions.ts deleted file mode 100644 index e309307a46..0000000000 --- a/core/modules/catalog/store/category/actions.ts +++ /dev/null @@ -1,368 +0,0 @@ -import Vue from 'vue' -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import { entityKeyName } from '@vue-storefront/core/lib/store/entities' -import rootStore from '@vue-storefront/core/store' -import i18n from '@vue-storefront/i18n' -import chunk from 'lodash-es/chunk' -import trim from 'lodash-es/trim' -import toString from 'lodash-es/toString' -import { optionLabel } from '../../helpers/optionLabel' -import RootState from '@vue-storefront/core/types/RootState' -import CategoryState from '../../types/CategoryState' -import { currentStoreView, localizedDispatcherRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore' -import { Logger } from '@vue-storefront/core/lib/logger' -import { isServer } from '@vue-storefront/core/helpers' -import config from 'config' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import createCategoryListQuery from '@vue-storefront/core/modules/catalog/helpers/createCategoryListQuery' -import { formatCategoryLink } from 'core/modules/url/helpers' -import { transformCategoryUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl'; - -const actions: ActionTree = { - /** - * Reset current category and path - * @param {Object} context - */ - reset (context) { - context.commit(types.CATEGORY_UPD_CURRENT_CATEGORY_PATH, []) - context.commit(types.CATEGORY_UPD_CURRENT_CATEGORY, {}) - rootStore.dispatch('stock/clearCache') - EventBus.$emit('category-after-reset', { }) - }, - /** - * Load categories within specified parent - * @param {Object} commit promise - * @param {Object} parent parent category - */ - async list ({ commit, state, dispatch }, { parent = null, key = null, value = null, level = null, onlyActive = true, onlyNotEmpty = false, size = 4000, start = 0, sort = 'position:asc', includeFields = config.entities.optimize ? config.entities.category.includeFields : null, excludeFields = config.entities.optimize ? config.entities.category.excludeFields : null, skipCache = false, updateState = true }) { - const { searchQuery, isCustomizedQuery } = createCategoryListQuery({ parent, level, key, value, onlyActive, onlyNotEmpty }) - const shouldLoadCategories = skipCache || ((!state.list || state.list.length === 0) || isCustomizedQuery) - - if (shouldLoadCategories) { - const resp = await quickSearchByQuery({ entityType: 'category', query: searchQuery, sort, size, start, includeFields, excludeFields }) - - if (updateState) { - await dispatch('registerCategoryMapping', { categories: resp.items }) - - commit(types.CATEGORY_UPD_CATEGORIES, { ...resp, includeFields, excludeFields }) - EventBus.$emit('category-after-list', { query: searchQuery, sort, size, start, list: resp }) - } - - return resp - } - - const list = { items: state.list, total: state.list.length } - - if (updateState) { - EventBus.$emit('category-after-list', { query: searchQuery, sort, size, start, list }) - } - - return list - }, - async registerCategoryMapping ({ dispatch }, { categories }) { - const { storeCode, appendStoreCode } = currentStoreView() - for (let category of categories) { - if (category.url_path) { - await dispatch('url/registerMapping', { - url: localizedDispatcherRoute(category.url_path, storeCode), - routeData: transformCategoryUrl(category) - }, { root: true }) - } - } - }, - - /** - * Load category object by specific field - using local storage/indexed Db - * loadCategories() should be called at first! - * @param {Object} commit - * @param {String} key - * @param {String} value - * @param {Bool} setCurrentCategory default=true and means that state.current_category is set to the one loaded - */ - single (context, { key, value, setCurrentCategory = true, setCurrentCategoryPath = true, populateRequestCacheTags = true, skipCache = false }) { - const state = context.state - const commit = context.commit - const dispatch = context.dispatch - - return new Promise((resolve, reject) => { - const fetchCat = ({ key, value }) => { - if (key !== 'id' || value >= config.entities.category.categoriesRootCategorylId/* root category */) { - context.dispatch('list', { key: key, value: value }).then(res => { - if (res && res.items && res.items.length) { - setcat(null, res.items[0]) // eslint-disable-line @typescript-eslint/no-use-before-define - } else { - reject(new Error('Category query returned empty result ' + key + ' = ' + value)) - } - }).catch(reject) - } else { - reject(new Error('Category query returned empty result ' + key + ' = ' + value)) - } - } - let setcat = (error, mainCategory) => { - if (!mainCategory) { - fetchCat({ key, value }) - return - } - if (error) { - Logger.error(error)() - reject(error) - } - - if (setCurrentCategory) { - commit(types.CATEGORY_UPD_CURRENT_CATEGORY, mainCategory) - } - if (populateRequestCacheTags && mainCategory && Vue.prototype.$cacheTags) { - Vue.prototype.$cacheTags.add(`C${mainCategory.id}`) - } - if (setCurrentCategoryPath) { - let currentPath = [] - let recurCatFinder = (category) => { - if (!category) { - return - } - if (category.parent_id >= config.entities.category.categoriesRootCategorylId) { - dispatch('single', { key: 'id', value: category.parent_id, setCurrentCategory: false, setCurrentCategoryPath: false }).then((sc) => { // TODO: move it to the server side for one requests OR cache in indexedDb - if (!sc || sc.parent_id === sc.id) { - commit(types.CATEGORY_UPD_CURRENT_CATEGORY_PATH, currentPath) - EventBus.$emit('category-after-single', { category: mainCategory }) - return resolve(mainCategory) - } - currentPath.unshift(sc) - recurCatFinder(sc) - }).catch(err => { - Logger.error(err)() - commit(types.CATEGORY_UPD_CURRENT_CATEGORY_PATH, currentPath) // this is the case when category is not binded to the root tree - for example 'Erin Recommends' - resolve(mainCategory) - }) - } else { - commit(types.CATEGORY_UPD_CURRENT_CATEGORY_PATH, currentPath) - EventBus.$emit('category-after-single', { category: mainCategory }) - resolve(mainCategory) - } - } - if (typeof mainCategory !== 'undefined') { - recurCatFinder(mainCategory) // TODO: Store breadcrumbs in IndexedDb for further usage to optimize speed? - } else { - reject(new Error('Category query returned empty result ' + key + ' = ' + value)) - } - } else { - EventBus.$emit('category-after-single', { category: mainCategory }) - resolve(mainCategory) - } - } - - let foundInLocalCache = false - if (state.list.length > 0 && !skipCache) { // SSR - there were some issues with using localForage, so it's the reason to use local state instead, when possible - let category = state.list.find((itm) => { return itm[key] === value }) - // Check if category exists in the store OR we have recursively reached Default category (id=1) - if (category && value >= config.entities.category.categoriesRootCategorylId/** root category parent */) { - foundInLocalCache = true - setcat(null, category) - } - } - if (!foundInLocalCache) { - if (skipCache || isServer) { - fetchCat({ key, value }) - } else { - const catCollection = StorageManager.get('categories') - // Check if category does not exist in the store AND we haven't recursively reached Default category (id=1) - catCollection.getItem(entityKeyName(key, value), setcat) - } - } - }) - }, - /** - * Filter category products - */ - products (context, { populateAggregations = false, filters = [], searchProductQuery, current = 0, perPage = 50, sort = '', includeFields = null, excludeFields = null, configuration = null, append = false, skipCache = false, cacheOnly = false }) { - context.dispatch('setSearchOptions', { - populateAggregations, - filters, - current, - perPage, - includeFields, - excludeFields, - configuration, - append, - sort - }) - - let prefetchGroupProducts = true - if (config.entities.twoStageCaching && config.entities.optimize && !isServer && !rootStore.state.twoStageCachingDisabled) { // only client side, only when two stage caching enabled - includeFields = config.entities.productListWithChildren.includeFields // we need configurable_children for filters to work - excludeFields = config.entities.productListWithChildren.excludeFields - prefetchGroupProducts = false - Logger.log('Using two stage caching for performance optimization - executing first stage product pre-fetching')() - } else { - prefetchGroupProducts = true - if (rootStore.state.twoStageCachingDisabled) { - Logger.log('Two stage caching is disabled runtime because of no performance gain')() - } else { - Logger.log('Two stage caching is disabled by the config')() - } - } - if (cacheOnly) { - excludeFields = null - includeFields = null - Logger.log('Caching request only, no state update')() - } - let t0 = new Date().getTime() - - const precachedQuery = searchProductQuery - let productPromise = rootStore.dispatch('product/list', { - query: precachedQuery, - start: current, - size: perPage, - excludeFields: excludeFields, - includeFields: includeFields, - configuration: configuration, - append: append, - sort: sort, - updateState: !cacheOnly, - prefetchGroupProducts: prefetchGroupProducts - }).then((res) => { - let t1 = new Date().getTime() - rootStore.state.twoStageCachingDelta1 = t1 - t0 - - let subloaders = [] - if (!res || (res.noresults)) { - rootStore.dispatch('notification/spawnNotification', { - type: 'warning', - message: i18n.t('No products synchronized for this category. Please come back while online!'), - action1: { label: i18n.t('OK') } - }) - if (!append) rootStore.dispatch('product/reset') - rootStore.state.product.list = { items: [] } // no products to show TODO: refactor to rootStore.state.category.reset() and rootStore.state.product.reset() - // rootStore.state.category.filters = { color: [], size: [], price: [] } - return [] - } else { - if (config.products.filterUnavailableVariants && config.products.configurableChildrenStockPrefetchStatic) { // prefetch the stock items - const skus = [] - let prefetchIndex = 0 - res.items.map(i => { - if (config.products.configurableChildrenStockPrefetchStaticPrefetchCount > 0) { - if (prefetchIndex > config.products.configurableChildrenStockPrefetchStaticPrefetchCount) return - } - skus.push(i.sku) // main product sku to be checked anyway - if (i.type_id === 'configurable' && i.configurable_children && i.configurable_children.length > 0) { - for (const confChild of i.configurable_children) { - const cachedItem = context.rootState.stock.cache[confChild.id] - if (typeof cachedItem === 'undefined' || cachedItem === null) { - skus.push(confChild.sku) - } - } - prefetchIndex++ - } - }) - for (const chunkItem of chunk(skus, 15)) { - rootStore.dispatch('stock/list', { skus: chunkItem, skipCache }) // store it in the cache - } - } - if (populateAggregations === true && res.aggregations) { // populate filter aggregates - for (let attrToFilter of filters) { // fill out the filter options - let filterOptions = [] - - let uniqueFilterValues = new Set() - if (attrToFilter !== 'price') { - if (res.aggregations['agg_terms_' + attrToFilter]) { - let buckets = res.aggregations['agg_terms_' + attrToFilter].buckets - if (res.aggregations['agg_terms_' + attrToFilter + '_options']) { - buckets = buckets.concat(res.aggregations['agg_terms_' + attrToFilter + '_options'].buckets) - } - - for (let option of buckets) { - uniqueFilterValues.add(toString(option.key)) - } - } - - uniqueFilterValues.forEach(key => { - const label = optionLabel(rootStore.state.attribute, { attributeKey: attrToFilter, optionId: key }) - if (trim(label) !== '') { // is there any situation when label could be empty and we should still support it? - filterOptions.push({ - id: key, - label: label - }) - } - }); - } else { // special case is range filter for prices - const storeView = currentStoreView() - const currencySign = storeView.i18n.currencySign - if (res.aggregations['agg_range_' + attrToFilter]) { - let index = 0 - let count = res.aggregations['agg_range_' + attrToFilter].buckets.length - for (let option of res.aggregations['agg_range_' + attrToFilter].buckets) { - filterOptions.push({ - id: option.key, - from: option.from, - to: option.to, - label: (index === 0 || (index === count - 1)) ? (option.to ? '< ' + currencySign + option.to : '> ' + currencySign + option.from) : currencySign + option.from + (option.to ? ' - ' + option.to : '')// TODO: add better way for formatting, extract currency sign - }) - index++ - } - } - } - context.dispatch('addAvailableFilter', { - key: attrToFilter, - options: filterOptions - }) - } - } - } - return subloaders - }).catch((err) => { - Logger.error(err)() - rootStore.dispatch('notification/spawnNotification', { - type: 'warning', - message: i18n.t('No products synchronized for this category. Please come back while online!'), - action1: { label: i18n.t('OK') } - }) - }) - - if (config.entities.twoStageCaching && config.entities.optimize && !isServer && !rootStore.state.twoStageCachingDisabled && !cacheOnly) { // second stage - request for caching entities; if cacheOnly set - the caching took place with the stage1 request! - Logger.log('Using two stage caching for performance optimization - executing second stage product caching', 'category') // TODO: in this case we can pre-fetch products in advance getting more products than set by pageSize() - rootStore.dispatch('product/list', { - query: precachedQuery, - start: current, - size: perPage, - excludeFields: null, - includeFields: null, - configuration: configuration, - sort: sort, - updateState: false, // not update the product listing - this request is only for caching - prefetchGroupProducts: prefetchGroupProducts - }).catch((err) => { - Logger.info("Problem with second stage caching - couldn't store the data", 'category')() - Logger.info(err, 'category')() - }).then((res) => { - let t2 = new Date().getTime() - rootStore.state.twoStageCachingDelta2 = t2 - t0 - Logger.log('Using two stage caching for performance optimization - Time comparison stage1 vs stage2' + rootStore.state.twoStageCachingDelta1 + rootStore.state.twoStageCachingDelta2, 'category')() - if (rootStore.state.twoStageCachingDelta1 > rootStore.state.twoStageCachingDelta2) { // two stage caching is not making any good - rootStore.state.twoStageCachingDisabled = true - Logger.log('Disabling two stage caching', 'category')() - } - }) - } - return productPromise - }, - addAvailableFilter ({ commit }, { key, options } = {}) { - if (key) commit(types.CATEGORY_ADD_AVAILABLE_FILTER, { key, options }) - }, - resetFilters (context) { - context.commit(types.CATEGORY_REMOVE_FILTERS) - }, - searchProductQuery (context, productQuery) { - context.commit(types.CATEGORY_UPD_SEARCH_PRODUCT_QUERY, productQuery) - }, - setSearchOptions ({ commit }, searchOptions) { - commit(types.CATEGORY_SET_SEARCH_OPTIONS, searchOptions) - }, - mergeSearchOptions ({ commit }, searchOptions) { - commit(types.CATEGORY_MERGE_SEARCH_OPTIONS, searchOptions) - } -} - -export default actions diff --git a/core/modules/catalog/store/category/getters.ts b/core/modules/catalog/store/category/getters.ts deleted file mode 100644 index df3c5e2929..0000000000 --- a/core/modules/catalog/store/category/getters.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import CategoryState from '../../types/CategoryState' - -const getters: GetterTree = { - getCurrentCategory: state => state.current, - getCurrentCategoryPath: state => state.current_path, - getAllCategoryFilters: state => state.filters, - getActiveCategoryFilters: state => state.filters.chosen, - getAvailableCategoryFilters: state => state.filters.available, - getCurrentCategoryProductQuery: state => state.current_product_query, - getCategories: state => state.list, - getCategoryBreadcrumbs: state => state.breadcrumbs, - /** - * @deprecated use getCurrentCategory instead - */ - current: (state, getters) => getters.getCurrentCategory, - /** - * @deprecated use getCategories instead - */ - list: (state, getters) => getters.getCategories -} - -export default getters diff --git a/core/modules/catalog/store/category/index.ts b/core/modules/catalog/store/category/index.ts deleted file mode 100644 index 5030f514cd..0000000000 --- a/core/modules/catalog/store/category/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import CategoryState from '../../types/CategoryState' - -export const categoryModule: Module = { - namespaced: true, - state: { - list: [], - current: {}, - filters: { - available: {}, - chosen: {} - }, - breadcrumbs: { - routes: [] - }, - current_product_query: null, - current_path: [] // list of categories from root to current - }, - getters, - actions, - mutations -} diff --git a/core/modules/catalog/store/category/mutation-types.ts b/core/modules/catalog/store/category/mutation-types.ts deleted file mode 100644 index ed0852a01f..0000000000 --- a/core/modules/catalog/store/category/mutation-types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const SN_CATEGORY = 'category' -export const CATEGORY_UPD_CATEGORIES = SN_CATEGORY + '/UPD_CATEGORIES' -export const CATEGORY_UPD_CURRENT_CATEGORY = SN_CATEGORY + '/UPD_CURRENT_CATEGORY' -export const CATEGORY_UPD_CURRENT_CATEGORY_PATH = SN_CATEGORY + '/UPD_CURRENT_CATEGORY_PATH' -export const CATEGORY_UPD_SEARCH_PRODUCT_QUERY = SN_CATEGORY + '/UPD_SEARCH_PRODUCT_QUERY' -export const CATEGORY_ADD_AVAILABLE_FILTER = `${SN_CATEGORY}/ADD_AVAILABLE_FILTER` -export const CATEGORY_REMOVE_FILTERS = SN_CATEGORY + '/REMOVE_FILTERS' -export const CATEGORY_SET_SEARCH_OPTIONS = `${SN_CATEGORY}/SET_SEARCH_OPTIONS` -export const CATEGORY_MERGE_SEARCH_OPTIONS = `${SN_CATEGORY}/MERGE_SEARCH_OPTIONS` diff --git a/core/modules/catalog/store/category/mutations.ts b/core/modules/catalog/store/category/mutations.ts deleted file mode 100644 index d749ae0cce..0000000000 --- a/core/modules/catalog/store/category/mutations.ts +++ /dev/null @@ -1,74 +0,0 @@ -import Vue from 'vue' -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import { formatBreadCrumbRoutes } from '@vue-storefront/core/helpers' -import { entityKeyName } from '@vue-storefront/core/lib/store/entities' -import CategoryState from '../../types/CategoryState' -import { Logger } from '@vue-storefront/core/lib/logger' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import slugifyCategories from '@vue-storefront/core/modules/catalog/helpers/slugifyCategories' - -const mutations: MutationTree = { - [types.CATEGORY_UPD_CURRENT_CATEGORY] (state, category) { - state.current = category - EventBus.$emit('category-after-current', { category: category }) - }, - [types.CATEGORY_UPD_CURRENT_CATEGORY_PATH] (state, path) { - state.current_path = path // TODO: store to cache - state.breadcrumbs.routes = formatBreadCrumbRoutes(state.current_path) - }, - [types.CATEGORY_UPD_CATEGORIES] (state, categories) { - const catCollection = StorageManager.get('categories') - - for (let category of categories.items) { - category = slugifyCategories(category) - const catExist = state.list.find(existingCat => existingCat.id === category.id) - - if (!catExist) { - state.list.push(category) - } - - if (!categories.includeFields) { - try { - catCollection - .setItem(entityKeyName('slug', category.slug.toLowerCase()), category) - .catch(reason => Logger.error(reason, 'category')) - - catCollection - .setItem(entityKeyName('id', category.id), category) - .catch(reason => Logger.error(reason, 'category')) - } catch (e) { - Logger.error(e, 'category')() - } - } - } - - state.list.sort((catA, catB) => { - if (catA.position && catB.position) { - if (catA.position < catB.position) return -1 - if (catA.position > catB.position) return 1 - } - return 0 - }) - }, - [types.CATEGORY_ADD_AVAILABLE_FILTER] (state, { key, options = [] }) { - Vue.set(state.filters.available, key, options) - }, - [types.CATEGORY_REMOVE_FILTERS] (state) { - state.filters.chosen = {} - state.current_product_query.configuration = {} - }, - [types.CATEGORY_UPD_SEARCH_PRODUCT_QUERY] (state, newQuery) { - state.current_product_query = newQuery - }, - [types.CATEGORY_SET_SEARCH_OPTIONS] (state, searchOptions) { - state.current_product_query = searchOptions || null - }, - [types.CATEGORY_MERGE_SEARCH_OPTIONS] (state, searchOptions = {}) { - let currentOptions = state.current_product_query || {} - state.current_product_query = Object.assign(currentOptions, searchOptions) - } -} - -export default mutations diff --git a/core/modules/catalog/store/product/actions.ts b/core/modules/catalog/store/product/actions.ts deleted file mode 100644 index af1d1583af..0000000000 --- a/core/modules/catalog/store/product/actions.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import { isServer } from '@vue-storefront/core/helpers' -import { SearchQuery } from 'storefront-query-builder' -import cloneDeep from 'lodash-es/cloneDeep' -import rootStore from '@vue-storefront/core/store' -import RootState from '@vue-storefront/core/types/RootState' -import ProductState from '../../types/ProductState' -import { Logger } from '@vue-storefront/core/lib/logger'; -import config from 'config' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' -import { - registerProductsMapping, - doPlatformPricesSync, - setCustomProductOptionsAsync, - setBundleProductOptionsAsync, - getProductGallery, - setRequestCacheTags -} from '@vue-storefront/core/modules/catalog/helpers' -import { getProductConfigurationOptions } from '@vue-storefront/core/modules/catalog/helpers/productOptions' -import { checkParentRedirection } from '@vue-storefront/core/modules/catalog/events' - -const actions: ActionTree = { - doPlatformPricesSync (context, { products }) { - return doPlatformPricesSync(products) - }, - /** - * This is fix for https://github.com/vuestorefront/vue-storefront/issues/508 - * TODO: probably it would be better to have "parent_id" for simple products or to just ensure configurable variants are not visible in categories/search - */ - checkConfigurableParent ({ commit, dispatch, getters }, { product }) { - if (product.type_id === 'simple') { - Logger.log('Checking configurable parent')() - const parent = dispatch('findConfigurableParent', { product: { sku: getters.getCurrentProduct.sku } }) - if (parent) { - commit(types.PRODUCT_SET_PARENT, parent) - } - return parent - } - }, - /** - * Search ElasticSearch catalog of products using simple text query - * Use bodybuilder to build the query, aggregations etc: http://bodybuilder.js.org/ - * @param {Object} query is the object of searchQuery class - * @param {Int} start start index - * @param {Int} size page size - * @return {Promise} - */ - async list (context, { - query, - start = 0, - size = 50, - sort = '', - prefetchGroupProducts = !isServer, - excludeFields = null, - includeFields = null, - configuration = null, - populateRequestCacheTags = true, - updateState = false, - append = false - } = {}) { - Logger.warn('`product/list` deprecated, will be not used from 1.12, use "findProducts" instead')() - const { items } = await context.dispatch('findProducts', { - query, - start, - size, - sort, - excludeFields, - includeFields, - configuration, - options: { - populateRequestCacheTags, - prefetchGroupProducts - } - }) - - if (updateState) { - Logger.warn('updateState and append are deprecated, will be not used from 1.12')() - if (append) context.commit(types.PRODUCT_ADD_PAGED_PRODUCTS, { items }) - else context.commit(types.PRODUCT_SET_PAGED_PRODUCTS, { items }) - } - - EventBus.$emit('product-after-list', { query, start, size, sort, entityType: 'product', result: { items } }) - - return { items } - }, - async findProducts (context, { - query, - start = 0, - size = 50, - sort = '', - excludeFields = null, - includeFields = null, - configuration = null, - populateRequestCacheTags = false, - options: { - populateRequestCacheTags: populateRequestCacheTagsNew = false, - prefetchGroupProducts = !isServer, - setProductErrors = false, - fallbackToDefaultWhenNoAvailable = true, - assignProductConfiguration = false, - separateSelectedVariant = false, - setConfigurableProductOptions = config.cart.setConfigurableProductOptions, - filterUnavailableVariants = config.products.filterUnavailableVariants - } = {} - } = {}) { - const { items, ...restResponseData } = await ProductService.getProducts({ - query, - start, - size, - sort, - excludeFields, - includeFields, - configuration, - options: { - prefetchGroupProducts, - fallbackToDefaultWhenNoAvailable, - setProductErrors, - setConfigurableProductOptions, - filterUnavailableVariants, - assignProductConfiguration, - separateSelectedVariant - } - }) - - registerProductsMapping(context, items) - - if (populateRequestCacheTags) { - Logger.warn('deprecated from 1.13, use "options.populateRequestCacheTags" instead')() - } - - if (populateRequestCacheTags || populateRequestCacheTagsNew) { - setRequestCacheTags({ products: items }) - } - - await context.dispatch('tax/calculateTaxes', { products: items }, { root: true }) - - return { ...restResponseData, items } - }, - async findConfigurableParent (context, { product, configuration }) { - const searchQuery = new SearchQuery() - const query = searchQuery.applyFilter({ key: 'configurable_children.sku', value: { 'eq': product.sku } }) - const products = await context.dispatch('findProducts', { query, configuration }) - return products.items && products.items.length > 0 ? products.items[0] : null - }, - /** - * Search products by specific field - * @param {Object} options - */ - async single (context, { - options = {}, - setCurrentProduct = false, - key = 'sku', - skipCache = false - } = {}) { - if (setCurrentProduct) { - Logger.warn('option `setCurrentProduct` is deprecated, will be not used from 1.13')() - } - if (!options[key]) { - throw new Error('Please provide the search key ' + key + ' for product/single action!') - } - const product = await ProductService.getProductByKey({ - options, - key, - skipCache - }) - - await context.dispatch('tax/calculateTaxes', { products: [product] }, { root: true }) - - if (setCurrentProduct) await context.dispatch('setCurrent', product) - EventBus.$emitFilter('product-after-single', { key, options, product }) - - return product - }, - /** - * Assign the custom options object to the currentl product - */ - setCustomOptions (context, { customOptions, product }) { - if (customOptions) { // TODO: this causes some kind of recurrency error - context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, product, { product_option: setCustomProductOptionsAsync(context, { product: context.getters.getCurrentProduct, customOptions: customOptions }) })) - } - }, - /** - * Assign the bundle options object to the vurrent product - */ - setBundleOptions (context, { bundleOptions, product }) { - if (bundleOptions) { // TODO: this causes some kind of recurrency error - context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, product, { product_option: setBundleProductOptionsAsync(context, { product: context.getters.getCurrentProduct, bundleOptions: bundleOptions }) })) - } - }, - /** - * Set current product with given variant's properties - * @param {Object} context - * @param {Object} productVariant - */ - setCurrent (context, product) { - if (product && typeof product === 'object') { - const { configuration, ...restProduct } = product - const productUpdated = Object.assign({}, restProduct) - if (!config.products.gallery.mergeConfigurableChildren) { - context.dispatch('setProductGallery', { product: productUpdated }) - } - const productOptions = getProductConfigurationOptions({ product, attribute: context.rootState.attribute }) - context.commit(types.PRODUCT_SET_CURRENT_OPTIONS, productOptions) - context.commit(types.PRODUCT_SET_CURRENT_CONFIGURATION, configuration || {}) - context.commit(types.PRODUCT_SET_CURRENT, productUpdated) - return productUpdated - } else Logger.debug('Unable to update current product.', 'product')() - }, - /** - * Set related products - */ - related (context, { key = 'related-products', items }) { - context.commit(types.PRODUCT_SET_RELATED, { key, items }) - }, - /** - * Load the product data and sets current product - */ - async loadProduct ({ dispatch, state }, { parentSku, childSku = null, route = null, skipCache = false }) { - Logger.info('Fetching product data asynchronously', 'product', { parentSku, childSku })() - EventBus.$emit('product-before-load', { store: rootStore, route: route }) - - const product = await dispatch('single', { - options: { - sku: parentSku, - childSku: childSku - }, - key: 'sku', - skipCache - }) - - setRequestCacheTags({ products: [product] }) - - await dispatch('setCurrent', product) - - if (product.status >= 2) { - throw new Error(`Product query returned empty result product status = ${product.status}`) - } - if (product.visibility === 1) { // not visible individually (https://magento.stackexchange.com/questions/171584/magento-2-table-name-for-product-visibility) - if (config.products.preventConfigurableChildrenDirectAccess) { - const parentProduct = await dispatch('findConfigurableParent', { product }) - checkParentRedirection(product, parentProduct) - } else { - throw new Error(`Product query returned empty result product visibility = ${product.visibility}`) - } - } - - if (config.entities.attribute.loadByAttributeMetadata) { - await dispatch('attribute/loadProductAttributes', { products: [product] }, { root: true }) - } else { - await dispatch('loadProductAttributes', { product }) - } - - const syncPromises = [] - const gallerySetup = dispatch('setProductGallery', { product }) - if (isServer) { - syncPromises.push(gallerySetup) - } - await Promise.all(syncPromises) - await EventBus.$emitFilter('product-after-load', { store: rootStore, route: route }) - return product - }, - /** - * Add custom option validator for product custom options - */ - addCustomOptionValidator (context, { validationRule, validatorFunction }) { - context.commit(types.PRODUCT_SET_CUSTOM_OPTION_VALIDATOR, { validationRule, validatorFunction }) - }, - - /** - * Set product gallery depending on product type - */ - - setProductGallery (context, { product }) { - const productGallery = getProductGallery(product) - context.commit(types.PRODUCT_SET_GALLERY, productGallery) - }, - async loadProductBreadcrumbs ({ dispatch, rootGetters }, { product } = {}) { - if (product && product.category_ids) { - const currentCategory = rootGetters['category-next/getCurrentCategory'] - let breadcrumbCategory - const categoryFilters = Object.assign({ 'id': [...product.category_ids] }, cloneDeep(config.entities.category.breadcrumbFilterFields)) - const categories = await dispatch('category-next/loadCategories', { filters: categoryFilters, reloadAll: Object.keys(config.entities.category.breadcrumbFilterFields).length > 0 }, { root: true }) - if ( - (currentCategory && currentCategory.id) && // current category exist - (config.entities.category.categoriesRootCategorylId !== currentCategory.id) && // is not highest category (All) - if we open product from different page then category page - (categories.findIndex(category => category.id === currentCategory.id) >= 0) // can be found in fetched categories - ) { - breadcrumbCategory = currentCategory // use current category if set and included in the filtered list - } else { - breadcrumbCategory = categories.sort((a, b) => (a.level > b.level) ? -1 : 1)[0] // sort starting by deepest level - } - await dispatch('category-next/loadCategoryBreadcrumbs', { category: breadcrumbCategory, currentRouteName: product.name }, { root: true }) - } - }, - async getProductVariant (context, { product, configuration } = {}) { - let searchQuery = new SearchQuery() - searchQuery = searchQuery.applyFilter({ key: 'sku', value: { 'eq': product.parentSku } }) - if (!product.parentSku) { - throw new Error('Product doesn\'t have parentSku, please check if this is configurable product') - } - const { items: [newProductVariant] } = await context.dispatch('findProducts', { - query: searchQuery, - size: 1, - configuration, - options: { - fallbackToDefaultWhenNoAvailable: false, - setProductErrors: true, - separateSelectedVariant: true - } - }) - const { selectedVariant = {}, options, product_option } = newProductVariant - - return { ...selectedVariant, options, product_option } - }, - /** Below actions are not used from 1.12 and can be removed to reduce bundle */ - ...require('./deprecatedActions').default -} - -export default actions diff --git a/core/modules/catalog/store/product/deprecatedActions.ts b/core/modules/catalog/store/product/deprecatedActions.ts deleted file mode 100644 index ef89406088..0000000000 --- a/core/modules/catalog/store/product/deprecatedActions.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { optionLabel } from '../../helpers/optionLabel' -import trim from 'lodash-es/trim' -import { formatBreadCrumbRoutes, isServer } from '@vue-storefront/core/helpers' -import { preConfigureProduct, storeProductToCache, isGroupedOrBundle } from '@vue-storefront/core/modules/catalog/helpers/search' -import toString from 'lodash-es/toString' -import { - registerProductsMapping, - filterOutUnavailableVariants -} from '@vue-storefront/core/modules/catalog/helpers' -import { Logger } from '@vue-storefront/core/lib/logger'; -import * as types from './mutation-types' -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' -import config from 'config' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -const { populateProductConfigurationAsync } = require('@vue-storefront/core/modules/catalog/helpers') - -const actions = { - /** - * Reset current configuration and selected variatnts - */ - reset (context) { - Logger.warn('`product/reset` deprecated, will be not used from 1.12')() - const originalProduct = Object.assign({}, context.getters.getOriginalProduct) - context.commit(types.PRODUCT_RESET_CURRENT, originalProduct) - }, - /** - * Setup product breadcrumbs path - */ - async setupBreadcrumbs (context, { product }) { - Logger.warn('`product/setupBreadcrumbs` deprecated, will be not used from 1.12')() - let breadcrumbsName = null - let setBreadcrumbRoutesFromPath = (path) => { - if (path.findIndex(itm => { - return itm.slug === context.rootGetters['category/getCurrentCategory'].slug - }) < 0) { - path.push({ - url_path: context.rootGetters['category/getCurrentCategory'].url_path, - slug: context.rootGetters['category/getCurrentCategory'].slug, - name: context.rootGetters['category/getCurrentCategory'].name - }) // current category at the end - } - // deprecated, TODO: base on breadcrumbs module - breadcrumbsName = product.name - const breadcrumbs = { - routes: formatBreadCrumbRoutes(path), - current: breadcrumbsName, - name: breadcrumbsName - } - context.commit(types.CATALOG_SET_BREADCRUMBS, breadcrumbs) - } - - if (product.category && product.category.length > 0) { - const categoryIds = product.category.reverse().map(cat => cat.category_id) - await context.dispatch('category/list', { key: 'id', value: categoryIds }, { root: true }).then(async (categories) => { - const catList = [] - - for (let catId of categoryIds) { - let category = categories.items.find((itm) => { return toString(itm['id']) === toString(catId) }) - if (category) { - catList.push(category) - } - } - - const rootCat = catList.shift() - let catForBreadcrumbs = rootCat - - for (let cat of catList) { - const catPath = cat.path - if (catPath && catPath.includes(rootCat.path) && (catPath.split('/').length > catForBreadcrumbs.path.split('/').length)) { - catForBreadcrumbs = cat - } - } - if (typeof catForBreadcrumbs !== 'undefined') { - await context.dispatch('category/single', { key: 'id', value: catForBreadcrumbs.id }, { root: true }).then(() => { // this sets up category path and current category - setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath']) - }).catch(err => { - setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath']) - Logger.error(err)() - }) - } else { - setBreadcrumbRoutesFromPath(context.rootGetters['category/getCurrentCategoryPath']) - } - }) - } - }, - /** - * Download Magento2 / other platform prices to put them over ElasticSearch prices - */ - async syncPlatformPricesOver ({ rootGetters }, { skus }) { - Logger.warn('`product/syncPlatformPricesOver`deprecated, will be not used from 1.12')() - const result = await ProductService.getProductRenderList({ - skus, - isUserGroupedTaxActive: rootGetters['tax/getIsUserGroupedTaxActive'], - userGroupId: rootGetters['tax/getUserTaxGroupId'], - token: rootGetters['user/getToken'] - }) - return result - }, - /** - * Setup associated products - */ - setupAssociated (context, { product, skipCache = true }) { - Logger.warn('`product/setupAssociated` deprecated, will be not used from 1.12')() - let subloaders = [] - if (product.type_id === 'grouped') { - product.price = 0 - product.price_incl_tax = 0 - Logger.debug(product.name + ' SETUP ASSOCIATED', product.type_id)() - if (product.product_links && product.product_links.length > 0) { - for (let pl of product.product_links) { - if (pl.link_type === 'associated' && pl.linked_product_type === 'simple') { // prefetch links - Logger.debug('Prefetching grouped product link for ' + pl.sku + ' = ' + pl.linked_product_sku)() - subloaders.push(context.dispatch('single', { - options: { sku: pl.linked_product_sku }, - setCurrentProduct: false, - selectDefaultVariant: false, - skipCache: skipCache - }).catch(err => { Logger.error(err) }).then((asocProd) => { - if (asocProd) { - pl.product = asocProd - pl.product.qty = 1 - product.price += pl.product.price - product.price_incl_tax += pl.product.price_incl_tax - product.tax += pl.product.tax - } else { - Logger.error('Product link not found', pl.linked_product_sku)() - } - })) - } - } - } else { - Logger.error('Product with type grouped has no product_links set!', product)() - } - } - if (product.type_id === 'bundle') { - product.price = 0 - product.price_incl_tax = 0 - Logger.debug(product.name + ' SETUP ASSOCIATED', product.type_id)() - if (product.bundle_options && product.bundle_options.length > 0) { - for (let bo of product.bundle_options) { - let defaultOption = bo.product_links.find((p) => { return p.is_default }) - if (!defaultOption) defaultOption = bo.product_links[0] - for (let pl of bo.product_links) { - Logger.debug('Prefetching bundle product link for ' + bo.sku + ' = ' + pl.sku)() - subloaders.push(context.dispatch('single', { - options: { sku: pl.sku }, - setCurrentProduct: false, - selectDefaultVariant: false, - skipCache: skipCache - }).catch(err => { Logger.error(err) }).then((asocProd) => { - if (asocProd) { - pl.product = asocProd - pl.product.qty = pl.qty - - if (pl.id === defaultOption.id) { - product.price += pl.product.price * pl.product.qty - product.price_incl_tax += pl.product.price_incl_tax * pl.product.qty - product.tax += pl.product.tax * pl.product.qty - } - } else { - Logger.error('Product link not found', pl.sku)() - } - })) - } - } - } - } - return Promise.all(subloaders) - }, - /** - * Load required configurable attributes - * @param context - * @param product - */ - loadConfigurableAttributes (context, { product }) { - Logger.warn('`product/loadConfigurableAttributes` deprecated, will be not used from 1.12')() - let attributeKey = 'attribute_id' - const configurableAttrKeys = product.configurable_options.map(opt => { - if (opt.attribute_id) { - attributeKey = 'attribute_id' - return opt.attribute_id - } else { - attributeKey = 'attribute_code' - return opt.attribute_code - } - }) - return context.dispatch('attribute/list', { - filterValues: configurableAttrKeys, - filterField: attributeKey - }, { root: true }) - }, - /** - * Setup product current variants - */ - async setupVariants (context, { product }) { - Logger.warn('`product/setupVariants` deprecated, will be not used from 1.12')() - if (product.type_id !== 'configurable' || !product.hasOwnProperty('configurable_options')) { - return - } - if (config.entities.attribute.loadByAttributeMetadata) { - await context.dispatch('attribute/loadProductAttributes', { products: [product] }, { root: true }) - } - let productOptions = {} - for (let option of product.configurable_options) { - for (let ov of option.values) { - let lb = ov.label ? ov.label : optionLabel(context.rootState.attribute, { attributeKey: option.attribute_id, searchBy: 'id', optionId: ov.value_index }) - if (trim(lb) !== '') { - let optionKey = option.attribute_code ? option.attribute_code : option.label.toLowerCase() - if (!productOptions[optionKey]) { - productOptions[optionKey] = [] - } - - productOptions[optionKey].push({ - label: lb, - id: ov.value_index, - attribute_code: option.attribute_code - }) - } - } - } - context.commit(types.PRODUCT_SET_CURRENT_OPTIONS, productOptions) - let selectedVariant = context.getters.getCurrentProduct - populateProductConfigurationAsync(context, { selectedVariant: selectedVariant, product: product }) - }, - filterUnavailableVariants (context, { product }) { - Logger.warn('`product/filterUnavailableVariants` deprecated, will be not used from 1.12')() - return filterOutUnavailableVariants(context, product) - }, - preConfigureAssociated (context, { searchResult, prefetchGroupProducts }) { - Logger.warn('`product/preConfigureAssociated` deprecated, will be not used from 1.12')() - registerProductsMapping(context, searchResult.items) - for (let product of searchResult.items) { - if (isGroupedOrBundle(product) && prefetchGroupProducts && !isServer) { - context.dispatch('setupAssociated', { product }) - } - } - }, - async preConfigureProduct (context, { product, populateRequestCacheTags, configuration }) { - Logger.warn('`product/preConfigureProduct` deprecated, will be not used from 1.12')() - let _product = preConfigureProduct({ product, populateRequestCacheTags }) - - if (configuration) { - const selectedVariant = await context.dispatch('getProductVariant', { product: _product, configuration }) - _product = Object.assign({}, _product, selectedVariant) - } - - return _product - }, - async configureLoadedProducts (context, { products, isCacheable, cacheByKey, populateRequestCacheTags, configuration }) { - Logger.warn('`product/configureLoadedProducts` deprecated, will be not used from 1.12')() - const configuredProducts = await context.dispatch( - 'category-next/configureProducts', - { - products: products.items, - filters: configuration || {}, - populateRequestCacheTags - }, - { root: true } - ) - - await context.dispatch('tax/calculateTaxes', { products: configuredProducts }, { root: true }) - - for (let product of configuredProducts) { // we store each product separately in cache to have offline access to products/single method - if (isCacheable) { // store cache only for full loads - storeProductToCache(product, cacheByKey) - } - } - - return products - }, - /** - * Update associated products for bundle product - * @param context - * @param product - */ - configureBundleAsync (context, product) { - Logger.warn('`product/configureBundleAsync` deprecated, will be not used from 1.12')() - return context.dispatch( - 'setupAssociated', { - product: product, - skipCache: true - }) - .then(() => { context.dispatch('setCurrent', product) }) - .then(() => { EventBus.$emit('product-after-setup-associated') }) - }, - - /** - * Update associated products for group product - * @param context - * @param product - */ - configureGroupedAsync (context, product) { - Logger.warn('`product/configureGroupedAsync` deprecated, will be not used from 1.12')() - return context.dispatch( - 'setupAssociated', { - product: product, - skipCache: true - }) - .then(() => { context.dispatch('setCurrent', product) }) - }, - /** - * Configure product with given configuration and set it as current - * @param {Object} context - * @param {Object} product - * @param {Array} configuration - */ - async configure (context, { product = null, configuration, selectDefaultVariant = true, fallbackToDefaultWhenNoAvailable = false }) { - Logger.warn('`product/configure` deprecated, will be not used from 1.12, use "product/getProductVariant"')() - const result = await context.dispatch('getProductVariant', { product, configuration }) - return result - }, - - setCurrentOption (context, productOption) { - Logger.warn('`product/setCurrentOption` deprecated, will be not used from 1.12')() - if (productOption && typeof productOption === 'object') { // TODO: this causes some kind of recurrency error - context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, context.getters.getCurrentProduct, { product_option: productOption })) - } - }, - - setCurrentErrors (context, errors) { - Logger.warn('`product/setCurrentErrors` deprecated, will be not used from 1.12')() - if (errors && typeof errors === 'object') { - context.commit(types.PRODUCT_SET_CURRENT, Object.assign({}, context.getters.getCurrentProduct, { errors: errors })) - } - }, - /** - * Set given product as original - * @param {Object} context - * @param {Object} originalProduct - */ - setOriginal (context, originalProduct) { - Logger.warn('`product/setOriginal` deprecated, will be not used from 1.12')() - if (originalProduct && typeof originalProduct === 'object') context.commit(types.PRODUCT_SET_ORIGINAL, Object.assign({}, originalProduct)) - else Logger.debug('Unable to setup original product.', 'product')() - }, - - /** - * Load product attributes - */ - async loadProductAttributes ({ dispatch }, { product }) { - Logger.warn('`product/loadProductAttributes` deprecated, will be not used from 1.12')() - const productFields = Object.keys(product).filter(fieldName => { - return !config.entities.product.standardSystemFields.includes(fieldName) // don't load metadata info for standard fields - }) - const { product: { useDynamicAttributeLoader }, optimize, attribute } = config.entities - return dispatch('attribute/list', { // load attributes to be shown on the product details - the request is now async - filterValues: useDynamicAttributeLoader ? productFields : null, - only_visible: !!useDynamicAttributeLoader, - only_user_defined: true, - includeFields: optimize ? attribute.includeFields : null - }, { root: true }) - } -} - -export default actions diff --git a/core/modules/catalog/store/product/getters.ts b/core/modules/catalog/store/product/getters.ts deleted file mode 100644 index 700c69420e..0000000000 --- a/core/modules/catalog/store/product/getters.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import ProductState from '../../types/ProductState' -import { Logger } from '@vue-storefront/core/lib/logger'; - -const getters: GetterTree = { - getCurrentProduct: state => state.current, - getCurrentProductConfiguration: state => state.current_configuration, - getCurrentProductOptions: state => state.current_options, - getOriginalProduct: (state, getters) => { - if (!getters.getCurrentProduct) return null - return state.original || { - ...getters.getCurrentProduct, - id: getters.getCurrentProduct.parentId || getters.getCurrentProduct.id - } - }, - getParentProduct: state => state.parent, - getProductsSearchResult: state => state.list, - getProducts: (state, getters) => getters.getProductsSearchResult.items, - getProductGallery: state => state.productGallery, - getProductRelated: state => state.related, - getCurrentCustomOptions: state => state.current_custom_options -} - -export default getters diff --git a/core/modules/catalog/store/product/index.ts b/core/modules/catalog/store/product/index.ts deleted file mode 100644 index 4928c277fb..0000000000 --- a/core/modules/catalog/store/product/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import ProductState from '../../types/ProductState' - -export const productModule: Module = { - namespaced: true, - state: { - // TODO use breadcrumbs from category-next, leave here for backward compatibility - breadcrumbs: { - routes: [], - name: '' - }, - current: null, // shown product - current_options: { - color: [], - size: [] - }, - current_configuration: {}, - parent: null, - list: { - start: 0, - perPage: 50, - total: 0, - items: [] - }, - original: null, // default, not configured product - related: {}, - offlineImage: null, - current_custom_options: {}, - current_bundle_options: {}, - custom_options_validators: {}, - productLoadStart: 0, - productLoadPromise: null, - productGallery: [] - }, - getters, - actions, - mutations -} - -export const nonReactiveState = { - list: [] -} diff --git a/core/modules/catalog/store/product/mutation-types.ts b/core/modules/catalog/store/product/mutation-types.ts deleted file mode 100644 index 8b868541fe..0000000000 --- a/core/modules/catalog/store/product/mutation-types.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const SN_PRODUCT = 'product' -export const PRODUCT_SET_PAGED_PRODUCTS = SN_PRODUCT + '/SET_PRODUCTS' -export const PRODUCT_ADD_PAGED_PRODUCTS = SN_PRODUCT + '/ADD_PRODUCTS' -export const PRODUCT_SET_RELATED = SN_PRODUCT + '/SET_RELATED' -export const PRODUCT_SET_CURRENT = SN_PRODUCT + '/SET_CURRENT' -export const PRODUCT_SET_CURRENT_OPTIONS = SN_PRODUCT + '/SET_CURRENT_OPTIONS' -export const PRODUCT_RESET_CURRENT = SN_PRODUCT + '/RESET_CURRENT' -export const PRODUCT_SET_ORIGINAL = SN_PRODUCT + '/SET_ORIGINAL' -export const PRODUCT_SET_CURRENT_CONFIGURATION = SN_PRODUCT + '/SET_CURRENT_CONFIGURATION' -export const PRODUCT_SET_PARENT = SN_PRODUCT + '/SET_PARENT' -export const PRODUCT_SET_CUSTOM_OPTION = SN_PRODUCT + '/SET_CUSTOM_OPTION' -export const PRODUCT_SET_CUSTOM_OPTION_VALIDATOR = SN_PRODUCT + '/SET_CUSTOM_OPTION_VALIDATOR' -export const PRODUCT_SET_BUNDLE_OPTION = SN_PRODUCT + '/SET_BUNDLE_OPTION' -export const PRODUCT_SET_GALLERY = SN_PRODUCT + '/SET_PRODUCT_GALLERY' -// remove later -export const CATALOG_UPD_PRODUCTS = SN_PRODUCT + '/UPD_PRODUCTS' -export const CATALOG_UPD_RELATED = SN_PRODUCT + '/UPD_RELATED' -export const CATALOG_SET_PRODUCT_CURRENT = SN_PRODUCT + '/SET_PRODUCT_CURRENT' -export const CATALOG_SET_PRODUCT_ORIGINAL = SN_PRODUCT + '/SET_PRODUCT_ORIGINAL' -export const CATALOG_RESET_PRODUCT = SN_PRODUCT + '/RESET_PRODUCT_ORIGINAL' -export const CATALOG_SET_PRODUCT_PARENT = SN_PRODUCT + '/SET_PARENT' -export const CATALOG_UPD_CUSTOM_OPTION = SN_PRODUCT + '/SET_CUSTOM_OPTION' -export const CATALOG_UPD_BUNDLE_OPTION = SN_PRODUCT + '/UPD_BUNDLE_OPTION' -export const CATALOG_ADD_CUSTOM_OPTION_VALIDATOR = SN_PRODUCT + '/ADD_CUSTOM_OPTION_VALIDATOR' -export const CATALOG_UPD_GALLERY = SN_PRODUCT + '/SET_GALLERY' -export const CATALOG_SET_BREADCRUMBS = SN_PRODUCT + '/SET_BREADCRUMBS' diff --git a/core/modules/catalog/store/product/mutations.ts b/core/modules/catalog/store/product/mutations.ts deleted file mode 100644 index bd415ba0d1..0000000000 --- a/core/modules/catalog/store/product/mutations.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { MutationTree } from 'vuex' -import { Logger } from '@vue-storefront/core/lib/logger' -import * as types from './mutation-types' -import ProductState, { PagedProductList } from '../../types/ProductState' -import Vue from 'vue' - -const mutations: MutationTree = { - [types.PRODUCT_SET_PAGED_PRODUCTS] (state, searchResult) { - const { start, perPage, total, items } = searchResult - state.list = { - start, - perPage, - total, - items - } - }, - [types.PRODUCT_ADD_PAGED_PRODUCTS] (state, searchResult) { - const { start, perPage, items } = searchResult - state.list = Object.assign( - {}, - state.list, - { - start, - perPage, - items: [...(state.list as PagedProductList).items, ...items] - } - ) - }, - [types.PRODUCT_SET_RELATED] (state, { key, items }) { - state.related = Object.assign( - {}, - state.related, - { [key]: items } - ) - }, - [types.PRODUCT_SET_CURRENT] (state, product) { - state.current = product - }, - [types.PRODUCT_RESET_CURRENT] (state, originalProduct) { - state.current = Object.assign({}, originalProduct) - state.current_configuration = {} - state.offlineImage = null - state.parent = null - state.current_options = { color: [], size: [] } - state.current_bundle_options = {} - state.current_custom_options = {} - }, - [types.PRODUCT_SET_CURRENT_OPTIONS] (state, configuration = {}) { - state.current_options = configuration - }, - [types.PRODUCT_SET_CURRENT_CONFIGURATION] (state, configuration = {}) { - Vue.set(state, 'current_configuration', configuration || {}) - }, - [types.PRODUCT_SET_ORIGINAL] (state, product) { - state.original = product - }, - [types.PRODUCT_SET_PARENT] (state, product) { - state.parent = product - }, - [types.PRODUCT_SET_CUSTOM_OPTION] (state, { optionId, optionValue }) { - state.current_custom_options = Object.assign( - {}, - state.current_custom_options, - { [optionId]: { - option_id: optionId, - option_value: optionValue - } } - ) - }, - [types.PRODUCT_SET_BUNDLE_OPTION] (state, { optionId, optionQty, optionSelections }) { - const option = { - option_id: optionId, - option_qty: optionQty, - option_selections: optionSelections - } - state.current_bundle_options = Object.assign( - {}, - state.current_bundle_options, - { [optionId]: option } - ) - }, - [types.PRODUCT_SET_CUSTOM_OPTION_VALIDATOR] (state, { validationRule, validatorFunction }) { - state.custom_options_validators = Object.assign( - {}, - state.custom_options_validators, - { [validationRule]: validatorFunction } - ) - }, - [types.PRODUCT_SET_GALLERY] (state, productGallery) { - state.productGallery = productGallery - }, - [types.CATALOG_SET_BREADCRUMBS] (state, payload) { - state.breadcrumbs = payload - }, - [types.CATALOG_ADD_CUSTOM_OPTION_VALIDATOR] (state, { validationRule, validatorFunction }) { - Logger.error('Deprecated mutation CATALOG_ADD_CUSTOM_OPTION_VALIDATOR - use PRODUCT_SET_CUSTOM_OPTION_VALIDATOR instead')() - }, - [types.CATALOG_UPD_RELATED] (state, { key, items }) { - Logger.error('Deprecated mutation CATALOG_UPD_RELATED - use PRODUCT_SET_RELATED instead')() - }, - [types.CATALOG_UPD_BUNDLE_OPTION] (state, { optionId, optionQty, optionSelections }) { - Logger.error('Deprecated mutation CATALOG_UPD_BUNDLE_OPTION - use PRODUCT_SET_BUNDLE_OPTION instead')() - }, - [types.CATALOG_UPD_PRODUCTS] (state, { products, append }) { - Logger.error('Deprecated mutation CATALOG_UPD_PRODUCTS - use PRODUCT_SET_PAGED_PRODUCTS or PRODUCT_ADD_PAGED_PRODUCTS instead')() - }, - [types.CATALOG_SET_PRODUCT_CURRENT] (state, product) { - Logger.error('Deprecated mutation CATALOG_SET_PRODUCT_CURRENT - use PRODUCT_SET_CURRENT instead')() - }, - [types.CATALOG_SET_PRODUCT_ORIGINAL] (state, product) { - Logger.error('Deprecated mutation CATALOG_SET_PRODUCT_ORIGINAL - use PRODUCT_SET_ORIGINAL instead')() - }, - [types.CATALOG_RESET_PRODUCT] (state, productOriginal) { - Logger.error('Deprecated mutation CATALOG_RESET_PRODUCT - use PRODUCT_RESET_CURRENT instead')() - }, - [types.CATALOG_UPD_GALLERY] (state, productGallery) { - Logger.error('Deprecated mutation CATALOG_UPD_GALLERY - use PRODUCT_SET_GALLERY instead')() - } -} - -export default mutations diff --git a/core/modules/catalog/store/stock/actions.ts b/core/modules/catalog/store/stock/actions.ts deleted file mode 100644 index 5989318f19..0000000000 --- a/core/modules/catalog/store/stock/actions.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { ActionTree } from 'vuex' -import * as stockMutationTypes from '@vue-storefront/core/modules/catalog/store/stock/mutation-types' -import RootState from '@vue-storefront/core/types/RootState' -import StockState from '../../types/StockState' -import config from 'config' -import { StockService } from '@vue-storefront/core/data-resolver' -import { getStatus, getProductInfos } from '@vue-storefront/core/modules/catalog/helpers/stock' -import { Logger } from '@vue-storefront/core/lib/logger' - -const actions: ActionTree = { - async queueCheck ({ dispatch }, { product }) { - const checkStatus = { - qty: product.stock ? product.stock.qty : 0, - status: getStatus(product, 'ok') - } - - if (config.stock.synchronize) { - const task = await StockService.queueCheck(product.sku, 'cart/stockSync') - - // @ts-ignore - Logger.debug(`Stock quantity checked for ${task.product_sku}, response time: ${task.transmited_at - task.created_at} ms`, 'stock')() - - return { - ...checkStatus, - onlineCheckTaskId: task.task_id - } - } - - return { - ...checkStatus, - status: getStatus(product, 'volatile') - } - }, - async check (context, { product }) { - if (config.stock.synchronize) { - const { result, task_id } = await StockService.check(product.sku) - - return { - qty: result ? result.qty : 0, - status: getStatus(result, 'ok'), - isManageStock: result.manage_stock, - onlineCheckTaskId: task_id - } - } - - return { - qty: product.stock ? product.stock.qty : 0, - status: getStatus(product, 'volatile') - } - }, - async list ({ commit }, { skus }) { - if (!config.stock.synchronize) return - - const task = await StockService.list(skus) - - if (task.resultCode === 200) { - const productInfos = getProductInfos(task.result) - - for (const productInfo of productInfos) { - commit(stockMutationTypes.SET_STOCK_CACHE_PRODUCT, { - productId: productInfo.product_id, - productInfo - }) - } - } - - return task - }, - clearCache ({ commit }) { - commit(stockMutationTypes.SET_STOCK_CACHE, {}) - } -} - -export default actions diff --git a/core/modules/catalog/store/stock/index.ts b/core/modules/catalog/store/stock/index.ts deleted file mode 100644 index 264dd31fc2..0000000000 --- a/core/modules/catalog/store/stock/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import StockState from '../../types/StockState' - -export const stockModule: Module = { - namespaced: true, - actions, - mutations, - state: { - cache: {} - } -} diff --git a/core/modules/catalog/store/stock/mutation-types.ts b/core/modules/catalog/store/stock/mutation-types.ts deleted file mode 100644 index 688b201064..0000000000 --- a/core/modules/catalog/store/stock/mutation-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SN_CATALOG = 'catalog' -export const SET_STOCK_CACHE = `${SN_CATALOG}/SET_STOCK_CACHE` -export const SET_STOCK_CACHE_PRODUCT = `${SN_CATALOG}/SET_STOCK_CACHE_PRODUCT` diff --git a/core/modules/catalog/store/stock/mutations.ts b/core/modules/catalog/store/stock/mutations.ts deleted file mode 100644 index b684e25992..0000000000 --- a/core/modules/catalog/store/stock/mutations.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { MutationTree } from 'vuex' -import StockState from '../../types/StockState' -import * as types from './mutation-types' - -const mutations: MutationTree = { - [types.SET_STOCK_CACHE] (state, cache) { - state.cache = cache - }, - [types.SET_STOCK_CACHE_PRODUCT] (state, { productId, productInfo }) { - state.cache = Object.assign({}, state.cache, { - [productId]: productInfo - }) - } -} - -export default mutations diff --git a/core/modules/catalog/store/tax/actions.ts b/core/modules/catalog/store/tax/actions.ts deleted file mode 100644 index 420c37613d..0000000000 --- a/core/modules/catalog/store/tax/actions.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import { SearchQuery } from 'storefront-query-builder' -import RootState from '@vue-storefront/core/types/RootState' -import TaxState from '../../types/TaxState' -import { Logger } from '@vue-storefront/core/lib/logger' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { entityKeyName } from '@vue-storefront/core/lib/store/entities' -import config from 'config' -import { calculateProductTax } from '@vue-storefront/core/modules/catalog/helpers/taxCalc' -import { doPlatformPricesSync } from '@vue-storefront/core/modules/catalog/helpers' -import { catalogHooksExecutors } from './../../hooks' -import { currentStoreView } from '@vue-storefront/core/lib/multistore'; - -const actions: ActionTree = { - async list ({ state, commit, dispatch }, { entityType = 'taxrule' }) { - if (state.rules.length > 0) { - Logger.info('Tax rules served from local memory', 'tax')() - return { items: state.rules } - } - - const resp = await quickSearchByQuery({ query: new SearchQuery(), entityType }) - dispatch('storeToRulesCache', { items: resp.items }) - commit(types.TAX_UPDATE_RULES, resp) - - return resp - }, - storeToRulesCache (context, { items }) { - const cache = StorageManager.get('elasticCache') - - for (let tc of items) { - const cacheKey = entityKeyName('tc', tc.id) - cache.setItem(cacheKey, tc).catch((err) => { - Logger.error('Cannot store cache for ' + cacheKey + ', ' + err)() - }) - } - }, - single ({ getters }, { productTaxClassId }) { - return getters.getRules.find((e) => - e.product_tax_class_ids.indexOf(parseInt(productTaxClassId)) >= 0 - ) - }, - async calculateTaxes ({ dispatch, getters, rootState }, { products }) { - const mutatedProducts = catalogHooksExecutors.beforeTaxesCalculated(products) - - if (config.tax.calculateServerSide) { - Logger.debug('Taxes calculated server side, skipping')() - return doPlatformPricesSync(mutatedProducts) - } - - let storeView = currentStoreView() - - const tcs = await dispatch('list', {}) - const { - defaultCountry, - defaultRegion, - sourcePriceIncludesTax, - finalPriceIncludesTax, - deprecatedPriceFieldsSupport - } = storeView.tax - - const recalculatedProducts = mutatedProducts.map(product => - calculateProductTax({ - product, - taxClasses: tcs.items, - taxCountry: defaultCountry, - taxRegion: defaultRegion, - finalPriceInclTax: finalPriceIncludesTax, - sourcePriceInclTax: sourcePriceIncludesTax, - userGroupId: getters.getUserTaxGroupId, - deprecatedPriceFieldsSupport: deprecatedPriceFieldsSupport, - isTaxWithUserGroupIsActive: getters.getIsUserGroupedTaxActive - }) - ) - - return doPlatformPricesSync(recalculatedProducts) - } -} - -export default actions diff --git a/core/modules/catalog/store/tax/getters.ts b/core/modules/catalog/store/tax/getters.ts deleted file mode 100644 index aa65d66f74..0000000000 --- a/core/modules/catalog/store/tax/getters.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import TaxState from '../../types/TaxState' -import { currentStoreView } from '@vue-storefront/core/lib/multistore'; - -const getters: GetterTree = { - getRules: (state) => state.rules, - getUserTaxGroupId: (state, getters, rootState) => { - if (!getters.getIsUserGroupedTaxActive) return - - const storeViewTax = rootState.storeView.tax - const currentUser = rootState.user.current - - if (storeViewTax.useOnlyDefaultUserGroupId || !currentUser) { - return storeViewTax.userGroupId - } - - return currentUser.group_id - }, - getIsUserGroupedTaxActive: (state, getters, rootState) => { - return typeof currentStoreView().tax.userGroupId === 'number' - } -} - -export default getters diff --git a/core/modules/catalog/store/tax/index.ts b/core/modules/catalog/store/tax/index.ts deleted file mode 100644 index c57a55eba9..0000000000 --- a/core/modules/catalog/store/tax/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import mutations from './mutations' -import getters from './getters' -import RootState from '@vue-storefront/core/types/RootState' -import TaxState from '../../types/TaxState' - -export const taxModule: Module = { - namespaced: true, - state: { - rules: [] - }, - actions, - mutations, - getters -} diff --git a/core/modules/catalog/store/tax/mutation-types.ts b/core/modules/catalog/store/tax/mutation-types.ts deleted file mode 100644 index b970dc05c5..0000000000 --- a/core/modules/catalog/store/tax/mutation-types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const SN_TAX = 'tax' -export const TAX_UPDATE_RULES = SN_TAX + '/UPDATE_RULES' diff --git a/core/modules/catalog/store/tax/mutations.ts b/core/modules/catalog/store/tax/mutations.ts deleted file mode 100644 index e731d955ac..0000000000 --- a/core/modules/catalog/store/tax/mutations.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import TaxState from '../../types/TaxState' - -const mutations: MutationTree = { - [types.TAX_UPDATE_RULES] (state, taxClasses) { - state.rules = taxClasses.items - } -} - -export default mutations diff --git a/core/modules/catalog/test/helpers/createProduct.ts b/core/modules/catalog/test/helpers/createProduct.ts deleted file mode 100644 index 2e3c689187..0000000000 --- a/core/modules/catalog/test/helpers/createProduct.ts +++ /dev/null @@ -1,96 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -export const createSimpleProduct = (): Product => ({ - 'id': 21, - 'sku': '24-WG084', - 'name': 'Sprite Foam Yoga Brick', - 'price': 5, - 'status': 1, - 'visibility': 4, - 'type_id': 'simple', - 'product_links': [], - 'custom_attributes': null, - 'final_price': 5, - 'max_price': 5, - 'max_regular_price': 5, - 'minimal_regular_price': 5, - 'special_price': null, - 'minimal_price': 5, - 'regular_price': 5, - 'description': '

Our top-selling yoga prop, the 4-inch, high-quality Sprite Foam Yoga Brick is popular among yoga novices and studio professionals alike. An essential yoga accessory, the yoga brick is a critical tool for finding balance and alignment in many common yoga poses. Choose from 5 color options.

', - 'image': '/l/u/luma-yoga-brick.jpg', - 'small_image': '/l/u/luma-yoga-brick.jpg', - 'thumbnail': '/l/u/luma-yoga-brick.jpg', - 'color': '50', - 'category_ids': [3, 5], - 'url_key': 'sprite-foam-yoga-brick', - 'tax_class_id': '2', - 'slug': 'sprite-foam-yoga-brick-21', - 'media_gallery': [{ 'image': '/l/u/luma-yoga-brick.jpg', 'pos': 1, 'typ': 'image', 'lab': 'Image', 'vid': null }], - 'stock': { 'item_id': 21, 'product_id': 21, 'stock_id': 1, 'qty': 986, 'is_in_stock': true, 'is_qty_decimal': false, 'show_default_notification_message': false, 'use_config_min_qty': true, 'min_qty': 0, 'use_config_min_sale_qty': 1, 'min_sale_qty': 1, 'use_config_max_sale_qty': true, 'max_sale_qty': 10000, 'use_config_backorders': true, 'backorders': 0, 'use_config_notify_stock_qty': true, 'notify_stock_qty': 1, 'use_config_qty_increments': true, 'qty_increments': 0, 'use_config_enable_qty_inc': true, 'enable_qty_increments': false, 'use_config_manage_stock': true, 'manage_stock': true, 'low_stock_date': null, 'is_decimal_divided': false, 'stock_status_changed_auto': 0 }, - 'category': [{ 'category_id': 3, 'name': 'Gear', 'slug': 'gear-3', 'path': 'gear/gear-3' }, { 'category_id': 5, 'name': 'Fitness Equipment', 'slug': 'fitness-equipment-5', 'path': 'gear/fitness-equipment/fitness-equipment-5' }], - 'url_path': 'gear/gear-3/sprite-foam-yoga-brick-21.html' -}) - -export const createGroupProduct = (): Product => ({ - 'id': 2046, - 'sku': '24-WG085_Group', - 'name': 'Set of Sprite Yoga Straps', - 'status': 1, - 'visibility': 4, - 'type_id': 'grouped', - 'product_links': [{ 'sku': '24-WG085_Group', 'link_type': 'associated', 'linked_product_sku': '24-WG085', 'linked_product_type': 'simple', 'position': 0, 'extension_attributes': { 'qty': 20 } }, { 'sku': '24-WG085_Group', 'link_type': 'associated', 'linked_product_sku': '24-WG086', 'linked_product_type': 'simple', 'position': 1, 'extension_attributes': { 'qty': 30 } }, { 'sku': '24-WG085_Group', 'link_type': 'associated', 'linked_product_sku': '24-WG087', 'linked_product_type': 'simple', 'position': 2, 'extension_attributes': { 'qty': 40 } }], - 'custom_attributes': null, - 'final_price': 12, - 'price': 12, - 'max_price': 12, - 'max_regular_price': 12, - 'minimal_regular_price': 12, - 'special_price': null, - 'minimal_price': 12, - 'regular_price': 0, - 'description': '

Great set of Sprite Yoga Straps for every stretch and hold you need. There are three straps in this set: 6', - 'image': '/l/u/luma-yoga-strap-set.jpg', - 'small_image': '/l/u/luma-yoga-strap-set.jpg', - 'thumbnail': '/l/u/luma-yoga-strap-set.jpg', - 'category_ids': [3, 5], - 'url_key': 'set-of-sprite-yoga-straps', - 'slug': 'set-of-sprite-yoga-straps-2046', - 'links': { 'associated': [{ 'sku': '24-WG085', 'pos': 0 }, { 'sku': '24-WG086', 'pos': 1 }, { 'sku': '24-WG087', 'pos': 2 }] }, - 'stock': { 'item_id': 2046, 'product_id': 2046, 'stock_id': 1, 'qty': 0, 'is_in_stock': true, 'is_qty_decimal': false, 'show_default_notification_message': false, 'use_config_min_qty': true, 'min_qty': 0, 'use_config_min_sale_qty': 1, 'min_sale_qty': 1, 'use_config_max_sale_qty': true, 'max_sale_qty': 10000, 'use_config_backorders': true, 'backorders': 0, 'use_config_notify_stock_qty': true, 'notify_stock_qty': 1, 'use_config_qty_increments': true, 'qty_increments': 0, 'use_config_enable_qty_inc': true, 'enable_qty_increments': false, 'use_config_manage_stock': true, 'manage_stock': true, 'low_stock_date': null, 'is_decimal_divided': false, 'stock_status_changed_auto': 0 }, - 'media_gallery': [{ 'image': '/l/u/luma-yoga-strap-set.jpg', 'pos': 1, 'typ': 'image', 'lab': 'Image', 'vid': null }], - 'category': [{ 'category_id': 3, 'name': 'Gear', 'slug': 'gear-3', 'path': 'gear/gear-3' }, { 'category_id': 5, 'name': 'Fitness Equipment', 'slug': 'fitness-equipment-5', 'path': 'gear/fitness-equipment/fitness-equipment-5' }], - 'url_path': 'gear/gear-3/set-of-sprite-yoga-straps-2046.html' -}) - -export const createBundleProduct = (): Product => ({ - 'id': 45, - 'sku': '24-WG080', - 'name': 'Sprite Yoga Companion Kit', - 'price': 0, - 'status': 1, - 'visibility': 4, - 'type_id': 'bundle', - 'product_links': [], - 'custom_attributes': null, - 'final_price': 64, - 'max_price': 77, - 'max_regular_price': 77, - 'minimal_regular_price': 64, - 'special_price': null, - 'minimal_price': 64, - 'regular_price': 64, - 'description': '

A well-rounded yoga workout takes more than a mat. The Sprite Yoga Companion Kit helps stock your studio with the basics you need for a full-range workout. The kit is composed of four best-selling Luma Sprite accessories in one easy bundle: statis ball, foam block, yoga strap, and foam roller. Choose sizes and colors and leave the rest to us. The kit includes:

  • Sprite Statis Ball
  • Sprite Foam Yoga Brick
  • Sprite Yoga Strap
  • Sprite Foam Roller
', - 'image': '/l/u/luma-yoga-kit-2.jpg', - 'small_image': '/l/u/luma-yoga-kit-2.jpg', - 'thumbnail': '/l/u/luma-yoga-kit-2.jpg', - 'category_ids': [3, 5], - 'url_key': 'sprite-yoga-companion-kit', - 'tax_class_id': '2', - 'slug': 'sprite-yoga-companion-kit-45', - 'stock': { 'item_id': 45, 'product_id': 45, 'stock_id': 1, 'qty': 0, 'is_in_stock': true, 'is_qty_decimal': false, 'show_default_notification_message': false, 'use_config_min_qty': true, 'min_qty': 0, 'use_config_min_sale_qty': 1, 'min_sale_qty': 1, 'use_config_max_sale_qty': true, 'max_sale_qty': 10000, 'use_config_backorders': true, 'backorders': 0, 'use_config_notify_stock_qty': true, 'notify_stock_qty': 1, 'use_config_qty_increments': true, 'qty_increments': 0, 'use_config_enable_qty_inc': true, 'enable_qty_increments': false, 'use_config_manage_stock': true, 'manage_stock': true, 'low_stock_date': null, 'is_decimal_divided': false, 'stock_status_changed_auto': 0 }, - 'media_gallery': [{ 'image': '/l/u/luma-yoga-kit-2.jpg', 'pos': 1, 'typ': 'image', 'lab': 'Image', 'vid': null }], - 'bundle_options': [{ 'option_id': 1, 'title': 'Sprite Stasis Ball', 'required': true, 'type': 'radio', 'position': 1, 'sku': '24-WG080', 'product_links': [{ 'id': '1', 'sku': '24-WG081-blue', 'option_id': 1, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '2', 'sku': '24-WG082-blue', 'option_id': 1, 'qty': 1, 'position': 2, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '3', 'sku': '24-WG083-blue', 'option_id': 1, 'qty': 1, 'position': 3, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }, { 'option_id': 2, 'title': 'Sprite Foam Yoga Brick', 'required': true, 'type': 'radio', 'position': 2, 'sku': '24-WG080', 'product_links': [{ 'id': '4', 'sku': '24-WG084', 'option_id': 2, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }, { 'option_id': 3, 'title': 'Sprite Yoga Strap', 'required': true, 'type': 'radio', 'position': 3, 'sku': '24-WG080', 'product_links': [{ 'id': '5', 'sku': '24-WG085', 'option_id': 3, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '6', 'sku': '24-WG086', 'option_id': 3, 'qty': 1, 'position': 2, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }, { 'id': '7', 'sku': '24-WG087', 'option_id': 3, 'qty': 1, 'position': 3, 'is_default': false, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }, { 'option_id': 4, 'title': 'Sprite Foam Roller', 'required': true, 'type': 'radio', 'position': 4, 'sku': '24-WG080', 'product_links': [{ 'id': '8', 'sku': '24-WG088', 'option_id': 4, 'qty': 1, 'position': 1, 'is_default': true, 'price': null, 'price_type': null, 'can_change_quantity': 1 }] }], - 'category': [{ 'category_id': 3, 'name': 'Gear', 'slug': 'gear-3', 'path': 'gear/gear-3' }, { 'category_id': 5, 'name': 'Fitness Equipment', 'slug': 'fitness-equipment-5', 'path': 'gear/fitness-equipment/fitness-equipment-5' }], - 'url_path': 'gear/gear-3/sprite-yoga-companion-kit-45.html' -}) diff --git a/core/modules/catalog/test/unit/helpers/associatedProducts/setBundleProduct.spec.ts b/core/modules/catalog/test/unit/helpers/associatedProducts/setBundleProduct.spec.ts deleted file mode 100644 index edb960af1f..0000000000 --- a/core/modules/catalog/test/unit/helpers/associatedProducts/setBundleProduct.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createBundleProduct, createGroupProduct, createSimpleProduct } from '../../../helpers/createProduct'; -import setBundleProducts from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setBundleProducts'; -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/store', () => ({})); -jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({ - ProductService: { - getProducts: jest.fn(), - getProductRenderList: jest.fn(), - getProductByKey: jest.fn() - } -})); - -describe('setBundleProducts helper', () => { - beforeEach(() => { - jest.clearAllMocks() - ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({ items: [] })); - }) - it('should not fire ProductService.getProducts if it is not bundle product', async () => { - const groupedProduct = createGroupProduct() - - setBundleProducts(groupedProduct) - - expect(ProductService.getProducts).toHaveBeenCalledTimes(0) - }) - it('should fire ProductService.getProducts with simple configuration', async () => { - const bundleProduct = createBundleProduct() - - setBundleProducts(bundleProduct) - - expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, { - query: expect.anything(), - excludeFields: null, - includeFields: null, - options: { - prefetchGroupProducts: false, - fallbackToDefaultWhenNoAvailable: false, - setProductErrors: false, - setConfigurableProductOptions: false, - assignProductConfiguration: false, - separateSelectedVariant: false - } - }) - }) -}) diff --git a/core/modules/catalog/test/unit/helpers/associatedProducts/setGroupedProduct.spec.ts b/core/modules/catalog/test/unit/helpers/associatedProducts/setGroupedProduct.spec.ts deleted file mode 100644 index 752de2cc11..0000000000 --- a/core/modules/catalog/test/unit/helpers/associatedProducts/setGroupedProduct.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createBundleProduct, createGroupProduct, createSimpleProduct } from '../../../helpers/createProduct'; -import setGroupedProduct from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setGroupedProduct'; -import setProductLink from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setProductLink'; -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/store', () => ({})); -jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({ - ProductService: { - getProducts: jest.fn(), - getProductRenderList: jest.fn(), - getProductByKey: jest.fn() - } -})); -jest.mock('@vue-storefront/core/modules/catalog/helpers/associatedProducts/setProductLink', () => jest.fn()); - -describe('setGroupedProduct helper', () => { - beforeEach(() => { - jest.clearAllMocks() - ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({ items: [] })); - }) - it('should not fire ProductService.getProducts if it is not grouped product', async () => { - const bundleProduct = createBundleProduct() - - setGroupedProduct(bundleProduct) - - expect(ProductService.getProducts).toHaveBeenCalledTimes(0) - }) - it('should fire ProductService.getProducts with simple configuration', async () => { - const groupedProduct = createGroupProduct() - - setGroupedProduct(groupedProduct) - - expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, { - query: expect.anything(), - excludeFields: null, - includeFields: null, - options: { - prefetchGroupProducts: false, - fallbackToDefaultWhenNoAvailable: false, - setProductErrors: false, - setConfigurableProductOptions: false, - assignProductConfiguration: false, - separateSelectedVariant: false - } - }) - }) -}) diff --git a/core/modules/catalog/test/unit/helpers/associatedProducts/setProductLink.spec.ts b/core/modules/catalog/test/unit/helpers/associatedProducts/setProductLink.spec.ts deleted file mode 100644 index 0dd2cff216..0000000000 --- a/core/modules/catalog/test/unit/helpers/associatedProducts/setProductLink.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createBundleProduct, createGroupProduct, createSimpleProduct } from '../../../helpers/createProduct'; -import setProductLink from '@vue-storefront/core/modules/catalog/helpers/associatedProducts/setProductLink'; - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/store', () => ({})); - -describe('setProductLink helper', () => { - it('should add product if associatedProduct exist for bundle link', async () => { - const bundleProduct = createBundleProduct() - const productLink = bundleProduct.bundle_options[0].product_links[0] - const simpleProduct = createSimpleProduct() - - setProductLink(productLink, simpleProduct) - - expect(productLink.product).toStrictEqual(simpleProduct) - expect(productLink.product.qty).toBe(1) - }) - it('should add product if associatedProduct exist for group link', async () => { - const groupProduct = createGroupProduct() - const productLink = groupProduct.product_links[0] - const simpleProduct = createSimpleProduct() - - setProductLink(productLink, simpleProduct) - - expect(productLink.product).toStrictEqual(simpleProduct) - expect(productLink.product.qty).toBe(1) - }) - it('should not add product if associatedProduct doesn\'t exist', async () => { - const groupProduct = createGroupProduct() - const productLink = groupProduct.product_links[0] - - setProductLink(productLink, null) - - expect(productLink.product).toBe(undefined) - }) -}) diff --git a/core/modules/catalog/test/unit/helpers/filters.spec.ts b/core/modules/catalog/test/unit/helpers/filters.spec.ts deleted file mode 100644 index 7a4f542119..0000000000 --- a/core/modules/catalog/test/unit/helpers/filters.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { ProductConfiguration } from '@vue-storefront/core/modules/catalog/types/ProductConfiguration'; -import { - getAvailableFiltersByProduct, - getSelectedFiltersByProduct -} from '@vue-storefront/core/modules/catalog/helpers/filters'; - -const product = ({ - configurable_options: [ - { - attribute_id: '93', - values: [ - { value_index: 50, label: 'Blue' }, - { value_index: 52, label: 'Gray' }, - { value_index: 58, label: 'Red' } - ], - product_id: 755, - id: 101, - label: 'Color', - position: 1, - attribute_code: 'color' - }, - { - attribute_id: '142', - values: [ - { value_index: 176, label: '32' }, - { value_index: 177, label: '33' }, - { value_index: 178, label: '34' }, - { value_index: 179, label: '36' } - ], - product_id: 755, - id: 100, - label: 'Size', - position: 0, - attribute_code: 'size' - } - ] -} as any) as Product; - -describe('Product configuration', () => { - it('returns available filters based on given product', () => { - const availableFilters = getAvailableFiltersByProduct(product) - - expect(availableFilters).toEqual({ - color: [ - { id: 50, label: 'Blue', type: 'color' }, - { id: 52, label: 'Gray', type: 'color' }, - { id: 58, label: 'Red', type: 'color' } - ], - size: [ - { id: 176, label: '32', type: 'size' }, - { id: 177, label: '33', type: 'size' }, - { id: 178, label: '34', type: 'size' }, - { id: 179, label: '36', type: 'size' } - ] - }); - }); - - it('returns selected filters based on given product and current configuration', () => { - const configuration: ProductConfiguration = { - color: { - attribute_code: 'color', - id: 52, - label: 'Gray' - }, - size: { - attribute_code: 'size', - id: 177, - label: '33' - } - } - const selectedFilters = getSelectedFiltersByProduct(product, configuration) - - expect(selectedFilters).toEqual({ - color: { id: 52, label: 'Gray', type: 'color' }, - size: { id: 177, label: '33', type: 'size' } - }) - }); -}); diff --git a/core/modules/catalog/test/unit/store/product/actions/findProducts.spec.ts b/core/modules/catalog/test/unit/store/product/actions/findProducts.spec.ts deleted file mode 100644 index f9aad29f23..0000000000 --- a/core/modules/catalog/test/unit/store/product/actions/findProducts.spec.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { DataResolver } from '@vue-storefront/core/data-resolver/types/DataResolver'; -import productActions from '@vue-storefront/core/modules/catalog/store/product/actions'; -import config from 'config'; -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' -import { registerProductsMapping, setRequestCacheTags } from '@vue-storefront/core/modules/catalog/helpers' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - registerProductsMapping: jest.fn(), - setRequestCacheTags: jest.fn() -})) - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/compatibility/plugins/event-bus', () => ({ - $emit: jest.fn() -})); -jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({ - ProductService: { - getProducts: jest.fn(), - getProductRenderList: jest.fn(), - getProductByKey: jest.fn() - } -})); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/modules/catalog/events', () => ({ - checkParentRedirection: (str) => jest.fn() -})) - -describe('product/findProducts action', () => { - let contextMock - let items - beforeEach(() => { - jest.clearAllMocks() - contextMock = { - dispatch: jest.fn(() => ({})) - } - items = [{ url_path: 'dsada', sku: 'dsad' }] - config.cart = { - setConfigurableProductOptions: true - } - config.products = { - filterUnavailableVariants: false - } - ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({ items })); - }) - it('should trigger ProductService.getProducts with default values', async () => { - const wrapper = (actions: any) => actions.findProducts(contextMock) - - await wrapper(productActions) - - expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, { - query: undefined, - start: 0, - size: 50, - sort: '', - excludeFields: null, - includeFields: null, - configuration: null, - options: { - prefetchGroupProducts: true, - setProductErrors: false, - fallbackToDefaultWhenNoAvailable: true, - assignProductConfiguration: false, - separateSelectedVariant: false, - setConfigurableProductOptions: true, - filterUnavailableVariants: false - } - }) - }) - - it('should trigger ProductService.getProducts with provided values', async () => { - const wrapper = (actions: any) => actions.findProducts(contextMock, { - query: { test: 'test' }, - start: 123, - size: 1221, - sort: 'test', - excludeFields: ['test'], - includeFields: ['test'], - configuration: { test: 'test' }, - populateRequestCacheTags: true, - options: { - populateRequestCacheTags: true, - prefetchGroupProducts: false, - setProductErrors: true, - fallbackToDefaultWhenNoAvailable: false, - assignProductConfiguration: true, - separateSelectedVariant: true, - setConfigurableProductOptions: false, - filterUnavailableVariants: false - } - }) - - await wrapper(productActions) - - expect(ProductService.getProducts).toHaveBeenNthCalledWith(1, { - query: { test: 'test' }, - start: 123, - size: 1221, - sort: 'test', - excludeFields: ['test'], - includeFields: ['test'], - configuration: { test: 'test' }, - options: { - prefetchGroupProducts: false, - setProductErrors: true, - fallbackToDefaultWhenNoAvailable: false, - assignProductConfiguration: true, - separateSelectedVariant: true, - setConfigurableProductOptions: false, - filterUnavailableVariants: false - } - }) - }) - it('should register mapping for products returned from ProductService.getProducts', async () => { - const wrapper = (actions: any) => actions.findProducts(contextMock) - - await wrapper(productActions) - - expect(registerProductsMapping).toHaveBeenNthCalledWith(1, contextMock, items) - }) - it('should not set cache tags if populateRequestCacheTags or options.populateRequestCacheTags is false', async () => { - const wrapper = (actions: any) => actions.findProducts(contextMock) - - await wrapper(productActions) - - expect(setRequestCacheTags).toHaveBeenCalledTimes(0) - }) - it('should set cache tags if populateRequestCacheTags is true', async () => { - const wrapper = (actions: any) => actions.findProducts(contextMock, { populateRequestCacheTags: true }) - - await wrapper(productActions) - - expect(setRequestCacheTags).toHaveBeenNthCalledWith(1, { products: items }) - }) - it('should set cache tags if options.populateRequestCacheTags is true', async () => { - const wrapper = (actions: any) => actions.findProducts(contextMock, { options: { populateRequestCacheTags: true } }) - - await wrapper(productActions) - - expect(setRequestCacheTags).toHaveBeenNthCalledWith(1, { products: items }) - }) - it('should mutatate prices by triggering tax/calculateTaxes', async () => { - const wrapper = (actions: any) => actions.findProducts(contextMock) - - await wrapper(productActions) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'tax/calculateTaxes', { products: items }, { root: true }) - }) - it('should return items and rest response data as one object', async () => { - ;(ProductService.getProducts as jest.Mock).mockImplementation(async () => ({ - items, - perPage: 1, - start: 2, - total: 3, - aggregations: [], - attributeMetadata: [] - })); - - const wrapper = (actions: any) => actions.findProducts(contextMock) - - const result: DataResolver.ProductsListResponse = await wrapper(productActions) - - expect(result).toStrictEqual({ - items, - perPage: 1, - start: 2, - total: 3, - aggregations: [], - attributeMetadata: [] - }) - }) -}) diff --git a/core/modules/catalog/test/unit/store/product/actions/getProductVariant.spec.ts b/core/modules/catalog/test/unit/store/product/actions/getProductVariant.spec.ts deleted file mode 100644 index 7d9b9e8ab6..0000000000 --- a/core/modules/catalog/test/unit/store/product/actions/getProductVariant.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import productActions from '@vue-storefront/core/modules/catalog/store/product/actions'; - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn() -})); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/modules/catalog/events', () => ({ - checkParentRedirection: (str) => jest.fn() -})) - -describe('product/getProductVariant action', () => { - let contextMock - beforeEach(() => { - jest.clearAllMocks() - contextMock = { - dispatch: jest.fn(() => ({ items: ['test'] })), - commit: jest.fn(() => ({})) - } - }) - it('should throw error if no arguments is provided', async () => { - const wrapper = (actions: any) => actions.getProductVariant(contextMock) - await expect(wrapper(productActions)).rejects.toThrow(expect.anything()) - }) - it('should throw error if product doesn\'t have parentSku', async () => { - const wrapper = (actions: any) => actions.getProductVariant(contextMock, { product: { sku: 'sku' } }) - await expect(wrapper(productActions)).rejects.toThrow(expect.anything()) - }) - - it('should dispatch findProducts', async () => { - const wrapper = (actions: any) => actions.getProductVariant(contextMock, { product: { parentSku: 'sku' } }) - - await wrapper(productActions) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProducts', { - query: expect.anything(), - size: 1, - configuration: undefined, - options: { - fallbackToDefaultWhenNoAvailable: false, - setProductErrors: true, - separateSelectedVariant: true - } - }) - }) - - it('should return options and product_option beside variant data', async () => { - contextMock = { - dispatch: jest.fn(() => ({ items: [{ sku: 'sku', options: ['test'], product_option: ['test2'] }] })) - } - const wrapper = (actions: any) => actions.getProductVariant(contextMock, { product: { parentSku: 'sku' } }) - - const result = await wrapper(productActions) - - expect(result.options).toStrictEqual(['test']) - expect(result.product_option).toStrictEqual(['test2']) - }) -}) diff --git a/core/modules/catalog/test/unit/store/product/actions/list.spec.ts b/core/modules/catalog/test/unit/store/product/actions/list.spec.ts deleted file mode 100644 index f9c9dd1d2d..0000000000 --- a/core/modules/catalog/test/unit/store/product/actions/list.spec.ts +++ /dev/null @@ -1,137 +0,0 @@ -import productActions from '@vue-storefront/core/modules/catalog/store/product/actions'; -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import * as mutationTypes from '@vue-storefront/core/modules/catalog/store/product/mutation-types' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/compatibility/plugins/event-bus', () => ({ - $emit: jest.fn() -})); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/modules/catalog/events', () => ({ - checkParentRedirection: (str) => jest.fn() -})) - -describe('product/list action', () => { - it('should dispatch findProducts with default values for list', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => ({ items: ['test'] })) - } - const wrapper = (actions: any) => actions.list(contextMock) - - await wrapper(productActions) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProducts', { - query: undefined, - start: 0, - size: 50, - sort: '', - excludeFields: null, - includeFields: null, - configuration: null, - options: { - populateRequestCacheTags: true, - prefetchGroupProducts: true - } - }) - }) - - it('should dispatch findProducts with provided values for list', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => ({ items: ['test'] })) - } - const wrapper = (actions: any) => actions.list(contextMock, { - query: { test: 'test' }, - start: 1, - size: 10, - sort: 'final_price', - excludeFields: [], - includeFields: [], - configuration: { test: 'test' }, - populateRequestCacheTags: false, - prefetchGroupProducts: false - }) - - await wrapper(productActions) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'findProducts', { - query: { test: 'test' }, - start: 1, - size: 10, - sort: 'final_price', - excludeFields: [], - includeFields: [], - configuration: { test: 'test' }, - options: { - populateRequestCacheTags: false, - prefetchGroupProducts: false - } - }) - }) - - it('should emit "product-after-list" event', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => ({ items: ['test'] })) - } - const wrapper = (actions: any) => actions.list(contextMock) - - await wrapper(productActions) - - expect(EventBus.$emit).toHaveBeenCalledWith('product-after-list', expect.anything()) - }) - - it('should not update state by deafult', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => ({ items: ['test'] })) - } - const wrapper = (actions: any) => actions.list(contextMock) - - await wrapper(productActions) - - expect(contextMock.commit).toHaveBeenCalledTimes(0) - }) - - it('should not append state by deafult if update store', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => ({ items: ['test'] })) - } - const wrapper = (actions: any) => actions.list(contextMock, { updateState: true }) - - await wrapper(productActions) - - expect(contextMock.commit).toHaveBeenCalledWith(mutationTypes.PRODUCT_SET_PAGED_PRODUCTS, { items: ['test'] }) - }) - - it('should append state', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => ({ items: ['test'] })) - } - const wrapper = (actions: any) => actions.list(contextMock, { updateState: true, append: true }) - - await wrapper(productActions) - - expect(contextMock.commit).toHaveBeenCalledWith(mutationTypes.PRODUCT_ADD_PAGED_PRODUCTS, { items: ['test'] }) - }) -}) diff --git a/core/modules/catalog/test/unit/store/product/actions/setCurrent.spec.ts b/core/modules/catalog/test/unit/store/product/actions/setCurrent.spec.ts deleted file mode 100644 index 774840a4d6..0000000000 --- a/core/modules/catalog/test/unit/store/product/actions/setCurrent.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import productActions from '@vue-storefront/core/modules/catalog/store/product/actions'; -import config from 'config'; -import * as mutationTypes from '@vue-storefront/core/modules/catalog/store/product/mutation-types' -import { getProductConfigurationOptions } from '@vue-storefront/core/modules/catalog/helpers/productOptions' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - commit: jest.fn() -})); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/modules/catalog/helpers/productOptions', () => ({ - getProductConfigurationOptions: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/catalog/events', () => ({ - checkParentRedirection: (str) => jest.fn() -})) - -describe('product/setCurrent action', () => { - let contextMock - let product - beforeEach(() => { - jest.clearAllMocks() - contextMock = { - dispatch: jest.fn(() => ({})), - commit: jest.fn(() => ({})), - rootState: { - attribute: {} - } - } - config.products = { - gallery: { - mergeConfigurableChildren: false - } - } - product = { - sku: 'sku', - configuration: { color: 42 } - } - }) - it('should return if no product provided', async () => { - const wrapper = (actions: any) => actions.setCurrent(contextMock) - await wrapper(productActions) - expect(contextMock.dispatch).toBeCalledTimes(0) - expect(contextMock.commit).toBeCalledTimes(0) - }) - it('should commit product data and configuration', async () => { - ;(getProductConfigurationOptions as jest.Mock).mockImplementation(() => ({ color: [{ attribute_code: 'color', id: '42', label: 'Green' }] })); - const wrapper = (actions: any) => actions.setCurrent(contextMock, product) - await wrapper(productActions) - expect(contextMock.commit).toHaveBeenNthCalledWith(1, mutationTypes.PRODUCT_SET_CURRENT_OPTIONS, { color: [{ attribute_code: 'color', id: '42', label: 'Green' }] }) - expect(contextMock.commit).toHaveBeenNthCalledWith(2, mutationTypes.PRODUCT_SET_CURRENT_CONFIGURATION, { color: 42 }) - expect(contextMock.commit).toHaveBeenNthCalledWith(3, mutationTypes.PRODUCT_SET_CURRENT, { sku: 'sku' }) - }) - it('should call setProductGallery if mergeConfigurableChildren is set false', async () => { - const wrapper = (actions: any) => actions.setCurrent(contextMock, product) - await wrapper(productActions) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'setProductGallery', { product: { sku: 'sku' } }) - }) - it('should not call setProductGallery if mergeConfigurableChildren is set true', async () => { - config.products = { - gallery: { - mergeConfigurableChildren: true - } - } - const wrapper = (actions: any) => actions.setCurrent(contextMock, product) - await wrapper(productActions) - expect(contextMock.dispatch).toHaveBeenCalledTimes(0) - }) -}) diff --git a/core/modules/catalog/test/unit/store/product/actions/single.spec.ts b/core/modules/catalog/test/unit/store/product/actions/single.spec.ts deleted file mode 100644 index 468ad22ea6..0000000000 --- a/core/modules/catalog/test/unit/store/product/actions/single.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import productActions from '@vue-storefront/core/modules/catalog/store/product/actions'; -import config from 'config'; -import { ProductService } from '@vue-storefront/core/data-resolver/ProductService' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) -jest.mock('@vue-storefront/core/modules/catalog/helpers', () => ({ - registerProductsMapping: jest.fn(), - setRequestCacheTags: jest.fn() -})) - -jest.mock('@vue-storefront/core/store', () => ({ - dispatch: jest.fn(), - state: {} -})); -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/compatibility/plugins/event-bus', () => ({ - $emit: jest.fn(), - $emitFilter: jest.fn() -})); -jest.mock('@vue-storefront/core/data-resolver/ProductService', () => ({ - ProductService: { - getProducts: jest.fn(), - getProductRenderList: jest.fn(), - getProductByKey: jest.fn() - } -})); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/modules/catalog/events', () => ({ - checkParentRedirection: (str) => jest.fn() -})) - -describe('product/single action', () => { - let contextMock - let product - let options - beforeEach(() => { - jest.clearAllMocks() - contextMock = { - dispatch: jest.fn(() => ({})) - } - options = { sku: 'sku' } - product = [{ url_path: 'dsada', sku: 'dsad' }] - config.cart = { - setConfigurableProductOptions: true - } - config.products = { - filterUnavailableVariants: false - } - ;(ProductService.getProductByKey as jest.Mock).mockImplementation(async () => product); - }) - it('should throw error if there is no option value based on key', async () => { - const wrapper = (actions: any) => actions.single(contextMock) - - await expect(wrapper(productActions)).rejects.toThrow('Please provide the search key sku for product/single action!') - }) - it('should trigger ProductService.getProductByKey with default values', async () => { - const wrapper = (actions: any) => actions.single(contextMock, { options }) - await wrapper(productActions) - expect(ProductService.getProductByKey).toHaveBeenCalledWith({ - options: { sku: 'sku' }, - key: 'sku', - skipCache: false - }) - }) - it('should mutatate prices by triggering tax/calculateTaxes', async () => { - const wrapper = (actions: any) => actions.single(contextMock, { options }) - - await wrapper(productActions) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'tax/calculateTaxes', { products: [product] }, { root: true }) - }) - it('should set current product', async () => { - const wrapper = (actions: any) => actions.single(contextMock, { options, setCurrentProduct: true }) - - await wrapper(productActions) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'setCurrent', product) - }) - it('should emit "product-after-single" event', async () => { - const wrapper = (actions: any) => actions.single(contextMock, { options }) - - await wrapper(productActions) - - expect(EventBus.$emitFilter).toHaveBeenCalledWith('product-after-single', expect.anything()) - }) -}) diff --git a/core/modules/catalog/types/Attribute.ts b/core/modules/catalog/types/Attribute.ts deleted file mode 100644 index 85baf9711c..0000000000 --- a/core/modules/catalog/types/Attribute.ts +++ /dev/null @@ -1,23 +0,0 @@ -export default interface Attribute { - attribute_code?: string, - attribute_id?: number | string -} - -export interface AttributesMetadata { - is_visible_on_front: string, - is_visible: boolean, - default_frontend_label: string, - attribute_id: number, - entity_type_id: string, - id: number, - frontend_input: string, - is_user_defined: boolean, - is_comparable: string, - attribute_code: string, - options: AttributesMetadataOptions[] -} - -export interface AttributesMetadataOptions { - label: string, - value: string -} diff --git a/core/modules/catalog/types/AttributeState.ts b/core/modules/catalog/types/AttributeState.ts deleted file mode 100644 index 21f9f76022..0000000000 --- a/core/modules/catalog/types/AttributeState.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default interface AttributeState { - list_by_code: any, - list_by_id: any, - labels: any, - blacklist: any[] -} diff --git a/core/modules/catalog/types/BundleOption.ts b/core/modules/catalog/types/BundleOption.ts deleted file mode 100644 index b2d9b3809b..0000000000 --- a/core/modules/catalog/types/BundleOption.ts +++ /dev/null @@ -1,30 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; - -export interface BundleOption { - option_id: number, - title: string, - required: boolean, - type: string, - position: number, - sku: string, - product_links: BundleOptionsProductLink[] -} - -export interface BundleOptionsProductLink { - id: string | number, - sku: string, - option_id: number, - qty: number, - position: number, - is_default: boolean, - price?: number, - price_type?: number, - can_change_quantity: number, - product?: Product -} - -export interface SelectedBundleOption { - option_id: number, - option_qty: number, - option_selections: number[] -} diff --git a/core/modules/catalog/types/CategoryState.ts b/core/modules/catalog/types/CategoryState.ts deleted file mode 100644 index ba13ec9c6c..0000000000 --- a/core/modules/catalog/types/CategoryState.ts +++ /dev/null @@ -1,13 +0,0 @@ -export default interface CategoryState { - list: any, - current: any, - filters: { - available: any, - chosen: any - }, - breadcrumbs: { - routes: any - }, - current_product_query: any, - current_path: any -} diff --git a/core/modules/catalog/types/ConfigurableOption.ts b/core/modules/catalog/types/ConfigurableOption.ts deleted file mode 100644 index 854464d43c..0000000000 --- a/core/modules/catalog/types/ConfigurableOption.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ConfigurableItemOption { - label: string, - option_id: string, - option_value: string, - value: string -} diff --git a/core/modules/catalog/types/CustomOption.ts b/core/modules/catalog/types/CustomOption.ts deleted file mode 100644 index ad41b66ab3..0000000000 --- a/core/modules/catalog/types/CustomOption.ts +++ /dev/null @@ -1,29 +0,0 @@ -export interface CustomOption { - image_size_x: number, - image_size_y: number, - is_require: boolean, - max_characters: number, - option_id: number, - product_sku: string, - sort_order: number, - title: string, - type: string, - price?: number, - price_type?: string, - values?: OptionValue[] -} - -export interface OptionValue { - option_type_id: number, - price: number, - price_type: string, - sort_order: number, - title: string -} - -export type InputValue = string | number | number[] - -export interface SelectedCustomOption { - option_id: number, - option_value?: string -} diff --git a/core/modules/catalog/types/Product.ts b/core/modules/catalog/types/Product.ts deleted file mode 100644 index 5f53bf254f..0000000000 --- a/core/modules/catalog/types/Product.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { ProductOption } from './ProductConfiguration'; -import { ConfigurableItemOption } from './ConfigurableOption'; -import { BundleOption, SelectedBundleOption } from './BundleOption'; -import { AttributesMetadata } from './Attribute'; -import { CustomOption } from './CustomOption'; - -export default interface Product { - attributes_metadata?: AttributesMetadata[], - bundle_options?: BundleOption[], - category: Record[], - category_ids: string[] | number[], - color?: string, - color_options?: number[] | string[], - configurable_children?: Record[], - configurable_options?: ProductOption[], - custom_attributes?: any, - custom_options?: CustomOption[], - description: string, - errors?: Record, - final_price?: number, - finalPrice?: number, - gift_message_available?: string, - has_options?: string, - id?: number | string, - image: string, - info?: Record, - is_configured?: true, - material?: string, - max_price?: number, - max_regular_price?: number, - media_gallery: Record[], - minimal_price: number, - minimal_regular_price: number, - name: string, - new?: string, - options?: Record[], - parentSku?: string, - pattern?: string, - price: number, - price_incl_tax?: number, - priceInclTax?: number, - price_tax?: number, - priceTax?: number, - product_links?: ProductLink[], - product_option?: ProductOptions, - regular_price: number, - required_options?: string, - sale?: string, - sgn?: string, - size?: string, - size_options?: number[] | string[], - sku: string, - slug?: string, - small_image?: string, - special_price_incl_tax?: any, - special_price_tax?: any, - special_price?: number, - specialPriceInclTax?: any, - specialPriceTax?: any, - specialPrice?: number, - status: number, - stock: Record, - style_general?: string, - tax_class_id?: string, - thumbnail?: string, - tsk?: number, - type_id: string, - url_key: string, - url_path?: string, - visibility: number, - _score?: number, - qty?: number, - tier_prices?: any[], - links?: any, - parentId?: number | string -} - -export interface ProductLink { - sku: string, - link_type: string, - linked_product_sku: string, - linked_product_type: string, - position: number, - extension_attributes: { - qty: number - }, - product?: Product -} - -export interface ProductOptions { - extension_attributes: { - custom_options: any[], - configurable_item_options: ConfigurableItemOption[], - bundle_options: SelectedBundleOption[] - } -} diff --git a/core/modules/catalog/types/ProductConfiguration.ts b/core/modules/catalog/types/ProductConfiguration.ts deleted file mode 100644 index e820335f2c..0000000000 --- a/core/modules/catalog/types/ProductConfiguration.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface ProductOption { - attribute_code?: string, - id: number | string, - label: string, - values?: any[] -} - -export interface ProductConfiguration { - color: ProductOption, - size: ProductOption -} diff --git a/core/modules/catalog/types/ProductState.ts b/core/modules/catalog/types/ProductState.ts deleted file mode 100644 index 128798e3a3..0000000000 --- a/core/modules/catalog/types/ProductState.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Product from './Product'; - -export interface PagedProductList { - start: number, - perPage: number, - total: number, - items: Product[] -} - -export default interface ProductState { - breadcrumbs: { - routes: any[], - name: string - }, - current: any, - current_options: any, - current_configuration: any, - parent: any, - list: PagedProductList, - original: any, - related: { [key: string]: Product[] }, - offlineImage: any, - current_custom_options: any, - current_bundle_options: any, - custom_options_validators: any, - productLoadStart: number, - productLoadPromise: Promise | null, - productGallery: any -} diff --git a/core/modules/catalog/types/StockState.ts b/core/modules/catalog/types/StockState.ts deleted file mode 100644 index e7b9333a0a..0000000000 --- a/core/modules/catalog/types/StockState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface StockState { - cache: any -} diff --git a/core/modules/catalog/types/TaxState.ts b/core/modules/catalog/types/TaxState.ts deleted file mode 100644 index 4b1210da4c..0000000000 --- a/core/modules/catalog/types/TaxState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface TaxState { - rules: any[] -} diff --git a/core/modules/checkout/components/CartSummary.ts b/core/modules/checkout/components/CartSummary.ts deleted file mode 100644 index 91cc55fbff..0000000000 --- a/core/modules/checkout/components/CartSummary.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { mapGetters } from 'vuex' -import Microcart from '@vue-storefront/core/compatibility/components/blocks/Microcart/Microcart' - -export const CartSummary = { - name: 'CartSummary', - mixins: [Microcart], - computed: { - ...mapGetters({ - totals: 'cart/getTotals', - isVirtualCart: 'cart/isVirtualCart' - }) - } -} diff --git a/core/modules/checkout/components/OrderReview.ts b/core/modules/checkout/components/OrderReview.ts deleted file mode 100644 index b350ba89d4..0000000000 --- a/core/modules/checkout/components/OrderReview.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { mapGetters } from 'vuex' -import i18n from '@vue-storefront/i18n' -import { Logger } from '@vue-storefront/core/lib/logger' - -export const OrderReview = { - name: 'OrderReview', - props: { - isActive: { - type: Boolean, - required: true - } - }, - data () { - return { - isFilled: false, - orderReview: { - terms: false - } - } - }, - computed: { - ...mapGetters({ - isVirtualCart: 'cart/isVirtualCart', - getShippingDetails: 'checkout/getShippingDetails', - getPersonalDetails: 'checkout/getPersonalDetails' - }) - }, - methods: { - placeOrder () { - if (this.getPersonalDetails.createAccount) { - this.register() - } else { - this.$bus.$emit('checkout-before-placeOrder') - } - }, - async register () { - this.$bus.$emit('notification-progress-start', i18n.t('Registering the account ...')) - - try { - const result = await this.$store.dispatch('user/register', { - email: this.getPersonalDetails.emailAddress, - password: this.getPersonalDetails.password, - firstname: this.getPersonalDetails.firstName, - lastname: this.getPersonalDetails.lastName, - addresses: [{ - firstname: this.getShippingDetails.firstName, - lastname: this.getShippingDetails.lastName, - street: [this.getShippingDetails.streetAddress, this.getShippingDetails.apartmentNumber], - city: this.getShippingDetails.city, - ...(this.getShippingDetails.state ? { region: { region: this.getShippingDetails.state } } : {}), - country_id: this.getShippingDetails.country, - postcode: this.getShippingDetails.zipCode, - ...(this.getShippingDetails.phoneNumber ? { telephone: this.getShippingDetails.phoneNumber } : {}), - default_shipping: true - }] - }) - - if (result.code !== 200) { - this.$bus.$emit('notification-progress-stop') - this.onFailure(result) - // If error includes a word 'password', emit event that eventually focuses on a corresponding field - if (result.result.includes(i18n.t('password'))) { - this.$bus.$emit('checkout-after-validationError', 'password') - } - // If error includes a word 'mail', emit event that eventually focuses on a corresponding field - if (result.result.includes(i18n.t('email'))) { - this.$bus.$emit('checkout-after-validationError', 'email-address') - } - } else { - this.$bus.$emit('modal-hide', 'modal-signup') - await this.$store.dispatch('user/login', { - username: this.getPersonalDetails.emailAddress, - password: this.getPersonalDetails.password - }) - this.$bus.$emit('notification-progress-stop') - this.$bus.$emit('checkout-before-placeOrder', result.result.id) - this.onSuccess() - } - } catch (err) { - this.$bus.$emit('notification-progress-stop') - Logger.error(err, 'checkout')() - } - } - } -} diff --git a/core/modules/checkout/components/Payment.ts b/core/modules/checkout/components/Payment.ts deleted file mode 100644 index c797b862e2..0000000000 --- a/core/modules/checkout/components/Payment.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { mapState, mapGetters } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import toString from 'lodash-es/toString' -import debounce from 'lodash-es/debounce' -const Countries = require('@vue-storefront/i18n/resource/countries.json') - -export const Payment = { - name: 'Payment', - props: { - isActive: { - type: Boolean, - required: true - } - }, - data () { - return { - isFilled: false, - countries: Countries, - payment: this.$store.getters['checkout/getPaymentDetails'], - generateInvoice: false, - sendToShippingAddress: false, - sendToBillingAddress: false - } - }, - computed: { - ...mapState({ - currentUser: (state: RootState) => state.user.current, - shippingDetails: (state: RootState) => state.checkout.shippingDetails - }), - ...mapGetters({ - paymentMethods: 'checkout/getPaymentMethods', - paymentDetails: 'checkout/getPaymentDetails', - isVirtualCart: 'cart/isVirtualCart' - }) - }, - created () { - if (!this.payment.paymentMethod || this.notInMethods(this.payment.paymentMethod)) { - this.payment.paymentMethod = this.paymentMethods.length > 0 ? this.paymentMethods[0].code : 'cashondelivery' - } - }, - beforeMount () { - this.$bus.$on('checkout-after-load', this.onCheckoutLoad) - }, - mounted () { - if (this.payment.firstName) { - this.initializeBillingAddress() - } else { - if (this.payment.company) { - this.generateInvoice = true - } - } - this.changePaymentMethod() - }, - beforeDestroy () { - this.$bus.$off('checkout-after-load', this.onCheckoutLoad) - }, - watch: { - shippingDetails: { - handler () { - if (this.sendToShippingAddress) { - this.copyShippingToBillingAddress() - } - }, - deep: true - }, - sendToShippingAddress: { - handler () { - this.useShippingAddress() - } - }, - sendToBillingAddress: { - handler () { - this.useBillingAddress() - } - }, - generateInvoice: { - handler () { - this.useGenerateInvoice() - } - }, - paymentMethods: { - handler: debounce(function () { - this.changePaymentMethod() - }, 500) - } - }, - methods: { - sendDataToCheckout () { - this.$bus.$emit('checkout-after-paymentDetails', this.payment, this.$v) - this.isFilled = true - }, - edit () { - if (this.isFilled) { - this.$bus.$emit('checkout-before-edit', 'payment') - } - }, - hasBillingData () { - if (this.currentUser) { - if (this.currentUser.hasOwnProperty('default_billing')) { - return true - } - } - return false - }, - initializeBillingAddress () { - let initialized = false - if (this.currentUser) { - if (this.currentUser.hasOwnProperty('default_billing')) { - let id = this.currentUser.default_billing - let addresses = this.currentUser.addresses - for (let i = 0; i < addresses.length; i++) { - if (toString(addresses[i].id) === toString(id)) { - this.payment = { - firstName: addresses[i].firstname, - lastName: addresses[i].lastname, - company: addresses[i].company, - country: addresses[i].country_id, - state: addresses[i].region.region ? addresses[i].region.region : '', - city: addresses[i].city, - streetAddress: addresses[i].street[0], - apartmentNumber: addresses[i].street[1], - zipCode: addresses[i].postcode, - taxId: addresses[i].vat_id, - phoneNumber: addresses[i].telephone, - paymentMethod: this.paymentMethods[0].code - } - this.generateInvoice = true - this.sendToBillingAddress = true - initialized = true - } - } - } - } - if (!initialized) { - this.payment = this.paymentDetails || { - firstName: '', - lastName: '', - company: '', - country: '', - state: '', - city: '', - streetAddress: '', - apartmentNumber: '', - postcode: '', - zipCode: '', - phoneNumber: '', - taxId: '', - paymentMethod: this.paymentMethods.length > 0 ? this.paymentMethods[0].code : '' - } - } - }, - useShippingAddress () { - if (this.sendToShippingAddress) { - this.copyShippingToBillingAddress() - this.sendToBillingAddress = false - } - - if (!this.sendToBillingAddress && !this.sendToShippingAddress) { - this.payment = this.paymentDetails - } - }, - copyShippingToBillingAddress () { - this.payment = { - firstName: this.shippingDetails.firstName, - lastName: this.shippingDetails.lastName, - country: this.shippingDetails.country, - state: this.shippingDetails.state, - city: this.shippingDetails.city, - streetAddress: this.shippingDetails.streetAddress, - apartmentNumber: this.shippingDetails.apartmentNumber, - zipCode: this.shippingDetails.zipCode, - phoneNumber: this.shippingDetails.phoneNumber, - paymentMethod: this.paymentMethods.length > 0 ? this.paymentMethods[0].code : '' - } - }, - useBillingAddress () { - if (this.sendToBillingAddress) { - let id = this.currentUser.default_billing - let addresses = this.currentUser.addresses - for (let i = 0; i < addresses.length; i++) { - if (toString(addresses[i].id) === toString(id)) { - this.payment = { - firstName: addresses[i].firstname, - lastName: addresses[i].lastname, - company: addresses[i].company, - country: addresses[i].country_id, - state: addresses[i].region.region ? addresses[i].region.region : '', - city: addresses[i].city, - streetAddress: addresses[i].street[0], - apartmentNumber: addresses[i].street[1], - zipCode: addresses[i].postcode, - taxId: addresses[i].vat_id, - phoneNumber: addresses[i].telephone, - paymentMethod: this.paymentMethods.length > 0 ? this.paymentMethods[0].code : '' - } - this.generateInvoice = true - } - } - this.sendToShippingAddress = false - } - - if (!this.sendToBillingAddress && !this.sendToShippingAddress) { - this.payment = this.paymentDetails - this.generateInvoice = false - } - }, - useGenerateInvoice () { - if (!this.generateInvoice) { - this.payment.company = '' - this.payment.taxId = '' - } - }, - getCountryName () { - for (let i = 0; i < this.countries.length; i++) { - if (this.countries[i].code === this.payment.country) { - return this.countries[i].name - } - } - return '' - }, - getPaymentMethod () { - for (let i = 0; i < this.paymentMethods.length; i++) { - if (this.paymentMethods[i].code === this.payment.paymentMethod) { - return { - title: this.paymentMethods[i].title ? this.paymentMethods[i].title : this.paymentMethods[i].name - } - } - } - return { - name: '' - } - }, - notInMethods (method) { - let availableMethods = this.paymentMethods - if (availableMethods.find(item => item.code === method)) { - return false - } - return true - }, - changePaymentMethod () { - // reset the additional payment method component container if exists. - if (document.getElementById('checkout-order-review-additional-container')) { - document.getElementById('checkout-order-review-additional-container').innerHTML = '
 
' // reset - } - - // Let anyone listening know that we've changed payment method, usually a payment extension. - if (this.payment.paymentMethod) { - this.$bus.$emit('checkout-payment-method-changed', this.payment.paymentMethod) - } - }, - changeCountry () { - this.$store.dispatch('checkout/updatePaymentDetails', { country: this.payment.country }) - this.$store.dispatch('cart/syncPaymentMethods', { forceServerSync: true }) - }, - onCheckoutLoad () { - this.payment = this.$store.getters['checkout/getPaymentDetails'] - } - } -} diff --git a/core/modules/checkout/components/PersonalDetails.ts b/core/modules/checkout/components/PersonalDetails.ts deleted file mode 100644 index ef3ba7a1f5..0000000000 --- a/core/modules/checkout/components/PersonalDetails.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { mapState, mapGetters } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' - -export const PersonalDetails = { - name: 'PersonalDetails', - props: { - isActive: { - type: Boolean, - required: true - }, - focusedField: { - type: String, - required: false - } - }, - data () { - return { - isFilled: false, - personalDetails: this.$store.state.checkout.personalDetails, - createAccount: false, - acceptConditions: false, - password: '', - rPassword: '', - isValidationError: false - } - }, - computed: { - ...mapState({ - currentUser: (state: RootState) => state.user.current - }), - ...mapGetters({ - isVirtualCart: 'cart/isVirtualCart' - }) - }, - methods: { - onLoggedIn (receivedData) { - this.personalDetails = { - firstName: receivedData.firstname, - lastName: receivedData.lastname, - emailAddress: receivedData.email - } - }, - sendDataToCheckout () { - if (this.createAccount) { - this.personalDetails.password = this.password - this.personalDetails.createAccount = true - } else { - this.personalDetails.createAccount = false - } - this.$bus.$emit('checkout-after-personalDetails', this.personalDetails, this.$v) - this.isFilled = true - this.isValidationError = false - }, - edit () { - if (this.isFilled) { - this.$bus.$emit('checkout-before-edit', 'personalDetails') - } - }, - gotoAccount () { - this.$bus.$emit('modal-show', 'modal-signup') - }, - onCheckoutLoad () { - this.personalDetails = this.$store.state.checkout.personalDetails - } - }, - updated () { - // Perform focusing on a field, name of which is passed through 'focusedField' prop - if (this.focusedField && !this.isValidationError) { - if (this.focusedField === 'password') { - this.isValidationError = true - this.password = '' - this.rPassword = '' - this.$refs['password'].setFocus('password') - } - } - }, - beforeMount () { - this.$bus.$on('checkout-after-load', this.onCheckoutLoad) - this.$bus.$on('user-after-loggedin', this.onLoggedIn) - }, - beforeDestroy () { - this.$bus.$off('checkout-after-load', this.onCheckoutLoad) - this.$bus.$off('user-after-loggedin', this.onLoggedIn) - } -} diff --git a/core/modules/checkout/components/Product.ts b/core/modules/checkout/components/Product.ts deleted file mode 100644 index 08c0292e14..0000000000 --- a/core/modules/checkout/components/Product.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const Product = { - name: 'Product', - props: { - product: { - type: Object, - required: true - } - }, - computed: { - thumbnail () { - return this.getThumbnail(this.product.image, 150, 150) - } - }, - methods: { - onProductChanged (event) { - if (event.item.sku === this.product.sku) { - this.$forceUpdate() - } - } - }, - beforeMount () { - this.$bus.$on('cart-after-itemchanged', this.onProductChanged) - }, - beforeDestroy () { - this.$bus.$off('cart-after-itemchanged', this.onProductChanged) - } -} diff --git a/core/modules/checkout/components/Shipping.ts b/core/modules/checkout/components/Shipping.ts deleted file mode 100644 index b8d96defa5..0000000000 --- a/core/modules/checkout/components/Shipping.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { mapState, mapGetters } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import toString from 'lodash-es/toString' -const Countries = require('@vue-storefront/i18n/resource/countries.json') - -export const Shipping = { - name: 'Shipping', - props: { - isActive: { - type: Boolean, - required: true - } - }, - beforeDestroy () { - this.$bus.$off('checkout-after-load', this.onCheckoutLoad) - this.$bus.$off('checkout-after-personalDetails', this.onAfterPersonalDetails) - this.$bus.$off('checkout-after-shippingset', this.onAfterShippingSet) - }, - beforeMount () { - this.$bus.$on('checkout-after-load', this.onCheckoutLoad) - this.$bus.$on('checkout-after-personalDetails', this.onAfterPersonalDetails) - this.$bus.$on('checkout-after-shippingset', this.onAfterShippingSet) - }, - data () { - return { - isFilled: false, - countries: Countries, - shipping: this.$store.state.checkout.shippingDetails, - shipToMyAddress: false, - myAddressDetails: { - firstname: '', - lastname: '', - country: '', - region: '', - city: '', - street: ['', ''], - postcode: '', - telephone: '' - } - } - }, - computed: { - ...mapState({ - currentUser: (state: RootState) => state.user.current - }), - ...mapGetters({ - shippingMethods: 'checkout/getShippingMethods' - }), - checkoutShippingDetails () { - return this.$store.state.checkout.shippingDetails - }, - paymentMethod () { - return this.$store.getters['checkout/getPaymentMethods'] - } - }, - watch: { - shippingMethods: { - handler () { - this.checkDefaultShippingMethod() - } - }, - shipToMyAddress: { - handler () { - this.useMyAddress() - } - }, - '$route.hash': 'useMyAddress' - }, - mounted () { - this.checkDefaultShippingAddress() - this.checkDefaultShippingMethod() - this.changeShippingMethod() - }, - methods: { - checkDefaultShippingAddress () { - this.shipToMyAddress = this.hasShippingDetails() - }, - checkDefaultShippingMethod () { - if (!this.shipping.shippingMethod || this.notInMethods(this.shipping.shippingMethod)) { - let shipping = this.shippingMethods.find(item => item.default) - if (!shipping && this.shippingMethods && this.shippingMethods.length > 0) { - shipping = this.shippingMethods[0] - } - this.shipping.shippingMethod = shipping.method_code - this.shipping.shippingCarrier = shipping.carrier_code - } - }, - onAfterShippingSet (receivedData) { - this.shipping = receivedData - this.isFilled = true - }, - onAfterPersonalDetails (receivedData) { - if (!this.isFilled) { - this.$store.dispatch('checkout/updatePropValue', ['firstName', receivedData.firstName]) - this.$store.dispatch('checkout/updatePropValue', ['lastName', receivedData.lastName]) - } - this.useMyAddress() - }, - sendDataToCheckout () { - this.$bus.$emit('checkout-after-shippingDetails', this.shipping, this.$v) - this.isFilled = true - }, - edit () { - if (this.isFilled) { - this.$bus.$emit('checkout-before-edit', 'shipping') - } - }, - hasShippingDetails () { - if (this.currentUser) { - if (this.currentUser.hasOwnProperty('default_shipping')) { - let id = this.currentUser.default_shipping - let addresses = this.currentUser.addresses - for (let i = 0; i < addresses.length; i++) { - if (toString(addresses[i].id) === toString(id)) { - this.myAddressDetails = addresses[i] - return true - } - } - } - } - return false - }, - useMyAddress () { - if (this.shipToMyAddress) { - this.$set(this, 'shipping', { - firstName: this.myAddressDetails.firstname, - lastName: this.myAddressDetails.lastname, - country: this.myAddressDetails.country_id, - state: this.myAddressDetails.region.region ? this.myAddressDetails.region.region : '', - city: this.myAddressDetails.city, - streetAddress: this.myAddressDetails.street[0], - apartmentNumber: this.myAddressDetails.street[1], - zipCode: this.myAddressDetails.postcode, - phoneNumber: this.myAddressDetails.telephone, - shippingMethod: this.checkoutShippingDetails.shippingMethod, - shippingCarrier: this.checkoutShippingDetails.shippingCarrier - }) - } else { - this.$set(this, 'shipping', this.checkoutShippingDetails) - } - this.changeCountry() - }, - getShippingMethod () { - for (let i = 0; i < this.shippingMethods.length; i++) { - if (this.shippingMethods[i].method_code === this.shipping.shippingMethod) { - return { - method_title: this.shippingMethods[i].method_title, - amount: this.shippingMethods[i].amount - } - } - } - return { - method_title: '', - amount: '' - } - }, - getCountryName () { - for (let i = 0; i < this.countries.length; i++) { - if (this.countries[i].code === this.shipping.country) { - return this.countries[i].name - } - } - return '' - }, - changeCountry () { - this.$bus.$emit('checkout-before-shippingMethods', this.shipping.country) - }, - getCurrentShippingMethod () { - let shippingCode = this.shipping.shippingMethod - let currentMethod = this.shippingMethods ? this.shippingMethods.find(item => item.method_code === shippingCode) : {} - return currentMethod - }, - changeShippingMethod () { - let currentShippingMethod = this.getCurrentShippingMethod() - if (currentShippingMethod) { - this.shipping = Object.assign(this.shipping, { shippingCarrier: currentShippingMethod.carrier_code }) - this.$bus.$emit('checkout-after-shippingMethodChanged', { - country: this.shipping.country, - method_code: currentShippingMethod.method_code, - carrier_code: currentShippingMethod.carrier_code, - payment_method: this.paymentMethod[0].code - }) - } - }, - notInMethods (method) { - let availableMethods = this.shippingMethods - if (availableMethods.find(item => item.method_code === method)) { - return false - } - return true - }, - onCheckoutLoad () { - this.shipping = this.$store.state.checkout.shippingDetails - } - } -} diff --git a/core/modules/checkout/index.ts b/core/modules/checkout/index.ts deleted file mode 100644 index 899e547fa9..0000000000 --- a/core/modules/checkout/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { checkoutModule } from './store/checkout' -import { paymentModule } from './store/payment' -import { shippingModule } from './store/shipping' -import { Logger } from '@vue-storefront/core/lib/logger' -import * as types from './store/checkout/mutation-types' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const CheckoutModule: StorefrontModule = function ({ store }) { - StorageManager.init('checkout') - - store.registerModule('shipping', shippingModule) - store.registerModule('payment', paymentModule) - store.registerModule('checkout', checkoutModule) - - store.subscribe((mutation, state) => { - const type = mutation.type - - if ( - type.endsWith(types.CHECKOUT_SAVE_PERSONAL_DETAILS) - ) { - StorageManager.get('checkout').setItem('personal-details', state.checkout.personalDetails).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - } - - if ( - type.endsWith(types.CHECKOUT_SAVE_SHIPPING_DETAILS) || type.endsWith(types.CHECKOUT_UPDATE_PROP_VALUE) - ) { - StorageManager.get('checkout').setItem('shipping-details', state.checkout.shippingDetails).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - } - - if ( - type.endsWith(types.CHECKOUT_SAVE_PAYMENT_DETAILS) - ) { - StorageManager.get('checkout').setItem('payment-details', state.checkout.paymentDetails).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - } - }) -} diff --git a/core/modules/checkout/store/checkout/actions.ts b/core/modules/checkout/store/checkout/actions.ts deleted file mode 100644 index b8c9869d9b..0000000000 --- a/core/modules/checkout/store/checkout/actions.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState' -import CheckoutState from '../../types/CheckoutState' -import { Logger } from '@vue-storefront/core/lib/logger' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -const actions: ActionTree = { - async placeOrder ({ dispatch }, { order }) { - try { - const result = await dispatch('order/placeOrder', order, { root: true }) - if (!result.resultCode || result.resultCode === 200) { - await dispatch('updateOrderTimestamp') - // clear cart without sync, because after order cart will be already cleared on backend - await dispatch('cart/clear', { sync: false }, { root: true }) - await dispatch('dropPassword') - } - } catch (e) { - Logger.error(e, 'checkout')() - } - }, - async updateOrderTimestamp () { - const userStorage = StorageManager.get('user') - await userStorage.setItem('last-cart-bypass-ts', new Date().getTime()) - }, - async dropPassword ({ commit, state }) { - if (state.personalDetails.createAccount) { - commit(types.CHECKOUT_DROP_PASSWORD) - } - }, - async setModifiedAt ({ commit }, timestamp) { - commit(types.CHECKOUT_SET_MODIFIED_AT, timestamp) - }, - async savePersonalDetails ({ commit }, personalDetails) { - commit(types.CHECKOUT_SAVE_PERSONAL_DETAILS, personalDetails) - }, - async saveShippingDetails ({ commit }, shippingDetails) { - commit(types.CHECKOUT_SAVE_SHIPPING_DETAILS, shippingDetails) - }, - async savePaymentDetails ({ commit }, paymentDetails) { - commit(types.CHECKOUT_SAVE_PAYMENT_DETAILS, paymentDetails) - }, - async load ({ commit }) { - const checkoutStorage = StorageManager.get('checkout') - const [ - personalDetails, - shippingDetails, - paymentDetails - ] = await Promise.all([ - checkoutStorage.getItem('personal-details'), - checkoutStorage.getItem('shipping-details'), - checkoutStorage.getItem('payment-details') - ]) - - if (personalDetails) { - commit(types.CHECKOUT_LOAD_PERSONAL_DETAILS, personalDetails) - } - - if (shippingDetails) { - commit(types.CHECKOUT_LOAD_SHIPPING_DETAILS, shippingDetails) - } - - if (paymentDetails) { - commit(types.CHECKOUT_LOAD_PAYMENT_DETAILS, paymentDetails) - } - }, - async updatePropValue ({ commit }, payload) { - commit(types.CHECKOUT_UPDATE_PROP_VALUE, payload) - }, - async setThankYouPage ({ commit }, payload) { - commit(types.CHECKOUT_SET_THANKYOU, payload) - }, - async addPaymentMethod ({ commit }, paymentMethod) { - commit(types.CHECKOUT_ADD_PAYMENT_METHOD, paymentMethod) - }, - async replacePaymentMethods ({ commit }, paymentMethods) { - commit(types.CHECKOUT_SET_PAYMENT_METHODS, paymentMethods) - }, - async addShippingMethod ({ commit }, shippingMethod) { - commit(types.CHECKOUT_ADD_SHIPPING_METHOD, shippingMethod) - }, - async replaceShippingMethods ({ commit }, shippingMethods) { - commit(types.CHECKOUT_SET_SHIPPING_METHODS, shippingMethods) - }, - async updatePaymentDetails ({ commit }, updateData) { - commit(types.CHECKOUT_UPDATE_PAYMENT_DETAILS, updateData) - } -} - -export default actions diff --git a/core/modules/checkout/store/checkout/getters.ts b/core/modules/checkout/store/checkout/getters.ts deleted file mode 100644 index 56dd61e3ee..0000000000 --- a/core/modules/checkout/store/checkout/getters.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { GetterTree } from 'vuex' -import CheckoutState from '../../types/CheckoutState' -import RootState from '@vue-storefront/core/types/RootState' - -const getters: GetterTree = { - getShippingDetails: (state, getters, rootState) => { - if (!state.shippingDetails.country) { - return { ...state.shippingDetails, country: rootState.storeView.tax.defaultCountry } - } - - return state.shippingDetails - }, - getPersonalDetails: state => state.personalDetails, - getPaymentDetails: state => state.paymentDetails, - isThankYouPage: state => state.isThankYouPage, - getModifiedAt: state => state.modifiedAt, - isUserInCheckout: state => ((Date.now() - state.modifiedAt) <= (60 * 30 * 1000)), - getPaymentMethods: (state, getters, rootState, rootGetters) => { - const isVirtualCart = rootGetters['cart/isVirtualCart'] - - return state.paymentMethods.filter(method => !isVirtualCart || method.code !== 'cashondelivery') - }, - getDefaultPaymentMethod: (state, getters) => getters.getPaymentMethods.find(item => item.default), - getNotServerPaymentMethods: (state, getters) => - getters.getPaymentMethods.filter((itm) => - (typeof itm !== 'object' || !itm.is_server_method) - ), - getShippingMethods: state => state.shippingMethods, - getDefaultShippingMethod: state => state.shippingMethods.find(item => item.default) -} - -export default getters diff --git a/core/modules/checkout/store/checkout/index.ts b/core/modules/checkout/store/checkout/index.ts deleted file mode 100644 index 99049f6485..0000000000 --- a/core/modules/checkout/store/checkout/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import CheckoutState from '../../types/CheckoutState' -import config from 'config' - -export const checkoutModule: Module = { - namespaced: true, - state: { - order: {}, - paymentMethods: [], - shippingMethods: config.shipping.methods, - personalDetails: { - firstName: '', - lastName: '', - emailAddress: '', - password: '', - createAccount: false - }, - shippingDetails: { - firstName: '', - lastName: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - shippingMethod: '' - }, - paymentDetails: { - firstName: '', - lastName: '', - company: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - taxId: '', - paymentMethod: '', - paymentMethodAdditional: {} - }, - isThankYouPage: false, - modifiedAt: 0 - }, - getters, - actions, - mutations -} diff --git a/core/modules/checkout/store/checkout/mutation-types.ts b/core/modules/checkout/store/checkout/mutation-types.ts deleted file mode 100644 index 6ff6c22c78..0000000000 --- a/core/modules/checkout/store/checkout/mutation-types.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const SN_CHECKOUT = 'checkout' -export const CHECKOUT_PLACE_ORDER = SN_CHECKOUT + '/PLACE_ORDER' -export const CHECKOUT_SAVE_PERSONAL_DETAILS = SN_CHECKOUT + '/SAVE_PERSONAL_DETAILS' -export const CHECKOUT_SAVE_SHIPPING_DETAILS = SN_CHECKOUT + '/SAVE_SHIPPING_DETAILS' -export const CHECKOUT_SAVE_PAYMENT_DETAILS = SN_CHECKOUT + '/SAVE_PAYMENT_DETAILS' -export const CHECKOUT_LOAD_PERSONAL_DETAILS = SN_CHECKOUT + '/LOAD_PERSONAL_DETAILS' -export const CHECKOUT_LOAD_SHIPPING_DETAILS = SN_CHECKOUT + '/LOAD_SHIPPING_DETAILS' -export const CHECKOUT_LOAD_PAYMENT_DETAILS = SN_CHECKOUT + '/LOAD_PAYMENT_DETAILS' -export const CHECKOUT_UPDATE_PROP_VALUE = SN_CHECKOUT + '/UPDATE_PROP_VALUE' -export const CHECKOUT_DROP_PASSWORD = SN_CHECKOUT + '/DROP_PASSWORD' -export const CHECKOUT_SET_THANKYOU = SN_CHECKOUT + '/SET_THANKYOU' -export const CHECKOUT_SET_MODIFIED_AT = SN_CHECKOUT + '/SET_MODIFIEDAT' -export const CHECKOUT_ADD_PAYMENT_METHOD = SN_CHECKOUT + '/ADD_PAYMENT_METHOD' -export const CHECKOUT_SET_PAYMENT_METHODS = SN_CHECKOUT + '/SET_PAYMENT_METHODS' -export const CHECKOUT_ADD_SHIPPING_METHOD = SN_CHECKOUT + '/ADD_SHIPPING_METHOD' -export const CHECKOUT_SET_SHIPPING_METHODS = SN_CHECKOUT + '/SET_SHIPPING_METHOD' -export const CHECKOUT_UPDATE_PAYMENT_DETAILS = SN_CHECKOUT + '/UPDATE_PAYMENT_DETAILS' diff --git a/core/modules/checkout/store/checkout/mutations.ts b/core/modules/checkout/store/checkout/mutations.ts deleted file mode 100644 index b9be63b6c0..0000000000 --- a/core/modules/checkout/store/checkout/mutations.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import CheckoutState from '../../types/CheckoutState' - -const mutations: MutationTree = { - /** - * Setup current order object - * @param {Object} order Object - */ - [types.CHECKOUT_PLACE_ORDER] (state, order) { - state.order = order - }, - [types.CHECKOUT_SET_MODIFIED_AT] (state, timestamp) { - state.modifiedAt = timestamp - }, - [types.CHECKOUT_SAVE_PERSONAL_DETAILS] (state, personalDetails) { - state.personalDetails = personalDetails - }, - [types.CHECKOUT_SAVE_SHIPPING_DETAILS] (state, shippingDetails) { - state.shippingDetails = shippingDetails - }, - [types.CHECKOUT_SAVE_PAYMENT_DETAILS] (state, paymentDetails) { - state.paymentDetails = paymentDetails - }, - [types.CHECKOUT_LOAD_PERSONAL_DETAILS] (state, storedPersonalDetails) { - state.personalDetails = storedPersonalDetails - }, - [types.CHECKOUT_LOAD_SHIPPING_DETAILS] (state, storedShippingDetails) { - state.shippingDetails = storedShippingDetails - }, - [types.CHECKOUT_LOAD_PAYMENT_DETAILS] (state, storedPaymentDetails) { - state.paymentDetails = storedPaymentDetails - }, - [types.CHECKOUT_UPDATE_PROP_VALUE] (state, payload) { - state.shippingDetails[payload[0]] = payload[1] - }, - [types.CHECKOUT_DROP_PASSWORD] (state) { - state.personalDetails.password = '' - state.personalDetails.createAccount = false - }, - [types.CHECKOUT_SET_THANKYOU] (state, payload) { - state.isThankYouPage = payload - }, - [types.CHECKOUT_ADD_PAYMENT_METHOD] (state, paymentMethod) { - state.paymentMethods.push(paymentMethod) - }, - [types.CHECKOUT_SET_PAYMENT_METHODS] (state, paymentMethods = []) { - state.paymentMethods = paymentMethods - }, - [types.CHECKOUT_ADD_SHIPPING_METHOD] (state, shippingMethods) { - state.shippingMethods.push(shippingMethods) - }, - [types.CHECKOUT_SET_SHIPPING_METHODS] (state, shippingMethods = []) { - state.shippingMethods = shippingMethods - }, - [types.CHECKOUT_UPDATE_PAYMENT_DETAILS] (state, updateData = {}) { - state.paymentDetails = Object.assign({}, state.paymentDetails, updateData) - } -} - -export default mutations diff --git a/core/modules/checkout/store/payment/index.ts b/core/modules/checkout/store/payment/index.ts deleted file mode 100644 index 38da27ef39..0000000000 --- a/core/modules/checkout/store/payment/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Module } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import PaymentState from '../../types/PaymentState' -import { Logger } from '@vue-storefront/core/lib/logger' - -// @deprecated -export const paymentModule: Module = { - namespaced: true, - actions: { - addMethod ({ dispatch }, paymentMethod) { - Logger.error('The action payment/addMethod has been deprecated please change to checkout/addPaymentMethod')() - - dispatch('checkout/addPaymentMethod', paymentMethod, { root: true }) - }, - replaceMethods ({ dispatch }, paymentMethods) { - Logger.error('The action payment/replaceMethods has been deprecated please change to checkout/replacePaymentMethods')() - - dispatch('checkout/replacePaymentMethods', paymentMethods, { root: true }) - } - }, - getters: { - // @deprecated - paymentMethods: (state, getters, rootState, rootGetters) => rootGetters['checkout/getPaymentMethods'], - // @deprecated - getDefaultPaymentMethod: (state, getters, rootState, rootGetters) => rootGetters['checkout/getDefaultPaymentMethod'], - // @deprecated - getNotServerPaymentMethods: (state, getters, rootState, rootGetters) => rootGetters['checkout/getNotServerPaymentMethods'] - } -} diff --git a/core/modules/checkout/store/shipping/index.ts b/core/modules/checkout/store/shipping/index.ts deleted file mode 100644 index a811366995..0000000000 --- a/core/modules/checkout/store/shipping/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Module } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import ShippingState from '../../types/ShippingState' -import { Logger } from '@vue-storefront/core/lib/logger' - -// @deprecated -export const shippingModule: Module = { - namespaced: true, - actions: { - addMethod ({ dispatch }, shippingMethod) { - Logger.error('The action shipping/addMethod has been deprecated please change to checkout/addShippingMethod')() - dispatch('checkout/addShippingMethod', shippingMethod, { root: true }) - }, - replaceMethods ({ dispatch }, shippingMethods) { - Logger.error('The action shipping/replaceMethods has been deprecated please change to checkout/replaceShippingMethods')() - dispatch('checkout/replaceShippingMethods', shippingMethods, { root: true }) - } - }, - getters: { - // @deprecated - shippingMethods: (state, getters, rootState, rootGetters) => rootGetters['checkout/getShippingMethods'], - // @deprecated - getShippingMethods: (state, getters, rootState, rootGetters) => rootGetters['checkout/getShippingMethods'], - // @deprecated - getDefaultShippingMethod: (state, getters, rootState, rootGetters) => rootGetters['checkout/getDefaultShippingMethod'] - } -} diff --git a/core/modules/checkout/test/unit/components/CartSummary.spec.ts b/core/modules/checkout/test/unit/components/CartSummary.spec.ts deleted file mode 100644 index 2e933a8f92..0000000000 --- a/core/modules/checkout/test/unit/components/CartSummary.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { CartSummary } from '../../../components/CartSummary'; - -jest.mock('@vue-storefront/core/compatibility/components/blocks/Microcart/Microcart'); - -describe('CartSummary', () => { - it('can be initialized', () => { - const wrapper = mountMixin(CartSummary); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('exposes computed properties', () => { - const mockStore = { - modules: { - cart: { - getters: { - getTotals: jest.fn(() => 1), - isVirtualCart: jest.fn(() => true) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(CartSummary, mockStore); - - expect((wrapper.vm as any).totals).toBeDefined(); - expect((wrapper.vm as any).isVirtualCart).toBeDefined(); - }); -}); diff --git a/core/modules/checkout/test/unit/components/OrderReview.spec.ts b/core/modules/checkout/test/unit/components/OrderReview.spec.ts deleted file mode 100644 index 69e005d1e2..0000000000 --- a/core/modules/checkout/test/unit/components/OrderReview.spec.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { OrderReview } from '../../../components/OrderReview'; - -jest.mock('@vue-storefront/i18n', () => ({ - t: jest.fn(t => t) -})); - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => jest.fn()) - } -})); - -describe('OrderReview', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('can be initialized', () => { - const wrapper = mountMixin(OrderReview, { - propsData: { - isActive: true - } - }); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('exposes computed properties', () => { - const mockStore = { - modules: { - cart: { - getters: { - isVirtualCart: jest.fn(() => true) - }, - namespaced: true - }, - checkout: { - getters: { - getShippingDetails: jest.fn(() => ([])), - getPersonalDetails: jest.fn(() => ([])) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(OrderReview, mockStore, { - propsData: { - isActive: true - } - }); - - expect((wrapper.vm as any).isVirtualCart).toBeDefined(); - expect((wrapper.vm as any).getShippingDetails).toBeDefined(); - expect((wrapper.vm as any).getPersonalDetails).toBeDefined(); - }); - - describe('placeOrder method', () => { - let wrapper; - const mockGetPersonalDetails = jest.fn(); - const mockRegisterFn = jest.fn(); - const mockEmitFn = jest.fn(); - - beforeEach(() => { - wrapper = mountMixin(OrderReview, { - propsData: { - isActive: true - }, - computed: { - getPersonalDetails: mockGetPersonalDetails - }, - methods: { - register: mockRegisterFn - }, - mocks: { - $bus: { - $emit: mockEmitFn - } - } - }); - }); - - it('registers an account if it is not created yet', () => { - mockGetPersonalDetails.mockImplementation(() => ({ - createAccount: true - })); - - (wrapper.vm as any).placeOrder(); - - expect(mockRegisterFn).toHaveBeenCalled(); - expect(mockEmitFn).not.toHaveBeenCalled(); - }); - - it('emits an event if account is already created', () => { - mockGetPersonalDetails.mockImplementation(() => ({ - createAccount: false - })); - - (wrapper.vm as any).placeOrder(); - - expect(mockRegisterFn).not.toHaveBeenCalled(); - expect(mockEmitFn).toHaveBeenCalledWith('checkout-before-placeOrder'); - }); - }); - - describe('register method', () => { - let wrapper; - - const mockGetPersonalDetails = jest.fn(() => ({ - emailAddress: 'example email address', - password: 'example password', - firstName: 'example first name', - lastName: 'example last name' - })); - - const mockGetShippingDetails = jest.fn(() => ({ - firstName: 'example first name', - lastName: 'example last name', - streetAddress: 'example street address', - apartmentNumber: 'example apartment number', - city: 'example city', - state: 'example state', - region: 'example region', - country: 'example country', - zipCode: 'example zip code', - phoneNumber: 'example phone number' - })); - - const mockOnSuccessFn = jest.fn(); - const mockOnFailureFn = jest.fn(); - const mockEmitFn = jest.fn(); - - const mockStore = { - modules: { - user: { - actions: { - login: jest.fn(), - register: jest.fn() - }, - namespaced: true - } - } - }; - - beforeEach(() => { - wrapper = mountMixinWithStore(OrderReview, mockStore, { - propsData: { - isActive: true - }, - computed: { - getPersonalDetails: mockGetPersonalDetails, - getShippingDetails: mockGetShippingDetails - }, - methods: { - onSuccess: mockOnSuccessFn, - onFailure: mockOnFailureFn - }, - mocks: { - $bus: { - $emit: mockEmitFn - } - } - }); - }); - - it('dispatches user/register action with proper payload', () => { - (wrapper.vm as any).register(); - - expect(mockStore.modules.user.actions.register).toHaveBeenCalledWith(expect.anything(), { - email: 'example email address', - password: 'example password', - firstname: 'example first name', - lastname: 'example last name', - addresses: [{ - firstname: 'example first name', - lastname: 'example last name', - street: ['example street address', 'example apartment number'], - city: 'example city', - region: { - region: 'example state' - }, - country_id: 'example country', - postcode: 'example zip code', - telephone: 'example phone number', - default_shipping: true - }] - }); - }); - - it('emits events about start and stop of notification progress', async () => { - const result = { - code: 500, - result: 'example error message' - }; - - mockStore.modules.user.actions.register.mockImplementation(() => Promise.resolve(result)); - - await (wrapper.vm as any).register(); - - expect(mockEmitFn).toHaveBeenCalledTimes(2); - expect(mockEmitFn).toHaveBeenNthCalledWith(1, 'notification-progress-start', 'Registering the account ...'); - expect(mockEmitFn).toHaveBeenNthCalledWith(2, 'notification-progress-stop'); - }); - - it('catches thrown error and sends event about stop of notification progress', async () => { - mockStore.modules.user.actions.register.mockImplementation(() => { throw Error('Error') }); - - await (wrapper.vm as any).register(); - - expect(mockEmitFn).toHaveBeenCalledWith('notification-progress-stop'); - }); - - describe('failed result if return code is other than 200', () => { - it('calls onFailure callback', async () => { - const result = { - code: 500, - result: 'example error message' - }; - - mockStore.modules.user.actions.register.mockImplementation(() => Promise.resolve(result)); - - await (wrapper.vm as any).register(); - - expect(mockOnFailureFn).toHaveBeenCalledWith(result); - expect(mockOnSuccessFn).not.toHaveBeenCalled(); - expect(mockEmitFn).not.toHaveBeenCalledWith('checkout-before-placeOrder', expect.anything()); - }); - - it('emits event to indicate validation error with password if error includes a word "password"', async () => { - const result = { - code: 500, - result: 'example error message with password' - }; - - mockStore.modules.user.actions.register.mockImplementation(() => Promise.resolve(result)); - - await (wrapper.vm as any).register(); - - expect(mockEmitFn).toHaveBeenCalledWith('checkout-after-validationError', 'password'); - }); - - it('emits event to indicate validation error with email address if error includes a word "email"', async () => { - const result = { - code: 500, - result: 'example error message with email' - }; - - mockStore.modules.user.actions.register.mockImplementation(() => Promise.resolve(result)); - - await (wrapper.vm as any).register(); - - expect(mockEmitFn).toHaveBeenCalledWith('checkout-after-validationError', 'email-address'); - }); - }); - - describe('successful result if return code is 200', () => { - it('calls onSuccess callback and emits proper events', async () => { - const result = { - code: 200, - result: { id: 42 } - }; - - mockStore.modules.user.actions.register.mockImplementation(() => Promise.resolve(result)); - - await (wrapper.vm as any).register(); - - expect(mockOnSuccessFn).toHaveBeenCalled(); - expect(mockOnFailureFn).not.toHaveBeenCalled(); - expect(mockEmitFn).toHaveBeenCalledWith('modal-hide', 'modal-signup'); - expect(mockEmitFn).toHaveBeenCalledWith('checkout-before-placeOrder', 42); - }); - - it('dispatches user/login action', async () => { - const result = { - code: 200, - result: { id: 42 } - }; - - mockStore.modules.user.actions.register.mockImplementation(() => Promise.resolve(result)); - - await (wrapper.vm as any).register(); - - expect(mockStore.modules.user.actions.login).toHaveBeenCalledWith(expect.anything(), { - username: 'example email address', - password: 'example password' - }); - }); - }); - }); -}); diff --git a/core/modules/checkout/test/unit/components/Payment.spec.ts b/core/modules/checkout/test/unit/components/Payment.spec.ts deleted file mode 100644 index 921ebe3b65..0000000000 --- a/core/modules/checkout/test/unit/components/Payment.spec.ts +++ /dev/null @@ -1,602 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { Payment } from '../../../components/Payment'; - -describe('Payment', () => { - let mockStore; - let mockMountingOptions; - let mockMethods; - let mockHooks; - - beforeEach(() => { - jest.clearAllMocks(); - - mockStore = { - modules: { - cart: { - getters: { - isVirtualCart: jest.fn(() => true) - }, - namespaced: true - }, - checkout: { - state: { - shippingDetails: {} - }, - getters: { - getPaymentMethods: jest.fn(() => ([])), - getPaymentDetails: jest.fn(() => ({ - paymentMethod: '', - firstName: '', - company: '', - country: '' - })) - }, - namespaced: true - }, - user: { - state: { - current: {} - }, - namespaced: true - } - } - }; - - mockMountingOptions = { - propsData: { - isActive: true - }, - mocks: { - $bus: { - $emit: jest.fn() - } - } - }; - - mockMethods = Object.entries(Payment.methods) - .reduce((result, [methodName]) => { - result[methodName] = jest.spyOn(Payment.methods, methodName as keyof typeof Payment.methods) - .mockImplementation(jest.fn()); - - return result; - }, {}); - - mockHooks = ['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed'] - .reduce((result, hookName) => { - if (Payment[hookName]) { - result[hookName] = jest.spyOn(Payment, hookName as any) - .mockImplementation(jest.fn()); - } - - return result; - }, {}); - }); - - it('can be initialized', () => { - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('exposes computed properties', () => { - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).currentUser).toBeDefined(); - expect((wrapper.vm as any).shippingDetails).toBeDefined(); - expect((wrapper.vm as any).paymentMethods).toBeDefined(); - expect((wrapper.vm as any).paymentDetails).toBeDefined(); - expect((wrapper.vm as any).isVirtualCart).toBeDefined(); - }); - - describe('hooks', () => { - describe('created hook', () => { - beforeEach(() => { - mockHooks['created'].mockRestore(); - }); - - it('should initialize payment method as "cashondelivery" if payment method is not configured', () => { - mockMethods['notInMethods'].mockReturnValue(false); - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([])); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - paymentMethod: '' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).payment.paymentMethod).toBe('cashondelivery'); - }); - - it('should initialize payment method as "cashondelivery" if payment method is not supported', () => { - mockMethods['notInMethods'].mockReturnValue(true); - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([])); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - paymentMethod: 'not supported payment method' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).payment.paymentMethod).toBe('cashondelivery'); - }); - - it('should initialize payment method as first one from all payment methods if it is not configured', () => { - mockMethods['notInMethods'].mockReturnValue(true); - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([ - { code: 'first payment method' }, { code: 'second payment method' } - ])); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - paymentMethod: 'not supported payment method' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).payment.paymentMethod).toBe('first payment method'); - }); - }); - - describe('mounted hook', () => { - beforeEach(() => { - mockHooks['mounted'].mockRestore(); - }); - - it('should initialize billing address if payment is from individual customer and then change payment method', () => { - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - firstName: 'example first name', - company: '' - })); - - mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect(mockMethods['initializeBillingAddress']).toHaveBeenCalled(); - expect(mockMethods['changePaymentMethod']).toHaveBeenCalled(); - }); - - it('should mark invoice generation and do not initialize billing address if payment is from company and then change payment method', () => { - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - firstName: '', - company: 'example company' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect(mockMethods['initializeBillingAddress']).not.toHaveBeenCalled(); - expect(mockMethods['changePaymentMethod']).toHaveBeenCalled(); - expect((wrapper.vm as any).generateInvoice).toBeTruthy(); - }); - }); - }); - - describe('watchers', () => { - it('should call copyShippingToBillingAddress method if "send to shipping address" flag is configured', () => { - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ sendToShippingAddress: true }); - (wrapper.vm as any).$options.watch.shippingDetails.handler.call(wrapper.vm); - - expect(mockMethods['copyShippingToBillingAddress']).toHaveBeenCalled(); - }); - - it('should not call copyShippingToBillingAddress method if "send to shipping address" flag is not configured', () => { - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ sendToShippingAddress: false }); - (wrapper.vm as any).$options.watch.shippingDetails.handler.call(wrapper.vm); - - expect(mockMethods['copyShippingToBillingAddress']).not.toHaveBeenCalled(); - }); - - it('should call useShippingAddress method if "send to shipping address" flag is changed', () => { - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - (wrapper.vm as any).$options.watch.sendToShippingAddress.handler.call(wrapper.vm); - - expect(mockMethods['useShippingAddress']).toHaveBeenCalled(); - }); - - it('should call useBillingAddress method if "send to billing address" flag is changed', () => { - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - (wrapper.vm as any).$options.watch.sendToBillingAddress.handler.call(wrapper.vm); - - expect(mockMethods['useBillingAddress']).toHaveBeenCalled(); - }); - - it('should call useGenerateInvoice method if "generate invoice" flag is changed', () => { - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - (wrapper.vm as any).$options.watch.generateInvoice.handler.call(wrapper.vm); - - expect(mockMethods['useGenerateInvoice']).toHaveBeenCalled(); - }); - }); - - describe('methods', () => { - it('sendDataToCheckout method should emit an event and set flag', () => { - mockMethods['sendDataToCheckout'].mockRestore(); - - const paymentDetails = { - firstName: 'example first name', - company: 'example company', - country: 'example country' - }; - - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => paymentDetails); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - (wrapper.vm as any).sendDataToCheckout(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-after-paymentDetails', paymentDetails, undefined); - expect((wrapper.vm as any).isFilled).toBe(true); - }); - - it('edit method should emit event if flag is set', () => { - mockMethods['edit'].mockRestore(); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ isFilled: true }); - (wrapper.vm as any).edit(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-before-edit', 'payment'); - }); - - it('edit method should not emit event if flag is not set', () => { - mockMethods['edit'].mockRestore(); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ isFilled: false }); - (wrapper.vm as any).edit(); - - expect(mockMountingOptions.mocks.$bus.$emit).not.toHaveBeenCalled(); - }); - - it('hasBillingData method should inform if current user has default_billing own property', () => { - mockMethods['hasBillingData'].mockRestore(); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - mockStore.modules.user.state.current = { default_billing: true }; - expect((wrapper.vm as any).hasBillingData()).toBe(true); - - mockStore.modules.user.state.current = {}; - expect((wrapper.vm as any).hasBillingData()).toBe(false); - }); - - it('initializeBillingAddress method should init payment properties with empty strings if current user and payment details are not set', () => { - mockMethods['initializeBillingAddress'].mockRestore(); - mockMountingOptions.computed = { - currentUser: jest.fn(), - paymentDetails: jest.fn() - }; - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - (wrapper.vm as any).initializeBillingAddress(); - - expect((wrapper.vm as any).payment).toEqual({ - firstName: '', - lastName: '', - company: '', - country: '', - state: '', - city: '', - streetAddress: '', - apartmentNumber: '', - postcode: '', - zipCode: '', - phoneNumber: '', - taxId: '', - paymentMethod: '' - }); - }); - - it('initializeBillingAddress method should copy billing address from address id from current user', () => { - mockMethods['initializeBillingAddress'].mockRestore(); - mockMountingOptions.computed = { - currentUser: jest.fn(() => ({ - default_billing: 123, - addresses: [ - { - id: 123, - firstname: 'example first name', - lastname: 'example last name', - company: 'example company', - country_id: 'example country', - region: { region: 'example region' }, - city: 'example city', - street: ['example street', 'example apartment number'], - postcode: 'example post code', - vat_id: 'example vat id', - telephone: 'example telephone' - } - ] - })), - paymentMethods: jest.fn(() => ([{ code: 'example payment method' }])), - paymentDetails: jest.fn() - }; - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - (wrapper.vm as any).initializeBillingAddress(); - - expect((wrapper.vm as any).payment).toEqual({ - firstName: 'example first name', - lastName: 'example last name', - company: 'example company', - country: 'example country', - state: 'example region', - city: 'example city', - streetAddress: 'example street', - apartmentNumber: 'example apartment number', - zipCode: 'example post code', - phoneNumber: 'example telephone', - taxId: 'example vat id', - paymentMethod: 'example payment method' - }); - expect((wrapper.vm as any).generateInvoice).toBe(true); - expect((wrapper.vm as any).sendToBillingAddress).toBe(true); - }); - - it('useShippingAddress method should call copyShippingToBillingAddress if shipping address is set', () => { - mockMethods['useShippingAddress'].mockRestore(); - mockMountingOptions.methods = { - copyShippingToBillingAddress: jest.fn() - }; - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ sendToShippingAddress: true }); - (wrapper.vm as any).useShippingAddress(); - - expect(mockMountingOptions.methods.copyShippingToBillingAddress).toHaveBeenCalled(); - expect((wrapper.vm as any).sendToBillingAddress).toBe(false); - }); - - it('useShippingAddress method should not call copyShippingToBillingAddress if shipping address is not set', () => { - mockMethods['useShippingAddress'].mockRestore(); - - const paymentDetails = { - firstName: 'example first name', - lastName: 'example last name', - company: 'example company', - country: 'example country', - state: 'example region', - city: 'example city', - streetAddress: 'example street', - apartmentNumber: 'example apartment number', - zipCode: 'example post code', - phoneNumber: 'example telephone', - taxId: 'example vat id', - paymentMethod: 'example payment method' - }; - - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => paymentDetails); - mockMountingOptions.methods = { - copyShippingToBillingAddress: jest.fn() - }; - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ sendToBillingAddress: false, sendToShippingAddress: false }); - (wrapper.vm as any).useShippingAddress(); - - expect(mockMountingOptions.methods.copyShippingToBillingAddress).not.toHaveBeenCalled(); - expect((wrapper.vm as any).payment).toEqual(paymentDetails); - }); - - it('copyShippingToBillingAddress method should copy data from shipping address', () => { - mockMethods['copyShippingToBillingAddress'].mockRestore(); - - const paymentDetails = { - firstName: 'example first name', - lastName: 'example last name', - country: 'example country', - state: 'example region', - city: 'example city', - streetAddress: 'example street', - apartmentNumber: 'example apartment number', - zipCode: 'example post code', - phoneNumber: 'example telephone' - }; - - mockStore.modules.checkout.state.shippingDetails = paymentDetails; - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([ - { code: 'first payment method' }, { code: 'second payment method' } - ])); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - (wrapper.vm as any).copyShippingToBillingAddress(); - - expect((wrapper.vm as any).payment).toEqual({ - ...paymentDetails, - paymentMethod: 'first payment method' - }); - }); - - it('useBillingAddress method should copy billing address from address id from current user', () => { - mockMethods['useBillingAddress'].mockRestore(); - mockMountingOptions.computed = { - currentUser: jest.fn(() => ({ - default_billing: 123, - addresses: [ - { - id: 123, - firstname: 'example first name', - lastname: 'example last name', - company: 'example company', - country_id: 'example country', - region: { region: 'example region' }, - city: 'example city', - street: ['example street', 'example apartment number'], - postcode: 'example post code', - vat_id: 'example vat id', - telephone: 'example telephone' - } - ] - })), - paymentMethods: jest.fn(() => ([{ code: 'example payment method' }])) - }; - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ sendToBillingAddress: true }); - (wrapper.vm as any).useBillingAddress(); - - expect((wrapper.vm as any).payment).toEqual({ - firstName: 'example first name', - lastName: 'example last name', - company: 'example company', - country: 'example country', - state: 'example region', - city: 'example city', - streetAddress: 'example street', - apartmentNumber: 'example apartment number', - zipCode: 'example post code', - phoneNumber: 'example telephone', - taxId: 'example vat id', - paymentMethod: 'example payment method' - }); - expect((wrapper.vm as any).generateInvoice).toBe(true); - expect((wrapper.vm as any).sendToShippingAddress).toBe(false); - }); - - it('useBillingAddress method should copy billing address from payment details if address is not set', () => { - mockMethods['useBillingAddress'].mockRestore(); - - const paymentDetails = { - firstName: 'example first name', - lastName: 'example last name', - company: 'example company', - country: 'example country', - state: 'example region', - city: 'example city', - streetAddress: 'example street', - apartmentNumber: 'example apartment number', - zipCode: 'example post code', - phoneNumber: 'example telephone', - taxId: 'example vat id', - paymentMethod: 'example payment method' - }; - - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => paymentDetails); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ sendToBillingAddress: false, sendToShippingAddress: false }); - (wrapper.vm as any).useBillingAddress(); - - expect((wrapper.vm as any).payment).toEqual(paymentDetails); - expect((wrapper.vm as any).generateInvoice).toBe(false); - }); - - it('useGenerateInvoice method should clear company and taxId fields if generateInvoice is not set', () => { - mockMethods['useGenerateInvoice'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - company: 'example company', - taxId: 'example taxId' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ generateInvoice: false }); - (wrapper.vm as any).useGenerateInvoice(); - - expect((wrapper.vm as any).payment.company).toBe(''); - expect((wrapper.vm as any).payment.taxId).toBe(''); - }); - - it('useGenerateInvoice method should not clear company and taxId fields if generateInvoice is set', () => { - mockMethods['useGenerateInvoice'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - company: 'example company', - taxId: 'example taxId' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - wrapper.setData({ generateInvoice: true }); - (wrapper.vm as any).useGenerateInvoice(); - - expect((wrapper.vm as any).payment.company).toBe('example company'); - expect((wrapper.vm as any).payment.taxId).toBe('example taxId'); - }); - - it('getCountryName method should return country name from payment', () => { - mockMethods['getCountryName'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - country: 'PL' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - const countryName = (wrapper.vm as any).getCountryName(); - - expect(countryName).toBe('Poland'); - }); - - it('getCountryName method should return empty string if country has not been found', () => { - mockMethods['getCountryName'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - country: 'invalid country code' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - const countryName = (wrapper.vm as any).getCountryName(); - - expect(countryName).toBe(''); - }); - - it('getPaymentMethod method should return payment name', () => { - mockMethods['getPaymentMethod'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - paymentMethod: 'second payment method' - })); - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([ - { code: 'first payment method', name: 'payment 1' }, { code: 'second payment method', name: 'payment 2' } - ])); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - const paymentMethod = (wrapper.vm as any).getPaymentMethod(); - - expect(paymentMethod).toEqual({ title: 'payment 2' }); - }); - - it('getPaymentMethod method should return empty name if it is not found', () => { - mockMethods['getPaymentMethod'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - paymentMethod: 'invalid payment method' - })); - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([ - { code: 'first payment method', name: 'payment 1' }, { code: 'second payment method', name: 'payment 2' } - ])); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - const paymentMethod = (wrapper.vm as any).getPaymentMethod(); - - expect(paymentMethod).toEqual({ name: '' }); - }); - - it('notInMethods method should inform if given payment method is not supported', () => { - mockMethods['notInMethods'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([ - { code: 'first payment method', name: 'payment 1' }, { code: 'second payment method', name: 'payment 2' } - ])); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).notInMethods('second payment method')).toBe(false); - expect((wrapper.vm as any).notInMethods('invalid payment method')).toBe(true); - }); - - it('changePaymentMethod method should emit an event when there is paymentMethod', () => { - mockMethods['changePaymentMethod'].mockRestore(); - mockStore.modules.checkout.getters.getPaymentDetails.mockImplementation(() => ({ - paymentMethod: 'payment method' - })); - - const wrapper = mountMixinWithStore(Payment, mockStore, mockMountingOptions); - (wrapper.vm as any).changePaymentMethod(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-payment-method-changed', expect.anything()); - }); - }); -}); diff --git a/core/modules/checkout/test/unit/components/PersonalDetails.spec.ts b/core/modules/checkout/test/unit/components/PersonalDetails.spec.ts deleted file mode 100644 index 7ff6a46b1e..0000000000 --- a/core/modules/checkout/test/unit/components/PersonalDetails.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { PersonalDetails } from '../../../components/PersonalDetails'; -import Vue from 'vue' - -describe('PersonalDetails', () => { - let mockStore; - let mockMountingOptions; - - beforeEach(() => { - jest.clearAllMocks(); - - mockStore = { - modules: { - cart: { - getters: { - isVirtualCart: jest.fn(() => true) - }, - namespaced: true - }, - checkout: { - state: { - personalDetails: {} - }, - namespaced: true - }, - user: { - state: { - current: {} - }, - namespaced: true - } - } - }; - - mockMountingOptions = { - propsData: { - isActive: true - }, - mocks: { - $bus: { - $emit: jest.fn(), - $on: jest.fn(), - $off: jest.fn() - } - } - }; - }); - - it('can be initialized', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('exposes computed properties', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).currentUser).toBeDefined(); - expect((wrapper.vm as any).isVirtualCart).toBeDefined(); - }); - - describe('hooks', () => { - it('beforeMount hook should start subscription for user-after-loggedin event', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - expect(mockMountingOptions.mocks.$bus.$on).toHaveBeenCalledWith('user-after-loggedin', (wrapper.vm as any).onLoggedIn); - }); - - it('destroyed hook should stop subscription for user-after-loggedin event', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - wrapper.destroy(); - - expect(mockMountingOptions.mocks.$bus.$off).toHaveBeenCalledWith('user-after-loggedin', (wrapper.vm as any).onLoggedIn); - }); - - it('updated hook should set focus on password field', async () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - (wrapper.vm as any).$refs.password = { setFocus: jest.fn() }; - - wrapper.setData({ isValidationError: false }); - wrapper.setProps({ focusedField: 'password' }); - - await Vue.nextTick() - - expect((wrapper.vm as any).isValidationError).toBe(true); - expect((wrapper.vm as any).password).toBe(''); - expect((wrapper.vm as any).rPassword).toBe(''); - expect((wrapper.vm as any).$refs.password.setFocus).toHaveBeenCalledWith('password'); - }); - }); - - describe('methods', () => { - it('onLoggedIn method should set names and email', () => { - const personalDetails = { - firstname: 'example first name', - lastname: 'example last name', - email: 'example email' - }; - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - (wrapper.vm as any).onLoggedIn(personalDetails); - - expect((wrapper.vm as any).personalDetails).toEqual({ - firstName: personalDetails.firstname, - lastName: personalDetails.lastname, - emailAddress: personalDetails.email - }); - }); - - it('sendDataToCheckout method should create new account if flag is set', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - wrapper.setData({ - createAccount: true, - password: 'example password' - }); - (wrapper.vm as any).sendDataToCheckout(); - - expect((wrapper.vm as any).personalDetails).toEqual({ - password: 'example password', - createAccount: true - }); - }); - - it('sendDataToCheckout method should not create new account if flag is not set', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - wrapper.setData({ createAccount: false }); - (wrapper.vm as any).sendDataToCheckout(); - - expect((wrapper.vm as any).personalDetails).toEqual({ createAccount: false }); - }); - - it('sendDataToCheckout method should emit event and init `filled` and `validation error` flags', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - wrapper.setData({ createAccount: false }); - (wrapper.vm as any).sendDataToCheckout(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-after-personalDetails', { createAccount: false }, undefined); - expect((wrapper.vm as any).isFilled).toBe(true); - expect((wrapper.vm as any).isValidationError).toBe(false); - }); - - it('edit method should emit event if flag is set', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - wrapper.setData({ isFilled: true }); - (wrapper.vm as any).edit(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-before-edit', 'personalDetails'); - }); - - it('edit method should not emit event if flag is not set', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - wrapper.setData({ isFilled: false }); - (wrapper.vm as any).edit(); - - expect(mockMountingOptions.mocks.$bus.$emit).not.toHaveBeenCalled(); - }); - - it('gotoAccount method should emit event', () => { - const wrapper = mountMixinWithStore(PersonalDetails, mockStore, mockMountingOptions); - - (wrapper.vm as any).gotoAccount(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('modal-show', 'modal-signup'); - }); - }); -}); diff --git a/core/modules/checkout/test/unit/components/Product.spec.ts b/core/modules/checkout/test/unit/components/Product.spec.ts deleted file mode 100644 index 75a18379f3..0000000000 --- a/core/modules/checkout/test/unit/components/Product.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { mountMixin } from '@vue-storefront/unit-tests/utils'; -import { Product } from '../../../components/Product'; - -describe('Product', () => { - let mockMountingOptions; - - beforeEach(() => { - jest.clearAllMocks(); - - mockMountingOptions = { - propsData: { - product: { - image: 'example image', - sku: 'example sku' - } - }, - mocks: { - $bus: { - $emit: jest.fn(), - $on: jest.fn(), - $off: jest.fn() - } - }, - methods: { - getThumbnail: jest.fn(() => '') - } - }; - }); - - it('can be initialized', () => { - const wrapper = mountMixin(Product, mockMountingOptions); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('exposes computed properties', () => { - const wrapper = mountMixin(Product, mockMountingOptions); - - expect((wrapper.vm as any).thumbnail).toBeDefined(); - }); - - describe('hooks', () => { - it('beforeMount hook should start subscription for cart-after-itemchanged event', () => { - const wrapper = mountMixin(Product, mockMountingOptions); - - expect(mockMountingOptions.mocks.$bus.$on).toHaveBeenCalledWith('cart-after-itemchanged', (wrapper.vm as any).onProductChanged); - }); - - it('beforeDestroy hook should stop subscription for user-after-itemchanged event', () => { - const wrapper = mountMixin(Product, mockMountingOptions); - - wrapper.destroy(); - - expect(mockMountingOptions.mocks.$bus.$off).toHaveBeenCalledWith('cart-after-itemchanged', (wrapper.vm as any).onProductChanged); - }); - }); - - describe('methods', () => { - it('onProductChanged method should update component only if product has changed', () => { - const wrapper = mountMixin(Product, mockMountingOptions); - const mockForceUpdateFn = jest.spyOn((wrapper.vm as any), '$forceUpdate'); - - mockForceUpdateFn.mockClear(); - (wrapper.vm as any).onProductChanged({ item: { sku: 'example sku' } }); - expect(mockForceUpdateFn).toHaveBeenCalled(); - - mockForceUpdateFn.mockClear(); - (wrapper.vm as any).onProductChanged({ item: { sku: 'another sku' } }); - expect(mockForceUpdateFn).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/core/modules/checkout/test/unit/components/Shipping.spec.ts b/core/modules/checkout/test/unit/components/Shipping.spec.ts deleted file mode 100644 index d2ddfae15a..0000000000 --- a/core/modules/checkout/test/unit/components/Shipping.spec.ts +++ /dev/null @@ -1,470 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { Shipping } from '../../../components/Shipping'; - -describe('Shipping', () => { - let mockStore; - let mockMountingOptions; - let mockMethods; - let mockHooks; - - beforeEach(() => { - jest.clearAllMocks(); - - mockStore = { - modules: { - checkout: { - state: { - shippingDetails: {} - }, - getters: { - getShippingMethods: jest.fn(() => ([])), - getPaymentMethods: jest.fn(() => ([])) - }, - actions: { - updatePropValue: jest.fn() - }, - namespaced: true - }, - user: { - state: { - current: {} - }, - namespaced: true - } - } - }; - - mockMountingOptions = { - propsData: { - isActive: true - }, - mocks: { - $bus: { - $emit: jest.fn(), - $on: jest.fn(), - $off: jest.fn() - } - } - }; - - mockMethods = Object.entries(Shipping.methods) - .reduce((result, [methodName]) => { - result[methodName] = jest.spyOn(Shipping.methods, methodName as keyof typeof Shipping.methods) - .mockImplementation(jest.fn()); - - return result; - }, {}); - - mockHooks = ['beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed'] - .reduce((result, hookName) => { - if (Shipping[hookName]) { - result[hookName] = jest.spyOn(Shipping, hookName as any) - .mockImplementation(jest.fn()); - } - - return result; - }, {}); - }); - - it('can be initialized', () => { - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('exposes computed properties', () => { - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).currentUser).toBeDefined(); - expect((wrapper.vm as any).shippingMethods).toBeDefined(); - expect((wrapper.vm as any).checkoutShippingDetails).toBeDefined(); - expect((wrapper.vm as any).paymentMethod).toBeDefined(); - }); - - describe('hooks', () => { - it('beforeMount hook should start subscription for checkout-after-personalDetails and checkout-after-shippingset events', () => { - mockHooks['beforeMount'].mockRestore(); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - expect(mockMountingOptions.mocks.$bus.$on) - .toHaveBeenCalledWith('checkout-after-personalDetails', (wrapper.vm as any).onAfterPersonalDetails); - expect(mockMountingOptions.mocks.$bus.$on) - .toHaveBeenCalledWith('checkout-after-shippingset', (wrapper.vm as any).onAfterShippingSet); - }); - - it('beforeDestroy hook should stop subscription for checkout-after-personalDetails and checkout-after-shippingset events', () => { - mockHooks['beforeDestroy'].mockRestore(); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - wrapper.destroy(); - - expect(mockMountingOptions.mocks.$bus.$off) - .toHaveBeenCalledWith('checkout-after-personalDetails', (wrapper.vm as any).onAfterPersonalDetails); - expect(mockMountingOptions.mocks.$bus.$off) - .toHaveBeenCalledWith('checkout-after-shippingset', (wrapper.vm as any).onAfterShippingSet); - }); - - it('mounted hook should call shipping details methods', () => { - mockHooks['mounted'].mockRestore(); - - mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - expect(mockMethods['checkDefaultShippingAddress']).toHaveBeenCalled(); - expect(mockMethods['checkDefaultShippingMethod']).toHaveBeenCalled(); - expect(mockMethods['changeShippingMethod']).toHaveBeenCalled(); - }); - }); - - describe('watchers', () => { - it('should call checkDefaultShippingMethod method if shipping methods have changed', () => { - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - (wrapper.vm as any).$options.watch.shippingMethods.handler.call(wrapper.vm); - - expect(mockMethods['checkDefaultShippingMethod']).toHaveBeenCalled(); - }); - - it('should call useMyAddress method if shipping address has changed', () => { - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - (wrapper.vm as any).$options.watch.shipToMyAddress.handler.call(wrapper.vm); - - expect(mockMethods['useMyAddress']).toHaveBeenCalled(); - }); - }); - - describe('methods', () => { - it('checkDefaultShippingAddress should check if default shipping address is configured', () => { - mockMethods['checkDefaultShippingAddress'].mockRestore(); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - mockMethods['hasShippingDetails'].mockClear(); - mockMethods['hasShippingDetails'].mockReturnValue(false); - (wrapper.vm as any).checkDefaultShippingAddress(); - - expect(mockMethods['hasShippingDetails']).toHaveBeenCalled(); - expect((wrapper.vm as any).shipToMyAddress).toBe(false); - - mockMethods['hasShippingDetails'].mockClear(); - mockMethods['hasShippingDetails'].mockReturnValue(true); - (wrapper.vm as any).checkDefaultShippingAddress(); - - expect(mockMethods['hasShippingDetails']).toHaveBeenCalled(); - expect((wrapper.vm as any).shipToMyAddress).toBe(true); - }); - - it('checkDefaultShippingMethod should configure default shipping method and carrier if current shipping method is falsy', () => { - mockMethods['checkDefaultShippingMethod'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.shippingMethod = ''; - mockStore.modules.checkout.getters.getShippingMethods.mockImplementation(() => ([ - { method_code: 'method code 1', carrier_code: 'carrier code 1' }, - { method_code: 'method code 2', carrier_code: 'carrier code 2' }, - { method_code: 'method code 3', carrier_code: 'carrier code 3', default: true } - ])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - mockMethods['notInMethods'].mockClear(); - mockMethods['notInMethods'].mockReturnValue(true); - (wrapper.vm as any).checkDefaultShippingMethod(); - - expect(mockMethods['notInMethods']).not.toHaveBeenCalled(); - expect((wrapper.vm as any).shipping).toEqual({ shippingMethod: 'method code 3', shippingCarrier: 'carrier code 3' }); - }); - - it('checkDefaultShippingMethod should configure default shipping method and carrier if current shipping method is not supported', () => { - mockMethods['checkDefaultShippingMethod'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.shippingMethod = 'not supported shipping method'; - mockStore.modules.checkout.getters.getShippingMethods.mockImplementation(() => ([ - { method_code: 'method code 1', carrier_code: 'carrier code 1' }, - { method_code: 'method code 2', carrier_code: 'carrier code 2' }, - { method_code: 'method code 3', carrier_code: 'carrier code 3', default: true } - ])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - mockMethods['notInMethods'].mockClear(); - mockMethods['notInMethods'].mockReturnValue(true); - (wrapper.vm as any).checkDefaultShippingMethod(); - - expect(mockMethods['notInMethods']).toHaveBeenCalledWith('not supported shipping method'); - expect((wrapper.vm as any).shipping).toEqual({ shippingMethod: 'method code 3', shippingCarrier: 'carrier code 3' }); - }); - - it('onAfterShippingSet should configure shipping data', () => { - mockMethods['onAfterShippingSet'].mockRestore(); - - const shippingData = { shippingMethod: 'method code 3', shippingCarrier: 'carrier code 3' }; - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - (wrapper.vm as any).onAfterShippingSet(shippingData); - - expect((wrapper.vm as any).shipping).toEqual(shippingData); - expect((wrapper.vm as any).isFilled).toBe(true); - }); - - it('onAfterPersonalDetails should configure personal data by dispatching actions', () => { - mockMethods['onAfterPersonalDetails'].mockRestore(); - - const personalData = { firstName: 'example first name', lastName: 'example last name' }; - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - wrapper.setData({ isFilled: false }); - (wrapper.vm as any).onAfterPersonalDetails(personalData); - - expect(mockStore.modules.checkout.actions.updatePropValue) - .toHaveBeenCalledWith(expect.anything(), ['firstName', 'example first name']); - expect(mockStore.modules.checkout.actions.updatePropValue) - .toHaveBeenCalledWith(expect.anything(), ['lastName', 'example last name']); - }); - - it('sendDataToCheckout should emit event', () => { - mockMethods['sendDataToCheckout'].mockRestore(); - - const shippingData = { shippingMethod: 'method code 3', shippingCarrier: 'carrier code 3' }; - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - wrapper.setData({ shipping: shippingData }); - (wrapper.vm as any).sendDataToCheckout(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-after-shippingDetails', shippingData, undefined); - expect((wrapper.vm as any).isFilled).toBe(true); - }); - - it('edit should emit event only if form is filled', () => { - mockMethods['edit'].mockRestore(); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - mockMountingOptions.mocks.$bus.$emit.mockClear(); - wrapper.setData({ isFilled: true }); - (wrapper.vm as any).edit(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-before-edit', 'shipping'); - - mockMountingOptions.mocks.$bus.$emit.mockClear(); - wrapper.setData({ isFilled: false }); - (wrapper.vm as any).edit(); - - expect(mockMountingOptions.mocks.$bus.$emit).not.toHaveBeenCalled(); - }); - - it('hasShippingDetails should check if shipping address is configured', () => { - mockMethods['hasShippingDetails'].mockRestore(); - mockStore.modules.user.state.current = {}; - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const hasShippingDetails = (wrapper.vm as any).hasShippingDetails(); - - expect(hasShippingDetails).toBe(false); - }); - - it('hasShippingDetails should init default shipping address only if it is configured', () => { - mockMethods['hasShippingDetails'].mockRestore(); - - const defaultAddress = { id: 123, city: 'example city', street: 'example street' }; - - mockStore.modules.user.state.current = { - default_shipping: 123, - addresses: [defaultAddress] - }; - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const hasShippingDetails = (wrapper.vm as any).hasShippingDetails(); - - expect(hasShippingDetails).toBe(true); - expect((wrapper.vm as any).myAddressDetails).toEqual(defaultAddress); - }); - - it('useMyAddress should init shipping address from myAddressDetails if shipToMyAddress is set', () => { - mockMethods['useMyAddress'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails = { - shippingMethod: 'example shipping method', - shippingCarrier: 'example shipping carrier' - }; - - const myAddressDetails = { - firstname: 'example first name', - lastname: 'example last name', - country_id: 'example country', - region: { region: 'example region' }, - city: 'example city', - street: ['example street', 'example apartment number'], - postcode: 'example zip code', - telephone: 'example phone number' - }; - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - wrapper.setData({ shipToMyAddress: true }); - wrapper.setData({ myAddressDetails }); - (wrapper.vm as any).useMyAddress(); - - expect((wrapper.vm as any).shipping).toEqual({ - firstName: 'example first name', - lastName: 'example last name', - country: 'example country', - state: 'example region', - city: 'example city', - streetAddress: 'example street', - apartmentNumber: 'example apartment number', - zipCode: 'example zip code', - phoneNumber: 'example phone number', - shippingMethod: 'example shipping method', - shippingCarrier: 'example shipping carrier' - }); - }); - - it('useMyAddress should init shipping address from shippingDetails if shipToMyAddress is not set', () => { - mockMethods['useMyAddress'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails = { - shippingMethod: 'example shipping method', - shippingCarrier: 'example shipping carrier' - }; - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - wrapper.setData({ shipToMyAddress: false }); - (wrapper.vm as any).useMyAddress(); - - expect((wrapper.vm as any).shipping).toEqual(mockStore.modules.checkout.state.shippingDetails); - }); - - it('useMyAddress should call changeCountry method', () => { - mockMethods['useMyAddress'].mockRestore(); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - (wrapper.vm as any).useMyAddress(); - - expect(mockMethods['changeCountry']).toHaveBeenCalled(); - }); - - it('getShippingMethod should return empty method_title and amount if shipping method is different than current one', () => { - mockMethods['getShippingMethod'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.shippingMethod = 'not supported shipping method'; - mockStore.modules.checkout.getters.getShippingMethods.mockImplementation(() => ([ - { method_code: 'method code 1', method_title: 'method title 1', amount: 'amount 1' }, - { method_code: 'method code 2', method_title: 'method title 2', amount: 'amount 2' }, - { method_code: 'method code 3', method_title: 'method title 3', amount: 'amount 3' } - ])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const shippingMethod = (wrapper.vm as any).getShippingMethod(); - - expect(shippingMethod).toEqual({ method_title: '', amount: '' }); - }); - - it('getShippingMethod should return method_title and amount if it is supported', () => { - mockMethods['getShippingMethod'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.shippingMethod = 'method code 2'; - mockStore.modules.checkout.getters.getShippingMethods.mockImplementation(() => ([ - { method_code: 'method code 1', method_title: 'method title 1', amount: 'amount 1' }, - { method_code: 'method code 2', method_title: 'method title 2', amount: 'amount 2' }, - { method_code: 'method code 3', method_title: 'method title 3', amount: 'amount 3' } - ])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const shippingMethod = (wrapper.vm as any).getShippingMethod(); - - expect(shippingMethod).toEqual({ method_title: 'method title 2', amount: 'amount 2' }); - }); - - it('getCountryName method should return country name from shipping', () => { - mockMethods['getCountryName'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.country = 'PL'; - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const countryName = (wrapper.vm as any).getCountryName(); - - expect(countryName).toBe('Poland'); - }); - - it('getCountryName method should return empty string if country has not been found', () => { - mockMethods['getCountryName'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.country = 'invalid country code'; - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const countryName = (wrapper.vm as any).getCountryName(); - - expect(countryName).toBe(''); - }); - - it('changeCountry should emit event', () => { - mockMethods['changeCountry'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.country = 'PL'; - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - (wrapper.vm as any).changeCountry(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-before-shippingMethods', 'PL'); - }); - - it('getCurrentShippingMethod should return undefined if there are no supported methods', () => { - mockMethods['getCurrentShippingMethod'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.shippingMethod = 'not supported shipping method'; - mockStore.modules.checkout.getters.getShippingMethods.mockImplementation(() => ([])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const shippingMethod = (wrapper.vm as any).getCurrentShippingMethod(); - - expect(shippingMethod).toBeUndefined(); - }); - - it('getCurrentShippingMethod should return shipping method details if it is supported', () => { - mockMethods['getCurrentShippingMethod'].mockRestore(); - mockStore.modules.checkout.state.shippingDetails.shippingMethod = 'method code 2'; - mockStore.modules.checkout.getters.getShippingMethods.mockImplementation(() => ([ - { method_code: 'method code 1', method_title: 'method title 1', amount: 'amount 1' }, - { method_code: 'method code 2', method_title: 'method title 2', amount: 'amount 2' }, - { method_code: 'method code 3', method_title: 'method title 3', amount: 'amount 3' } - ])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - const shippingMethod = (wrapper.vm as any).getCurrentShippingMethod(); - - expect(shippingMethod).toEqual({ method_code: 'method code 2', method_title: 'method title 2', amount: 'amount 2' }); - }); - - it('changeShippingMethod should not emit event if there is no current shipping method', () => { - mockMethods['changeShippingMethod'].mockRestore(); - mockMethods['getCurrentShippingMethod'].mockReturnValue(); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - (wrapper.vm as any).changeShippingMethod(); - - expect(mockMountingOptions.mocks.$bus.$emit).not.toHaveBeenCalled(); - }); - - it('changeShippingMethod should emit event with current shipping method if it is configured', () => { - mockMethods['changeShippingMethod'].mockRestore(); - mockMethods['getCurrentShippingMethod'].mockReturnValue({ method_code: 'method code', carrier_code: 'carrier code' }); - mockStore.modules.checkout.state.shippingDetails.country = 'PL'; - mockStore.modules.checkout.getters.getPaymentMethods.mockImplementation(() => ([{ code: 'payment code' }])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - (wrapper.vm as any).changeShippingMethod(); - - expect(mockMountingOptions.mocks.$bus.$emit).toHaveBeenCalledWith('checkout-after-shippingMethodChanged', { - country: 'PL', - method_code: 'method code', - carrier_code: 'carrier code', - payment_method: 'payment code' - }); - }); - - it('notInMethods method should inform if given shipping method is not supported', () => { - mockMethods['notInMethods'].mockRestore(); - mockStore.modules.checkout.getters.getShippingMethods.mockImplementation(() => ([ - { method_code: 'method code 1' }, - { method_code: 'method code 2' }, - { method_code: 'method code 3' } - ])); - - const wrapper = mountMixinWithStore(Shipping, mockStore, mockMountingOptions); - - expect((wrapper.vm as any).notInMethods('method code 2')).toBe(false); - expect((wrapper.vm as any).notInMethods('invalid method code')).toBe(true); - }); - }); -}); diff --git a/core/modules/checkout/test/unit/index.spec.ts b/core/modules/checkout/test/unit/index.spec.ts deleted file mode 100644 index 11dab280c2..0000000000 --- a/core/modules/checkout/test/unit/index.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; -import { CheckoutModule } from '@vue-storefront/core/modules/checkout'; -import { checkoutModule } from '@vue-storefront/core/modules/checkout/store/checkout'; -import { paymentModule } from '@vue-storefront/core/modules/checkout/store/payment'; -import { shippingModule } from '@vue-storefront/core/modules/checkout/store/shipping'; -import * as types from '@vue-storefront/core/modules/checkout/store/checkout/mutation-types'; - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: () => jest.fn() -})); - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - init: jest.fn(), - get: jest.fn() - } -})); - -describe('CheckoutModule', () => { - let store; - let subscription; - let mockSetItem; - - beforeEach(() => { - jest.clearAllMocks(); - - store = { - registerModule: jest.fn(), - subscribe: jest.fn(fn => { - subscription = fn; - }) - }; - - mockSetItem = jest.fn().mockResolvedValue({}); - - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - setItem: mockSetItem - })); - }); - - it('should init and register modules', () => { - CheckoutModule({ store } as any); - - expect(StorageManager.init).toHaveBeenCalledWith('checkout'); - expect(store.registerModule).toHaveBeenCalledWith('shipping', shippingModule); - expect(store.registerModule).toHaveBeenCalledWith('payment', paymentModule); - expect(store.registerModule).toHaveBeenCalledWith('checkout', checkoutModule); - expect(store.subscribe).toHaveBeenCalled(); - }); - - it('should subscribe and set personal details on updates', () => { - const mockState = { - checkout: { - personalDetails: { - firstName: 'example first name', - lastName: 'example last name' - } - } - }; - - CheckoutModule({ store } as any); - subscription({ type: types.CHECKOUT_SAVE_PERSONAL_DETAILS }, mockState); - - expect(StorageManager.get).toHaveBeenCalledWith('checkout'); - expect(mockSetItem).toHaveBeenCalledWith('personal-details', mockState.checkout.personalDetails); - }); - - it('should subscribe and set shipping details on updates', () => { - const mockState = { - checkout: { - shippingDetails: { - firstName: 'example first name', - lastName: 'example last name' - } - } - }; - - CheckoutModule({ store } as any); - subscription({ type: types.CHECKOUT_SAVE_SHIPPING_DETAILS }, mockState); - - expect(StorageManager.get).toHaveBeenCalledWith('checkout'); - expect(mockSetItem).toHaveBeenCalledWith('shipping-details', mockState.checkout.shippingDetails); - }); - - it('should subscribe and set payment details on updates', () => { - const mockState = { - checkout: { - paymentDetails: { - firstName: 'example first name', - lastName: 'example last name' - } - } - }; - - CheckoutModule({ store } as any); - subscription({ type: types.CHECKOUT_SAVE_PAYMENT_DETAILS }, mockState); - - expect(StorageManager.get).toHaveBeenCalledWith('checkout'); - expect(mockSetItem).toHaveBeenCalledWith('payment-details', mockState.checkout.paymentDetails); - }); -}); diff --git a/core/modules/checkout/test/unit/store/checkout/actions.spec.ts b/core/modules/checkout/test/unit/store/checkout/actions.spec.ts deleted file mode 100644 index df3d7b8bb6..0000000000 --- a/core/modules/checkout/test/unit/store/checkout/actions.spec.ts +++ /dev/null @@ -1,235 +0,0 @@ -import * as types from '@vue-storefront/core/modules/checkout/store/checkout/mutation-types'; -import checkoutActions from '@vue-storefront/core/modules/checkout/store/checkout/actions'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; -import { Logger } from '@vue-storefront/core/lib/logger'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => jest.fn()) - } -})); - -describe('Checkout actions', () => { - let mockContext; - - beforeEach(() => { - jest.clearAllMocks(); - - mockContext = { - commit: jest.fn(), - dispatch: jest.fn() - }; - }); - - describe('placeOrder', () => { - let order; - - beforeEach(() => { - order = { - sku: 123456789 - }; - }); - - it('should place order if return code is successful', async () => { - mockContext.dispatch.mockResolvedValue({ resultCode: 200 }); - await (checkoutActions as any).placeOrder(mockContext, { order }); - - expect(mockContext.dispatch).toHaveBeenCalledTimes(4); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(4, 'dropPassword'); - }); - - it('should place order if return code is missing', async () => { - mockContext.dispatch.mockResolvedValue({ resultCode: undefined }); - await (checkoutActions as any).placeOrder(mockContext, { order }); - - expect(mockContext.dispatch).toHaveBeenCalledTimes(4); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(4, 'dropPassword'); - }); - - it('should not place order if return code is not successful', async () => { - mockContext.dispatch.mockResolvedValue({ resultCode: 500 }); - await (checkoutActions as any).placeOrder(mockContext, { order }); - - expect(mockContext.dispatch).toHaveBeenCalledTimes(1); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(4, 'dropPassword'); - }); - - it('should log thrown error', async () => { - mockContext.dispatch.mockImplementation(() => { throw new Error(); }); - await (checkoutActions as any).placeOrder(mockContext, { order }); - - expect(mockContext.dispatch).toHaveBeenCalledTimes(1); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(4, 'dropPassword'); - expect(Logger.error).toHaveBeenCalled(); - }); - }); - - it('updateOrderTimestamp should update timestamp of order in cache', async () => { - const mockSetItem = jest.fn(() => ({})); - - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - setItem: mockSetItem - })); - - await (checkoutActions as any).updateOrderTimestamp(); - - expect(StorageManager.get).toHaveBeenCalledWith('user'); - expect(mockSetItem).toHaveBeenCalledWith('last-cart-bypass-ts', expect.any(Number)); - }); - - describe('dropPassword', () => { - it('should drop password for account creation', async () => { - mockContext.state = { - personalDetails: { - createAccount: true - } - }; - await (checkoutActions as any).dropPassword(mockContext); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_DROP_PASSWORD); - }); - - it('should not drop password if account is not created', async () => { - mockContext.state = { - personalDetails: { - createAccount: false - } - }; - await (checkoutActions as any).dropPassword(mockContext); - - expect(mockContext.commit).not.toHaveBeenCalled(); - }); - }); - - it('setModifiedAt should configure modified at date', async () => { - const timestamp = 1234567890; - await (checkoutActions as any).setModifiedAt(mockContext, timestamp); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_SET_MODIFIED_AT, timestamp); - }); - - it('savePersonalDetails should configure personal details', async () => { - const personalDetails = { - firstName: 'example first name', - lastName: 'example last name' - }; - await (checkoutActions as any).savePersonalDetails(mockContext, personalDetails); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_SAVE_PERSONAL_DETAILS, personalDetails); - }); - - it('saveShippingDetails should configure shipping details', async () => { - const shippingDetails = { - firstName: 'example first name', - lastName: 'example last name' - }; - await (checkoutActions as any).saveShippingDetails(mockContext, shippingDetails); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_SAVE_SHIPPING_DETAILS, shippingDetails); - }); - - it('savePaymentDetails should configure payment details', async () => { - const paymentDetails = { - paymentMethod: 'example payment method' - }; - await (checkoutActions as any).savePaymentDetails(mockContext, paymentDetails); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_SAVE_PAYMENT_DETAILS, paymentDetails); - }); - - it('load personal, shipping and payment details from cache', async () => { - const personalDetails = { - firstName: 'example personal first name', - lastName: 'example personal last name' - }; - const shippingDetails = { - firstName: 'example shipping first name', - lastName: 'example shipping last name' - }; - const paymentDetails = { - paymentMethod: 'example payment method' - }; - const mockGetItem = jest.fn() - .mockResolvedValueOnce(personalDetails) - .mockResolvedValueOnce(shippingDetails) - .mockResolvedValueOnce(paymentDetails); - - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: mockGetItem - })); - - await (checkoutActions as any).load(mockContext); - - expect(StorageManager.get).toHaveBeenCalledWith('checkout'); - expect(mockGetItem).toHaveBeenCalledWith('personal-details'); - expect(mockGetItem).toHaveBeenCalledWith('shipping-details'); - expect(mockGetItem).toHaveBeenCalledWith('payment-details'); - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_LOAD_PERSONAL_DETAILS, personalDetails); - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_LOAD_SHIPPING_DETAILS, shippingDetails); - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_LOAD_PAYMENT_DETAILS, paymentDetails); - }); - - it('updatePropValue should configure prop value', async () => { - const payload = { - data: 'example data' - }; - await (checkoutActions as any).updatePropValue(mockContext, payload); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_UPDATE_PROP_VALUE, payload); - }); - - it('setThankYouPage should configure thank you page', async () => { - const payload = { - data: 'example data' - }; - await (checkoutActions as any).setThankYouPage(mockContext, payload); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_SET_THANKYOU, payload); - }); - - it('addPaymentMethod should configure add payment method', async () => { - const paymentMethod = 'example payment method'; - await (checkoutActions as any).addPaymentMethod(mockContext, paymentMethod); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_ADD_PAYMENT_METHOD, paymentMethod); - }); - - it('replacePaymentMethods should replace payment method', async () => { - const paymentMethod = 'example payment method'; - await (checkoutActions as any).replacePaymentMethods(mockContext, paymentMethod); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_SET_PAYMENT_METHODS, paymentMethod); - }); - - it('addShippingMethod should add shipping method', async () => { - const shippingMethod = 'example shipping method'; - await (checkoutActions as any).addShippingMethod(mockContext, shippingMethod); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_ADD_SHIPPING_METHOD, shippingMethod); - }); - - it('replaceShippingMethods should replace shipping method', async () => { - const shippingMethod = 'example shipping method'; - await (checkoutActions as any).replaceShippingMethods(mockContext, shippingMethod); - - expect(mockContext.commit).toHaveBeenCalledWith(types.CHECKOUT_SET_SHIPPING_METHODS, shippingMethod); - }); -}); diff --git a/core/modules/checkout/test/unit/store/checkout/getters.spec.ts b/core/modules/checkout/test/unit/store/checkout/getters.spec.ts deleted file mode 100644 index 9552fab31f..0000000000 --- a/core/modules/checkout/test/unit/store/checkout/getters.spec.ts +++ /dev/null @@ -1,172 +0,0 @@ -import checkoutGetters from '@vue-storefront/core/modules/checkout/store/checkout/getters'; - -describe('Checkout getters', () => { - it('getShippingDetails should return shipping details with default country from rootState if country is missing in shipping details', () => { - const mockState = { - shippingDetails: { - firstName: 'example first name', - lastName: 'example last name', - country: '' - } - }; - - const mockRootState = { - storeView: { - tax: { - defaultCountry: 'Poland' - } - } - }; - - const shippingDetails = (checkoutGetters as any).getShippingDetails(mockState, null, mockRootState); - - expect(shippingDetails).toEqual({ ...mockState.shippingDetails, country: mockRootState.storeView.tax.defaultCountry }); - }); - - it('getShippingDetails should return shipping details with shipping country if it exists', () => { - const mockState = { - shippingDetails: { - firstName: 'example first name', - lastName: 'example last name', - country: 'USA' - } - }; - - const mockRootState = { - storeView: { - tax: { - defaultCountry: 'Poland' - } - } - }; - - const shippingDetails = (checkoutGetters as any).getShippingDetails(mockState, null, mockRootState); - - expect(shippingDetails).toEqual({ ...mockState.shippingDetails }); - }); - - it('getPersonalDetails should return personal details', () => { - const mockState = { - personalDetails: { - firstName: 'example first name', - lastName: 'example last name' - } - }; - - const personalDetails = (checkoutGetters as any).getPersonalDetails(mockState); - - expect(personalDetails).toEqual({ ...mockState.personalDetails }); - }); - - it('getPaymentDetails should return personal details', () => { - const mockState = { - paymentDetails: { - paymentMethod: 'example payment method' - } - }; - - const paymentDetails = (checkoutGetters as any).getPaymentDetails(mockState); - - expect(paymentDetails).toEqual({ ...mockState.paymentDetails }); - }); - - it('isThankYouPage should inform if thank you page must be shown', () => { - const isThankYouPage = (checkoutGetters as any).isThankYouPage({ isThankYouPage: true }); - const isNotThankYouPage = (checkoutGetters as any).isThankYouPage({ isThankYouPage: false }); - - expect(isThankYouPage).toBe(true); - expect(isNotThankYouPage).toBe(false); - }); - - it('getModifiedAt should return modified at time', () => { - const modifiedAt = (checkoutGetters as any).getModifiedAt({ modifiedAt: 1234567890 }); - - expect(modifiedAt).toBe(1234567890); - }); - - it('isUserInCheckout should inform if user is in checkout if it has been modified less than 30 minutes ago', () => { - const isUserInCheckout = (checkoutGetters as any).isUserInCheckout({ modifiedAt: Date.now() - (1000 * 60 * 29) }); - const isUserNotInCheckout = (checkoutGetters as any).isUserInCheckout({ modifiedAt: Date.now() - (1000 * 60 * 31) }); - - expect(isUserInCheckout).toBe(true); - expect(isUserNotInCheckout).toBe(false); - }); - - it('getPaymentMethods should return all configured payment methods if virtual cart is not set', () => { - const mockState = { - paymentMethods: [ - { code: 'example method 1' }, { code: 'example method 2' }, { code: 'cashondelivery' } - ] - }; - const rootGetters = { - 'cart/isVirtualCart': false - }; - - const paymentMethods = (checkoutGetters as any).getPaymentMethods(mockState, null, null, rootGetters); - - expect(paymentMethods).toEqual([{ code: 'example method 1' }, { code: 'example method 2' }, { code: 'cashondelivery' }]); - }); - - it('getPaymentMethods should return all configured payment methods except cashondelivery if virtual cart is set', () => { - const mockState = { - paymentMethods: [ - { code: 'example method 1' }, { code: 'example method 2' }, { code: 'cashondelivery' } - ] - }; - const rootGetters = { - 'cart/isVirtualCart': true - }; - - const paymentMethods = (checkoutGetters as any).getPaymentMethods(mockState, null, null, rootGetters); - - expect(paymentMethods).toEqual([{ code: 'example method 1' }, { code: 'example method 2' }]); - }); - - it('getDefaultPaymentMethod should return default payment method', () => { - const mockGetters = { - getPaymentMethods: [ - { code: 'example method 1' }, { code: 'example method 2', default: true }, { code: 'cashondelivery' } - ] - }; - - const paymentMethod = (checkoutGetters as any).getDefaultPaymentMethod(null, mockGetters); - - expect(paymentMethod).toEqual({ code: 'example method 2', default: true }); - }); - - it('getNotServerPaymentMethods should return methods not for server', () => { - const mockGetters = { - getPaymentMethods: [ - { code: 'example method 1' }, { code: 'example method 2', is_server_method: true } - ] - }; - - const paymentMethods = (checkoutGetters as any).getNotServerPaymentMethods(null, mockGetters); - - expect(paymentMethods).toEqual([{ code: 'example method 1' }]); - }); - - it('getShippingMethods should return shipping methods', () => { - const mockState = { - shippingMethods: [ - { code: 'example method 1' }, { code: 'example method 2' } - ] - }; - - const shippingMethods = (checkoutGetters as any).getShippingMethods(mockState); - - expect(shippingMethods).toEqual([{ code: 'example method 1' }, { code: 'example method 2' }]); - }); - - it('getDefaultShippingMethod should return default shipping method', () => { - const mockState = { - shippingMethods: [ - { code: 'example method 1' }, { code: 'example method 2', default: true } - ] - }; - - const shippingMethod = (checkoutGetters as any).getDefaultShippingMethod(mockState); - - expect(shippingMethod).toEqual({ code: 'example method 2', default: true }); - }); -}); diff --git a/core/modules/checkout/test/unit/store/checkout/mutations.spec.ts b/core/modules/checkout/test/unit/store/checkout/mutations.spec.ts deleted file mode 100644 index bbdfb7de95..0000000000 --- a/core/modules/checkout/test/unit/store/checkout/mutations.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -import * as types from '@vue-storefront/core/modules/checkout/store/checkout/mutation-types'; -import checkoutMutations from '@vue-storefront/core/modules/checkout/store/checkout/mutations' - -describe('Checkout mutations', () => { - it('CHECKOUT_PLACE_ORDER should set order', () => { - const order = { id: 1234567890 }; - const mockState = { - order: null - }; - const expectedState = { - order: { ...order } - }; - - (checkoutMutations as any)[types.CHECKOUT_PLACE_ORDER](mockState, order); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_SET_MODIFIED_AT should set modified at time', () => { - const mockState = { - modifiedAt: 1 - }; - const expectedState = { - modifiedAt: 1234567890 - }; - - (checkoutMutations as any)[types.CHECKOUT_SET_MODIFIED_AT](mockState, 1234567890); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_SAVE_PERSONAL_DETAILS should set personal details', () => { - const personalDetails = { id: 1234567890 }; - const mockState = { - personalDetails: null - }; - const expectedState = { - personalDetails: { ...personalDetails } - }; - - (checkoutMutations as any)[types.CHECKOUT_SAVE_PERSONAL_DETAILS](mockState, personalDetails); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_SAVE_SHIPPING_DETAILS should set shipping details', () => { - const shippingDetails = { id: 1234567890 }; - const mockState = { - shippingDetails: null - }; - const expectedState = { - shippingDetails: { ...shippingDetails } - }; - - (checkoutMutations as any)[types.CHECKOUT_SAVE_SHIPPING_DETAILS](mockState, shippingDetails); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_SAVE_PAYMENT_DETAILS should set payment details', () => { - const paymentDetails = { id: 1234567890 }; - const mockState = { - paymentDetails: null - }; - const expectedState = { - paymentDetails: { ...paymentDetails } - }; - - (checkoutMutations as any)[types.CHECKOUT_SAVE_PAYMENT_DETAILS](mockState, paymentDetails); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_LOAD_PERSONAL_DETAILS should load personal details', () => { - const personalDetails = { id: 1234567890 }; - const mockState = { - personalDetails: null - }; - const expectedState = { - personalDetails: { ...personalDetails } - }; - - (checkoutMutations as any)[types.CHECKOUT_LOAD_PERSONAL_DETAILS](mockState, personalDetails); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_LOAD_SHIPPING_DETAILS should load personal details', () => { - const shippingDetails = { id: 1234567890 }; - const mockState = { - shippingDetails: null - }; - const expectedState = { - shippingDetails: { ...shippingDetails } - }; - - (checkoutMutations as any)[types.CHECKOUT_LOAD_SHIPPING_DETAILS](mockState, shippingDetails); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_LOAD_PAYMENT_DETAILS should load payment details', () => { - const paymentDetails = { id: 1234567890 }; - const mockState = { - paymentDetails: null - }; - const expectedState = { - paymentDetails: { ...paymentDetails } - }; - - (checkoutMutations as any)[types.CHECKOUT_LOAD_PAYMENT_DETAILS](mockState, paymentDetails); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_UPDATE_PROP_VALUE should update shipping property', () => { - const payload = ['prop', 'value']; - const mockState = { - shippingDetails: { - prop: null - } - }; - const expectedState = { - shippingDetails: { - prop: 'value' - } - }; - - (checkoutMutations as any)[types.CHECKOUT_UPDATE_PROP_VALUE](mockState, payload); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_DROP_PASSWORD should init password and create account flag', () => { - const mockState = { - personalDetails: { - password: 'example password', - createAccount: true - } - }; - const expectedState = { - personalDetails: { - password: '', - createAccount: false - } - }; - - (checkoutMutations as any)[types.CHECKOUT_DROP_PASSWORD](mockState); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_SET_THANKYOU should init thank you page', () => { - const mockState = { - isThankYouPage: false - }; - const expectedState = { - isThankYouPage: true - }; - - (checkoutMutations as any)[types.CHECKOUT_SET_THANKYOU](mockState, true); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_ADD_PAYMENT_METHOD should add payment method', () => { - const mockState = { - paymentMethods: [] - }; - const expectedState = { - paymentMethods: [{ code: 'example payment method' }] - }; - - (checkoutMutations as any)[types.CHECKOUT_ADD_PAYMENT_METHOD](mockState, { code: 'example payment method' }); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_SET_PAYMENT_METHODS should set payment methods', () => { - const mockState = { - paymentMethods: [{ code: 'previous example payment method' }] - }; - const expectedState = { - paymentMethods: [{ code: 'example payment method' }] - }; - - (checkoutMutations as any)[types.CHECKOUT_SET_PAYMENT_METHODS](mockState, [{ code: 'example payment method' }]); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_ADD_SHIPPING_METHOD should add shipping method', () => { - const mockState = { - shippingMethods: [] - }; - const expectedState = { - shippingMethods: [{ code: 'example shipping method' }] - }; - - (checkoutMutations as any)[types.CHECKOUT_ADD_SHIPPING_METHOD](mockState, { code: 'example shipping method' }); - - expect(mockState).toEqual(expectedState); - }); - - it('CHECKOUT_SET_SHIPPING_METHODS should set shipping methods', () => { - const mockState = { - shippingMethods: [{ code: 'previous example shipping method' }] - }; - const expectedState = { - shippingMethods: [{ code: 'example shipping method' }] - }; - - (checkoutMutations as any)[types.CHECKOUT_SET_SHIPPING_METHODS](mockState, [{ code: 'example shipping method' }]); - - expect(mockState).toEqual(expectedState); - }); -}); diff --git a/core/modules/checkout/test/unit/store/payment/index.spec.ts b/core/modules/checkout/test/unit/store/payment/index.spec.ts deleted file mode 100644 index 504ab83d4d..0000000000 --- a/core/modules/checkout/test/unit/store/payment/index.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { paymentModule } from '@vue-storefront/core/modules/checkout/store/payment'; - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => jest.fn()) - } -})); - -describe('Payment actions', () => { - let mockContext; - - beforeEach(() => { - jest.clearAllMocks(); - - mockContext = { - dispatch: jest.fn() - }; - }); - - it('addMethod should configure new payment method', async () => { - await (paymentModule.actions as any).addMethod(mockContext, 'example payment method'); - - expect(mockContext.dispatch).toHaveBeenCalledWith('checkout/addPaymentMethod', 'example payment method', { root: true }); - }); - - it('replaceMethods should replace payment method', async () => { - await (paymentModule.actions as any).replaceMethods(mockContext, 'example payment method'); - - expect(mockContext.dispatch).toHaveBeenCalledWith('checkout/replacePaymentMethods', 'example payment method', { root: true }); - }); -}); - -describe('Payment getters', () => { - it('paymentMethods should return payment methods from rootStore', () => { - const rootGetters = { - 'checkout/getPaymentMethods': [ - { code: 'example payment method' } - ] - }; - - const personalDetails = (paymentModule.getters as any).paymentMethods(null, null, null, rootGetters); - - expect(personalDetails).toEqual([{ code: 'example payment method' }]); - }); - - it('getDefaultPaymentMethod should return default payment method from rootStore', () => { - const rootGetters = { - 'checkout/getDefaultPaymentMethod': { code: 'example payment method' } - }; - - const personalDetails = (paymentModule.getters as any).getDefaultPaymentMethod(null, null, null, rootGetters); - - expect(personalDetails).toEqual({ code: 'example payment method' }); - }); - - it('getNotServerPaymentMethods should return not server methods from rootStore', () => { - const rootGetters = { - 'checkout/getNotServerPaymentMethods': { code: 'example payment method' } - }; - - const personalDetails = (paymentModule.getters as any).getNotServerPaymentMethods(null, null, null, rootGetters); - - expect(personalDetails).toEqual({ code: 'example payment method' }); - }); -}); diff --git a/core/modules/checkout/test/unit/store/shipping/index.spec.ts b/core/modules/checkout/test/unit/store/shipping/index.spec.ts deleted file mode 100644 index e92333234b..0000000000 --- a/core/modules/checkout/test/unit/store/shipping/index.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { shippingModule } from '@vue-storefront/core/modules/checkout/store/shipping'; - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => jest.fn()) - } -})); - -describe('Payment actions', () => { - let mockContext; - - beforeEach(() => { - jest.clearAllMocks(); - - mockContext = { - dispatch: jest.fn() - }; - }); - - it('addMethod should configure new shipping method', async () => { - await (shippingModule.actions as any).addMethod(mockContext, 'example shipping method'); - - expect(mockContext.dispatch).toHaveBeenCalledWith('checkout/addShippingMethod', 'example shipping method', { root: true }); - }); - - it('replaceMethods should replace shipping method', async () => { - await (shippingModule.actions as any).replaceMethods(mockContext, 'example shipping method'); - - expect(mockContext.dispatch).toHaveBeenCalledWith('checkout/replaceShippingMethods', 'example shipping method', { root: true }); - }); -}); - -describe('Shipping getters', () => { - it('shippingMethods should return shipping methods from rootStore', () => { - const rootGetters = { - 'checkout/getShippingMethods': [ - { code: 'example shipping method' } - ] - }; - - const shippingDetails = (shippingModule.getters as any).shippingMethods(null, null, null, rootGetters); - - expect(shippingDetails).toEqual([{ code: 'example shipping method' }]); - }); - - it('getShippingMethods should return shipping methods from rootStore', () => { - const rootGetters = { - 'checkout/getShippingMethods': [ - { code: 'example shipping method' } - ] - }; - - const shippingDetails = (shippingModule.getters as any).getShippingMethods(null, null, null, rootGetters); - - expect(shippingDetails).toEqual([{ code: 'example shipping method' }]); - }); - - it('getDefaultShippingMethod should return default shipping method from rootStore', () => { - const rootGetters = { - 'checkout/getDefaultShippingMethod': { code: 'example shipping method' } - }; - - const personalDetails = (shippingModule.getters as any).getDefaultShippingMethod(null, null, null, rootGetters); - - expect(personalDetails).toEqual({ code: 'example shipping method' }); - }); -}); diff --git a/core/modules/checkout/types/CheckoutState.ts b/core/modules/checkout/types/CheckoutState.ts deleted file mode 100644 index 29164f08d4..0000000000 --- a/core/modules/checkout/types/CheckoutState.ts +++ /dev/null @@ -1,19 +0,0 @@ -import ShippingDetails from './ShippingDetails' -import PaymentDetails from './PaymentDetails' - -export default interface CheckoutState { - order: any, - paymentMethods: any[], - shippingMethods: any[], - personalDetails: { - firstName: string, - lastName: string, - emailAddress: string, - password: string, - createAccount: boolean - }, - shippingDetails: ShippingDetails, - paymentDetails: PaymentDetails, - isThankYouPage: boolean, - modifiedAt: number -} diff --git a/core/modules/checkout/types/PaymentDetails.ts b/core/modules/checkout/types/PaymentDetails.ts deleted file mode 100644 index 0f14fd254f..0000000000 --- a/core/modules/checkout/types/PaymentDetails.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default interface PaymentDetails { - firstName: string, - lastName: string, - company: string, - country: string, - streetAddress: string, - apartmentNumber: string, - city: string, - region_id: number | string, - state: string, - zipCode: string, - phoneNumber: string, - taxId: string, - paymentMethod: string, - paymentMethodAdditional: any -} diff --git a/core/modules/checkout/types/PaymentState.ts b/core/modules/checkout/types/PaymentState.ts deleted file mode 100644 index 4a81f21f44..0000000000 --- a/core/modules/checkout/types/PaymentState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface PaymentState { - methods: any[] -} diff --git a/core/modules/checkout/types/ShippingDetails.ts b/core/modules/checkout/types/ShippingDetails.ts deleted file mode 100644 index cf35b93643..0000000000 --- a/core/modules/checkout/types/ShippingDetails.ts +++ /dev/null @@ -1,13 +0,0 @@ -export default interface ShippingDetails { - firstName: string, - lastName: string, - country: string, - streetAddress: string, - apartmentNumber: string, - city: string, - state: string, - region_id: number | string, - zipCode: string, - phoneNumber: string, - shippingMethod: string -} diff --git a/core/modules/checkout/types/ShippingState.ts b/core/modules/checkout/types/ShippingState.ts deleted file mode 100644 index 98efd39a14..0000000000 --- a/core/modules/checkout/types/ShippingState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface ShippingState { - methods: any[] -} diff --git a/core/modules/cms/helpers/createHierarchyLoadQuery.ts b/core/modules/cms/helpers/createHierarchyLoadQuery.ts deleted file mode 100644 index 6710720694..0000000000 --- a/core/modules/cms/helpers/createHierarchyLoadQuery.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const createHierarchyLoadQuery = ({ id }): SearchQuery => { - let query = new SearchQuery() - - if (id) { - query = query.applyFilter({ key: 'identifier', value: { eq: id } }) - } - - return query -} - -export default createHierarchyLoadQuery diff --git a/core/modules/cms/helpers/createLoadingBlockQuery.ts b/core/modules/cms/helpers/createLoadingBlockQuery.ts deleted file mode 100644 index 8cbb305305..0000000000 --- a/core/modules/cms/helpers/createLoadingBlockQuery.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const createLoadingBlockQuery = ({ filterField, filterValues }): SearchQuery => { - let query = new SearchQuery() - - if (filterValues) { - query = query.applyFilter({ key: filterField, value: { like: filterValues } }) - } - - return query -} - -export default createLoadingBlockQuery diff --git a/core/modules/cms/helpers/createPageLoadingQuery.ts b/core/modules/cms/helpers/createPageLoadingQuery.ts deleted file mode 100644 index b93f514fe7..0000000000 --- a/core/modules/cms/helpers/createPageLoadingQuery.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const createPageLoadingQuery = ({ filterField, filterValues }): SearchQuery => { - let query = new SearchQuery() - - if (filterValues) { - query = query.applyFilter({ key: filterField, value: { like: filterValues } }) - } - - return query -} - -export default createPageLoadingQuery diff --git a/core/modules/cms/helpers/createSingleBlockQuery.ts b/core/modules/cms/helpers/createSingleBlockQuery.ts deleted file mode 100644 index 1277a5d76f..0000000000 --- a/core/modules/cms/helpers/createSingleBlockQuery.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const createSingleBlockQuery = ({ key, value }): SearchQuery => { - let query = new SearchQuery() - - if (value) { - query = query.applyFilter({ key, value: { like: value } }) - } - - return query -} - -export default createSingleBlockQuery diff --git a/core/modules/cms/helpers/createSinglePageLoadQuery.ts b/core/modules/cms/helpers/createSinglePageLoadQuery.ts deleted file mode 100644 index ba7db2e08e..0000000000 --- a/core/modules/cms/helpers/createSinglePageLoadQuery.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const createSinglePageLoadQuery = ({ key, value }): SearchQuery => { - let query = new SearchQuery() - - if (value) { - query = query.applyFilter({ key, value: { like: value } }) - } - - return query -} - -export default createSinglePageLoadQuery diff --git a/core/modules/cms/helpers/index.ts b/core/modules/cms/helpers/index.ts deleted file mode 100644 index 9fd71d7974..0000000000 --- a/core/modules/cms/helpers/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import createLoadingBlockQuery from './createLoadingBlockQuery' -import createSingleBlockQuery from './createSingleBlockQuery' -import createHierarchyLoadQuery from './createHierarchyLoadQuery' -import createPageLoadingQuery from './createPageLoadingQuery' -import createSinglePageLoadQuery from './createSinglePageLoadQuery' - -export { - createLoadingBlockQuery, - createSingleBlockQuery, - createHierarchyLoadQuery, - createPageLoadingQuery, - createSinglePageLoadQuery -} diff --git a/core/modules/cms/index.ts b/core/modules/cms/index.ts deleted file mode 100644 index baaa06fb0b..0000000000 --- a/core/modules/cms/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { cmsPageModule } from './store/page' -import { cmsBlockModule } from './store/block' -import { cmsHierarchyModule } from './store/hierarchy' -import cmsPersistPlugin from './store/cmsPersistPlugin' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const CmsModule: StorefrontModule = function ({ store }) { - StorageManager.init('cms') - store.registerModule('cmsPage', cmsPageModule) - store.registerModule('cmsBlock', cmsBlockModule) - store.registerModule('cmsHierarchy', cmsHierarchyModule) - store.subscribe(cmsPersistPlugin) -} diff --git a/core/modules/cms/store/block/actions.ts b/core/modules/cms/store/block/actions.ts deleted file mode 100644 index 65b04a58bc..0000000000 --- a/core/modules/cms/store/block/actions.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ActionTree } from 'vuex' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState'; -import CmsBlockState from '../../types/CmsBlockState' -import { createLoadingBlockQuery, createSingleBlockQuery } from '@vue-storefront/core/modules/cms/helpers' - -const actions: ActionTree = { - async list ({ getters, commit }, { filterValues = null, filterField = 'identifier', size = 150, start = 0, excludeFields = null, includeFields = null, skipCache = false }) { - if (skipCache || !getters.hasItems) { - const blockResponse = await quickSearchByQuery({ - query: createLoadingBlockQuery({ filterField, filterValues }), - entityType: 'cms_block', - size, - start, - excludeFields, - includeFields - }) - - commit(types.CMS_BLOCK_UPDATE_CMS_BLOCKS, blockResponse.items) - return blockResponse.items - } - - return getters.getCmsBlocks - }, - async single ({ getters, commit }, { key = 'identifier', value, excludeFields = null, includeFields = null, skipCache = false }) { - let cmsBlock = getters.findCmsBlocks({ key, value }) - - if (skipCache || cmsBlock.length === 0) { - const blockResponse = await quickSearchByQuery({ - query: createSingleBlockQuery({ key, value }), - entityType: 'cms_block', - excludeFields, - includeFields - }) - - if (blockResponse.items.length > 0) { - const items = blockResponse.items.filter(item => item[key] === value) - commit(types.CMS_BLOCK_ADD_CMS_BLOCK, items[0]) - return items[0] - } - } - - return cmsBlock[0] - }, - addItem ({ commit }, block) { - commit(types.CMS_BLOCK_ADD_CMS_BLOCK, block) - } -} - -export default actions diff --git a/core/modules/cms/store/block/getters.ts b/core/modules/cms/store/block/getters.ts deleted file mode 100644 index c70f5333f7..0000000000 --- a/core/modules/cms/store/block/getters.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { GetterTree } from 'vuex' -import CmsBlockState from '../../types/CmsBlockState' -import RootState from '@vue-storefront/core/types/RootState' - -const getters: GetterTree = { - // @deprecated - cmsBlocks: (state, getters) => getters.getCmsBlocks, - // @deprecated - cmsBlockIdentifier: (state, getters) => (identifier) => getters.getCmsBlockByIdentifier(identifier), - // @deprecated - cmsBlockId: (state, getters) => (id) => getters.getCmsBlockById(id), - getCmsBlockByIdentifier: (state) => (identifier) => - state.items.find(item => typeof item === 'object' && item.identifier === identifier), - getCmsBlockById: (state) => (id) => state.items.find(item => item.id === id), - getCmsBlocks: (state) => state.items, - hasItems: (state) => state.items && state.items.length > 0, - findCmsBlocks: (state) => ({ key, value }) => state.items.filter(item => item[key] === value) -} - -export default getters diff --git a/core/modules/cms/store/block/index.ts b/core/modules/cms/store/block/index.ts deleted file mode 100644 index 8f97976b92..0000000000 --- a/core/modules/cms/store/block/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import CmsBlockState from '../../types/CmsBlockState' - -export const cmsBlockStorageKey = 'cms-blocks' - -export const cmsBlockModule: Module = { - namespaced: true, - state: { - items: [] - }, - getters, - actions, - mutations -} diff --git a/core/modules/cms/store/block/mutation-types.ts b/core/modules/cms/store/block/mutation-types.ts deleted file mode 100644 index 6aa596e05a..0000000000 --- a/core/modules/cms/store/block/mutation-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SN_CMS_BLOCK = 'cmsBlock' -export const CMS_BLOCK_UPDATE_CMS_BLOCKS = SN_CMS_BLOCK + '/UPDATE_CMS_BLOCKS' -export const CMS_BLOCK_ADD_CMS_BLOCK = SN_CMS_BLOCK + '/ADD_CMS_BLOCK' diff --git a/core/modules/cms/store/block/mutations.ts b/core/modules/cms/store/block/mutations.ts deleted file mode 100644 index 4b15610d56..0000000000 --- a/core/modules/cms/store/block/mutations.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import CmsBlockState from '../../types/CmsBlockState' - -const mutations: MutationTree = { - [types.CMS_BLOCK_UPDATE_CMS_BLOCKS] (state, cmsBlocks) { - state.items = cmsBlocks || [] - }, - [types.CMS_BLOCK_ADD_CMS_BLOCK] (state, cmsBlock) { - const record = state.items.find(c => c.id === cmsBlock.id) - if (!record) { - state.items.push(cmsBlock) - } - } -} - -export default mutations diff --git a/core/modules/cms/store/cmsPersistPlugin.ts b/core/modules/cms/store/cmsPersistPlugin.ts deleted file mode 100644 index 562db10bf3..0000000000 --- a/core/modules/cms/store/cmsPersistPlugin.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as pageTypes from './page/mutation-types' -import * as blockTypes from './block/mutation-types' -import { cmsPagesStorageKey } from './page' -import { cmsBlockStorageKey } from './block' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { Logger } from '@vue-storefront/core/lib/logger' - -const cmsPersistPlugin = (mutation, state) => { - const cmsStorage = StorageManager.get('cms') - - if (mutation.type.startsWith(pageTypes.SN_CMS_PAGE)) { - cmsStorage.setItem(cmsPagesStorageKey, state.cmsPage.items).catch((reason) => { - Logger.error(reason, 'cms') // it doesn't work on SSR - }) - } - - if (mutation.type.startsWith(blockTypes.SN_CMS_BLOCK)) { - cmsStorage.setItem(cmsBlockStorageKey, state.cmsBlock.items).catch((reason) => { - Logger.error(reason, 'cms') // it doesn't work on SSR - }) - } -} - -export default cmsPersistPlugin diff --git a/core/modules/cms/store/hierarchy/actions.ts b/core/modules/cms/store/hierarchy/actions.ts deleted file mode 100644 index b556fa567f..0000000000 --- a/core/modules/cms/store/hierarchy/actions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ActionTree } from 'vuex' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import RootState from '@vue-storefront/core/types/RootState'; -import CmsHierarchyState from '../../types/CmsHierarchyState' -import { createHierarchyLoadQuery } from '@vue-storefront/core/modules/cms/helpers' - -const actions: ActionTree = { - list (context, { id, entityType = 'cms_hierarchy', excludeFields = null, includeFields = null }) { - return quickSearchByQuery({ - query: createHierarchyLoadQuery({ id }), - entityType, - excludeFields, - includeFields - }) - } -} - -export default actions diff --git a/core/modules/cms/store/hierarchy/index.ts b/core/modules/cms/store/hierarchy/index.ts deleted file mode 100644 index dece79fcfc..0000000000 --- a/core/modules/cms/store/hierarchy/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import RootState from '@vue-storefront/core/types/RootState' -import CmsHierarchyState from '../../types/CmsHierarchyState' - -export const cmsHierarchyModule: Module = { - namespaced: true, - state: { - items: [] - }, - actions -} diff --git a/core/modules/cms/store/hierarchy/mutation-types.ts b/core/modules/cms/store/hierarchy/mutation-types.ts deleted file mode 100644 index 0240915c5e..0000000000 --- a/core/modules/cms/store/hierarchy/mutation-types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const SN_CMS_HIERARCHY = 'cms_hierarchy' -export const CMS_HIERARCHY_UPDATE_CMS_HIERARCHIES = SN_CMS_HIERARCHY + '/UPDATE_CMS_HIERARCHIES' diff --git a/core/modules/cms/store/page/actions.ts b/core/modules/cms/store/page/actions.ts deleted file mode 100644 index f005d7b3ba..0000000000 --- a/core/modules/cms/store/page/actions.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ActionTree } from 'vuex' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState'; -import CmsPageState from '../../types/CmsPageState' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { cmsPagesStorageKey } from './' -import { createPageLoadingQuery, createSinglePageLoadQuery } from '@vue-storefront/core/modules/cms/helpers' - -const actions: ActionTree = { - async list ({ commit }, { filterValues = null, filterField = 'identifier', size = 150, start = 0, excludeFields = null, includeFields = null, skipCache = false }) { - let query = createPageLoadingQuery({ filterField, filterValues }) - const pageResponse = await quickSearchByQuery({ query, entityType: 'cms_page', excludeFields, includeFields }) - - commit(types.CMS_PAGE_UPDATE_CMS_PAGES, pageResponse.items) - return pageResponse.items - }, - async single ({ getters, commit, dispatch }, { key = 'identifier', value, excludeFields = null, includeFields = null, skipCache = false, setCurrent = true }) { - const currentItems = getters.findItems({ key, value }) - - if (skipCache || !getters.hasItems || !currentItems) { - const pageResponse = await quickSearchByQuery({ - query: createSinglePageLoadQuery({ key, value }), - entityType: 'cms_page', - excludeFields, - includeFields - }) - - if (pageResponse && pageResponse.items && pageResponse.items.length > 0) { - commit(types.CMS_PAGE_ADD_CMS_PAGE, pageResponse.items[0]) - if (setCurrent) commit(types.CMS_PAGE_SET_CURRENT, pageResponse.items[0]) - return pageResponse.items[0] - } - - throw new Error('CMS query returned empty result') - } - - if (currentItems) { - if (setCurrent) { - commit(types.CMS_PAGE_SET_CURRENT, currentItems) - } - return currentItems - } - }, - async loadFromCache ({ commit }, { key, value, setCurrent }) { - const cmsStorage = StorageManager.get('cms') - const storedItems = await cmsStorage.getItem(cmsPagesStorageKey) - - if (storedItems) { - commit(types.CMS_PAGE_UPDATE_CMS_PAGES, storedItems) - const resp = storedItems.find(p => p[key] === value) - if (!resp) { - throw new Error('CMS query returned empty result') - } - - if (setCurrent) { - commit(types.CMS_PAGE_SET_CURRENT, resp) - } - - return resp - } - - throw new Error('CMS query returned empty result') - }, - addItem ({ commit }, page) { - commit(types.CMS_PAGE_ADD_CMS_PAGE, page) - } -} - -export default actions diff --git a/core/modules/cms/store/page/getters.ts b/core/modules/cms/store/page/getters.ts deleted file mode 100644 index 767d693fa8..0000000000 --- a/core/modules/cms/store/page/getters.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import CmsPageState from '../../types/CmsPageState' -import { Logger } from '@vue-storefront/core/lib/logger' - -const getters: GetterTree = { - cmsPages: (state, getters) => { - Logger.error('The getter cmsPage/cmsPages has been deprecated please change to cmsPage/getCmsPages')() - - return getters.getCmsPages - }, - getCmsPages: (state) => state.items, - hasItems: (state) => state.items && state.items.length > 0, - findItems: (state) => ({ key, value }) => state.items.find(p => p[key] === value) -} - -export default getters diff --git a/core/modules/cms/store/page/index.ts b/core/modules/cms/store/page/index.ts deleted file mode 100644 index e79c27d549..0000000000 --- a/core/modules/cms/store/page/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import CmsPageState from '../../types/CmsPageState' - -export const cmsPagesStorageKey = 'cms-page' - -export const cmsPageModule: Module = { - namespaced: true, - state: { - items: [], - current: null - }, - getters, - actions, - mutations -} diff --git a/core/modules/cms/store/page/mutation-types.ts b/core/modules/cms/store/page/mutation-types.ts deleted file mode 100644 index 6bf05f54b2..0000000000 --- a/core/modules/cms/store/page/mutation-types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const SN_CMS_PAGE = 'cmsPage' -export const CMS_PAGE_UPDATE_CMS_PAGES = SN_CMS_PAGE + '/UPDATE_CMS_PAGES' -export const CMS_PAGE_ADD_CMS_PAGE = SN_CMS_PAGE + '/ADD_CMS_PAGE' -export const CMS_PAGE_SET_CURRENT = SN_CMS_PAGE + '/SET_CURRENT_CMS_PAGE' diff --git a/core/modules/cms/store/page/mutations.ts b/core/modules/cms/store/page/mutations.ts deleted file mode 100644 index 008910efec..0000000000 --- a/core/modules/cms/store/page/mutations.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import CmsPageState from '../../types/CmsPageState' - -const mutations: MutationTree = { - [types.CMS_PAGE_UPDATE_CMS_PAGES] (state, cmsPages) { - state.items = cmsPages || [] - }, - [types.CMS_PAGE_SET_CURRENT] (state, current) { - state.current = current - }, - [types.CMS_PAGE_ADD_CMS_PAGE] (state, cmsPage) { - const record = state.items.find(c => c.id === cmsPage.id) - if (!record) { - state.items.push(cmsPage) - } - } -} - -export default mutations diff --git a/core/modules/cms/test/unit/blockActions.spec.ts b/core/modules/cms/test/unit/blockActions.spec.ts deleted file mode 100644 index 766b4293bd..0000000000 --- a/core/modules/cms/test/unit/blockActions.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import * as types from '../../store/block/mutation-types'; -import blockActions from '../../store/block/actions' - -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/store', () => ({ Module: jest.fn() })) - -jest.mock('@vue-storefront/core/types/RootState') -jest.mock('@vue-storefront/core/lib/search') -jest.mock('@vue-storefront/core/modules/cms/helpers', () => ({ - createLoadingBlockQuery: jest.fn(), - createSingleBlockQuery: jest.fn() -})) - -describe('Block actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('list method', () => { - it('should update block', async () => { - const contextMock = { - commit: jest.fn(), - getters: { hasItems: false } - } - const filter = {}; - const items = ['item1', 'item2', 'item3']; - - (quickSearchByQuery as any).mockResolvedValue({ items }); - - const wrapper = (actions: any) => actions.list(contextMock, filter) - let listAction = await wrapper(blockActions) - - expect(quickSearchByQuery).toHaveBeenCalled() - expect(contextMock.commit).toBeCalledWith(types.CMS_BLOCK_UPDATE_CMS_BLOCKS, items) - expect(listAction).toBe(items) - }) - - it('should NOT update blocks if already cached', async () => { - const contextMock = { - commit: jest.fn(), - getters: { hasItems: true, getCmsBlocks: ['item1'] } - } - const filter = {}; - - const wrapper = (actions: any) => actions.list(contextMock, filter) - let listAction = await wrapper(blockActions) - - expect(quickSearchByQuery).not.toHaveBeenCalled() - expect(listAction).toEqual(['item1']) - }) - - it('should NOT update cms_blocks if cache is NOT skipped', async () => { - const contextMock = { - commit: jest.fn(), - getters: { hasItems: true, getCmsBlocks: ['item1'] } - } - const filter = { skipCache: false }; - - const wrapper = (actions: any) => actions.list(contextMock, filter) - let listAction = await wrapper(blockActions) - - expect(quickSearchByQuery).not.toHaveBeenCalled() - expect(listAction).toEqual(['item1']) - }) - }) - - describe('single method', () => { - it('should add single block if NOT found', async () => { - const contextMock = { - commit: jest.fn(), - getters: { findCmsBlocks: jest.fn(() => []) } - } - const filter = {}; - - (quickSearchByQuery as any).mockResolvedValue({ items: ['item1', 'item2'] }); - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(blockActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_BLOCK_ADD_CMS_BLOCK, 'item1') - expect(singleAction).toEqual('item1') - }) - - it('should add single block if cache is NOT skipped', async () => { - const contextMock = { - commit: jest.fn(), - getters: { findCmsBlocks: jest.fn(() => [{ key: 'key1', value: 'val1' }]) } - } - const filter = { skipCache: true }; - - (quickSearchByQuery as any).mockResolvedValue({ items: ['item1', 'item2'] }); - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(blockActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_BLOCK_ADD_CMS_BLOCK, 'item1') - expect(singleAction).toEqual('item1') - }) - - it('should ONLY return block if found one', async () => { - const contextMock = { - commit: jest.fn(), - getters: { findCmsBlocks: jest.fn(() => [{ test: 'val1' }]) } - } - const filter = { key: 'test', value: 'val1' }; - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(blockActions) - - expect(contextMock.commit).not.toBeCalled() - expect(singleAction).toEqual({ test: 'val1' }) - }) - }) - - describe('addItem method', () => { - it('should add block', async () => { - const block = { query: {}, entityType: 'cms_block', excludeFields: [], includeFields: [] } - const contextMock = { - commit: jest.fn() - } - - const wrapper = (actions: any) => actions.addItem(contextMock, block) - await wrapper(blockActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_BLOCK_ADD_CMS_BLOCK, block) - }) - }) -}) diff --git a/core/modules/cms/test/unit/blockMutations.spec.ts b/core/modules/cms/test/unit/blockMutations.spec.ts deleted file mode 100644 index ff5447fcac..0000000000 --- a/core/modules/cms/test/unit/blockMutations.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as types from '../../store/block/mutation-types' -import blockMutations from '../../store/block/mutations' - -jest.mock('@vue-storefront/core/app', () => jest.fn()) - -describe('Block mutations', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should update cms blocks', () => { - const cmsBlock = ['item1', 'item2', 'item3'] - const stateMock = { items: [] } - const expectedState = { - items: ['item1', 'item2', 'item3'] - } - - const wrapper = (mutations: any) => mutations[types.CMS_BLOCK_UPDATE_CMS_BLOCKS](stateMock, cmsBlock) - wrapper(blockMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('should clear cms blocks after update without args', () => { - const cmsBlock = ['item1', 'item2', 'item3'] - const stateMock = { items: [] } - const expectedState = { - items: [] - } - - const wrapper = (mutations: any) => mutations[types.CMS_BLOCK_UPDATE_CMS_BLOCKS](stateMock, false) - wrapper(blockMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('should add new item if item with the same id does NOT exist', () => { - const cmsBlock = { id: 2, data: 'item-id2-data' } - const stateMock = { items: [{ id: 1, data: 'item-id1-data' }] } - const expectedState = { - items: [{ id: 1, data: 'item-id1-data' }, { id: 2, data: 'item-id2-data' }] - } - - const wrapper = (mutations: any) => mutations[types.CMS_BLOCK_ADD_CMS_BLOCK](stateMock, cmsBlock) - wrapper(blockMutations) - expect(stateMock).toEqual(expectedState) - }) - - it('should NOT add new item if item with the same id exists', () => { - const cmsBlock = { id: 1, data: 'item-id1-new-data' } - const stateMock = { - items: [{ id: 1, data: 'item-id1-data' }] - } - const expectedState = { - items: [{ id: 1, data: 'item-id1-data' }] - } - - const wrapper = (mutations: any) => mutations[types.CMS_BLOCK_ADD_CMS_BLOCK](stateMock, cmsBlock) - wrapper(blockMutations) - expect(stateMock).toEqual(expectedState) - }) -}) diff --git a/core/modules/cms/test/unit/createHierarchyLoadQuery.spec.ts b/core/modules/cms/test/unit/createHierarchyLoadQuery.spec.ts deleted file mode 100644 index b21c9494a0..0000000000 --- a/core/modules/cms/test/unit/createHierarchyLoadQuery.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import createHierarchyLoadQuery from '../../helpers/createHierarchyLoadQuery' - -describe('createHierarchyLoadQuery', () => { - it('should return hierarchic load query obj with applied filters if id is provided', () => { - const filter = { id: 3 } - let loadQuery = createHierarchyLoadQuery(filter) - - expect(loadQuery).toHaveProperty('_availableFilters') - expect(loadQuery).toHaveProperty('_searchText') - - let [ appliedFilter ] = loadQuery.getAppliedFilters() - - expect(appliedFilter).toHaveProperty('value', { eq: filter.id }) - expect(appliedFilter).toHaveProperty('attribute', 'identifier') - expect(appliedFilter).toHaveProperty('scope') - expect(appliedFilter).toHaveProperty('options') - }) - - it('should return load query obj with base hierarchy if id is not provided', () => { - const filter = { id: null } - let hierarchyLoadQuery = createHierarchyLoadQuery(filter) - - expect(hierarchyLoadQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' }) - }) -}) diff --git a/core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts b/core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts deleted file mode 100644 index 4673de015a..0000000000 --- a/core/modules/cms/test/unit/createLoadingBlockQuery.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import createLoadingBlockQuery from '../../helpers/createLoadingBlockQuery' - -describe('createLoadingBlockQuery', () => { - it('should return loading block query with applied proper filters', () => { - const filter = { filterField: 'test', filterValues: ['test1', 'test2'] } - - let loadingBlockQuery = createLoadingBlockQuery(filter) - let [ appliedFilter ] = loadingBlockQuery.getAppliedFilters() - - expect(appliedFilter).toHaveProperty('attribute', filter.filterField) - expect(appliedFilter).toHaveProperty('value', { like: filter.filterValues }) - }) - - it('should return loading block query object with base hierarchy if filter values are not provided', () => { - const filter = { filterField: 'test', filterValues: undefined } - let loadingBlockQuery = createLoadingBlockQuery(filter) - - expect(loadingBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' }) - }) -}) diff --git a/core/modules/cms/test/unit/createPageLoadingQuery.spec.ts b/core/modules/cms/test/unit/createPageLoadingQuery.spec.ts deleted file mode 100644 index 471fe1ac59..0000000000 --- a/core/modules/cms/test/unit/createPageLoadingQuery.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import createPageLoadingQuery from '../../helpers/createPageLoadingQuery' - -describe('createPageLoadingQuery', () => { - it('should return page loading query with applied proper filters', () => { - const filter = { filterField: 'test', filterValues: ['test1', 'test2'] } - - let pageLoadingQuery = createPageLoadingQuery(filter) - let [ appliedFilter ] = pageLoadingQuery.getAppliedFilters() - - expect(appliedFilter).toHaveProperty('attribute', filter.filterField) - expect(appliedFilter).toHaveProperty('value', { like: filter.filterValues }) - }) - - it('should return page loading query with base hierarchy if filter values are not provided', () => { - const filter = { filterField: 'test', filterValues: undefined } - let pageLoadingQuery = createPageLoadingQuery(filter) - - expect(pageLoadingQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' }) - }) -}) diff --git a/core/modules/cms/test/unit/createSingleBlockQuery.spec.ts b/core/modules/cms/test/unit/createSingleBlockQuery.spec.ts deleted file mode 100644 index feca385fa9..0000000000 --- a/core/modules/cms/test/unit/createSingleBlockQuery.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import createSingleBlockQuery from '../../helpers/createSingleBlockQuery' - -describe('createSingleBlockLoadQuery should', () => { - it('return single block load query with applied proper filters', () => { - const argsMock = { key: 'test', value: ['test1', 'test2'] } - - let mockSingleBlockQuery = createSingleBlockQuery(argsMock) - let [ appliedFilter ] = mockSingleBlockQuery.getAppliedFilters() - - expect(appliedFilter).toHaveProperty('attribute', argsMock.key) - expect(appliedFilter).toHaveProperty('value', { like: argsMock.value }) - }) - - it('return create single block load query with base hierarchy if value is not provided', () => { - const argsMock = { key: 'test', value: undefined } - let mockSingleBlockQuery = createSingleBlockQuery(argsMock) - - expect(mockSingleBlockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' }) - }) -}) diff --git a/core/modules/cms/test/unit/createSinglePageLoadQuery.spec.ts b/core/modules/cms/test/unit/createSinglePageLoadQuery.spec.ts deleted file mode 100644 index 343b3d4b4a..0000000000 --- a/core/modules/cms/test/unit/createSinglePageLoadQuery.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import createSinglePageLoadQuery from '../../helpers/createSinglePageLoadQuery' - -describe('createSinglePageLoadQuery should', () => { - it('return page loading query with applied proper filters', () => { - const filter = { key: 'test', value: ['test1', 'test2'] } - - let singlePageMockQuery = createSinglePageLoadQuery(filter) - let [ appliedFilter ] = singlePageMockQuery.getAppliedFilters() - - expect(appliedFilter).toHaveProperty('attribute', filter.key) - expect(appliedFilter).toHaveProperty('value', { like: filter.value }) - }) - - it('return page loading query with base hierarchy if value is not provided', () => { - const filter = { key: 'test', value: undefined } - let singlePageMockQuery = createSinglePageLoadQuery(filter) - - expect(singlePageMockQuery).toEqual({ _availableFilters: [], _appliedFilters: [], _appliedSort: [], _searchText: '' }) - }) -}) diff --git a/core/modules/cms/test/unit/hierarchyActions.spec.ts b/core/modules/cms/test/unit/hierarchyActions.spec.ts deleted file mode 100644 index 77242031c2..0000000000 --- a/core/modules/cms/test/unit/hierarchyActions.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import hierarchyActions from '../../store/hierarchy/actions' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' - -jest.mock('@vue-storefront/core/lib/search') - -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/store', () => ({ Module: jest.fn() })) -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); - -describe('Hierarchy actions', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should list hierarchy', async () => { - const contextMock = {}; - const filter = { id: 1, entityType: 'cms_hierarchy', excludeFields: null, includeFields: null } - - const wrapper = (actions: any) => actions.list(contextMock, filter); - const listAction = await wrapper(hierarchyActions) - - expect(quickSearchByQuery).toHaveBeenCalled() - }) -}) diff --git a/core/modules/cms/test/unit/pageActions.spec.ts b/core/modules/cms/test/unit/pageActions.spec.ts deleted file mode 100644 index bcf14f34b8..0000000000 --- a/core/modules/cms/test/unit/pageActions.spec.ts +++ /dev/null @@ -1,247 +0,0 @@ -import * as types from '../../store/page/mutation-types'; -import pageActions from '../../store/page/actions' - -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/store', () => ({ Module: jest.fn() })) - -jest.mock('@vue-storefront/core/types/RootState') -jest.mock('@vue-storefront/core/lib/search') -jest.mock('@vue-storefront/core/lib/storage-manager') -jest.mock('@vue-storefront/core/modules/cms/helpers', () => ({ - createSinglePageLoadQuery: jest.fn(), - createPageLoadingQuery: jest.fn() -})) - -describe('Page actions', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - describe('list method', () => { - it('should update pages and list them', async () => { - const filter = {} - const items = ['item1, item2, item3'] - const contextMock = { - commit: jest.fn() - }; - - (quickSearchByQuery as any).mockResolvedValue({ items: items }) - - const wrapper = (actions: any) => actions.list(contextMock, filter) - const listAction = await wrapper(pageActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_UPDATE_CMS_PAGES, items) - expect(listAction).toEqual(items) - }) - }) - - describe('single method', () => { - it('should add page if cache is skipped', async () => { - const filter = { skipCache: true, setCurrent: false } - const contextMock = { - getters: { - findItems: () => 'item1', - hasItems: true - }, - commit: jest.fn(), - dispatch: jest.fn() - }; - - (quickSearchByQuery as any).mockResolvedValue({ items: ['item1'] }) - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(pageActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_ADD_CMS_PAGE, 'item1') - expect(singleAction).toEqual('item1') - }) - - it('should add page if cache is skipped and set as current', async () => { - const filter = { skipCache: true, setCurrent: true } - const contextMock = { - getters: { - findItems: () => 'item1', - hasItems: true - }, - commit: jest.fn(), - dispatch: jest.fn() - }; - - (quickSearchByQuery as any).mockResolvedValue({ items: ['item1'] }) - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(pageActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_ADD_CMS_PAGE, 'item1') - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_SET_CURRENT, 'item1') - expect(singleAction).toEqual('item1') - }) - - it('should add page if does NOT have items', async () => { - const filter = {} - const contextMock = { - getters: { - findItems: () => 'item1', - hasItems: false - }, - commit: jest.fn(), - dispatch: jest.fn() - }; - - (quickSearchByQuery as any).mockResolvedValue({ items: ['item1'] }) - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(pageActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_ADD_CMS_PAGE, 'item1') - expect(singleAction).toEqual('item1') - }) - - it('should add page if does NOT have current items', async () => { - const filter = {} - const contextMock = { - getters: { - findItems: () => undefined, - hasItems: false - }, - commit: jest.fn(), - dispatch: jest.fn() - }; - - (quickSearchByQuery as any).mockResolvedValue({ items: ['item1'] }) - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(pageActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_ADD_CMS_PAGE, 'item1') - expect(singleAction).toEqual('item1') - }) - - it('should throw error if query returned empty value', async () => { - const filter = {} - const contextMock = { - getters: { - findItems: () => undefined, - hasItems: false - }, - commit: jest.fn(), - dispatch: jest.fn() - }; - - (quickSearchByQuery as any).mockResolvedValue({ items: ['item1'] }) - - const wrapper = (actions: any) => actions.single(contextMock, filter) - try { - await wrapper(pageActions) - } catch (e) { - expect(e.message).toBe('CMS query returned empty result') - } - }) - - it('should NOT add new page but set current one', async () => { - const filter = {} - const contextMock = { - getters: { - findItems: () => 'item1', - hasItems: false - }, - commit: jest.fn(), - dispatch: jest.fn() - }; - - const wrapper = (actions: any) => actions.single(contextMock, filter) - const singleAction = await wrapper(pageActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_SET_CURRENT, 'item1') - expect(singleAction).toEqual('item1') - }) - }) - - describe('loadFromCache action', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should return cached response and set the page as current', async () => { - const filter = { key: 'test', value: 'value', setCurrent: true } - const contextMock = { commit: jest.fn() } - const wrapper = (actions: any) => actions.loadFromCache(contextMock, filter); - (StorageManager as any).get.mockImplementationOnce((...args) => { - return { - getItem: (...args) => Promise.resolve([{ test: 'value' }]) - } - }) - - const loadFromCacheAction = await wrapper(pageActions) - - expect(contextMock.commit).toHaveBeenCalledWith(types.CMS_PAGE_SET_CURRENT, { test: 'value' }) - expect(loadFromCacheAction).toEqual({ test: 'value' }) - }) - - it('should return cached response and NOT set the page as current', async () => { - const filter = { key: 'test', value: 'value', setCurrent: false } - const contextMock = { commit: jest.fn() } - const wrapper = (actions: any) => actions.loadFromCache(contextMock, filter); - (StorageManager as any).get.mockImplementationOnce((...args) => { - return { - getItem: (...args: any) => Promise.resolve([{ test: 'value' }]) - } - }) - - const loadFromCacheAction = await wrapper(pageActions) - - expect(contextMock.commit).not.toHaveBeenCalledWith(types.CMS_PAGE_SET_CURRENT, { test: 'value' }) - expect(loadFromCacheAction).toEqual({ test: 'value' }) - }) - - it('should throw error when storedItems are empty', async () => { - const filter = { key: 'test', value: 'value', setCurrent: false } - const contextMock = { commit: jest.fn() } - const wrapper = (actions: any) => actions.loadFromCache(contextMock, filter); - (StorageManager as any).get.mockImplementationOnce((...args: any) => { - return ({ getItem: (...args: any) => Promise.resolve(undefined) }) - }) - - try { - await wrapper(pageActions) - } catch (e) { - expect(e.message).toBe('CMS query returned empty result') - } - }) - }) - - describe('loadFromCache action', () => { - it('should throw error when cannot find given element in stored items', async () => { - const filter = { key: 'test', value: 'value', setCurrent: false } - const contextMock = { commit: jest.fn() } - const wrapper = (actions: any) => actions.loadFromCache(contextMock, filter); - (StorageManager as any).get.mockImplementationOnce((...args: any) => { - return ({ getItem: (...args: any) => Promise.resolve([]) }) - }) - - try { - await wrapper(pageActions) - } catch (e) { - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_UPDATE_CMS_PAGES, []) - expect(e.message).toBe('CMS query returned empty result') - } - }) - }) - - describe('addItem method', () => { - it('should add new page', async () => { - const page = 'page_name' - const contextMock = { - commit: jest.fn() - } - const wrapper = (actions: any) => actions.addItem(contextMock, page) - await wrapper(pageActions) - - expect(contextMock.commit).toBeCalledWith(types.CMS_PAGE_ADD_CMS_PAGE, page) - }) - }) -}) diff --git a/core/modules/cms/test/unit/pageMutation.spec.ts b/core/modules/cms/test/unit/pageMutation.spec.ts deleted file mode 100644 index 00a4c7a1b0..0000000000 --- a/core/modules/cms/test/unit/pageMutation.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import * as types from '../../store/page/mutation-types' -import pageMutations from '../../store/page/mutations' - -jest.mock('@vue-storefront/core/app', () => jest.fn()) - -describe('Page mutations', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('should update cms pages', () => { - const cmsPage = ['new-page1', 'new-page2'] - const stateMock = { items: [] } - const expectedState = { - items: ['new-page1', 'new-page2'] - } - - const wrapper = (mutations: any) => mutations[types.CMS_PAGE_UPDATE_CMS_PAGES](stateMock, cmsPage) - wrapper(pageMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('should clear cms pages after update without args', () => { - const stateMock = { items: ['new-page1', 'new-page2'] } - const expectedState = { items: [] } - - const wrapper = (mutations: any) => mutations[types.CMS_PAGE_UPDATE_CMS_PAGES](stateMock, false) - wrapper(pageMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('should set page to current', () => { - const current = { id: 2, url: 'new-page' } - const stateMock = { current: { id: 1, url: 'old-page' } } - const expectedState = { current: { id: 2, url: 'new-page' } } - - const wrapper = (mutations: any) => mutations[types.CMS_PAGE_SET_CURRENT](stateMock, current) - wrapper(pageMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('should add new page if page with the same id does NOT exist', () => { - const cmsPage = { id: 2, url: 'new-page' } - const stateMock = { items: [{ id: 1, url: 'old-page' }] } - const expectedState = { - items: [ { id: 1, url: 'old-page' }, { id: 2, url: 'new-page' } ] - } - - const wrapper = (mutations: any) => mutations[types.CMS_PAGE_ADD_CMS_PAGE](stateMock, cmsPage) - wrapper(pageMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('should NOT add new page if page with the same id exists', () => { - const cmsPage = { id: 1, url: 'new-page' } - const stateMock = { items: [{ id: 1, url: 'old-page' }] } - const expectedState = { - items: [ { id: 1, url: 'old-page' } ] - } - - const wrapper = (mutations: any) => mutations[types.CMS_PAGE_ADD_CMS_PAGE](stateMock, cmsPage) - wrapper(pageMutations) - - expect(stateMock).toEqual(expectedState) - }) -}) diff --git a/core/modules/cms/types/CmsBlockState.ts b/core/modules/cms/types/CmsBlockState.ts deleted file mode 100644 index 74bff79245..0000000000 --- a/core/modules/cms/types/CmsBlockState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface CmsBlockState { - items: any[] -} diff --git a/core/modules/cms/types/CmsHierarchyState.ts b/core/modules/cms/types/CmsHierarchyState.ts deleted file mode 100644 index 55e3c48e51..0000000000 --- a/core/modules/cms/types/CmsHierarchyState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface CmsHierarchyState { - items: any[] -} diff --git a/core/modules/cms/types/CmsPageState.ts b/core/modules/cms/types/CmsPageState.ts deleted file mode 100644 index ffd592ba3d..0000000000 --- a/core/modules/cms/types/CmsPageState.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface CmsPageState { - items: any[], - current: any -} diff --git a/core/modules/compare/components/AddToCompare.ts b/core/modules/compare/components/AddToCompare.ts deleted file mode 100644 index abdddc4985..0000000000 --- a/core/modules/compare/components/AddToCompare.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import { CompareModule } from '../' -import compareMountedMixin from '@vue-storefront/core/modules/compare/mixins/compareMountedMixin' -import { registerModule } from '@vue-storefront/core/lib/modules'; - -export const AddToCompare = { - name: 'AddToCompare', - mixins: [compareMountedMixin], - created () { - registerModule(CompareModule) - }, - methods: { - addToCompare (product: Product) { - return this.$store.state['compare'] - ? this.$store.dispatch('compare/addItem', product) - : false - } - } -} diff --git a/core/modules/compare/components/Compare.ts b/core/modules/compare/components/Compare.ts deleted file mode 100644 index 1a231eda80..0000000000 --- a/core/modules/compare/components/Compare.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { mapGetters } from 'vuex' -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import compareMountedMixin from '@vue-storefront/core/modules/compare/mixins/compareMountedMixin' -import config from 'config' - -export const Compare = { - name: 'Compare', - mixins: [compareMountedMixin], - computed: { - ...mapGetters({ - items: 'compare/getCompareItems', - allComparableAttributes: 'attribute/getAllComparableAttributes' - }) - }, - created () { - if (!config.entities.attribute.loadByAttributeMetadata) { - this.$store.dispatch('attribute/list', { - filterValues: [], - filterField: 'is_user_defined' - }) - } - }, - methods: { - removeFromCompare (product: Product) { - return this.$store.state['compare'] - ? this.$store.dispatch('compare/removeItem', product) - : false - } - }, - asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - return new Promise((resolve, reject) => { - if (context) context.output.cacheTags.add(`compare`) - resolve() - }) - } -} diff --git a/core/modules/compare/components/CompareButton.ts b/core/modules/compare/components/CompareButton.ts deleted file mode 100644 index 54ab5dd04c..0000000000 --- a/core/modules/compare/components/CompareButton.ts +++ /dev/null @@ -1,11 +0,0 @@ -import compareMountedMixin from '@vue-storefront/core/modules/compare/mixins/compareMountedMixin' - -export const CompareButton = { - name: 'CompareButton', - mixins: [compareMountedMixin], - computed: { - isEmpty (): boolean { - return this.$store.getters['compare/isEmpty'] - } - } -} diff --git a/core/modules/compare/components/IsOnCompare.ts b/core/modules/compare/components/IsOnCompare.ts deleted file mode 100644 index 8f0f3ec410..0000000000 --- a/core/modules/compare/components/IsOnCompare.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { CompareModule } from '..' -import compareMountedMixin from '@vue-storefront/core/modules/compare/mixins/compareMountedMixin' -import { registerModule } from '@vue-storefront/core/lib/modules'; - -export const IsOnCompare = { - name: 'IsOnCompare', - mixins: [compareMountedMixin], - props: { - product: { - required: true, - type: Object - } - }, - created () { - registerModule(CompareModule) - }, - computed: { - isOnCompare () { - return this.$store.getters['compare/isOnCompare'](this.product) - } - } -} diff --git a/core/modules/compare/components/Product.ts b/core/modules/compare/components/Product.ts deleted file mode 100644 index 07e2a565f5..0000000000 --- a/core/modules/compare/components/Product.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import compareMountedMixin from '@vue-storefront/core/modules/compare/mixins/compareMountedMixin' - -export const CompareProduct = { - name: 'CompareProduct', - mixins: [compareMountedMixin], - computed: { - isOnCompare (): boolean { - return this.$store.getters['compare/isOnCompare'](this.product) - } - }, - methods: { - removeFromCompare (product: Product) { - return this.$store.state['compare'] - ? this.$store.dispatch('compare/removeItem', product) - : false - } - } -} diff --git a/core/modules/compare/components/RemoveFromCompare.ts b/core/modules/compare/components/RemoveFromCompare.ts deleted file mode 100644 index 4b737e23da..0000000000 --- a/core/modules/compare/components/RemoveFromCompare.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product'; -import { CompareModule } from '..'; -import compareMountedMixin from '@vue-storefront/core/modules/compare/mixins/compareMountedMixin'; -import { registerModule } from '@vue-storefront/core/lib/modules'; - -export const RemoveFromCompare = { - name: 'RemoveFromCompare', - mixins: [compareMountedMixin], - methods: { - removeFromCompare (product: Product) { - registerModule(CompareModule) - this.$store.dispatch('compare/removeItem', product); - } - } -}; diff --git a/core/modules/compare/index.ts b/core/modules/compare/index.ts deleted file mode 100644 index a33cb82560..0000000000 --- a/core/modules/compare/index.ts +++ /dev/null @@ -1,11 +0,0 @@ - -import { compareStore } from './store' -import cachePersistPlugin from './store/plugin' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const CompareModule: StorefrontModule = function ({ store }) { - StorageManager.init('compare') - store.registerModule('compare', compareStore) - store.subscribe(cachePersistPlugin) -} diff --git a/core/modules/compare/mixins/compareMountedMixin.js b/core/modules/compare/mixins/compareMountedMixin.js deleted file mode 100644 index 160289ad65..0000000000 --- a/core/modules/compare/mixins/compareMountedMixin.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This is mixin for all module components. - * Invoke here actions, which were not invoked on server-side render, like reading LocalStorage or checking window size. - */ - -export default { - mounted () { - this.$store.dispatch('compare/load') - } -} diff --git a/core/modules/compare/store/actions.ts b/core/modules/compare/store/actions.ts deleted file mode 100644 index d930385f6e..0000000000 --- a/core/modules/compare/store/actions.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState' -import CompareState from '../types/CompareState' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { Logger } from '@vue-storefront/core/lib/logger' -import config from 'config' - -const actions: ActionTree = { - async load ({ commit, getters, dispatch }, force: boolean = false) { - if (force || !getters.isCompareLoaded) { - commit(types.SET_COMPARE_LOADED) - const storedItems = await dispatch('fetchCurrentCompare') - - if (storedItems) { - commit(types.COMPARE_LOAD_COMPARE, storedItems) - Logger.info('Compare state loaded from browser cache: ', 'cache', storedItems)() - } - - if (config.entities.attribute.loadByAttributeMetadata) { - dispatch( - 'attribute/loadProductAttributes', - { products: getters.getCompareItems, merge: true }, - { root: true } - ) - } - } - }, - async fetchCurrentCompare () { - const cacheStorage = StorageManager.get('compare') - return cacheStorage.getItem('current-compare') - }, - async addItem ({ commit }, product) { - commit(types.COMPARE_ADD_ITEM, { product }) - }, - async removeItem ({ commit }, product) { - commit(types.COMPARE_DEL_ITEM, { product }) - }, - async clear ({ commit }) { - commit(types.COMPARE_LOAD_COMPARE, []) - } -} - -export default actions diff --git a/core/modules/compare/store/getters.ts b/core/modules/compare/store/getters.ts deleted file mode 100644 index 8483a3ace4..0000000000 --- a/core/modules/compare/store/getters.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import CompareState from '../types/CompareState' - -const getters: GetterTree = { - isEmpty: state => state.items.length === 0, - isOnCompare: state => product => state.items.some(p => p.sku === product.sku), - isCompareLoaded: state => state.loaded, - getCompareProductsCount: state => state.items.length, - getCompareItems: state => state.items -} - -export default getters diff --git a/core/modules/compare/store/index.ts b/core/modules/compare/store/index.ts deleted file mode 100644 index 58f9714794..0000000000 --- a/core/modules/compare/store/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import CompareState from '../types/CompareState' - -export const compareStore: Module = { - namespaced: true, - state: { - loaded: false, - items: [] - }, - getters, - actions, - mutations -} diff --git a/core/modules/compare/store/mutation-types.ts b/core/modules/compare/store/mutation-types.ts deleted file mode 100644 index 6be6dabc86..0000000000 --- a/core/modules/compare/store/mutation-types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const SN_COMPARE = 'compare' -export const COMPARE_ADD_ITEM = `${SN_COMPARE}/ADD` -export const COMPARE_DEL_ITEM = `${SN_COMPARE}/DEL` -export const COMPARE_LOAD_COMPARE = `${SN_COMPARE}/LOAD` -export const SET_COMPARE_LOADED = `${SN_COMPARE}/SET_COMPARE_LOADED` diff --git a/core/modules/compare/store/mutations.ts b/core/modules/compare/store/mutations.ts deleted file mode 100644 index 117b4936fc..0000000000 --- a/core/modules/compare/store/mutations.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import CompareState from '../types/CompareState' - -const mutations: MutationTree = { - /** - * Add product to Compare - * @param {Object} product data format for products is described in /doc/ElasticSearch data formats.md - */ - [types.COMPARE_ADD_ITEM] (state, { product }) { - const record = state.items.find(p => p.sku === product.sku) - if (!record) { - state.items.push(product) - } - }, - [types.COMPARE_DEL_ITEM] (state, { product }) { - state.items = state.items.filter(p => p.sku !== product.sku) - }, - [types.COMPARE_LOAD_COMPARE] (state, storedItems) { - state.items = storedItems || [] - }, - [types.SET_COMPARE_LOADED] (state, isLoaded: boolean = true) { - state.loaded = isLoaded - } -} - -export default mutations diff --git a/core/modules/compare/store/plugin.ts b/core/modules/compare/store/plugin.ts deleted file mode 100644 index d6ab6359c8..0000000000 --- a/core/modules/compare/store/plugin.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as types from './mutation-types' -import { Logger } from '@vue-storefront/core/lib/logger' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -const watchedMutations = [types.COMPARE_ADD_ITEM, types.COMPARE_DEL_ITEM, types.COMPARE_LOAD_COMPARE].map(m => `compare/${m}`) - -const cachePersistPlugin = (mutation, state) => { - const cacheStorage = StorageManager.get('compare') - - if (watchedMutations.includes(mutation.type)) { - cacheStorage.setItem('current-compare', state.compare.items).catch((reason) => { - Logger.error(reason, 'compare') - }) - } -} - -export default cachePersistPlugin diff --git a/core/modules/compare/test/unit/components/AddToCompare.spec.ts b/core/modules/compare/test/unit/components/AddToCompare.spec.ts deleted file mode 100644 index a1b8feb4f4..0000000000 --- a/core/modules/compare/test/unit/components/AddToCompare.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { AddToCompare } from '../../../components/AddToCompare' -import { registerModule } from '@vue-storefront/core/lib/modules'; -import { CompareModule } from '@vue-storefront/core/modules/compare'; - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/compare/mixins/compareMountedMixin', () => ({})) -jest.mock('@vue-storefront/core/lib/modules', () => ({ - registerModule: jest.fn() -})) -jest.mock('@vue-storefront/core/modules/compare', () => ({})) - -describe('AddToCompare', () => { - it('addToCompare dispatches addItem action', () => { - const product = {}; - - const storeMock = { - modules: { - compare: { - actions: { - addItem: jest.fn(() => []) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(AddToCompare, storeMock); - - (wrapper.vm as any).addToCompare(product); - - expect(storeMock.modules.compare.actions.addItem).toBeCalledWith(expect.anything(), product); - }) - - it('compare module has been registered on created', () => { - mountMixinWithStore(AddToCompare); - - expect(registerModule).toBeCalledWith(CompareModule); - }) -}); diff --git a/core/modules/compare/test/unit/components/Compare.spec.ts b/core/modules/compare/test/unit/components/Compare.spec.ts deleted file mode 100644 index 6053af3154..0000000000 --- a/core/modules/compare/test/unit/components/Compare.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { Compare } from '../../../components/Compare' -import config from 'config' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/compare/mixins/compareMountedMixin', () => ({})) -jest.mock('config', () => ({})); - -describe('Compare', () => { - it('Compare dispatches attribute list action on created', () => { - const storeMock = { - modules: { - attribute: { - actions: { - list: jest.fn(() => []) - }, - namespaced: true - } - } - }; - - config.entities = { - attribute: { - loadByAttributeMetadata: false - } - } - - mountMixinWithStore(Compare, storeMock); - - expect(storeMock.modules.attribute.actions.list).toBeCalledWith(expect.anything(), { - filterValues: [], - filterField: 'is_user_defined' - }); - }) - - it('removeFromCompare dispatches addItem action', () => { - const product = {}; - - const storeMock = { - modules: { - compare: { - actions: { - removeItem: jest.fn() - }, - namespaced: true - }, - attribute: { - actions: { - list: jest.fn(() => []) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Compare, storeMock); - - (wrapper.vm as any).removeFromCompare(product); - - expect(storeMock.modules.compare.actions.removeItem).toBeCalledWith(expect.anything(), product); - }) -}); diff --git a/core/modules/compare/test/unit/components/Product.spec.ts b/core/modules/compare/test/unit/components/Product.spec.ts deleted file mode 100644 index b60a930c36..0000000000 --- a/core/modules/compare/test/unit/components/Product.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { CompareProduct as Product } from '../../../components/Product' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/compare/mixins/compareMountedMixin', () => ({})) - -describe('Product', () => { - it('removeFromCompare dispatches addItem action', () => { - const product = {}; - - const storeMock = { - modules: { - compare: { - actions: { - removeItem: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Product, storeMock); - - (wrapper.vm as any).removeFromCompare(product); - - expect(storeMock.modules.compare.actions.removeItem).toBeCalledWith(expect.anything(), product); - }) -}); diff --git a/core/modules/compare/test/unit/mixins/compareMountedMixin.spec.ts b/core/modules/compare/test/unit/mixins/compareMountedMixin.spec.ts deleted file mode 100644 index df8d23f1b8..0000000000 --- a/core/modules/compare/test/unit/mixins/compareMountedMixin.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils' - -import compareMountedMixin from '../../../mixins/compareMountedMixin' - -describe('compareMountedMixin', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('load compare state on mount', () => { - const storeMock = { - modules: { - compare: { - actions: { - load: jest.fn() - }, - namespaced: true - } - } - } - - mountMixinWithStore(compareMountedMixin, storeMock) - - expect(storeMock.modules.compare.actions.load).toBeCalled() - }) -}) diff --git a/core/modules/compare/test/unit/store/actions.spec.ts b/core/modules/compare/test/unit/store/actions.spec.ts deleted file mode 100644 index 93cd16e885..0000000000 --- a/core/modules/compare/test/unit/store/actions.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -import * as types from '../../../store/mutation-types'; -import compareActions from '../../../store/actions'; -import { cacheStorage } from '@vue-storefront/core/modules/recently-viewed/index' - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - info: jest.fn(() => jest.fn()) - } -})) -jest.mock('@vue-storefront/core/modules/recently-viewed/index', () => ({ - cacheStorage: { - getItem: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn(() => cacheStorage) - } -})) - -let product - -describe('Compare actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - product = { id: 'xyz' }; - }); - - describe('load', () => { - it('should NOT load state if is already loaded', () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isCompareLoaded: true } - }; - const wrapper = (actions: any) => actions.load(contextMock); - - wrapper(compareActions); - - expect(contextMock.commit).not.toBeCalledWith(types.SET_COMPARE_LOADED); - }); - - it('should load state if forced', () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isCompareLoaded: true } - }; - const wrapper = (actions: any) => actions.load(contextMock, true); - - wrapper(compareActions); - - expect(contextMock.commit).toBeCalledWith(types.SET_COMPARE_LOADED); - }); - - it('should try to load state', () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isCompareLoaded: false } - }; - const wrapper = (actions: any) => actions.load(contextMock); - - wrapper(compareActions); - - expect(contextMock.commit).toBeCalledWith(types.SET_COMPARE_LOADED); - expect(contextMock.dispatch).toBeCalledWith('fetchCurrentCompare'); - }); - - it('should NOT commit state if there are no items', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => null), - getters: { isCompareLoaded: false } - }; - const wrapper = (actions: any) => actions.load(contextMock); - - await wrapper(compareActions); - - expect(contextMock.commit).not.toBeCalledWith(types.COMPARE_LOAD_COMPARE, null); - }); - - it('should commit state if are any items', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(() => [product]), - getters: { isCompareLoaded: false } - }; - const wrapper = (actions: any) => actions.load(contextMock); - - await wrapper(compareActions); - - expect(contextMock.commit).toBeCalledWith(types.COMPARE_LOAD_COMPARE, [product]); - }); - }); - - describe('fetchCurrentCompare', () => { - it('should fetch items from cache', async () => { - const wrapper = (actions: any) => actions.fetchCurrentCompare(); - - await wrapper(compareActions); - - expect(cacheStorage.getItem).toBeCalledWith('current-compare'); - }); - }); - - describe('addItem', () => { - it('should call add product commit', async () => { - const contextMock = { - commit: jest.fn() - }; - const wrapper = (actions: any) => actions.addItem(contextMock, product); - - await wrapper(compareActions); - - expect(contextMock.commit).toBeCalledWith(types.COMPARE_ADD_ITEM, { product }); - }); - }); - - describe('removeItem', () => { - it('should call remove product commit', async () => { - const contextMock = { - commit: jest.fn() - }; - const wrapper = (actions: any) => actions.removeItem(contextMock, product); - - await wrapper(compareActions); - - expect(contextMock.commit).toBeCalledWith(types.COMPARE_DEL_ITEM, { product }); - }); - }); - - describe('clear', () => { - it('should call clear state commit', async () => { - const contextMock = { - commit: jest.fn() - }; - const wrapper = (actions: any) => actions.clear(contextMock); - - await wrapper(compareActions); - - expect(contextMock.commit).toBeCalledWith(types.COMPARE_LOAD_COMPARE, []); - }); - }); -}); diff --git a/core/modules/compare/test/unit/store/mutations.spec.ts b/core/modules/compare/test/unit/store/mutations.spec.ts deleted file mode 100644 index 4a9293d9c2..0000000000 --- a/core/modules/compare/test/unit/store/mutations.spec.ts +++ /dev/null @@ -1,164 +0,0 @@ -import * as types from '../../../store/mutation-types'; -import compareMutations from '../../../store/mutations' - -describe('Compare mutations', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('COMPARE_ADD_ITEM', () => { - it('add product to compare', () => { - const stateMock = { - items: [] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [ - { - qty: 123, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.COMPARE_ADD_ITEM](stateMock, { product }) - - wrapper(compareMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('don\'t add product if there is one with the same sku', () => { - const stateMock = { - items: [ - { - qty: 1233, - sku: 'foo' - } - ] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [ - { - qty: 1233, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.COMPARE_ADD_ITEM](stateMock, { product }) - - wrapper(compareMutations) - - expect(stateMock).toEqual(expectedState) - }) - }); - - describe('COMPARE_DEL_ITEM', () => { - it('remove product if there is one with the same sku', () => { - const stateMock = { - items: [ - { - qty: 1233, - sku: 'foo' - } - ] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [] - } - const wrapper = (mutations: any) => mutations[types.COMPARE_DEL_ITEM](stateMock, { product }) - - wrapper(compareMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('don\'t remove product if there is not any with same sku', () => { - const stateMock = { - items: [ - { - qty: 1233, - sku: 'boo' - } - ] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [ - { - qty: 1233, - sku: 'boo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.COMPARE_DEL_ITEM](stateMock, { product }) - - wrapper(compareMutations) - - expect(stateMock).toEqual(expectedState) - }) - }); - - describe('COMPARE_LOAD_COMPARE', () => { - it('should load state with products to compare', () => { - const stateMock = { - items: [] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [product, product] - } - const wrapper = (mutations: any) => mutations[types.COMPARE_LOAD_COMPARE](stateMock, [product, product]) - - wrapper(compareMutations) - - expect(stateMock).toEqual(expectedState) - }) - }); - - describe('SET_COMPARE_LOADED', () => { - it('should set state as loaded', () => { - const stateMock = { - loaded: false - } - const expectedState = { - loaded: true - } - const wrapper = (mutations: any) => mutations[types.SET_COMPARE_LOADED](stateMock) - - wrapper(compareMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('should set state as not loaded', () => { - const stateMock = { - loaded: true - } - const expectedState = { - loaded: false - } - const wrapper = (mutations: any) => mutations[types.SET_COMPARE_LOADED](stateMock, false) - - wrapper(compareMutations) - - expect(stateMock).toEqual(expectedState) - }) - }); -}); diff --git a/core/modules/compare/types/CompareState.ts b/core/modules/compare/types/CompareState.ts deleted file mode 100644 index 8f013bc0f7..0000000000 --- a/core/modules/compare/types/CompareState.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface CompareState { - /** - * Informs if items to compare are already loaded from local cache. - */ - loaded: boolean, - items: any[] -} diff --git a/core/modules/initial-resources/clientResourcesLoader.ts b/core/modules/initial-resources/clientResourcesLoader.ts deleted file mode 100644 index 99a301266c..0000000000 --- a/core/modules/initial-resources/clientResourcesLoader.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { addRegexpListToConfig, createRegexpMatcher, flatToRegexpList } from './helpers'; -import config from 'config' - -const initialResources = addRegexpListToConfig(config) - -const prefetchRegexps = flatToRegexpList( - initialResources.filter(filterConfig => filterConfig.rel !== 'preload' && filterConfig.onload) -) -const preloadRegexps = flatToRegexpList( - initialResources.filter(filterConfig => filterConfig.rel === 'preload' && filterConfig.onload) -) - -/** - * Build links that need to be load and add them on the end of head. - */ -const addLinksFromManifest = (manifestFilesUrls: string[], regexps: RegExp[], publicPath: string) => { - manifestFilesUrls - .filter((file) => createRegexpMatcher(file)(regexps)) - .forEach((file) => { - const link = document.createElement('link') - link.href = publicPath + file - link.rel = 'prefetch' - - document.head.appendChild(link) - }) -} - -const getManifest = async () => { - let ssrManifest = null - try { - ssrManifest = (await (await fetch('/dist/vue-ssr-client-manifest.json')).json()) || null - } catch (_) { - ssrManifest = null - } - return ssrManifest -} - -/** - * Add links from manifest to head element. - */ -export default async () => { - const ssrManifest = await getManifest() - if (!ssrManifest) return - - addLinksFromManifest(ssrManifest.async, prefetchRegexps, ssrManifest.publicPath) - addLinksFromManifest(ssrManifest.initial, preloadRegexps, ssrManifest.publicPath) -} diff --git a/core/modules/initial-resources/helpers.ts b/core/modules/initial-resources/helpers.ts deleted file mode 100644 index 38ab5be572..0000000000 --- a/core/modules/initial-resources/helpers.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { InitialResources } from './types'; - -/** - * Creates RegExp based on type and provided filter/filname in config. - * There is option to create custom regexp by not providing type. - * @param type - type of resources - * @param filter - part of regex that will filter files from prefetch or preload - */ -const createRegexp = (type, filter = ''): RegExp => { - switch (type) { - case 'script': { - return new RegExp(`^${filter}(\\..+\\.|\\.)js`, 'gi') - } - case 'style': { - return new RegExp(`^${filter}(\\..+\\.|\\.)css`, 'gi') - } - default: { - return new RegExp(`${filter}`, 'gi') - } - } -} - -/** - * Create RegExp list based on initialResources config - */ -const createRegexpList = ({ type, filters = [] }: InitialResources): RegExp[] => filters.map(createRegexp.bind(null, type)) - -/** - * Returns function that require RegExp list. Then after second call we will get boolean that determines if file match any regexp. - * @param file - this is filename that will be checked - */ -export const createRegexpMatcher = (file: string) => (regexps: RegExp[]): boolean => regexps.some(regexp => file.match(regexp)) - -/** - * Extended initialResurces config by adding to it list of RegExp. - */ -export const addRegexpListToConfig = (config): InitialResources[] => { - const initialResourcesConfig: InitialResources[] = config.initialResources || [] - - return initialResourcesConfig - .map(resourceConfig => ({ - ...resourceConfig, - regexps: createRegexpList(resourceConfig) - })) -} - -/** - * Returns RegExp list from extended initialResources config. - */ -export const flatToRegexpList = (configs: InitialResources[]) => configs - .map(pConfig => pConfig.regexps) - .reduce((acc, val) => acc.concat(val), []) diff --git a/core/modules/initial-resources/index.ts b/core/modules/initial-resources/index.ts deleted file mode 100644 index 630019f0af..0000000000 --- a/core/modules/initial-resources/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers' -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import clientResourcesLoader from './clientResourcesLoader' - -export const InitialResourcesModule: StorefrontModule = function () { - if (isServer) return - window.addEventListener('load', clientResourcesLoader) -} diff --git a/core/modules/initial-resources/serverResourcesFilter.ts b/core/modules/initial-resources/serverResourcesFilter.ts deleted file mode 100644 index e0167c5aa9..0000000000 --- a/core/modules/initial-resources/serverResourcesFilter.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { addRegexpListToConfig, createRegexpMatcher, flatToRegexpList } from './helpers'; - -const config = require('config') -const initialResources = addRegexpListToConfig(config) - -const prefetchRegexps = flatToRegexpList( - initialResources.filter(filterConfig => filterConfig.rel !== 'preload') -) -/** - * vue-ssr method that filters prefetch files based on initialResources config - */ -export const shouldPrefetch = (file: string) => { - if (prefetchRegexps.length) { - const checkRegexpList = createRegexpMatcher(file) - const matchFilter = checkRegexpList(prefetchRegexps) - - return !matchFilter - } - return true -} - -const preloadRegexps = flatToRegexpList( - initialResources.filter(filterConfig => filterConfig.rel === 'preload') -) -/** - * vue-ssr method that filters preload files based on initialResources config - */ -export const shouldPreload = (file: string) => { - if (preloadRegexps.length) { - const checkRegexpList = createRegexpMatcher(file) - const matchFilter = checkRegexpList(preloadRegexps) - - return !matchFilter - } - return true -} diff --git a/core/modules/initial-resources/types.ts b/core/modules/initial-resources/types.ts deleted file mode 100644 index e31f3319e6..0000000000 --- a/core/modules/initial-resources/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface InitialResources { - filters: string[], - regexps?: RegExp[], - type?: 'script' | 'style', - onload?: boolean, - rel?: 'prefetch' | 'preload' -} diff --git a/core/modules/mailer/components/EmailForm.ts b/core/modules/mailer/components/EmailForm.ts deleted file mode 100644 index e437aaa55b..0000000000 --- a/core/modules/mailer/components/EmailForm.ts +++ /dev/null @@ -1,32 +0,0 @@ -import i18n from '@vue-storefront/i18n' -import MailItem from '../types/MailItem' - -export const EmailForm = { - name: 'EmailForm', - data () { - return { - token: null - } - }, - methods: { - sendEmail (letter: MailItem, success, failure) { - this.$store.dispatch('mailer/sendEmail', letter) - .then(res => { - if (res.ok) { - if (success) success(i18n.t('Email has successfully been sent')) - } else { - return res.json() - } - }) - .then(errorResponse => { - if (errorResponse) { - const errorMessage = errorResponse.result - if (failure) failure(i18n.t(errorMessage)) - } - }) - .catch(() => { - if (failure) failure(i18n.t('Could not send an email. Please try again later.')) - }) - } - } -} diff --git a/core/modules/mailer/index.ts b/core/modules/mailer/index.ts deleted file mode 100644 index bcfc61ad2b..0000000000 --- a/core/modules/mailer/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { mailerStore } from './store' - -export const MailerModule: StorefrontModule = function ({ store }) { - store.registerModule('mailer', mailerStore) -} diff --git a/core/modules/mailer/store/index.ts b/core/modules/mailer/store/index.ts deleted file mode 100644 index 6f505af09b..0000000000 --- a/core/modules/mailer/store/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Logger } from '@vue-storefront/core/lib/logger' -import MailItem from '../types/MailItem' -import { Module } from 'vuex' -import config from 'config' -import { processURLAddress } from '@vue-storefront/core/helpers' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -export const mailerStore: Module = { - namespaced: true, - actions: { - async sendEmail (context, letter: MailItem) { - try { - const res = await fetch(processURLAddress(getApiEndpointUrl(config.mailer.endpoint, 'token'))) - const resData = await res.json() - if (resData.code === 200) { - try { - const res = await fetch( - processURLAddress(config.mailer.endpoint.send), - { - method: 'POST', - mode: 'cors', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - ...letter, - token: resData.result - }) - } - ) - return res - } catch (e) { - Logger.error(e, 'mailer')() - throw new Error(e) - } - } else { - throw new Error(resData.code) - } - } catch (e) { - Logger.error(e, 'mailer')() - throw new Error(e) - } - } - } -} diff --git a/core/modules/mailer/test/unit/sendEmail.spec.ts b/core/modules/mailer/test/unit/sendEmail.spec.ts deleted file mode 100644 index a36001b3e8..0000000000 --- a/core/modules/mailer/test/unit/sendEmail.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { mailerStore } from '../../store/index' -import config from 'config' - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/storage-manager', () => jest.fn()) -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/lib/multistore', () => jest.fn()) -jest.mock('@vue-storefront/core/store', () => ({ Module: jest.fn() })) -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - error: jest.fn(() => jest.fn()) - } -})) - -describe('Mailer store module', () => { - const letterMock = {} - const contextMock = {}; - const wrapper = (actions: any) => actions.sendEmail(contextMock, letterMock) - - beforeEach(() => { - jest.clearAllMocks(); - fetchMock.resetMocks() - }) - - it('should send email succesfully', async () => { - fetchMock.mockResponses( - [ JSON.stringify({ code: 200 }), { status: 200 } ], - [ JSON.stringify({ send: true }), { status: 200 } ] - ) - - const res = await wrapper(mailerStore.actions); - const resData = await res.json() - - expect(resData.send).toBe(true) - }) - - it('should thrown error when response code is wrong', async () => { - const wrongResponseCode = 201; - fetchMock.mockResponseOnce(JSON.stringify({ code: wrongResponseCode })) - - try { - const res = await wrapper(mailerStore.actions) - } catch (e) { - expect(e.message).toBe(`Error: ${wrongResponseCode}`) - } - }) -}) diff --git a/core/modules/mailer/types/MailItem.ts b/core/modules/mailer/types/MailItem.ts deleted file mode 100644 index 3580d252cb..0000000000 --- a/core/modules/mailer/types/MailItem.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface MailItem { - sourceAddress: string, - targetAddress: string, - subject: string, - emailText: string, - confirmation?: boolean -} diff --git a/core/modules/newsletter/components/Newsletter.ts b/core/modules/newsletter/components/Newsletter.ts deleted file mode 100644 index c481644085..0000000000 --- a/core/modules/newsletter/components/Newsletter.ts +++ /dev/null @@ -1,8 +0,0 @@ -import SubscriptionStatus from '@vue-storefront/core/modules/newsletter/mixins/SubscriptionStatus' -import Subscribe from '@vue-storefront/core/modules/newsletter/mixins/Subscribe' -import Unsubscribe from '@vue-storefront/core/modules/newsletter/mixins/Unsubscribe' - -export const Newsletter = { - name: 'Newsletter', - mixins: [SubscriptionStatus, Subscribe, Unsubscribe] -} diff --git a/core/modules/newsletter/index.ts b/core/modules/newsletter/index.ts deleted file mode 100644 index 1650a2301c..0000000000 --- a/core/modules/newsletter/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { newsletterStore } from './store' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const NewsletterModule: StorefrontModule = function ({ store }) { - StorageManager.init('newsletter') - store.registerModule('newsletter', newsletterStore) -} diff --git a/core/modules/newsletter/mixins/Subscribe.ts b/core/modules/newsletter/mixins/Subscribe.ts deleted file mode 100644 index 9b41ed24f6..0000000000 --- a/core/modules/newsletter/mixins/Subscribe.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { required, email } from 'vuelidate/lib/validators' - -/** - * Newsletter subscription form component. - * - * #### Data - * - `email: String` - email that will be used for subscription, validated with vuelidate (email, required) - * - * #### Methods - * - `subscribe(success?: Function, failure?: Function)` dispatches `newsletter/subscribe` with `email` data property. `success(res)` and `failure(err)` are callback functions called depending on subscription result and contain response info or error. - * - */ -export default { - name: 'NewsletterSubscribe', - data () { - return { - email: '' - } - }, - validations: { - email: { - required, - email - } - }, - methods: { - subscribe (success?: Function, failure?: Function) { - // argument omitted for validation purposes - if (!this.$v.$invalid) { - return this.$store.dispatch('newsletter/subscribe', this.email).then(res => { - if (success) success(res) - }).catch(err => { - if (failure) failure(err) - } - ) - } - } - } -} diff --git a/core/modules/newsletter/mixins/SubscriptionStatus.ts b/core/modules/newsletter/mixins/SubscriptionStatus.ts deleted file mode 100644 index c906ab7efc..0000000000 --- a/core/modules/newsletter/mixins/SubscriptionStatus.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { required, email } from 'vuelidate/lib/validators' - -/** - * Newsletter subscription form component. - * - * #### Data - * - `email: String` - email that will be used for subscription, validated with vuelidate (email, required) - * - * ### Computed - * - `isSubscribed: boolean` - returns true if user subscribed to the newsletter in this session - * - * #### Methods - * - `checkStatus(success?: Function, failure?: Function)` dispatches `newsletter/status` with `email` data property. `success(res)` and `failure(err)` are callback functions called depending on subscription result and contain response info or error. - * - */ -export default { - name: 'SubscriptionStatus', - data () { - return { - email: '', - user: { - isSubscribed: false - } - } - }, - validations: { - email: { - required, - email - } - }, - methods: { - async onLoggedIn () { - this.email = this.$store.state.user.current.email - this.user.isSubscribed = await this.$store.dispatch('newsletter/status', this.email) - } - }, - beforeMount () { - // the user might already be logged in, so check the subscription status - if (this.$store.state.user.current) this.onLoggedIn() - this.$bus.$on('user-after-loggedin', this.onLoggedIn) - }, - beforeDestroy () { - this.$bus.$off('user-after-loggedin', this.onLoggedIn) - }, - computed: { - isSubscribed (): boolean { - return this.$store.getters['newsletter/isSubscribed'] - } - } -} diff --git a/core/modules/newsletter/mixins/Unsubscribe.ts b/core/modules/newsletter/mixins/Unsubscribe.ts deleted file mode 100644 index 169e9be7b1..0000000000 --- a/core/modules/newsletter/mixins/Unsubscribe.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { required, email } from 'vuelidate/lib/validators' - -/** - * Newsletter subscription form component. - * - * #### Data - * - `email: String` - email that will be used for subscription, validated with vuelidate (email, required) - * - * #### Methods - * - `unsubscribe(success?: Function, failure?: Function)` dispatches `newsletter/unsubscribe` with `email` data property. `success(res)` and `failure(err)` are callback functions called depending on subscription result and contain response info or error. - * - */ -export default { - name: 'NewsletterUnsubscribe', - data () { - return { - email: '' - } - }, - validations: { - email: { - required, - email - } - }, - methods: { - unsubscribe (success?: Function, failure?: Function) { - // argument omitted for validation purposes - if (!this.$v.$invalid) { - return this.$store.dispatch('newsletter/unsubscribe', this.email).then(res => { - if (success) success(res) - this.$emit('unsubscribed', res) - }).catch(err => { - if (failure) failure(err) - this.$emit('unsubscription-error', err) - }) - } - } - } -} diff --git a/core/modules/newsletter/store/index.ts b/core/modules/newsletter/store/index.ts deleted file mode 100644 index e91edceff7..0000000000 --- a/core/modules/newsletter/store/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as types from './mutation-types' -import { Module } from 'vuex' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { NewsletterState } from '../types/NewsletterState' -import { NewsletterService } from '@vue-storefront/core/data-resolver' - -export const newsletterStore: Module = { - namespaced: true, - state: { - isSubscribed: null, - email: null - }, - getters: { - isSubscribed: state => state.isSubscribed, - email: state => state.email - }, - mutations: { - [types.NEWSLETTER_SUBSCRIBE] (state) { - state.isSubscribed = true - }, - [types.NEWSLETTER_UNSUBSCRIBE] (state) { - state.isSubscribed = false - }, - [types.SET_EMAIL] (state, payload) { - state.email = payload - } - }, - actions: { - async status ({ commit }, email): Promise { - const isSubscribed = await NewsletterService.isSubscribed(email) - - if (isSubscribed) { - commit(types.SET_EMAIL, email) - commit(types.NEWSLETTER_SUBSCRIBE) - } else { - commit(types.NEWSLETTER_UNSUBSCRIBE) - } - - return isSubscribed - }, - async subscribe ({ commit, getters, dispatch }, email): Promise { - if (getters.isSubscribed) return - - const subscribeResponse = await NewsletterService.subscribe(email) - - commit(types.NEWSLETTER_SUBSCRIBE) - commit(types.SET_EMAIL, email) - await dispatch('storeToCache', { email }) - - return subscribeResponse - }, - async unsubscribe ({ commit, getters }, email): Promise { - if (!getters.isSubscribed) return - - const unsubscribeResponse = await NewsletterService.unsubscribe(email) - commit(types.NEWSLETTER_UNSUBSCRIBE) - - return unsubscribeResponse - }, - async storeToCache (context, { email }) { - const newsletterStorage = StorageManager.get('newsletter') - await newsletterStorage.setItem('email', email) - } - } -} diff --git a/core/modules/newsletter/store/mutation-types.ts b/core/modules/newsletter/store/mutation-types.ts deleted file mode 100644 index 61a155ca2c..0000000000 --- a/core/modules/newsletter/store/mutation-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const NEWSLETTER_SUBSCRIBE = 'NEWSLETTER_SUBSCRIBE' -export const NEWSLETTER_UNSUBSCRIBE = 'NEWSLETTER_UNSUBSCRIBE' -export const SET_EMAIL = 'SET_EMAIL' diff --git a/core/modules/newsletter/test/unit/components/Newsletter.spec.ts b/core/modules/newsletter/test/unit/components/Newsletter.spec.ts deleted file mode 100644 index 231d2ff7d1..0000000000 --- a/core/modules/newsletter/test/unit/components/Newsletter.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils' - -import { Newsletter } from '../../../components/Newsletter' - -jest.mock('@vue-storefront/core/modules/newsletter/mixins/SubscriptionStatus', () => ({})) -jest.mock('@vue-storefront/core/modules/newsletter/mixins/Subscribe', () => ({})) -jest.mock('@vue-storefront/core/modules/newsletter/mixins/Unsubscribe', () => ({})) - -describe('Newsletter', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('can be initialized', () => { - const wrapper = mountMixinWithStore(Newsletter) - - expect(wrapper.isVueInstance()).toBe(true) - }) -}) diff --git a/core/modules/newsletter/test/unit/mixins/Subscribe.spec.ts b/core/modules/newsletter/test/unit/mixins/Subscribe.spec.ts deleted file mode 100644 index ff29a25b16..0000000000 --- a/core/modules/newsletter/test/unit/mixins/Subscribe.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; - -import Subscribe from '../../../mixins/Subscribe' - -jest.mock('vuelidate/lib/validators', () => ({ - email: {}, - required: {} -})) - -describe('Subscribe', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('method subscribe dispatches subscription action successfully', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - subscribe: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Subscribe, storeMock, { - mocks: { - $v: { - $invalid: false - } - } - }); - - (wrapper.vm as any).subscribe() - - expect(storeMock.modules.newsletter.actions.subscribe).toBeCalledWith(expect.anything(), ''); - }) - - it('method subscribe dispatches subscription action successfully with success Callback', async () => { - const storeMock = { - modules: { - newsletter: { - actions: { - subscribe: jest.fn(() => true) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Subscribe, storeMock, { - mocks: { - $v: { - $invalid: false - } - } - }); - - const successCallback = jest.fn() - - await (wrapper.vm as any).subscribe(successCallback) - - expect(storeMock.modules.newsletter.actions.subscribe).toBeCalledWith(expect.anything(), ''); - expect(successCallback).toBeCalledWith(true) - }) - - it('method subscribe handles dispatching subscription action that fails', async () => { - const storeMock = { - modules: { - newsletter: { - actions: { - subscribe: jest.fn(() => Promise.reject('subscription failed')) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Subscribe, storeMock, { - mocks: { - $v: { - $invalid: false - } - } - }); - - await (wrapper.vm as any).subscribe(() => {}) - - expect(storeMock.modules.newsletter.actions.subscribe).toBeCalledWith(expect.anything(), ''); - }) - - it('method subscribe dispatches subscription action that fails given an error handler', async () => { - const storeMock = { - modules: { - newsletter: { - actions: { - subscribe: jest.fn(() => Promise.reject('subscription failed')) - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Subscribe, storeMock, { - mocks: { - $v: { - $invalid: false - } - } - }); - - const errorCallback = jest.fn() - - await (wrapper.vm as any).subscribe(() => {}, errorCallback) - - expect(storeMock.modules.newsletter.actions.subscribe).toBeCalledWith(expect.anything(), ''); - expect(errorCallback).toBeCalledWith('subscription failed') - }) -}); diff --git a/core/modules/newsletter/test/unit/mixins/SubscriptionStatus.spec.ts b/core/modules/newsletter/test/unit/mixins/SubscriptionStatus.spec.ts deleted file mode 100644 index 92b9bbc39f..0000000000 --- a/core/modules/newsletter/test/unit/mixins/SubscriptionStatus.spec.ts +++ /dev/null @@ -1,287 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils' - -import SubscriptionStatus from '../../../mixins/SubscriptionStatus' - -jest.mock('vuelidate/lib/validators', () => ({ - email: {}, - required: {} -})) - -describe('SubscriptionStatus', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - it('updates subscription status on mount if user emails is set', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - status: jest.fn(() => true) - }, - getters: { - isSubscribed: jest.fn(() => false) - }, - namespaced: true - }, - user: { - state: { - current: { - email: 'e@ma.il' - } - }, - namespaced: true - } - } - } - - mountMixinWithStore(SubscriptionStatus, storeMock, { - mocks: { - $emit: jest.fn(), - $v: { - $invalid: false - }, - $bus: { - $on: jest.fn() - } - } - }) - - expect(storeMock.modules.newsletter.actions.status).toBeCalled() - }) - - it('does not update subscription status on mount if user data is not available', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - status: jest.fn(() => true) - }, - getters: { - isSubscribed: jest.fn(() => false) - }, - namespaced: true - }, - user: { - state: { - current: null - }, - namespaced: true - } - } - } - - mountMixinWithStore(SubscriptionStatus, storeMock, { - mocks: { - $emit: jest.fn(), - $v: { - $invalid: false - }, - $bus: { - $on: jest.fn() - } - } - }) - - expect(storeMock.modules.newsletter.actions.status).not.toBeCalled() - }) - - it('starts watching subscription status on mount, stops watching on destroy', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - status: jest.fn(() => true) - }, - getters: { - isSubscribed: jest.fn(() => false) - }, - namespaced: true - }, - user: { - namespaced: true - } - } - } - - const busOnHook = jest.fn() - const busOffHook = jest.fn() - - const wrapper = mountMixinWithStore(SubscriptionStatus, storeMock, { - mocks: { - $emit: jest.fn(), - $v: { - $invalid: false - }, - $bus: { - $on: busOnHook, - $off: busOffHook - } - } - }) - - wrapper.destroy() - - expect(busOnHook).toBeCalled() - expect(busOffHook).toBeCalled() - }) - - it('updates subscription status on mount if user emails is set', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - status: jest.fn(() => true) - }, - getters: { - isSubscribed: jest.fn(() => false) - }, - namespaced: true - }, - user: { - state: { - current: { - email: 'e@ma.il' - } - }, - namespaced: true - } - } - } - - const busOnHook = jest.fn() - const busOffHook = jest.fn() - - const wrapper = mountMixinWithStore(SubscriptionStatus, storeMock, { - mocks: { - $emit: jest.fn(), - $v: { - $invalid: false - }, - $bus: { - $on: busOnHook, - $off: busOffHook - } - } - }) - - wrapper.destroy() - - expect(storeMock.modules.newsletter.actions.status).toBeCalled() - expect(busOnHook).toBeCalled() - expect(busOffHook).toBeCalled() - }) - - it('renders subscription status', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - status: jest.fn(() => true) - }, - getters: { - isSubscribed: jest.fn(() => true) - }, - namespaced: true - }, - user: { - namespaced: true - } - } - } - - const wrapper = mountMixinWithStore(SubscriptionStatus, storeMock, { - mocks: { - $emit: jest.fn(), - $v: { - $invalid: false - }, - $bus: { - $on: jest.fn() - } - } - }, "

should be displayed

") - - expect(wrapper).toMatchInlineSnapshot('

should be displayed

') - }) - - it('method onLoggedIn can be called without callbacks', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - status: jest.fn(() => true) - }, - getters: { - isSubscribed: jest.fn(() => true) - }, - namespaced: true - }, - user: { - namespaced: true, - state: { - current: { - email: 'john@doe.com' - } - } - } - } - } - - const wrapper = mountMixinWithStore(SubscriptionStatus, storeMock, { - mocks: { - $emit: jest.fn(), - $v: { - $invalid: false - }, - $bus: { - $on: jest.fn() - } - } - }); - - (wrapper.vm as any).onLoggedIn() - - expect(storeMock.modules.newsletter.actions.status).toBeCalled() - }) - - it('method onLoggedIn can be called without callbacks', () => { - const storeMock = { - modules: { - newsletter: { - actions: { - status: jest.fn(() => true) - }, - getters: { - isSubscribed: jest.fn(() => true) - }, - namespaced: true - }, - user: { - namespaced: true, - state: { - current: { - email: 'john@doe.com' - } - } - } - } - } - - const wrapper = mountMixinWithStore(SubscriptionStatus, storeMock, { - mocks: { - $emit: jest.fn(), - $v: { - $invalid: false - }, - $bus: { - $on: jest.fn() - } - } - }); - - (wrapper.vm as any).onLoggedIn() - - expect(storeMock.modules.newsletter.actions.status).toBeCalled() - }) -}) diff --git a/core/modules/newsletter/test/unit/mixins/Unsubscribe.spec.ts b/core/modules/newsletter/test/unit/mixins/Unsubscribe.spec.ts deleted file mode 100644 index ea4cd9928e..0000000000 --- a/core/modules/newsletter/test/unit/mixins/Unsubscribe.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; - -import Unsubscribe from '../../../mixins/Unsubscribe' - -jest.mock('vuelidate/lib/validators', () => ({ - email: {}, - required: {} -})) - -describe('Unsubscribe', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('method unsubscribe dispatches unsubscribe action successfully', async () => { - const storeMock = { - modules: { - newsletter: { - actions: { - unsubscribe: jest.fn(() => true) - }, - namespaced: true - } - } - }; - const emit = jest.fn() - - const wrapper = mountMixinWithStore(Unsubscribe, storeMock, { - mocks: { - $emit: emit, - $v: { - $invalid: false - } - } - }); - - await (wrapper.vm as any).unsubscribe() - - expect(storeMock.modules.newsletter.actions.unsubscribe).toBeCalledWith(expect.anything(), ''); - expect(emit).toBeCalledWith('unsubscribed', true) - }) - - it('method unsubscribe dispatches unsubscribe action but it fails', async () => { - const storeMock = { - modules: { - newsletter: { - actions: { - unsubscribe: jest.fn(() => Promise.reject('error')) - }, - namespaced: true - } - } - }; - const emit = jest.fn() - - const wrapper = mountMixinWithStore(Unsubscribe, storeMock, { - mocks: { - $emit: emit, - $v: { - $invalid: false - } - } - }); - - await (wrapper.vm as any).unsubscribe() - - expect(storeMock.modules.newsletter.actions.unsubscribe).toBeCalledWith(expect.anything(), ''); - expect(emit).toBeCalledWith('unsubscription-error', 'error') - }) -}); diff --git a/core/modules/newsletter/test/unit/store/index.spec.ts b/core/modules/newsletter/test/unit/store/index.spec.ts deleted file mode 100644 index 5eaec3960a..0000000000 --- a/core/modules/newsletter/test/unit/store/index.spec.ts +++ /dev/null @@ -1,201 +0,0 @@ -import * as types from '@vue-storefront/core/modules/newsletter/store/mutation-types'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; -import { NewsletterService } from '@vue-storefront/core/data-resolver'; -import { newsletterStore } from '@vue-storefront/core/modules/newsletter/store'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); - -jest.mock('@vue-storefront/core/data-resolver', () => ({ - NewsletterService: { - isSubscribed: jest.fn(), - subscribe: jest.fn(), - unsubscribe: jest.fn() - } -})); - -describe('Newsletter actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('status', () => { - it('should set e-mail and update newsletter status if it is subscribed', async () => { - const isSubscribed = true; - const email = 'example@domain.com'; - const mockContext = { - commit: jest.fn() - }; - - (NewsletterService.isSubscribed as any).mockImplementation(() => (new Promise(resolve => resolve(isSubscribed)))); - - const status = await (newsletterStore.actions as any).status(mockContext, email); - - expect(NewsletterService.isSubscribed).toHaveBeenCalledWith(email); - expect(mockContext.commit).toHaveBeenCalledTimes(2); - expect(mockContext.commit).toHaveBeenNthCalledWith(1, types.SET_EMAIL, email); - expect(mockContext.commit).toHaveBeenNthCalledWith(2, types.NEWSLETTER_SUBSCRIBE); - expect(status).toBe(isSubscribed); - }); - - it('should not set e-mail but only update newsletter status if it is not subscribed', async () => { - const isSubscribed = false; - const email = 'example@domain.com'; - const mockContext = { - commit: jest.fn() - }; - - (NewsletterService.isSubscribed as any).mockImplementation(() => (new Promise(resolve => resolve(isSubscribed)))); - - const status = await (newsletterStore.actions as any).status(mockContext, email); - - expect(NewsletterService.isSubscribed).toHaveBeenCalledWith(email); - expect(mockContext.commit).toHaveBeenCalledTimes(1); - expect(mockContext.commit).toHaveBeenNthCalledWith(1, types.NEWSLETTER_UNSUBSCRIBE); - expect(status).toBe(isSubscribed); - }); - }); - - describe('subscribe', () => { - it('should not subscribe if it is already subscribed', async () => { - const mockContext = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - isSubscribed: true - } - }; - - await (newsletterStore.actions as any).subscribe(mockContext); - - expect(NewsletterService.subscribe).not.toHaveBeenCalled(); - expect(mockContext.commit).not.toHaveBeenCalled(); - expect(mockContext.dispatch).not.toHaveBeenCalled(); - }); - - it('should subscribe if it is not subscribed', async () => { - const email = 'example@domain.com'; - const mockContext = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - isSubscribed: false - } - }; - - (NewsletterService.subscribe as any).mockImplementation(() => (new Promise(resolve => resolve(true)))); - - const status = await (newsletterStore.actions as any).subscribe(mockContext, email); - - expect(NewsletterService.subscribe).toHaveBeenCalledWith(email); - expect(mockContext.commit).toHaveBeenCalledTimes(2); - expect(mockContext.commit).toHaveBeenNthCalledWith(1, types.NEWSLETTER_SUBSCRIBE); - expect(mockContext.commit).toHaveBeenNthCalledWith(2, types.SET_EMAIL, email); - expect(mockContext.dispatch).toHaveBeenCalledWith('storeToCache', { email }); - expect(status).toBe(true); - }); - }); - - describe('unsubscribe', () => { - it('should not unsubscribe if it is already unsubscribed', async () => { - const mockContext = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - isSubscribed: false - } - }; - - await (newsletterStore.actions as any).unsubscribe(mockContext); - - expect(NewsletterService.unsubscribe).not.toHaveBeenCalled(); - expect(mockContext.commit).not.toHaveBeenCalled(); - expect(mockContext.dispatch).not.toHaveBeenCalled(); - }); - - it('should unsubscribe if it is subscribed', async () => { - const email = 'example@domain.com'; - const mockContext = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - isSubscribed: true - } - }; - - (NewsletterService.unsubscribe as any).mockImplementation(() => (new Promise(resolve => resolve(true)))); - - const status = await (newsletterStore.actions as any).unsubscribe(mockContext, email); - - expect(NewsletterService.unsubscribe).toHaveBeenCalledWith(email); - expect(mockContext.commit).toHaveBeenCalledWith(types.NEWSLETTER_UNSUBSCRIBE); - expect(status).toBe(true); - }); - }); - - describe('storeToCache', () => { - it('should store email in cache', () => { - const email = 'example@domain.com'; - const mockSetItem = jest.fn(() => (new Promise(resolve => resolve(true)))); - - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - setItem: mockSetItem - })); - - (newsletterStore.actions as any).storeToCache(null, { email }); - - expect(StorageManager.get).toHaveBeenCalledWith('newsletter'); - expect(mockSetItem).toHaveBeenCalledWith('email', email); - }); - }); -}); - -describe('Newsletter mutations', () => { - it('NEWSLETTER_SUBSCRIBE should set subscription state', () => { - const mockState = { isSubscribed: false }; - const expectedState = { isSubscribed: true }; - - (newsletterStore.mutations as any)[types.NEWSLETTER_SUBSCRIBE](mockState); - - expect(mockState).toEqual(expectedState); - }); - - it('NEWSLETTER_UNSUBSCRIBE should set unsubscription state', () => { - const mockState = { isSubscribed: true }; - const expectedState = { isSubscribed: false }; - - (newsletterStore.mutations as any)[types.NEWSLETTER_UNSUBSCRIBE](mockState); - - expect(mockState).toEqual(expectedState); - }); - - it('SET_EMAIL should set email address', () => { - const email = 'example@domain.com'; - const mockState = { email: '' }; - const expectedState = { email }; - - (newsletterStore.mutations as any)[types.SET_EMAIL](mockState, email); - - expect(mockState).toEqual(expectedState); - }); -}); - -describe('Newsletter getters', () => { - it('should return subscription status', () => { - const isSubscribed = (newsletterStore.getters as any).isSubscribed({ isSubscribed: true }); - const isNotSubscribed = (newsletterStore.getters as any).isSubscribed({ isSubscribed: false }); - - expect(isSubscribed).toBe(true); - expect(isNotSubscribed).toBe(false); - }); - - it('should return email address', () => { - const email = 'example@domain.com'; - const expectedEmail = (newsletterStore.getters as any).email({ email }); - - expect(expectedEmail).toBe(email); - }); -}); diff --git a/core/modules/newsletter/types/NewsletterState.ts b/core/modules/newsletter/types/NewsletterState.ts deleted file mode 100644 index eaca2f08e1..0000000000 --- a/core/modules/newsletter/types/NewsletterState.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface NewsletterState { - isSubscribed: boolean | null, - email: string | null -} diff --git a/core/modules/notification/components/Notification.ts b/core/modules/notification/components/Notification.ts deleted file mode 100644 index 52928b0795..0000000000 --- a/core/modules/notification/components/Notification.ts +++ /dev/null @@ -1,10 +0,0 @@ -import NotificationItem from '../types/NotificationItem' - -export const Notification = { - name: 'Notification', - computed: { - notifications (): NotificationItem[] { - return this.$store.getters['notification/notifications'] - } - } -} diff --git a/core/modules/notification/index.ts b/core/modules/notification/index.ts deleted file mode 100644 index 3bf77ae235..0000000000 --- a/core/modules/notification/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { notificationStore } from './store' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -export const NotificationModule: StorefrontModule = function ({ store }) { - store.registerModule('notification', notificationStore) -} diff --git a/core/modules/notification/store/index.ts b/core/modules/notification/store/index.ts deleted file mode 100644 index 05bd8ab721..0000000000 --- a/core/modules/notification/store/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Module } from 'vuex' -import NotificationItem from '../types/NotificationItem' -import NotificationState from '../types/NotificationState' - -export const notificationStore: Module = { - namespaced: true, - state: { - notifications: [] - }, - getters: { - notifications: state => state.notifications - }, - mutations: { - add (state, payload) { - state.notifications.push(payload) - }, - remove (state, index) { - state.notifications.splice(index, 1) - } - }, - actions: { - spawnNotification ({ commit, state, dispatch }, notification: NotificationItem): NotificationItem { - if (state.notifications.length > 0 && - state.notifications[state.notifications.length - 1].message === notification.message - ) { - return - } - - const id = Math.floor(Math.random() * 100000) - const newNotification = { id, ...notification } - - commit('add', newNotification) - - if (!newNotification.hasNoTimeout) { - setTimeout(() => { - dispatch('removeNotificationById', id) - }, newNotification.timeToLive || 5000) - } - - return newNotification - }, - removeNotification ({ commit, state }, index?: number) { - if (!index) { - commit('remove', state.notifications.length - 1) - } else { - commit('remove', index) - } - }, - removeNotificationById ({ commit, state }, id: number) { - const index = state.notifications.findIndex(notification => notification.id === id) - - if (index !== -1) { - commit('remove', index) - } - } - } -} diff --git a/core/modules/notification/test/unit/actions.spec.ts b/core/modules/notification/test/unit/actions.spec.ts deleted file mode 100644 index f4e3499a43..0000000000 --- a/core/modules/notification/test/unit/actions.spec.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { notificationStore } from '../../store'; -import NotificationItem from '../../types/NotificationItem'; - -jest.useFakeTimers() - -describe('Notification actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.clearAllTimers() - }); - - describe('spawnNotification', () => { - it('should add new notification', async () => { - const contextMock = { - commit: jest.fn(), - state: { - notifications: [] - } - }; - const notification: NotificationItem = { - type: 'success', - message: 'Success text.', - action1: { label: 'OK' } - } - const wrapper = (actions: any) => actions.spawnNotification(contextMock, notification); - - const newNotification = await wrapper(notificationStore.actions); - - expect(contextMock.commit).toBeCalledWith('add', newNotification); - }); - - it('should NOT add new notification if last one has the same message', async () => { - const notification: NotificationItem = { - type: 'success', - message: 'Success text.', - action1: { label: 'OK' } - } - const contextMock = { - commit: jest.fn(), - state: { - notifications: [notification] - } - }; - const wrapper = (actions: any) => actions.spawnNotification(contextMock, notification); - - await wrapper(notificationStore.actions); - - expect(contextMock.commit).not.toBeCalledWith('add', notification); - }); - - it('should remove new notification after timeToLive (3000ms)', async () => { - const dispatch = jest.fn() - const contextMock = { - dispatch, - commit: jest.fn(), - state: { - notifications: [] - } - }; - const notification: NotificationItem = { - type: 'success', - message: 'Success text.', - action1: { label: 'OK' }, - timeToLive: 3000 - } - const wrapper = (actions: any) => actions.spawnNotification(contextMock, notification); - - const newNotification = await wrapper(notificationStore.actions); - - expect(contextMock.dispatch).not.toHaveBeenLastCalledWith('removeNotificationById'); - - jest.advanceTimersByTime(3000); - - expect(contextMock.dispatch).toHaveBeenLastCalledWith('removeNotificationById', newNotification.id); - }); - - it('should NOT remove new notification if hasNoTimeout is set on true', async () => { - const dispatch = jest.fn() - const contextMock = { - dispatch, - commit: jest.fn(), - state: { - notifications: [] - } - }; - const notification: NotificationItem = { - type: 'success', - message: 'Success text.', - action1: { label: 'OK' }, - hasNoTimeout: true - } - const wrapper = (actions: any) => actions.spawnNotification(contextMock, notification); - - await wrapper(notificationStore.actions); - - jest.advanceTimersByTime(5000); - - expect(contextMock.dispatch).not.toHaveBeenLastCalledWith('removeNotificationById'); - }); - }); - - describe('removeNotification', () => { - it('should call \'remove\' commit with specific index', async () => { - const contextMock = { - commit: jest.fn(), - state: { - notifications: [] - } - }; - const wrapper = (actions: any) => actions.removeNotification(contextMock, 1); - - await wrapper(notificationStore.actions); - - expect(contextMock.commit).toBeCalledWith('remove', 1); - }); - - it('if there is no index provided then should call \'remove\' commit with last index', async () => { - const notification: NotificationItem = { - type: 'success', - message: 'Success text.', - action1: { label: 'OK' } - } - const contextMock = { - commit: jest.fn(), - state: { - notifications: [notification, notification] - } - }; - const wrapper = (actions: any) => actions.removeNotification(contextMock); - - await wrapper(notificationStore.actions); - - expect(contextMock.commit).toBeCalledWith('remove', 1); - }); - }) - - describe('removeNotificationById', () => { - it('should call \'remove\' commit if id is found', async () => { - const contextMock = { - commit: jest.fn(), - state: { - notifications: [ - { - id: 1234, - type: 'success', - message: 'Success text.', - action1: { label: 'OK' } - } - ] - } - }; - const wrapper = (actions: any) => actions.removeNotificationById(contextMock, 1234); - - await wrapper(notificationStore.actions); - - expect(contextMock.commit).toBeCalledWith('remove', 0); - }); - - it('should not call \'remove\' commit if id is not found', async () => { - const contextMock = { - commit: jest.fn(), - state: { - notifications: [ - { - id: 1230, - type: 'success', - message: 'Success text.', - action1: { label: 'OK' } - } - ] - } - }; - const wrapper = (actions: any) => actions.removeNotificationById(contextMock, 1234); - - await wrapper(notificationStore.actions); - - expect(contextMock.commit).not.toBeCalledWith('remove', 0); - }); - }) -}) diff --git a/core/modules/notification/test/unit/mutations.spec.ts b/core/modules/notification/test/unit/mutations.spec.ts deleted file mode 100644 index 5f4a2e77ab..0000000000 --- a/core/modules/notification/test/unit/mutations.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { notificationStore } from '../../store'; -import NotificationItem from '../../types/NotificationItem'; - -describe('Notification actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('add', () => { - it('should add new notification to state', async () => { - const stateMock = { - notifications: [] - } - const notification: NotificationItem = { - type: 'success', - message: 'Success text.', - action1: { label: 'OK' } - } - const expectedState = { - notifications: [notification] - } - const wrapper = (mutations: any) => mutations['add'](stateMock, notification) - - wrapper(notificationStore.mutations) - - expect(stateMock).toEqual(expectedState) - }); - }) - - describe('remove', () => { - it('should remove notification from state with provided index', async () => { - const notification: NotificationItem = { - type: 'success', - message: 'Success text.', - action1: { label: 'OK' } - } - const notification2: NotificationItem = { - type: 'success', - message: 'Success text2.', - action1: { label: 'OK' } - } - const stateMock = { - notifications: [notification, notification2] - } - const expectedState = { - notifications: [notification2] - } - const wrapper = (mutations: any) => mutations['remove'](stateMock, 0) - - wrapper(notificationStore.mutations) - - expect(stateMock).toEqual(expectedState) - }); - }) -}) diff --git a/core/modules/notification/types/NotificationItem.ts b/core/modules/notification/types/NotificationItem.ts deleted file mode 100644 index ed55e44fa3..0000000000 --- a/core/modules/notification/types/NotificationItem.ts +++ /dev/null @@ -1,14 +0,0 @@ -interface ActionItem { - label: string, - action?(): any -} - -export default interface NotificationItem { - id?: number, - type: string, - message: string, - timeToLive?: number, - action1: ActionItem, - action2?: ActionItem, - hasNoTimeout?: boolean -} diff --git a/core/modules/notification/types/NotificationState.ts b/core/modules/notification/types/NotificationState.ts deleted file mode 100644 index 332922be3e..0000000000 --- a/core/modules/notification/types/NotificationState.ts +++ /dev/null @@ -1,5 +0,0 @@ -import NotificationItem from './NotificationItem' - -export default interface NotificationState { - notifications: NotificationItem[] -} diff --git a/core/modules/offline-order/README.md b/core/modules/offline-order/README.md deleted file mode 100644 index fd442c0137..0000000000 --- a/core/modules/offline-order/README.md +++ /dev/null @@ -1 +0,0 @@ -Needs refactoring \ No newline at end of file diff --git a/core/modules/offline-order/components/CancelOrders.ts b/core/modules/offline-order/components/CancelOrders.ts deleted file mode 100644 index 220372f624..0000000000 --- a/core/modules/offline-order/components/CancelOrders.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Logger } from '@vue-storefront/core/lib/logger' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const CancelOrders = { - methods: { - cancelOrders () { - const ordersCollection = StorageManager.get('orders') - ordersCollection.iterate((order, id, iterationNumber) => { - if (!order.transmited) { - ordersCollection.removeItem(id) - } - }).catch(err => { - Logger.error(err, 'offline-order')() - Logger.log('Not transmitted orders have been deleted', 'offline-order')() - }) - } - } -} diff --git a/core/modules/offline-order/components/ConfirmOrders.ts b/core/modules/offline-order/components/ConfirmOrders.ts deleted file mode 100644 index e19b3ebb16..0000000000 --- a/core/modules/offline-order/components/ConfirmOrders.ts +++ /dev/null @@ -1,10 +0,0 @@ -import config from 'config' -export const ConfirmOrders = { - methods: { - confirmOrders () { - this.$bus.$emit('order/PROCESS_QUEUE', { config: config }) - this.$bus.$emit('sync/PROCESS_QUEUE', { config: config }) - this.$store.dispatch('cart/load') - } - } -} diff --git a/core/modules/offline-order/extends/service-worker.js b/core/modules/offline-order/extends/service-worker.js deleted file mode 100644 index 587217eb57..0000000000 --- a/core/modules/offline-order/extends/service-worker.js +++ /dev/null @@ -1,30 +0,0 @@ -// Offline order notification object -import config from 'config' - -function sendNotification () { - const title = config.orders.offline_orders.notification.title - const message = config.orders.offline_orders.notification.message - const icon = config.orders.offline_orders.notification.icon - - self.registration.showNotification(title, { - body: message, - icon: icon, - requireInteraction: true - }) -} - -function openShop (notification) { - const pageUrl = '/?order=offline' - self.clients.openWindow(pageUrl) - notification.close() -} - -self.addEventListener('sync', event => { - if (event.tag === 'orderSync') { - event.waitUntil(sendNotification()) - } -}) - -self.addEventListener('notificationclick', event => { - event.waitUntil(openShop(event.notification)) -}) diff --git a/core/modules/offline-order/helpers/onNetworkStatusChange.ts b/core/modules/offline-order/helpers/onNetworkStatusChange.ts deleted file mode 100644 index ecd806c19e..0000000000 --- a/core/modules/offline-order/helpers/onNetworkStatusChange.ts +++ /dev/null @@ -1,31 +0,0 @@ -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus/index' -import { Logger } from '@vue-storefront/core/lib/logger' -import config from 'config' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export function onNetworkStatusChange (store) { - Logger.log('Are we online: ' + navigator.onLine, 'offline-order')() - - if (typeof navigator !== 'undefined' && navigator.onLine) { - EventBus.$emit('sync/PROCESS_QUEUE', { config: config }) // process checkout queue - store.dispatch('cart/load', { forceClientState: true }) - if (config.orders.offline_orders.automatic_transmission_enabled || store.getters['checkout/isThankYouPage']) { - EventBus.$emit('order/PROCESS_QUEUE', { config: config }) // process checkout queue - } else { - const ordersToConfirm = [] - const ordersCollection = StorageManager.get('orders') - - ordersCollection.iterate((order, id, iterationNumber) => { - if (!order.transmited) { - ordersToConfirm.push(order) - } - }).catch(err => { - Logger.error(err, 'offline-order')() - }) - - if (ordersToConfirm.length > 0) { - EventBus.$emit('offline-order-confirmation', ordersToConfirm) - } - } - } -} diff --git a/core/modules/order/components/UserOrders.ts b/core/modules/order/components/UserOrders.ts deleted file mode 100644 index fdf4f38b29..0000000000 --- a/core/modules/order/components/UserOrders.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { mapGetters } from 'vuex'; - -/** - * Component responsible for displaying user orders. Requires User module. - */ -export const UserOrders = { - name: 'UserOrders', - computed: { - ...mapGetters('user', ['getOrdersHistory']), - ordersHistory () { - return this.getOrdersHistory - }, - isHistoryEmpty () { - return this.getOrdersHistory.length < 1 - } - }, - methods: { - async remakeOrder (products) { - this.$bus.$emit('notification-progress-start', this.$t('Please wait ...')) - const productsToAdd = [] - for (const item of products) { - const product = await this.$store.dispatch('product/single', { options: { sku: item.sku } }) - product.qty = item.qty_ordered - productsToAdd.push(product) - } - await this.$store.dispatch('cart/addItems', { productsToAdd }) - this.$bus.$emit('notification-progress-stop', {}) - // Redirect to the cart straight away. - this.$router.push(this.localizedRoute('/checkout')) - }, - skipGrouped (items) { - return items.filter((item) => { - return !item.parent_item_id - }) - } - } -} diff --git a/core/modules/order/components/UserOrdersHistory.ts b/core/modules/order/components/UserOrdersHistory.ts deleted file mode 100644 index 76c9ec1044..0000000000 --- a/core/modules/order/components/UserOrdersHistory.ts +++ /dev/null @@ -1,39 +0,0 @@ -import MyOrders from '@vue-storefront/core/compatibility/components/blocks/MyAccount/MyOrders' -import onBottomScroll from '@vue-storefront/core/mixins/onBottomScroll' - -export default { - name: 'UserOrdersHistory', - mixins: [MyOrders, onBottomScroll], - data () { - return { - pagination: { - perPage: 10, - current: 1, - enabled: false - }, - lazyLoadOrdersOnScroll: true - } - }, - computed: { - ordersHistory () { - let items = this.getOrdersHistory - if (this.lazyLoadOrdersOnScroll) { - items = items.slice(0, this.pagination.perPage * this.pagination.current) - } - return items - } - }, - methods: { - onBottomScroll () { - const totalCount = this.$store.state.user.orders_history.total_count ? this.$store.state.user.orders_history.total_count : 0; - const isLastPage = this.pagination.current > Math.ceil(totalCount / this.pagination.perPage); - if (!isLastPage) { - this.pagination.current++; - this.$store.dispatch('user/appendOrdersHistory', { - pageSize: this.pagination.perPage, - currentPage: this.pagination.current - }); - } - } - } -} diff --git a/core/modules/order/components/UserSingleOrder.ts b/core/modules/order/components/UserSingleOrder.ts deleted file mode 100644 index 07b2459796..0000000000 --- a/core/modules/order/components/UserSingleOrder.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { mapGetters } from 'vuex'; - -/** - * Component responsible for displaying single user order. Requires User module. - */ -export const UserSingleOrder = { - name: 'UserSingleOrder', - computed: { - ...mapGetters({ - ordersHistory: 'user/getOrdersHistory' - }), - order () { - return this.ordersHistory.find(order => - parseInt(order.entity_id) === parseInt(this.$route.params.orderId) - ) - }, - paymentMethod () { - return this.order && this.order.payment.additional_information[0] - }, - billingAddress () { - return this.order && this.order.billing_address - }, - shippingAddress () { - return this.order && this.order.extension_attributes.shipping_assignments[0].shipping.address - }, - singleOrderItems () { - if (!this.order) return [] - - return this.order.items.filter((item) => { - return !item.parent_item_id - }) - } - }, - methods: { - async remakeOrder (products) { - this.$bus.$emit('notification-progress-start', this.$t('Please wait ...')) - const productsToAdd = [] - for (const item of products) { - const product = await this.$store.dispatch('product/single', { options: { sku: item.sku } }) - product.qty = item.qty_ordered - productsToAdd.push(product) - } - await this.$store.dispatch('cart/addItems', { productsToAdd }) - this.$bus.$emit('notification-progress-stop', {}) - // Redirect to the cart straight away. - this.$router.push(this.localizedRoute('/checkout')) - }, - skipGrouped (items) { - return items.filter((item) => { - return !item.parent_item_id - }) - } - } -} diff --git a/core/modules/order/helpers/index.ts b/core/modules/order/helpers/index.ts deleted file mode 100644 index 22095bfec3..0000000000 --- a/core/modules/order/helpers/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import optimizeOrder from './optimizeOrder' -import prepareOrder from './prepareOrder' -import notifications from './notifications' - -export { optimizeOrder, prepareOrder, notifications } diff --git a/core/modules/order/helpers/notifications.ts b/core/modules/order/helpers/notifications.ts deleted file mode 100644 index 2c419bcce1..0000000000 --- a/core/modules/order/helpers/notifications.ts +++ /dev/null @@ -1,18 +0,0 @@ -import i18n from '@vue-storefront/i18n' -import config from 'config' - -const internalValidationError = () => ({ - type: 'error', - message: i18n.t('Internal validation error. Please check if all required fields are filled in. Please contact us on {email}', { email: config.mailer.contactAddress }), - action1: { label: i18n.t('OK') } -}) - -const orderCannotTransfered = () => ({ - type: 'error', - message: i18n.t('The order can not be transfered because of server error. Order has been queued'), - action1: { label: i18n.t('OK') } -}) - -const notifications = { internalValidationError, orderCannotTransfered } - -export default notifications diff --git a/core/modules/order/helpers/optimizeOrder.ts b/core/modules/order/helpers/optimizeOrder.ts deleted file mode 100644 index d2b349bf98..0000000000 --- a/core/modules/order/helpers/optimizeOrder.ts +++ /dev/null @@ -1,16 +0,0 @@ -import config from 'config' -import omit from 'lodash-es/omit' -import { Order } from '@vue-storefront/core/modules/order/types/Order' - -const optimizeOrder = (order: Order): Order => { - if (config.entities.optimize && config.entities.optimizeShoppingCart) { - return { - ...order, - products: order.products.map(product => omit(product, ['configurable_options', 'configurable_children'])) as Order['products'] - } - } - - return order -} - -export default optimizeOrder diff --git a/core/modules/order/helpers/prepareOrder.ts b/core/modules/order/helpers/prepareOrder.ts deleted file mode 100644 index 424cc00dc0..0000000000 --- a/core/modules/order/helpers/prepareOrder.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Order } from '@vue-storefront/core/modules/order/types/Order' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' - -const prepareOrder = (order: Order): Order => { - const storeView = currentStoreView() - const storeCode = storeView.storeCode ? storeView.storeCode : order.store_code - - return { - ...order, - store_code: storeCode - } -} - -export default prepareOrder diff --git a/core/modules/order/hooks.ts b/core/modules/order/hooks.ts deleted file mode 100644 index e157457957..0000000000 --- a/core/modules/order/hooks.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { createListenerHook, createMutatorHook } from '@vue-storefront/core/lib/hooks' - -const { - hook: beforePlaceOrderHook, - executor: beforePlaceOrderExecutor -} = createMutatorHook() - -const { - hook: afterPlaceOrderHook, - executor: afterPlaceOrdeExecutor -} = createListenerHook<{ order: any, task: any }>() - -/** Only for internal usage in this module */ -const orderHooksExecutors = { - beforePlaceOrder: beforePlaceOrderExecutor, - afterPlaceOrder: afterPlaceOrdeExecutor -} - -const orderHooks = { - /** Hook is fired directly before sending order to the server, after all client-side validations - * @param order Inside this function you have access to order object that you can access and modify. It should return order object. - */ - beforePlaceOrder: beforePlaceOrderHook, - /** Hook is fired right after order has been sent to server - * @param result `{ order, task }` task is a result of sending order to backend and order is order that has been sent there - */ - afterPlaceOrder: afterPlaceOrderHook -} - -export { - orderHooks, - orderHooksExecutors -} diff --git a/core/modules/order/index.ts b/core/modules/order/index.ts deleted file mode 100644 index ed972d3e86..0000000000 --- a/core/modules/order/index.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { orderStore } from './store' -import * as localForage from 'localforage' -import UniversalStorage from '@vue-storefront/core/lib/store/storage' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus/index' -import { Logger } from '@vue-storefront/core/lib/logger' -import rootStore from '@vue-storefront/core/store' -import i18n from '@vue-storefront/i18n' -import { serial, onlineHelper, processURLAddress } from '@vue-storefront/core/helpers' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { isServer } from '@vue-storefront/core/helpers' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -export const OrderModule: StorefrontModule = function ({ store }) { - StorageManager.init('orders') - - if (!isServer) { - const orderMutex = {} - // TODO: move to external file - EventBus.$on('order/PROCESS_QUEUE', async event => { - if (onlineHelper.isOnline) { - Logger.log('Sending out orders queue to server ...')() - const ordersCollection = StorageManager.get('orders') - - const fetchQueue = [] - ordersCollection.iterate((order, id) => { - // Resulting key/value pair -- this callback - // will be executed for every item in the - // database. - - if (!order.transmited && !orderMutex[id]) { // not sent to the server yet - orderMutex[id] = true - const config = event.config - const orderData = order - const orderId = id - Logger.log('Pushing out order ' + orderId)() - fetchQueue.push( - /** @todo refactor order synchronisation to proper handling through vuex actions to avoid code duplication */ - fetch(processURLAddress(config.orders.endpoint), - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(orderData) - }).then(response => { - const contentType = response.headers.get('content-type') - if (contentType && contentType.includes('application/json')) { - return response.json() - } else { - orderMutex[id] = false - Logger.error('Error with response - bad content-type!')() - } - }) - .then(jsonResponse => { - if (jsonResponse) { - Logger.info('Response for: ' + orderId + ' = ' + JSON.stringify(jsonResponse.result))() - orderData.transmited = true // by default don't retry to transmit this order - orderData.transmited_at = new Date() - - if (jsonResponse.code !== 200) { - Logger.error(jsonResponse, 'order-sync')() - - if (jsonResponse.code === 400) { - rootStore.dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t('Address provided in checkout contains invalid data. Please check if all required fields are filled in and also contact us on {email} to resolve this issue for future. Your order has been canceled.', { email: config.mailer.contactAddress }), - action1: { label: i18n.t('OK') } - }) - } else if (jsonResponse.code === 500 && jsonResponse.result === i18n.t('Error: Error while adding products')) { - rootStore.dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t('Some products you\'ve ordered are out of stock. Your order has been canceled.'), - action1: { label: i18n.t('OK') } - }) - } else { - orderData.transmited = false // probably some server related error. Enqueue - } - } - - ordersCollection.setItem(orderId.toString(), orderData) - } else { - Logger.error(jsonResponse)() - } - orderMutex[id] = false - }).catch(err => { - if (config.orders.offline_orders.notification.enabled) { - navigator.serviceWorker.ready.then(registration => { - registration.sync.register('orderSync') - .then(() => { - Logger.log('Order sync registered')() - }) - .catch(error => { - Logger.log('Unable to sync', error)() - }) - }) - } - Logger.error('Error sending order: ' + orderId, err)() - orderMutex[id] = false - }) - ) - } - }, (err) => { - if (err) Logger.error(err)() - Logger.log('Iteration has completed')() - - // execute them serially - serial(fetchQueue) - Logger.info('Processing orders queue has finished')() - }).catch(err => { - // This code runs if there were any errors - Logger.log(err)() - }) - } - }) - } - store.registerModule('order', orderStore) -} diff --git a/core/modules/order/store/actions.ts b/core/modules/order/store/actions.ts deleted file mode 100644 index 21f304c433..0000000000 --- a/core/modules/order/store/actions.ts +++ /dev/null @@ -1,109 +0,0 @@ -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import * as types from './mutation-types' -import { ActionTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import OrderState from '../types/OrderState' -import { Order } from '../types/Order' -import { isOnline } from '@vue-storefront/core/lib/search' -import i18n from '@vue-storefront/i18n' -import { OrderService } from '@vue-storefront/core/data-resolver' -import { sha3_224 } from 'js-sha3' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { Logger } from '@vue-storefront/core/lib/logger' -import config from 'config' -import { orderHooksExecutors } from '../hooks' -import * as entities from '@vue-storefront/core/lib/store/entities' -import { prepareOrder, optimizeOrder, notifications } from './../helpers' - -const actions: ActionTree = { - /** - * Place order - send it to service worker queue - * @param {Object} commit method - * @param {Order} order order data to be send - */ - async placeOrder ({ commit, getters, dispatch }, newOrder: Order) { - // Check if order is already processed/processing - const optimizedOrder = optimizeOrder(newOrder) - const currentOrderHash = sha3_224(JSON.stringify(optimizedOrder)) - const isAlreadyProcessed = getters.getSessionOrderHashes.includes(currentOrderHash) - if (isAlreadyProcessed) return - commit(types.ORDER_ADD_SESSION_STAMPS, newOrder) - commit(types.ORDER_ADD_SESSION_ORDER_HASH, currentOrderHash) - const preparedOrder = prepareOrder(optimizedOrder) - - EventBus.$emit('order-before-placed', { order: preparedOrder }) - const order = orderHooksExecutors.beforePlaceOrder(preparedOrder) - - if (!isOnline()) { - dispatch('enqueueOrder', { newOrder: order }) - EventBus.$emit('order-after-placed', { order }) - orderHooksExecutors.beforePlaceOrder({ order, task: { resultCode: 200 } }) - return { resultCode: 200 } - } - - EventBus.$emit('notification-progress-start', i18n.t('Processing order...')) - - try { - return await dispatch('processOrder', { newOrder: order, currentOrderHash }) - } catch (error) { - dispatch('handlePlacingOrderFailed', { newOrder: order, currentOrderHash }) - throw error - } - }, - async processOrder ({ commit, dispatch }, { newOrder, currentOrderHash }) { - const order = { ...newOrder, transmited: true } - const task = await OrderService.placeOrder(order) - - if (task.resultCode === 200) { - dispatch('enqueueOrder', { newOrder: order }) - - commit(types.ORDER_LAST_ORDER_WITH_CONFIRMATION, { order, confirmation: task.result }) - orderHooksExecutors.afterPlaceOrder({ order, task }) - EventBus.$emit('order-after-placed', { order, confirmation: task.result }) - EventBus.$emit('notification-progress-stop') - return task - } - - if (task.resultCode === 400) { - commit(types.ORDER_REMOVE_SESSION_ORDER_HASH, currentOrderHash) - - Logger.error('Internal validation error; Order entity is not compliant with the schema: ' + JSON.stringify(task.result), 'orders')() - dispatch('notification/spawnNotification', notifications.internalValidationError(), { root: true }) - dispatch('enqueueOrder', { newOrder: order }) - EventBus.$emit('notification-progress-stop') - return task - } - EventBus.$emit('notification-progress-stop') - throw new Error('Unhandled place order request error') - }, - handlePlacingOrderFailed ({ commit, dispatch }, { newOrder, currentOrderHash }) { - const order = { newOrder, transmited: false } - commit(types.ORDER_REMOVE_SESSION_ORDER_HASH, currentOrderHash) - dispatch('notification/spawnNotification', notifications.orderCannotTransfered(), { root: true }) - dispatch('enqueueOrder', { newOrder: order }) - - EventBus.$emit('notification-progress-stop') - }, - enqueueOrder (context, { newOrder }) { - const orderId = entities.uniqueEntityId(newOrder) - const ordersCollection = StorageManager.get('orders') - const order = { - ...newOrder, - order_id: orderId.toString(), - created_at: new Date(), - updated_at: new Date() - } - - ordersCollection.setItem(orderId.toString(), order, (err, resp) => { - if (err) Logger.error(err, 'orders')() - - if (!order.transmited) { - EventBus.$emit('order/PROCESS_QUEUE', { config: config }) - } - - Logger.info('Order placed, orderId = ' + orderId, 'orders')() - }).catch((reason) => Logger.error(reason, 'orders')) - } -} - -export default actions diff --git a/core/modules/order/store/getters.ts b/core/modules/order/store/getters.ts deleted file mode 100644 index 8b73ef39c3..0000000000 --- a/core/modules/order/store/getters.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { GetterTree } from 'vuex' -import OrderState from '../types/OrderState' -import RootState from '@vue-storefront/core/types/RootState' - -const getters: GetterTree = { - getSessionOrderHashes: state => state.session_order_hashes -} - -export default getters diff --git a/core/modules/order/store/index.ts b/core/modules/order/store/index.ts deleted file mode 100644 index 51e22f6e1d..0000000000 --- a/core/modules/order/store/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import mutations from './mutations' -import getters from './getters' -import RootState from '@vue-storefront/core/types/RootState' -import OrderState from '../types/OrderState' - -export const orderStore: Module = { - namespaced: true, - state: { - last_order_confirmation: null, - session_order_hashes: [] - }, - actions, - mutations, - getters -} diff --git a/core/modules/order/store/mutation-types.ts b/core/modules/order/store/mutation-types.ts deleted file mode 100644 index d234039cc2..0000000000 --- a/core/modules/order/store/mutation-types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const SN_ORDER = 'orders' -export const ORDER_PROCESS_QUEUE = SN_ORDER + '/PROCESS_QUEUE' -export const ORDER_LAST_ORDER_WITH_CONFIRMATION = SN_ORDER + '/LAST_ORDER_CONFIRMATION' -export const ORDER_ADD_SESSION_ORDER_HASH = SN_ORDER + '/ADD_SESSION_ORDER_HASH' -export const ORDER_REMOVE_SESSION_ORDER_HASH = SN_ORDER + '/REMOVE_SESSION_ORDER_HASH' -export const ORDER_ADD_SESSION_STAMPS = SN_ORDER + '/ADD_SESSION_STAMPS' diff --git a/core/modules/order/store/mutations.ts b/core/modules/order/store/mutations.ts deleted file mode 100644 index fa52d80e49..0000000000 --- a/core/modules/order/store/mutations.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import OrderState from '../types/OrderState' -import { Order } from '../types/Order' -import * as entities from '@vue-storefront/core/lib/store/entities' - -const mutations: MutationTree = { - [types.ORDER_LAST_ORDER_WITH_CONFIRMATION] (state, payload) { - state.last_order_confirmation = payload - }, - [types.ORDER_ADD_SESSION_STAMPS] (state, order: Order) { - const orderId = entities.uniqueEntityId(order) // timestamp as a order id is not the best we can do but it's enough - order.order_id = orderId.toString() - order.created_at = new Date().toString() - order.updated_at = new Date().toString() - }, - [types.ORDER_ADD_SESSION_ORDER_HASH] (state, hash: string) { - state.session_order_hashes.push(hash) - }, - [types.ORDER_REMOVE_SESSION_ORDER_HASH] (state, hash: string) { - state.session_order_hashes = state.session_order_hashes.filter(sessionHash => sessionHash !== hash) - } -} - -export default mutations diff --git a/core/modules/order/store/order.schema.extension.json b/core/modules/order/store/order.schema.extension.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/core/modules/order/store/order.schema.extension.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/core/modules/order/store/order.schema.json b/core/modules/order/store/order.schema.json deleted file mode 100644 index d5910dcd4b..0000000000 --- a/core/modules/order/store/order.schema.json +++ /dev/null @@ -1,89 +0,0 @@ -// DEPRECATED -> Moved to Order.ts -{ - "additionalProperties": false, - "required": [ "products", "addressInformation" ], - "properties": { - "order_id" : { "type": "string" }, - "created_at" : { "type": "string" }, - "updated_at": { "type": "string" }, - "transmited" : { "type": "boolean" }, - "transmited_at": { "type": "string" }, - "status": { "type": "string" }, - "state": { "type": "string" }, - "user_id": { "type": "string" }, - "cart_id": { "type": "string" }, - "store_code": { "type": "string" }, - "store_id": { "type": "number" }, - "products": { - "description": "Products list", - "type": "array", - "minItems": 1, - "items": [ - { - "required": [ "sku", "price", "qty" ], - "properties": { - "sku": { "type": "string", "minLength": 1, "pattern": "[a-zA-Z0-9_]+" }, - "qty": { "type" : "number", "minimum": 1 }, - "name": { "type": "string", "minLength": 1, "pattern": "[a-zA-Z0-9_]+" }, - "price": { "type" : "number", "minimum": 1 }, - "product_type": { "type": "string", "minLength": 1, "pattern": "[a-zA-Z]+" } - } - } - ] - }, - "addressInformation": { - - "properties": { - "shippingAddress": { - "required": [ "street", "city", "postcode", "firstname", "lastname" ], - "properties": { - "region": { "type": "string" }, - "region_id": { "type": "number" }, - "country_id": { "type": "string", "pattern": "[a-zA-Z]" }, - "street": { - "description": "Street name", - "minItems": 1, - "items": { "minLength": 1, "description": "Street name"} - }, - "company": { "type": "string" }, - "telephone": { "type": "string" }, - "postcode": { "type": "string", "minLength": 3, "pattern": "[a-zA-Z0-9_]+" }, - "city": { "type": "string", "pattern": "[a-zA-Z]+" }, - "firstname": { "type": "string", "pattern": "[a-zA-Z]+", "description": "First name" }, - "lastname": { "type": "string", "pattern": "[a-zA-Z]+" }, - "email": { "type": "string", "pattern": "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" }, - "region_code":{ "type": "string" }, - "sameAsBilling": { "type": "number"} - } - }, - "billingAddress": { - "required": [ "street", "city", "postcode", "firstname", "lastname" ], - "properties": { - "properties": { - "region": { "type": "string" }, - "region_id": { "type": "number" }, - "country_id": { "type": "string", "pattern": "[a-zA-Z]" }, - "street": { - "minItems": 1, - "items": { "minLength": 1} - }, - "company": { "type": "string" }, - "telephone": { "type": "string" }, - "postcode": { "type": "string", "minLength": 3, "pattern": "[a-zA-Z0-9_]+" }, - "city": { "type": "string", "pattern": "[a-zA-Z]+" }, - "firstname": { "type": "string", "pattern": "[a-zA-Z]+" }, - "lastname": { "type": "string", "pattern": "[a-zA-Z]+" }, - "email": { "type": "string", "pattern": "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?" }, - "region_code":{ "type": "string" }, - "sameAsBilling": { "type": "number"} - } - } - }, - "shipping_method_code": { "type": "string", "pattern": "[a-zA-Z]+" }, - "shipping_carrier_code": { "type": "string", "pattern": "[a-zA-Z]+" }, - "payment_method_code": { "type": "string", "pattern": "[a-zA-Z]+" }, - "payment_method_additional": {} - } - } - } -} \ No newline at end of file diff --git a/core/modules/order/test/unit/helpers/optimizeOrder.spec.ts b/core/modules/order/test/unit/helpers/optimizeOrder.spec.ts deleted file mode 100644 index cb65882719..0000000000 --- a/core/modules/order/test/unit/helpers/optimizeOrder.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Order } from '@vue-storefront/core/modules/order/types/Order' - -describe('optimizeOrder method', () => { - it('should return order without configurable_options and configurable_children', () => { - const expectedOrder: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - /** - * Products list - */ - products: [ - { - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1' - } - ], - addressInformation: { - shippingAddress: { - region: 'Region here', - region_id: 4, - country_id: '15', - /** - * Street name - */ - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - /** - * First name - */ - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { - properties: {} - }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - }; - const optimizedOrder: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - products: - [{ - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1' - }], - addressInformation: - { - shippingAddress: - { - region: 'Region here', - region_id: 4, - country_id: '15', - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { properties: {} }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - - expect(optimizedOrder).toEqual(expectedOrder) - }) -}); diff --git a/core/modules/order/test/unit/helpers/prepareOrder.spec.ts b/core/modules/order/test/unit/helpers/prepareOrder.spec.ts deleted file mode 100644 index 6d3fb0c046..0000000000 --- a/core/modules/order/test/unit/helpers/prepareOrder.spec.ts +++ /dev/null @@ -1,1857 +0,0 @@ -import { Order } from '@vue-storefront/core/modules/order/types/Order' - -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '2' - })) -})); - -describe('prepareOrder method', () => { - it('should return order', () => { - const result: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - products: - [{ - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1', - configurable_options: [ - { - 'attribute_id': '93', - 'values': [ - { - 'value_index': 49 - }, - { - 'value_index': 52 - }, - { - 'value_index': 56 - } - ], - 'product_id': 19, - 'id': 3, - 'label': 'Color', - 'position': 0 - }, - { - 'attribute_id': '157', - 'values': [ - { - 'value_index': 167 - }, - { - 'value_index': 168 - }, - { - 'value_index': 169 - }, - { - 'value_index': 170 - }, - { - 'value_index': 171 - } - ], - 'product_id': 19, - 'id': 2, - 'label': 'Size', - 'position': 0 - } - ], - configurable_children: [ - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Black', - 'sku': 'MH01-XS-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Gray', - 'sku': 'MH01-XS-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Orange', - 'sku': 'MH01-XS-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Black', - 'sku': 'MH01-S-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Gray', - 'sku': 'MH01-S-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Orange', - 'sku': 'MH01-S-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Black', - 'sku': 'MH01-M-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Gray', - 'sku': 'MH01-M-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Orange', - 'sku': 'MH01-M-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Black', - 'sku': 'MH01-L-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Gray', - 'sku': 'MH01-L-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Orange', - 'sku': 'MH01-L-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Black', - 'sku': 'MH01-XL-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Gray', - 'sku': 'MH01-XL-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Orange', - 'sku': 'MH01-XL-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - } - ] - }], - addressInformation: - { - shippingAddress: - { - region: 'Region here', - region_id: 4, - country_id: '15', - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { properties: {} }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - }; - const expectedOrder: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - /** - * Products list - */ - products: [ - { - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1', - configurable_options: [ - { - 'attribute_id': '93', - 'values': [ - { - 'value_index': 49 - }, - { - 'value_index': 52 - }, - { - 'value_index': 56 - } - ], - 'product_id': 19, - 'id': 3, - 'label': 'Color', - 'position': 0 - }, - { - 'attribute_id': '157', - 'values': [ - { - 'value_index': 167 - }, - { - 'value_index': 168 - }, - { - 'value_index': 169 - }, - { - 'value_index': 170 - }, - { - 'value_index': 171 - } - ], - 'product_id': 19, - 'id': 2, - 'label': 'Size', - 'position': 0 - } - ], - configurable_children: [ - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Black', - 'sku': 'MH01-XS-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Gray', - 'sku': 'MH01-XS-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Orange', - 'sku': 'MH01-XS-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Black', - 'sku': 'MH01-S-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Gray', - 'sku': 'MH01-S-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Orange', - 'sku': 'MH01-S-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Black', - 'sku': 'MH01-M-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Gray', - 'sku': 'MH01-M-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Orange', - 'sku': 'MH01-M-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Black', - 'sku': 'MH01-L-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Gray', - 'sku': 'MH01-L-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Orange', - 'sku': 'MH01-L-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Black', - 'sku': 'MH01-XL-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Gray', - 'sku': 'MH01-XL-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Orange', - 'sku': 'MH01-XL-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - } - ] - } - ], - addressInformation: { - shippingAddress: { - region: 'Region here', - region_id: 4, - country_id: '15', - /** - * Street name - */ - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - /** - * First name - */ - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { - properties: { - } - }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - }; - - expect(result).toEqual(expectedOrder); - }) -}); diff --git a/core/modules/order/test/unit/store/actions.spec.ts b/core/modules/order/test/unit/store/actions.spec.ts deleted file mode 100644 index 453672c7d2..0000000000 --- a/core/modules/order/test/unit/store/actions.spec.ts +++ /dev/null @@ -1,1234 +0,0 @@ -import * as types from '../../../store/mutation-types'; -import orderActions from '../../../store/actions'; -import { createContextMock } from '@vue-storefront/unit-tests/utils'; -import { notifications } from '../../../helpers'; -import { Order } from '../../../types/Order'; -import { OrderService } from '@vue-storefront/core/data-resolver' -import config from 'config'; - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '2', - localizedRoute: jest.fn() - })) -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - OrderService: { - placeOrder: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => { }), - debug: jest.fn(() => () => { }), - warn: jest.fn(() => () => { }), - error: jest.fn(() => () => { }), - info: jest.fn(() => () => { }) - } -})); - -let order: Order; -let task: any; -let currentOrderHash: string; - -describe('Order actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - /** - * Products list - */ - products: [ - { - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1', - configurable_options: [ - { - 'attribute_id': '93', - 'values': [ - { - 'value_index': 49 - }, - { - 'value_index': 52 - }, - { - 'value_index': 56 - } - ], - 'product_id': 19, - 'id': 3, - 'label': 'Color', - 'position': 0 - }, - { - 'attribute_id': '157', - 'values': [ - { - 'value_index': 167 - }, - { - 'value_index': 168 - }, - { - 'value_index': 169 - }, - { - 'value_index': 170 - }, - { - 'value_index': 171 - } - ], - 'product_id': 19, - 'id': 2, - 'label': 'Size', - 'position': 0 - } - ], - configurable_children: [ - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Black', - 'sku': 'MH01-XS-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Gray', - 'sku': 'MH01-XS-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Orange', - 'sku': 'MH01-XS-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Black', - 'sku': 'MH01-S-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Gray', - 'sku': 'MH01-S-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Orange', - 'sku': 'MH01-S-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Black', - 'sku': 'MH01-M-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Gray', - 'sku': 'MH01-M-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Orange', - 'sku': 'MH01-M-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Black', - 'sku': 'MH01-L-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Gray', - 'sku': 'MH01-L-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Orange', - 'sku': 'MH01-L-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Black', - 'sku': 'MH01-XL-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Gray', - 'sku': 'MH01-XL-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Orange', - 'sku': 'MH01-XL-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - } - ] - } - ], - addressInformation: { - shippingAddress: { - region: 'Region here', - region_id: 4, - country_id: '15', - /** - * Street name - */ - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - /** - * First name - */ - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { - properties: { - } - }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - task = { resultCode: 200, result: 'server-order-token' } - currentOrderHash = '4884598394f87665bceddb7585d5d7c5b08b6e0eb6a3ebaf6710fc48' - }); - - describe('placeOrder action', () => { - it('should not add session stamps if it is alrady processed', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { getSessionOrderHashes: 'current-order-hash' } - }; - - await (orderActions as any).placeOrder(contextMock, order); - - expect(contextMock.commit).not.toBeCalledWith(types.ORDER_ADD_SESSION_STAMPS); - }) - - it('should dispatch processOrder', async () => { - const contextMock = createContextMock({ - getters: { getSessionOrderHashes: 'current-order-hash' } - }); - const newOrder: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - products: - [{ - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1' - }], - addressInformation: - { - shippingAddress: - { - region: 'Region here', - region_id: 4, - country_id: '15', - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { properties: {} }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - - config.orders = { - directBackendSync: false - } - - await (orderActions as any).placeOrder(contextMock, order) - - expect(contextMock.dispatch).toBeCalledWith('processOrder', { newOrder: newOrder, currentOrderHash }) - }) - - it('should dispatch processOrder', async () => { - (OrderService.placeOrder as jest.Mock).mockImplementation(async () => - (task) - ); - const newOrder: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - products: - [{ - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1' - }], - addressInformation: - { - shippingAddress: - { - region: 'Region here', - region_id: 4, - country_id: '15', - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { properties: {} }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - const contextMock = createContextMock({ - getters: { getSessionOrderHashes: 'current-order-hash' } - }); - config.orders = { - directBackendSync: true - } - - await (orderActions as any).placeOrder(contextMock, order) - - expect(contextMock.commit).toBeCalledWith(types.ORDER_ADD_SESSION_STAMPS, order); - expect(contextMock.dispatch).toBeCalledWith('processOrder', { newOrder: newOrder, currentOrderHash }) - }) - }); - describe('processOrder action', () => { - it('should add last order with confirmation', async () => { - (OrderService.placeOrder as jest.Mock).mockImplementation(async () => - (task) - ); - const contextMock = createContextMock(); - const order = { 'transmited': true } - const order1 = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - products: - [{ - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1' - }], - addressInformation: - { - shippingAddress: - { - region: 'Region here', - region_id: 4, - country_id: '15', - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { properties: {} }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - const wrapper = (actions: any) => actions.processOrder(contextMock, { order1, currentOrderHash }) - const processOrderAction = await wrapper(orderActions); - - expect(contextMock.commit).toBeCalledWith(types.ORDER_LAST_ORDER_WITH_CONFIRMATION, { order, confirmation: task.result }) - expect(processOrderAction).toEqual(task) - }) - - it('should remove session order hash', async () => { - task = { resultCode: 400, result: 'server-order-token' }; - (OrderService.placeOrder as jest.Mock).mockImplementation(async () => - (task) - ); - const contextMock = createContextMock(); - const wrapper = (actions: any) => actions.processOrder(contextMock, { order, currentOrderHash }) - const processOrderAction = await wrapper(orderActions); - - expect(contextMock.commit).toBeCalledWith(types.ORDER_REMOVE_SESSION_ORDER_HASH, currentOrderHash); - expect(processOrderAction).toEqual(task) - }) - }); - - describe('handlePlacingOrderFailed action', () => { - it('should dispatch enqueue action', () => { - const contextMock = createContextMock(); - const newOrder: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - products: - [{ - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1' - }], - addressInformation: - { - shippingAddress: - { - region: 'Region here', - region_id: 4, - country_id: '15', - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { properties: {} }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - const expectedOrder = { newOrder, transmited: false } - - const wrapper = (orderActions: any) => orderActions.handlePlacingOrderFailed(contextMock, { newOrder, currentOrderHash }); - - wrapper(orderActions); - - expect(contextMock.dispatch).toBeCalledWith('enqueueOrder', { newOrder: expectedOrder }) - }) - }) -}); diff --git a/core/modules/order/test/unit/store/mutations.spec.ts b/core/modules/order/test/unit/store/mutations.spec.ts deleted file mode 100644 index c136841bb9..0000000000 --- a/core/modules/order/test/unit/store/mutations.spec.ts +++ /dev/null @@ -1,1925 +0,0 @@ -import { orderStore } from '../../../store'; -import * as types from '../../../store/mutation-types'; -import { Order } from '../../../types/Order' - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/app', () => jest.fn()) -jest.mock('@vue-storefront/core/lib/multistore', () => jest.fn()) -jest.mock('@vue-storefront/core/lib/storage-manager', () => jest.fn()) - -describe('Order mutations', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('ORDER_ADD_SESSION_ORDER_HASH', () => { - it('adds session order hash', () => { - const stateMock = { - session_order_hashes: [] - } - const session_order_hash = 'session-order-hash' - const expectedState = { - session_order_hashes: [ - 'session-order-hash' - ] - } - const wrapper = (mutations: any) => mutations[types.ORDER_ADD_SESSION_ORDER_HASH](stateMock, session_order_hash) - - wrapper(orderStore.mutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('ORDER_LAST_ORDER_WITH_CONFIRMATION', () => { - it('adds last order with confirmation', () => { - const stateMock = { - last_order_confirmation: 1, - session_order_hashes: 2 - } - const order: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - /** - * Products list - */ - products: [ - { - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1', - configurable_options: [ - { - 'attribute_id': '93', - 'values': [ - { - 'value_index': 49 - }, - { - 'value_index': 52 - }, - { - 'value_index': 56 - } - ], - 'product_id': 19, - 'id': 3, - 'label': 'Color', - 'position': 0 - }, - { - 'attribute_id': '157', - 'values': [ - { - 'value_index': 167 - }, - { - 'value_index': 168 - }, - { - 'value_index': 169 - }, - { - 'value_index': 170 - }, - { - 'value_index': 171 - } - ], - 'product_id': 19, - 'id': 2, - 'label': 'Size', - 'position': 0 - } - ], - configurable_children: [ - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Black', - 'sku': 'MH01-XS-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Gray', - 'sku': 'MH01-XS-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Orange', - 'sku': 'MH01-XS-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Black', - 'sku': 'MH01-S-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Gray', - 'sku': 'MH01-S-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Orange', - 'sku': 'MH01-S-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Black', - 'sku': 'MH01-M-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Gray', - 'sku': 'MH01-M-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Orange', - 'sku': 'MH01-M-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Black', - 'sku': 'MH01-L-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Gray', - 'sku': 'MH01-L-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Orange', - 'sku': 'MH01-L-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Black', - 'sku': 'MH01-XL-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Gray', - 'sku': 'MH01-XL-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Orange', - 'sku': 'MH01-XL-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - } - ] - } - ], - addressInformation: { - shippingAddress: { - region: 'Region here', - region_id: 4, - country_id: '15', - /** - * Street name - */ - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - /** - * First name - */ - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { - properties: { - } - }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - const expectedOrder: Order = { - order_id: 'orderId', - created_at: '10-29-2019', - updated_at: '11-29-2019', - transmited: true, - transmited_at: '10-29-2019', - status: 'pending', - state: 'pending', - user_id: '15', - cart_id: '20', - store_code: '2', - store_id: 2, - /** - * Products list - */ - products: [ - { - sku: 'sku1', - qty: 5, - name: 'Product 1', - price: 50, - product_type: 'Product type 1', - configurable_options: [ - { - 'attribute_id': '93', - 'values': [ - { - 'value_index': 49 - }, - { - 'value_index': 52 - }, - { - 'value_index': 56 - } - ], - 'product_id': 19, - 'id': 3, - 'label': 'Color', - 'position': 0 - }, - { - 'attribute_id': '157', - 'values': [ - { - 'value_index': 167 - }, - { - 'value_index': 168 - }, - { - 'value_index': 169 - }, - { - 'value_index': 170 - }, - { - 'value_index': 171 - } - ], - 'product_id': 19, - 'id': 2, - 'label': 'Size', - 'position': 0 - } - ], - configurable_children: [ - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Black', - 'sku': 'MH01-XS-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Gray', - 'sku': 'MH01-XS-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XS-Orange', - 'sku': 'MH01-XS-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '167', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xs-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Black', - 'sku': 'MH01-S-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Gray', - 'sku': 'MH01-S-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-S-Orange', - 'sku': 'MH01-S-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '168', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-s-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Black', - 'sku': 'MH01-M-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Gray', - 'sku': 'MH01-M-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-M-Orange', - 'sku': 'MH01-M-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '169', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-m-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Black', - 'sku': 'MH01-L-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Gray', - 'sku': 'MH01-L-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-L-Orange', - 'sku': 'MH01-L-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '170', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-l-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Black', - 'sku': 'MH01-XL-Black', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '49', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-black_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-black', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Gray', - 'sku': 'MH01-XL-Gray', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '52', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-gray_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-gray', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - }, - { - 'price': 52, - 'name': 'Chaz Kangeroo Hoodie-XL-Orange', - 'sku': 'MH01-XL-Orange', - 'custom_attributes': [ - { - 'value': '0', - 'attribute_code': 'required_options' - }, - { - 'value': '0', - 'attribute_code': 'has_options' - }, - { - 'value': '2', - 'attribute_code': 'tax_class_id' - }, - { - 'value': [ - '15', - '36', - '2' - ], - 'attribute_code': 'category_ids' - }, - { - 'value': '171', - 'attribute_code': 'size' - }, - { - 'value': '56', - 'attribute_code': 'color' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'small_image' - }, - { - 'value': '/m/h/mh01-orange_main.jpg', - 'attribute_code': 'thumbnail' - }, - { - 'value': 'chaz-kangeroo-hoodie-xl-orange', - 'attribute_code': 'url_key' - }, - { - 'value': '0', - 'attribute_code': 'msrp_display_actual_price_type' - } - ] - } - ] - } - ], - addressInformation: { - shippingAddress: { - region: 'Region here', - region_id: 4, - country_id: '15', - /** - * Street name - */ - street: [], - company: 'Company here', - telephone: 'telephone', - postcode: 'postcode', - city: 'City name', - /** - * First name - */ - firstname: 'first name', - lastname: 'last name', - email: 'example@example.com', - region_code: '20', - sameAsBilling: 1 - }, - billingAddress: { - properties: { - } - }, - shipping_method_code: 'one', - shipping_carrier_code: 'two', - payment_method_code: 'three', - payment_method_additional: 'four' - } - } - const wrapper = (mutations: any) => mutations[types.ORDER_LAST_ORDER_WITH_CONFIRMATION](stateMock, order) - - wrapper(orderStore.mutations) - - expect(order).toEqual(expectedOrder) - }) - }); - - describe('ORDER_REMOVE_SESSION_ORDER_HASH', () => { - it('removes session order hash', () => { - const stateMock = { - session_order_hashes: [ - 'session-order-hash-one', - 'session-order-hash-two', - 'session-order-hash-three' - ] - } - const session_order_hash_two = 'session-order-hash-two' - const expectedState = { - session_order_hashes: [ - 'session-order-hash-one', - 'session-order-hash-three' - ] - } - const wrapper = (mutations: any) => mutations[types.ORDER_REMOVE_SESSION_ORDER_HASH](stateMock, session_order_hash_two) - - wrapper(orderStore.mutations) - - expect(stateMock).toEqual(expectedState) - }) - }) -}); diff --git a/core/modules/order/types/Order.ts b/core/modules/order/types/Order.ts deleted file mode 100644 index 3fa0620734..0000000000 --- a/core/modules/order/types/Order.ts +++ /dev/null @@ -1,63 +0,0 @@ -export interface Order { - order_id?: string, - created_at?: string, - updated_at?: string, - transmited?: boolean, - transmited_at?: string, - status?: string, - state?: string, - user_id?: string, - cart_id?: string, - store_code?: string, - store_id?: number | string, - /** - * Products list - */ - products: [ - { - sku: string, - qty: number, - name?: string, - price: number, - product_type?: string, - [k: string]: any - } - ], - addressInformation: { - shippingAddress?: { - region?: string, - region_id?: number | string, - country_id?: string, - /** - * Street name - */ - street: { - [k: string]: any - }[], - company?: string, - telephone?: string, - postcode: string, - city: string, - /** - * First name - */ - firstname: string, - lastname: string, - email?: string, - region_code?: string, - sameAsBilling?: number, - [k: string]: any - }, - billingAddress?: { - properties?: { - [k: string]: any - }, - [k: string]: any - }, - shipping_method_code?: string, - shipping_carrier_code?: string, - payment_method_code?: string, - payment_method_additional?: any, - [k: string]: any - } -} diff --git a/core/modules/order/types/OrderState.ts b/core/modules/order/types/OrderState.ts deleted file mode 100644 index b0d8e9be5b..0000000000 --- a/core/modules/order/types/OrderState.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface OrderState { - last_order_confirmation: any, - session_order_hashes: string[] -} diff --git a/core/modules/recently-viewed/components/RecentlyViewed.js b/core/modules/recently-viewed/components/RecentlyViewed.js deleted file mode 100644 index c402f9a307..0000000000 --- a/core/modules/recently-viewed/components/RecentlyViewed.js +++ /dev/null @@ -1,10 +0,0 @@ -import { mapState } from 'vuex' - -export default { - name: 'RecentlyViewed', - computed: { - ...mapState('recently-viewed', [ - 'items' - ]) - } -} diff --git a/core/modules/recently-viewed/index.ts b/core/modules/recently-viewed/index.ts deleted file mode 100644 index 3de14433c5..0000000000 --- a/core/modules/recently-viewed/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { recentlyViewedStore } from './store' -import { plugin } from './store/plugin' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import { isServer } from '@vue-storefront/core/helpers' - -export const cacheStorage = StorageManager.init('recently-viewed') - -export const RecentlyViewedModule: StorefrontModule = function ({ store }) { - store.registerModule('recently-viewed', recentlyViewedStore) - store.subscribe(plugin) - - if (!isServer) store.dispatch('recently-viewed/load') -} diff --git a/core/modules/recently-viewed/store/actions.ts b/core/modules/recently-viewed/store/actions.ts deleted file mode 100644 index c077cf9364..0000000000 --- a/core/modules/recently-viewed/store/actions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState' -import RecentlyViewedState from '../types/RecentlyViewedState' -import { cacheStorage } from '../' - -const actions: ActionTree = { - load ({ commit }) { - cacheStorage.getItem('recently-viewed', (err, storedItems) => { - if (err) throw new Error(err) - commit(types.RECENTLY_VIEWED_LOAD, storedItems) - }) - }, - addItem ({ commit }, product) { - commit(types.RECENTLY_VIEWED_ADD_ITEM, { product }) - } -} - -export default actions diff --git a/core/modules/recently-viewed/store/index.ts b/core/modules/recently-viewed/store/index.ts deleted file mode 100644 index 69b73eaf50..0000000000 --- a/core/modules/recently-viewed/store/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import RecentlyViewedState from '../types/RecentlyViewedState' - -export const recentlyViewedStore: Module = { - namespaced: true, - state: { - items: [] - }, - actions, - mutations -} diff --git a/core/modules/recently-viewed/store/mutation-types.ts b/core/modules/recently-viewed/store/mutation-types.ts deleted file mode 100644 index df1361e485..0000000000 --- a/core/modules/recently-viewed/store/mutation-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SN_RECENTLY_VIEWED = 'recently-viewed' -export const RECENTLY_VIEWED_ADD_ITEM = SN_RECENTLY_VIEWED + '/ADD' -export const RECENTLY_VIEWED_LOAD = SN_RECENTLY_VIEWED + '/LOAD' diff --git a/core/modules/recently-viewed/store/mutations.ts b/core/modules/recently-viewed/store/mutations.ts deleted file mode 100644 index b81db83709..0000000000 --- a/core/modules/recently-viewed/store/mutations.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import RecentlyViewedState from '../types/RecentlyViewedState' - -const mutations: MutationTree = { - /** - * Add product to Recently Viewed Products - * @param {Object} product data format for products is described in /doc/ElasticSearch data formats.md - */ - [types.RECENTLY_VIEWED_ADD_ITEM] (state, { product }) { - const record = state.items.find(p => p.sku === product.sku) - if (!record) { - state.items.unshift(product) - } - }, - [types.RECENTLY_VIEWED_LOAD] (state, storedItems) { - state.items = storedItems || [] - } -} - -export default mutations diff --git a/core/modules/recently-viewed/store/plugin.ts b/core/modules/recently-viewed/store/plugin.ts deleted file mode 100644 index fc6a91df71..0000000000 --- a/core/modules/recently-viewed/store/plugin.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as types from './mutation-types' -import { cacheStorage } from '../' -import { Logger } from '@vue-storefront/core/lib/logger' - -export function plugin (mutation, state) { - const type = mutation.type - - if (type.startsWith(types.SN_RECENTLY_VIEWED)) { // check if this mutation is recently-viewed related - cacheStorage.setItem('recently-viewed', state['recently-viewed'].items).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) - } -} diff --git a/core/modules/recently-viewed/test/unit/actions.spec.ts b/core/modules/recently-viewed/test/unit/actions.spec.ts deleted file mode 100644 index 5e95bff9cb..0000000000 --- a/core/modules/recently-viewed/test/unit/actions.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as types from '../../store/mutation-types'; -import recentlyViewedActions from '../../store/actions'; -import { cacheStorage } from '@vue-storefront/core/modules/recently-viewed/index' - -jest.mock('@vue-storefront/core/modules/recently-viewed/index', () => ({ - cacheStorage: { - getItem: jest.fn() - } -})); - -let product - -describe('RecentlyViewed actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - product = { id: 'xyz' }; - }); - - describe('addItem', () => { - it('should add recently viewed item', () => { - const contextMock = { - commit: jest.fn() - }; - const wrapper = (actions: any) => actions.addItem(contextMock, product); - - wrapper(recentlyViewedActions); - - expect(contextMock.commit).toBeCalledWith(types.RECENTLY_VIEWED_ADD_ITEM, { product }); - }); - }); - - describe('load', () => { - it('should add storedItems from cache', () => { - const contextMock = { - commit: jest.fn() - }; - const wrapper = (actions: any) => actions.load(contextMock); - - cacheStorage.getItem.mockImplementationOnce( - jest.fn((cacheType, callback) => callback(null, [product])) - ) - - wrapper(recentlyViewedActions); - - expect(contextMock.commit).toBeCalledWith(types.RECENTLY_VIEWED_LOAD, [product]); - }); - - it('should throw error if there is a problem while loading storedItems', () => { - const contextMock = { - commit: jest.fn() - }; - const wrapper = (actions: any) => actions.load(contextMock); - - cacheStorage.getItem.mockImplementationOnce( - jest.fn((cacheType, callback) => callback(new Error('test'), [product])) - ); - - expect(wrapper.bind(null, recentlyViewedActions)).toThrowError('test'); - }); - }); -}) diff --git a/core/modules/recently-viewed/test/unit/mutations.spec.ts b/core/modules/recently-viewed/test/unit/mutations.spec.ts deleted file mode 100644 index fee3615b39..0000000000 --- a/core/modules/recently-viewed/test/unit/mutations.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import * as types from '../../store/mutation-types' -import recentlyViewedMutations from '../../store/mutations' - -describe('RecentlyViewed mutations', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - describe('RECENTLY_VIEWED_ADD_ITEM', () => { - it('adds a product to recently viewed if none of its sku is there yet', () => { - const stateMock = { - items: [] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [ - { - qty: 123, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.RECENTLY_VIEWED_ADD_ITEM](stateMock, { product }) - - wrapper(recentlyViewedMutations) - - expect(stateMock).toEqual(expectedState) - }) - - it('don\'t adds a product to recently viewed if there is one with the same sku', () => { - const stateMock = { - items: [ - { - qty: 123, - sku: 'foo' - } - ] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [ - { - qty: 123, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.RECENTLY_VIEWED_ADD_ITEM](stateMock, { product }) - - wrapper(recentlyViewedMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('RECENTLY_VIEWED_LOAD', () => { - it('loads recently viewed items with given products', () => { - const stateMock = { - items: [] - } - const product = { - qty: 123, - sku: 'foo' - } - const expectedState = { - items: [ - { - qty: 123, - sku: 'foo' - } - ] - } - const wrapper = (mutations: any) => mutations[types.RECENTLY_VIEWED_LOAD](stateMock, [ product ]) - - wrapper(recentlyViewedMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) -}) diff --git a/core/modules/recently-viewed/types/RecentlyViewedState.ts b/core/modules/recently-viewed/types/RecentlyViewedState.ts deleted file mode 100644 index b857e32f74..0000000000 --- a/core/modules/recently-viewed/types/RecentlyViewedState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface RecentlyViewedState { - items: any[] -} diff --git a/core/modules/review/components/AddReview.ts b/core/modules/review/components/AddReview.ts deleted file mode 100644 index 7fe9da2776..0000000000 --- a/core/modules/review/components/AddReview.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Review from '@vue-storefront/core/modules/review/types/Review' - -export const AddReview = { - name: 'AddReview', - methods: { - addReview (review: Review) { - this.$store.dispatch('review/add', review) - } - } -} diff --git a/core/modules/review/components/Reviews.ts b/core/modules/review/components/Reviews.ts deleted file mode 100644 index ebb19e1865..0000000000 --- a/core/modules/review/components/Reviews.ts +++ /dev/null @@ -1,10 +0,0 @@ -import Review from '../types/Review' - -export const Reviews = { - name: 'Reviews', - computed: { - reviews (): Review[] { - return this.$store.state.review.items.items - } - } -} diff --git a/core/modules/review/helpers/createLoadReviewsQuery.ts b/core/modules/review/helpers/createLoadReviewsQuery.ts deleted file mode 100644 index 8a1de303d1..0000000000 --- a/core/modules/review/helpers/createLoadReviewsQuery.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { SearchQuery } from 'storefront-query-builder' - -const createLoadReviewsQuery = ({ productId, approved }) => { - let query = new SearchQuery() - - if (productId) { - query = query.applyFilter({ key: 'product_id', value: { 'eq': productId } }) - } - - if (approved) { - query = query.applyFilter({ key: 'review_status', value: { 'eq': 1 } }) - } - - return query -} -export default createLoadReviewsQuery diff --git a/core/modules/review/helpers/index.ts b/core/modules/review/helpers/index.ts deleted file mode 100644 index 13e1473744..0000000000 --- a/core/modules/review/helpers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import createLoadReviewsQuery from './createLoadReviewsQuery' - -export { createLoadReviewsQuery } diff --git a/core/modules/review/index.ts b/core/modules/review/index.ts deleted file mode 100644 index ae53aa64d7..0000000000 --- a/core/modules/review/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { reviewStore } from './store' - -export const ReviewModule: StorefrontModule = function ({ store }) { - store.registerModule('review', reviewStore) -} diff --git a/core/modules/review/store/actions.ts b/core/modules/review/store/actions.ts deleted file mode 100644 index 55503d3fac..0000000000 --- a/core/modules/review/store/actions.ts +++ /dev/null @@ -1,33 +0,0 @@ -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { ActionTree } from 'vuex' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import RootState from '@vue-storefront/core/types/RootState' -import ReviewState from '../types/ReviewState' -import * as types from './mutation-types' -import i18n from '@vue-storefront/i18n' -import Review from '@vue-storefront/core/modules/review/types/Review' -import { createLoadReviewsQuery } from '@vue-storefront/core/modules/review/helpers' -import { ReviewsService } from '@vue-storefront/core/data-resolver' - -const actions: ActionTree = { - async list (context, { productId, approved = true, start = 0, size = 50, entityType = 'review', sort = '', excludeFields = null, includeFields = null }) { - const query = createLoadReviewsQuery({ productId, approved }) - - const reviewResponse = await quickSearchByQuery({ query, start, size, entityType, sort, excludeFields, includeFields }) - context.commit(types.REVIEW_UPD_REVIEWS, reviewResponse) - }, - async add (context, review: Review) { - EventBus.$emit('notification-progress-start', i18n.t('Adding a review ...')) - - const isReviewCreated = await ReviewsService.createReview(review) - EventBus.$emit('notification-progress-stop') - - if (isReviewCreated) { - EventBus.$emit('clear-add-review-form') - } - - return isReviewCreated - } -} - -export default actions diff --git a/core/modules/review/store/index.ts b/core/modules/review/store/index.ts deleted file mode 100644 index 70778f8ec6..0000000000 --- a/core/modules/review/store/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import mutations from './mutations'; -import RootState from '@vue-storefront/core/types/RootState'; -import ReviewState from '../types/ReviewState'; - -export const reviewStore: Module = { - namespaced: true, - state: { - items: [] - }, - actions, - mutations -} diff --git a/core/modules/review/store/mutation-types.ts b/core/modules/review/store/mutation-types.ts deleted file mode 100644 index 33b5fccc72..0000000000 --- a/core/modules/review/store/mutation-types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const SN_REVIEW = 'review' -export const REVIEW_UPD_REVIEWS = SN_REVIEW + '/UPD_REVIEWS' diff --git a/core/modules/review/store/mutations.ts b/core/modules/review/store/mutations.ts deleted file mode 100644 index c6b9c43a40..0000000000 --- a/core/modules/review/store/mutations.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import ReviewState from '../types/ReviewState' - -const mutations: MutationTree = { - [types.REVIEW_UPD_REVIEWS] (state, items) { - state.items = items - } -} - -export default mutations diff --git a/core/modules/review/test/unit/helpers/createLoadReviewsQuery.spec.ts b/core/modules/review/test/unit/helpers/createLoadReviewsQuery.spec.ts deleted file mode 100644 index cce7301944..0000000000 --- a/core/modules/review/test/unit/helpers/createLoadReviewsQuery.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import createLoadReviewsQuery from '../../../helpers/createLoadReviewsQuery' - -const SearchQuery = { - applyFilter: jest.fn(() => SearchQuery) -} - -jest.mock('storefront-query-builder', () => ({ - SearchQuery: function () { - return SearchQuery - } -})); - -describe('createLoadReviewsQuery', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('add filter only for productId argument', () => { - createLoadReviewsQuery({ productId: 123, approved: false }) - - expect(SearchQuery.applyFilter).toBeCalledTimes(1) - expect(SearchQuery.applyFilter).toBeCalledWith({ key: 'product_id', value: { 'eq': 123 } }); - }); - - it('add filter for productId and approved arguments', () => { - createLoadReviewsQuery({ productId: 123, approved: true }) - - expect(SearchQuery.applyFilter).toBeCalledTimes(2) - expect(SearchQuery.applyFilter).toBeCalledWith({ key: 'product_id', value: { 'eq': 123 } }); - expect(SearchQuery.applyFilter).toBeCalledWith({ key: 'review_status', value: { 'eq': 1 } }); - }); -}) diff --git a/core/modules/review/test/unit/store/actions.spec.ts b/core/modules/review/test/unit/store/actions.spec.ts deleted file mode 100644 index af5c438da4..0000000000 --- a/core/modules/review/test/unit/store/actions.spec.ts +++ /dev/null @@ -1,149 +0,0 @@ -import * as types from '../../../store/mutation-types'; -import reviewActions from '../../../store/actions'; -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { createLoadReviewsQuery } from '@vue-storefront/core/modules/review/helpers' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import { SearchQuery } from 'storefront-query-builder' -import { ReviewsService } from '@vue-storefront/core/data-resolver' - -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn(), - processLocalizedURLAddress: jest.fn() -})) -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/store', () => ({ - state: { - items: [] - } -})) -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: 'de' - })) -})); -jest.mock('@vue-storefront/core/lib/search', () => ({ - quickSearchByQuery: jest.fn() -})); -jest.mock('@vue-storefront/core/modules/review/helpers', () => ({ - createLoadReviewsQuery: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/sync', () => ({ - TaskQueue: { - execute: jest.fn(() => Promise.resolve({ code: 200 })) - } -})) -jest.mock('@vue-storefront/core/data-resolver', () => ({ - ReviewsService: { - createReview: jest.fn(() => true) - } -})) - -EventBus.$emit = jest.fn() - -describe('Review actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('list', () => { - it('create load reviews query', async () => { - const contextMock = { - commit: jest.fn() - }; - const payload = { productId: 1 } - const wrapper = (actions: any) => actions.list(contextMock, payload); - - await wrapper(reviewActions); - - expect(createLoadReviewsQuery).toBeCalledWith({ ...payload, approved: true }); - }); - - it('make quick search by query with default values', async () => { - const contextMock = { - commit: jest.fn() - }; - const payload = { productId: 1 } - const wrapper = (actions: any) => actions.list(contextMock, payload); - - (createLoadReviewsQuery as jest.Mock).mockImplementationOnce(() => SearchQuery); - - await wrapper(reviewActions); - - expect(quickSearchByQuery).toBeCalledWith({ - query: SearchQuery, - start: 0, - size: 50, - entityType: 'review', - sort: '', - excludeFields: null, - includeFields: null - }); - }); - - it('call review update commit', async () => { - const contextMock = { - commit: jest.fn() - }; - const wrapper = (actions: any) => actions.list(contextMock, { productId: 1 }); - - (quickSearchByQuery as jest.Mock).mockImplementationOnce(() => Promise.resolve(expect.anything())); - - await wrapper(reviewActions); - - expect(contextMock.commit).toBeCalledWith(types.REVIEW_UPD_REVIEWS, expect.anything()); - }); - }); - - describe('add', () => { - it('notify about starting process of adding a review', () => { - const contextMock = { - commit: jest.fn() - }; - const payload = expect.anything() - const wrapper = (actions: any) => actions.add(contextMock, payload); - - wrapper(reviewActions); - - expect(EventBus.$emit).toBeCalledWith('notification-progress-start', expect.anything()) - }); - - it('notify about finished process of adding a review', async () => { - const contextMock = { - commit: jest.fn() - }; - const payload = expect.anything() - const wrapper = (actions: any) => actions.add(contextMock, payload); - - await wrapper(reviewActions); - - expect(EventBus.$emit).toBeCalledWith('notification-progress-stop') - }); - - it('send event to clear review form after success', async () => { - const contextMock = { - commit: jest.fn() - }; - const payload = expect.anything() - const wrapper = (actions: any) => actions.add(contextMock, payload); - - await wrapper(reviewActions); - - expect(EventBus.$emit).toBeCalledTimes(3) - expect(EventBus.$emit).toBeCalledWith('clear-add-review-form') - }); - - it('don\'t send event to clear review form after fail', async () => { - const contextMock = { - commit: jest.fn() - }; - const payload = expect.anything() - const wrapper = (actions: any) => actions.add(contextMock, payload); - - (ReviewsService.createReview as jest.Mock).mockImplementationOnce(jest.fn(() => false)) - - await wrapper(reviewActions); - - expect(EventBus.$emit).toBeCalledTimes(2) - }); - }) -}) diff --git a/core/modules/review/test/unit/store/mutations.spec.ts b/core/modules/review/test/unit/store/mutations.spec.ts deleted file mode 100644 index 4e7a70c034..0000000000 --- a/core/modules/review/test/unit/store/mutations.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as types from '../../../store/mutation-types' -import reviewedMutations from '../../../store/mutations' - -describe('Review mutations', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - describe('REVIEW_UPD_REVIEWS', () => { - it('update review items', () => { - const stateMock = { - items: [] - } - const reviewItem = { foo: '123' } - const expectedState = { - items: [ - reviewItem - ] - } - const wrapper = (mutations: any) => mutations[types.REVIEW_UPD_REVIEWS](stateMock, [ reviewItem ]) - - wrapper(reviewedMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) -}) diff --git a/core/modules/review/types/Review.ts b/core/modules/review/types/Review.ts deleted file mode 100644 index 8786c9eb4f..0000000000 --- a/core/modules/review/types/Review.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default interface Review { - product_id: number | string, - title: string, - detail: string, - nickname: string, - review_entity: string, - review_status: number, - customer_id?: number | string | null, - [k: string]: any -} diff --git a/core/modules/review/types/ReviewRequest.ts b/core/modules/review/types/ReviewRequest.ts deleted file mode 100644 index d8e9bdf999..0000000000 --- a/core/modules/review/types/ReviewRequest.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Review from './Review'; - -export interface ReviewRequest { - review: Review, - [k: string]: any -} diff --git a/core/modules/review/types/ReviewState.ts b/core/modules/review/types/ReviewState.ts deleted file mode 100644 index b0fc96348f..0000000000 --- a/core/modules/review/types/ReviewState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface ReviewState { - items: any[] -} diff --git a/core/modules/url/helpers/index.ts b/core/modules/url/helpers/index.ts deleted file mode 100644 index 9453182e92..0000000000 --- a/core/modules/url/helpers/index.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { router } from '@vue-storefront/core/app' -import config from 'config' -import { LocalizedRoute } from '@vue-storefront/core/lib/types' -import { localizedDispatcherRoute, localizedRoute, currentStoreView } from '@vue-storefront/core/lib/multistore' -import { RouteConfig } from 'vue-router/types/router'; -import { RouterManager } from '@vue-storefront/core/lib/router-manager' -import { Category } from 'core/modules/catalog-next/types/Category' -import { Logger } from '@vue-storefront/core/lib/logger' - -export function parametrizeRouteData (routeData: LocalizedRoute, query: { [id: string]: any } | string, storeCodeInPath: string): LocalizedRoute { - const parametrizedRoute = Object.assign({}, routeData) - parametrizedRoute.params = Object.assign({}, parametrizedRoute.params || {}, query) - if (storeCodeInPath && !parametrizedRoute.name.startsWith(storeCodeInPath + '-')) { - parametrizedRoute.name = storeCodeInPath + '-' + parametrizedRoute.name - } - return parametrizedRoute -} - -function prepareDynamicRoute (routeData: LocalizedRoute, path: string): RouteConfig { - const userRoute = RouterManager.findByName(routeData.name) - if (userRoute) { - const normalizedPath = `${path.startsWith('/') ? '' : '/'}${path}` - - return { - ...userRoute, - ...routeData, - path: normalizedPath, - name: `urldispatcher-${normalizedPath}`, - pathToRegexpOptions: { strict: true } - } - } else { - Logger.error('Route not found ' + routeData['name'], 'dispatcher')() - return null - } -} - -export function processDynamicRoute (routeData: LocalizedRoute, path: string, addToRoutes: boolean = true): LocalizedRoute { - const preparedRoute = prepareDynamicRoute(routeData, path) - if (addToRoutes && preparedRoute) { - router.addRoutes([preparedRoute], true) - } - return preparedRoute -} - -export function preProcessDynamicRoutes (dispatcherMap: {}, addToRoutes: boolean = true): LocalizedRoute[] { - const preparedRoutes = [] - for (const [url, routeData] of Object.entries(dispatcherMap)) { - const preparedRoute = prepareDynamicRoute(routeData, url) - if (preparedRoute) { - preparedRoutes.push(preparedRoute) - } - } - if (addToRoutes) { - router.addRoutes(preparedRoutes, true) - } - return preparedRoutes -} - -export function findRouteByPath (path: string): RouteConfig { - return RouterManager.findByPath(path) -} - -export function normalizeUrlPath (url: string, clearTrailingSlash: boolean = true): string { - if (url && url.length > 0) { - if (url.length > 0 && !url.startsWith('/')) url = `/${url}` - if (url.endsWith('/') && clearTrailingSlash) url = url.slice(0, -1) - const queryPos = url.indexOf('?') - if (queryPos > 0) url = url.slice(0, queryPos) - } - return url -} - -export function formatCategoryLink (category: Category, storeCode: string = currentStoreView().storeCode): string { - storeCode ? storeCode += '/' : storeCode = ''; - - if (currentStoreView().appendStoreCode === false) { - storeCode = '' - } - - if (category) { - return config.seo.useUrlDispatcher ? ('/' + storeCode + category.url_path) : ('/' + storeCode + 'c/' + category.slug) - } - return '/' + storeCode; -} - -export function formatProductLink ( - product: { - parentSku?: string, - sku: string, - url_path?: string, - type_id: string, - slug: string, - options?: [], - configurable_children?: [] - }, - storeCode -): string | LocalizedRoute { - if (config.seo.useUrlDispatcher && product.url_path) { - let routeData: LocalizedRoute; - if ((product.options && product.options.length > 0) || (product.configurable_children && product.configurable_children.length > 0)) { - routeData = { - path: product.url_path, - params: { childSku: product.sku } - } - } else { - routeData = { path: product.url_path } - } - return localizedDispatcherRoute(routeData, storeCode) - } else { - const routeData: LocalizedRoute = { - name: product.type_id + '-product', // we should use here localizedDispatcherRouteName? - params: { - parentSku: product.parentSku ? product.parentSku : product.sku, - slug: product.slug, - childSku: product.sku - } - } - return localizedRoute(routeData, storeCode) - } -} - -export const getFallbackRouteData = ({ mappedFallback, url }) => { - if (Array.isArray(mappedFallback)) { - return mappedFallback - .reverse() - .filter(f => f.params && f.params.slug) - .find(f => url.includes(f.params.slug)) - } - - return mappedFallback -} diff --git a/core/modules/url/helpers/transformUrl.ts b/core/modules/url/helpers/transformUrl.ts deleted file mode 100644 index 8f30c7990e..0000000000 --- a/core/modules/url/helpers/transformUrl.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { localizedDispatcherRouteName, currentStoreView } from '@vue-storefront/core/lib/multistore'; - -export const transformProductUrl = (product, urlParams = {}) => { - const { storeCode, appendStoreCode } = currentStoreView() - return { - name: localizedDispatcherRouteName(product.type_id + '-product', storeCode, appendStoreCode), - params: { - slug: product.slug, - parentSku: product.parentSku || product.sku, - childSku: urlParams['childSku'] ? urlParams['childSku'] : product.sku - } - } -} - -export const transformCategoryUrl = (category) => { - const { storeCode, appendStoreCode } = currentStoreView() - return { - name: localizedDispatcherRouteName('category', storeCode, appendStoreCode), - params: { - slug: category.slug - } - } -} - -export const transformCmsPageUrl = (cmsPage) => { - return { - name: 'cms-page', - params: { - slug: cmsPage.identifier - } - } -} diff --git a/core/modules/url/index.ts b/core/modules/url/index.ts deleted file mode 100644 index 7bf79ef533..0000000000 --- a/core/modules/url/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { urlStore } from './store' -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { beforeEachGuard } from './router/beforeEach' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const cacheStorage = StorageManager.init('url') - -export const UrlModule: StorefrontModule = function ({ store, router }) { - store.registerModule('url', urlStore) - router.beforeEach(beforeEachGuard) -} diff --git a/core/modules/url/router/beforeEach.ts b/core/modules/url/router/beforeEach.ts deleted file mode 100644 index 23c464e85c..0000000000 --- a/core/modules/url/router/beforeEach.ts +++ /dev/null @@ -1,59 +0,0 @@ -// This function will be executed before entering each route. -// It's important to have 'next()'. It enables navigation to new route. -// See https://router.vuejs.org/guide/advanced/navigation-guards.html#global-guards -import { Route } from 'vue-router' -import store from '@vue-storefront/core/store' -import { Logger } from '@vue-storefront/core/lib/logger' -import { processDynamicRoute, normalizeUrlPath } from '../helpers' -import { LocalizedRoute } from '@vue-storefront/core/lib/types' -import { RouterManager } from '@vue-storefront/core/lib/router-manager' -import { routerHelper } from '@vue-storefront/core/helpers' - -export const UrlDispatchMapper = async (to) => { - const routeData = await store.dispatch('url/mapUrl', { url: to.path, query: to.query }) - return Object.assign({}, to, routeData) -} - -export async function beforeEachGuard (to: Route, from: Route, next) { - if (RouterManager.isRouteProcessing()) { - await RouterManager.getRouteLockPromise() - next() - return - } - RouterManager.lockRoute() - - const path = normalizeUrlPath(to.path, false) - const hasRouteParams = to.hasOwnProperty('params') && Object.values(to.params).length > 0 - const isPreviouslyDispatchedDynamicRoute = to.matched.length > 0 && to.name && to.name.startsWith('urldispatcher') - if (!to.matched.length || to.matched[0].name.endsWith('page-not-found') || (isPreviouslyDispatchedDynamicRoute && !hasRouteParams)) { - try { - const routeData = await UrlDispatchMapper(to) - if (routeData && !routeData.name.endsWith('page-not-found')) { - let dynamicRoute: LocalizedRoute = processDynamicRoute(routeData, path, !isPreviouslyDispatchedDynamicRoute) - if (dynamicRoute) { - next({ - ...dynamicRoute, - replace: routerHelper.popStateDetected || dynamicRoute.fullPath === from.fullPath - }) - } else { - Logger.error('Route not found ' + routeData['name'], 'dispatcher')() - next() - } - } else { - Logger.error('No mapping found for ' + path, 'dispatcher')() - next() - } - } catch (e) { - Logger.error(e, 'dispatcher')() - next() - } finally { - RouterManager.unlockRoute() - } - } else { - next() - RouterManager.unlockRoute() - routerHelper.popStateDetected = false - } - - routerHelper.popStateDetected = false -} diff --git a/core/modules/url/store/actions.ts b/core/modules/url/store/actions.ts deleted file mode 100644 index efbd456a12..0000000000 --- a/core/modules/url/store/actions.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { transformProductUrl, transformCategoryUrl, transformCmsPageUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl'; -import { isServer } from '@vue-storefront/core/helpers'; -import { UrlState } from '../types/UrlState' -import { ActionTree } from 'vuex'; -// you can use this storage if you want to enable offline capabilities -import { cacheStorage } from '../' -import queryString from 'query-string' -import config from 'config' -import { SearchQuery } from 'storefront-query-builder' -import { preProcessDynamicRoutes, normalizeUrlPath, parametrizeRouteData, getFallbackRouteData } from '../helpers' -import { removeStoreCodeFromRoute, currentStoreView, localizedDispatcherRouteName, adjustMultistoreApiUrl } from '@vue-storefront/core/lib/multistore' -import storeCodeFromRoute from '@vue-storefront/core/lib/storeCodeFromRoute' -import fetch from 'isomorphic-fetch' -import { Logger } from '@vue-storefront/core/lib/logger' -import { processURLAddress } from '@vue-storefront/core/helpers'; -import * as categoryMutationTypes from '@vue-storefront/core/modules/catalog-next/store/category/mutation-types' -import * as cmsPageMutationTypes from '@vue-storefront/core/modules/cms/store/page/mutation-types' -import isEqual from 'lodash-es/isEqual' -import * as types from './mutation-types' -import omit from 'lodash-es/omit' -import { storeProductToCache } from '@vue-storefront/core/modules/catalog/helpers/search'; -import { prepareProducts } from '@vue-storefront/core/modules/catalog/helpers/prepare'; - -// it's a good practice for all actions to return Promises with effect of their execution -export const actions: ActionTree = { - // if you want to use cache in your module you can load cached data like this - async registerMapping ({ state }, { url, routeData }: { url: string, routeData: any}) { - if (!state.dispatcherMap[url]) { - state.dispatcherMap[url] = routeData - } - try { - await cacheStorage.setItem(normalizeUrlPath(url), routeData, null, config.seo.disableUrlRoutesPersistentCache) - } catch (err) { - if ( - err.name === 'QuotaExceededError' || - err.name === 'NS_ERROR_DOM_QUOTA_REACHED' - ) { // quota exceeded error - cacheStorage.clear() // clear the url cache if quota has been exceeded - } - } - return routeData - }, - /** - * Register dynamic vue-router routes - */ - async registerDynamicRoutes ({ state, dispatch }) { - if (!state.dispatcherMap) return - - preProcessDynamicRoutes(state.dispatcherMap) - const registrationRoutePromises = Object.keys(state.dispatcherMap).map(url => { - const routeData = state.dispatcherMap[url] - return dispatch('registerMapping', { url, routeData }) - }) - await Promise.all(registrationRoutePromises) - }, - mapUrl ({ state, dispatch }, { url, query }: { url: string, query: string}) { - const parsedQuery = typeof query === 'string' ? queryString.parse(query) : query - const storeCodeInPath = storeCodeFromRoute(url) - url = normalizeUrlPath(url) - return new Promise((resolve, reject) => { - if (state.dispatcherMap[url]) { - return resolve(parametrizeRouteData(state.dispatcherMap[url], query, storeCodeInPath)) - } - cacheStorage.getItem(url).then(routeData => { - if (routeData !== null) { - return resolve(parametrizeRouteData(routeData, query, storeCodeInPath)) - } else { - const mappingActionName = config.urlModule.enableMapFallbackUrl ? 'mapFallbackUrl' : 'mappingFallback' - dispatch(mappingActionName, { url, params: parsedQuery }).then(mappedFallback => { - const routeData = getFallbackRouteData({ mappedFallback, url }) - dispatch('registerMapping', { url, routeData }) // register mapping for further usage - resolve(parametrizeRouteData(routeData, query, storeCodeInPath)) - }).catch(reject) - } - }).catch(reject) - }) - }, - /** - * @deprecated from 1.12 - * Router mapping fallback - get the proper URL from API - * This method could be overriden in custom module to provide custom URL mapping logic - */ - async mappingFallback ({ dispatch }, { url, params }: { url: string, params: any}) { - Logger.warn(` - Deprecated action mappingFallback - use mapFallbackUrl instead. - You can enable mapFallbackUrl by changing 'config.urlModule.enableMapFallbackUrl' to true - `)() - const productQuery = new SearchQuery() - url = (removeStoreCodeFromRoute(url.startsWith('/') ? url.slice(1) : url) as string) - productQuery.applyFilter({ key: 'url_path', value: { 'eq': url } }) // Tees category - const products = await dispatch('product/list', { query: productQuery }, { root: true }) - if (products && products.items && products.items.length) { - const product = products.items[0] - return transformProductUrl(product, params) - } else { - const category = await dispatch('category/single', { key: 'url_path', value: url }, { root: true }) - if (category !== null) { - return transformCategoryUrl(category) - } - } - }, - /** - * Router mapping fallback - get the proper URL from API - * This method could be overriden in custom module to provide custom URL mapping logic - */ - async mapFallbackUrl ({ dispatch }, { url, params }: { url: string, params: any}) { - url = (removeStoreCodeFromRoute(url.startsWith('/') ? url.slice(1) : url) as string) - - // search for record in ES based on `url` - const fallbackData = await dispatch('getFallbackByUrl', { url, params }) - - // if there is record in ES then map data - if (fallbackData) { - const [result] = await Promise.all([ - dispatch('transformFallback', { ...fallbackData, params }), - dispatch('saveFallbackData', fallbackData) - ]) - return result - } - - return { - name: 'page-not-found', - params: { - slug: 'page-not-found' - } - } - }, - /** - * Search for record in ES which contains url value (check which fields it searches in vsf-api config.urlModule.map.searchedFields) - */ - async getFallbackByUrl (context, { url, params }) { - const groupId = (config.usePriceTiers && context.rootState.user.groupId) || null - const groupToken = context.rootState.user.groupToken || null - try { - const requestUrl = `${adjustMultistoreApiUrl(processURLAddress(config.urlModule.map_endpoint))}` - let response: any = await fetch( - requestUrl, - { - method: 'POST', - mode: 'cors', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - url, - includeFields: null, // send `includeFields: null || undefined` to fetch all fields - excludeFields: [], - options: { - prefetchGroupProducts: true, - assignProductConfiguration: true, - populateRequestCacheTags: false, - setProductErrors: false, - fallbackToDefaultWhenNoAvailable: true, - separateSelectedVariant: false, - setConfigurableProductOptions: config.cart.setConfigurableProductOptions, - filterUnavailableVariants: config.products.filterUnavailableVariants - }, - filters: { sku: params.childSku }, - groupId, - groupToken - }) - } - ) - if (!response.ok) { - return null - } - response = await response.json() - return response - } catch (err) { - Logger.error('FetchError in request to ES: ', 'search', err)() - return null - } - }, - /** - * Transforms data to vue-router route format - */ - async transformFallback (context, { _type, _source, params }) { - switch (_type) { - case 'product': { - return transformProductUrl(_source, params) - } - case 'category': { - return transformCategoryUrl(_source) - } - case 'cms_page': { - return transformCmsPageUrl(_source) - } - default: { - return { - name: 'page-not-found', - params: { - slug: 'page-not-found' - } - } - } - } - }, - /** - * Here we can save data based on _type, so there will be no need to create another request for it. - */ - async saveFallbackData ({ commit }, { _type, _source }) { - switch (_type) { - case 'product': { - const [product] = prepareProducts([_source]) - storeProductToCache(product, 'sku') - break - } - case 'category': { - commit('category-next/' + categoryMutationTypes.CATEGORY_ADD_CATEGORY, _source, { root: true }) - break - } - case 'cms_page': { - commit('cmsPage/' + cmsPageMutationTypes.CMS_PAGE_ADD_CMS_PAGE, _source, { root: true }) - commit('cmsPage/' + cmsPageMutationTypes.CMS_PAGE_SET_CURRENT, _source, { root: true }) - break - } - default: { - break - } - } - }, - setCurrentRoute ({ commit, state, rootGetters }, { to, from } = {}) { - commit(types.SET_CURRENT_ROUTE, { - ...to, - scrollPosition: { ...state.prevRoute.scrollPosition }, - categoryPageSize: state.prevRoute.categoryPageSize - }) - - const sameAsPrevRoute = isEqual( - omit(state.prevRoute, ['scrollPosition', 'categoryPageSize']), - omit(state.currentRoute, ['scrollPosition', 'categoryPageSize']) - ) - const hasDifferentPath = (state.currentRoute && state.currentRoute.path) !== (from && from.path) - commit(types.IS_BACK_ROUTE, sameAsPrevRoute && hasDifferentPath) - - const scrollPosition = { - x: !isServer ? window.pageXOffset : 0, - y: !isServer ? window.pageYOffset : 0 - } - commit(types.SET_PREV_ROUTE, { - ...from, - scrollPosition, - categoryPageSize: rootGetters['category-next/getCategoryProducts'].length - }) - } -} diff --git a/core/modules/url/store/getters.ts b/core/modules/url/store/getters.ts deleted file mode 100644 index 9de326e23c..0000000000 --- a/core/modules/url/store/getters.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const getters = { - getCurrentRoute: (state) => state.currentRoute, - isBackRoute: (state) => state.isBackRoute, - getPrevRoute: (state) => state.prevRoute -} diff --git a/core/modules/url/store/index.ts b/core/modules/url/store/index.ts deleted file mode 100644 index 12101dfff1..0000000000 --- a/core/modules/url/store/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from 'vuex' -import { UrlState } from '../types/UrlState' -import { actions } from './actions' -import { state } from './state' -import { getters } from './getters' -import { mutations } from './mutations' - -export const urlStore: Module = { - namespaced: true, - actions, - state, - getters, - mutations -} diff --git a/core/modules/url/store/mutation-types.ts b/core/modules/url/store/mutation-types.ts deleted file mode 100644 index 556addc9bf..0000000000 --- a/core/modules/url/store/mutation-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SET_CURRENT_ROUTE = 'URL/SET_CURRENT_ROUTE' -export const SET_PREV_ROUTE = 'URL/SET_PREV_ROUTE' -export const IS_BACK_ROUTE = 'URL/IS_BACK_ROUTE' diff --git a/core/modules/url/store/mutations.ts b/core/modules/url/store/mutations.ts deleted file mode 100644 index dc0bbeab4d..0000000000 --- a/core/modules/url/store/mutations.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import omit from 'lodash-es/omit' - -export const mutations: MutationTree = { - [types.SET_CURRENT_ROUTE] (state, payload = {}) { - state.currentRoute = omit({ ...payload }, ['matched']) - }, - [types.SET_PREV_ROUTE] (state, payload = {}) { - state.prevRoute = omit({ ...payload }, ['matched']) - }, - [types.IS_BACK_ROUTE] (state, payload) { - state.isBackRoute = payload - } -} diff --git a/core/modules/url/store/state.ts b/core/modules/url/store/state.ts deleted file mode 100644 index bc87f84916..0000000000 --- a/core/modules/url/store/state.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { UrlState } from '../types/UrlState' - -export const state: UrlState = { - dispatcherMap: {}, - currentRoute: {}, - prevRoute: {}, - isBackRoute: false -} diff --git a/core/modules/url/test/unit/helpers/data.ts b/core/modules/url/test/unit/helpers/data.ts deleted file mode 100644 index 410b0645a0..0000000000 --- a/core/modules/url/test/unit/helpers/data.ts +++ /dev/null @@ -1,11 +0,0 @@ -let product = { - sku: 'MSH09', - slug: 'troy-yoga-short-994', - url_path: 'men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html', - type_id: 'configurable', - parentSku: 'MSH09' -} - -export { - product -} diff --git a/core/modules/url/test/unit/helpers/formatCategoryLink.spec.ts b/core/modules/url/test/unit/helpers/formatCategoryLink.spec.ts deleted file mode 100644 index d18bd96794..0000000000 --- a/core/modules/url/test/unit/helpers/formatCategoryLink.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { formatCategoryLink } from '@vue-storefront/core/modules/url/helpers'; -import { Category } from '@vue-storefront/core/modules/catalog-next/types/Category'; -import { currentStoreView } from '@vue-storefront/core/lib/multistore'; -import config from 'config'; - -jest.mock('@vue-storefront/core/app', () => jest.fn()); -jest.mock('@vue-storefront/core/lib/router-manager', () => jest.fn()); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedDispatcherRoute: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) - -describe('formatCategoryLink method', () => { - let category: Category; - - beforeEach(() => { - jest.clearAllMocks(); - jest.mock('config', () => ({})); - (currentStoreView as jest.Mock).mockImplementation(() => ({ storeCode: '' })); - category = { - path: '1/2', - is_active: true, - level: 1, - product_count: 1181, - children_count: '38', - parent_id: 1, - name: 'All', - id: 2, - url_key: 'all-2', - children_data: [], - url_path: 'all-2/women/women-20', - slug: 'all-2' - }; - }); - - describe('with active urlDispatcher', () => { - beforeEach(() => { - config.seo = { - useUrlDispatcher: true - }; - }); - - it('should return formatted category url_path', () => { - const result = formatCategoryLink(category); - expect(result).toEqual('/all-2/women/women-20'); - }); - - it('should return formatted category url_path when storeCode passed as null', () => { - const result = formatCategoryLink(category, null); - expect(result).toEqual('/all-2/women/women-20'); - }); - - it('should return formatted category url_path when storeCode passed as \'de\'', () => { - const result = formatCategoryLink(category, 'de'); - expect(result).toEqual('/de/all-2/women/women-20'); - }); - - it('should return formatted category url_path when storeCode passed as \'\'', () => { - const result = formatCategoryLink(category, ''); - expect(result).toEqual('/all-2/women/women-20'); - }); - - it('should return homepage path when category passed as \'null\'', () => { - const result = formatCategoryLink(null); - expect(result).toEqual('/'); - }); - - describe('with default storeCode set to \'de\'', () => { - beforeEach(() => { - (currentStoreView as jest.Mock).mockImplementation(() => ({ storeCode: 'de' })); - }); - - it('should return formatted category url_path', () => { - const result = formatCategoryLink(category); - expect(result).toEqual('/de/all-2/women/women-20'); - }); - - it('should return homepage path when category passed as \'null\'', () => { - const result = formatCategoryLink(null); - expect(result).toEqual('/de/'); - }); - }); - }); - - describe('without urlDispatcher', () => { - beforeEach(() => { - config.seo = { - useUrlDispatcher: false - }; - }); - - it('should return old path with c and category slug', () => { - const result = formatCategoryLink(category); - expect(result).toEqual('/c/all-2'); - }); - - it('should return old path with c and category slug when storeCode passed as null', () => { - const result = formatCategoryLink(category, null); - expect(result).toEqual('/c/all-2'); - }); - - it('should return old path with c and category slug when storeCode passed as \'de\'', () => { - const result = formatCategoryLink(category, 'de'); - expect(result).toEqual('/de/c/all-2'); - }); - - it('should return old path with c and category slug when storeCode passed as \'\'', () => { - const result = formatCategoryLink(category, ''); - expect(result).toEqual('/c/all-2'); - }); - - describe('with default storeCode set to \'de\'', () => { - beforeEach(() => { - (currentStoreView as jest.Mock).mockImplementation(() => ({ storeCode: 'de' })); - }); - - it('should return formatted category url_path', () => { - const result = formatCategoryLink(category); - expect(result).toEqual('/de/c/all-2'); - }); - - it('should return homepage path when category passed as \'null\'', () => { - const result = formatCategoryLink(null); - expect(result).toEqual('/de/'); - }); - }) - - describe('with default storeCode set to \'de\' and appendStoreCode is false', () => { - beforeEach(() => { - (currentStoreView as jest.Mock).mockImplementation(() => ({ storeCode: 'de', appendStoreCode: false })); - }); - - it('should return formatted category url_path', () => { - const result = formatCategoryLink(category); - expect(result).toEqual('/c/all-2'); - }); - - it('should return homepage path when category passed as \'null\'', () => { - const result = formatCategoryLink(null); - expect(result).toEqual('/'); - }); - }) - }); -}); diff --git a/core/modules/url/test/unit/helpers/formatProductLink.spec.ts b/core/modules/url/test/unit/helpers/formatProductLink.spec.ts deleted file mode 100644 index 1276cd5db9..0000000000 --- a/core/modules/url/test/unit/helpers/formatProductLink.spec.ts +++ /dev/null @@ -1,85 +0,0 @@ - -import { formatProductLink } from '@vue-storefront/core/modules/url/helpers'; -import { LocalizedRoute } from '@vue-storefront/core/lib/types' -import * as data from './data' -import config from 'config'; -import { currentStoreView } from '@vue-storefront/core/lib/multistore'; - -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedDispatcherRoute: jest.fn(() => { - return '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html?childSku=MSH09' - }), - localizedRoute: jest.fn(() => { - return '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html?childSku=MSH09' - }) -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})) -jest.mock('@vue-storefront/core/helpers/router', () => ({ - createRouter: jest.fn(), - createRouterProxy: jest.fn() -})) -jest.mock('@vue-storefront/core/lib/router-manager', () => ({ - RouterManager: { - findByName: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ - createApp: jest.fn(), - router: { - addRoutes: jest.fn() - } -})) -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => { - }), - debug: jest.fn(() => () => { - }), - warn: jest.fn(() => () => { - }), - error: jest.fn(() => () => { - }), - info: jest.fn(() => () => { - }) - } -})); - -let expectedProductLink: LocalizedRoute | string; - -describe('formatProductLink helper with useUrlDispatcher set to true', () => { - beforeEach(() => { - jest.clearAllMocks(); - (currentStoreView as jest.Mock).mockImplementationOnce(() => ({ storeCode: '' })); - config.seo = { - useUrlDispatcher: true - }; - }) - - it('should return localized dispatcher route', () => { - expectedProductLink = '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html?childSku=MSH09' - const result = formatProductLink(data.product, ''); - - expect(result).toEqual(expectedProductLink); - }) -}) - -describe('formatProductLink helper with userUrlDispatcher set to false', () => { - beforeEach(() => { - jest.clearAllMocks(); - (currentStoreView as jest.Mock).mockImplementationOnce(() => ({ storeCode: '' })); - config.seo = { - useUrlDispatcher: false - }; - }) - - it('should return localized route', () => { - expectedProductLink = '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html?childSku=MSH09' - const result = formatProductLink(data.product, ''); - - expect(result).toEqual(expectedProductLink); - }) -}) diff --git a/core/modules/url/test/unit/helpers/normalizeUrlPath.spec.ts b/core/modules/url/test/unit/helpers/normalizeUrlPath.spec.ts deleted file mode 100644 index 25dafeaf6b..0000000000 --- a/core/modules/url/test/unit/helpers/normalizeUrlPath.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { normalizeUrlPath } from '@vue-storefront/core/modules/url/helpers'; - -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '2', - localizedRoute: jest.fn() - })) -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})) -jest.mock('@vue-storefront/core/helpers/router', () => ({ - createRouter: jest.fn(), - createRouterProxy: jest.fn() -})) -jest.mock('@vue-storefront/core/app', () => ({ - createApp: jest.fn(), - router: { - addRoutes: jest.fn() - } -})) - -let expectedUrl: string; - -describe('normalizeUrlPath helper', () => { - beforeEach(() => { - jest.clearAllMocks() - - expectedUrl = '/gear/gear-3' - }) - - it('should return normalized url path', () => { - const result = normalizeUrlPath('/gear/gear-3') - - expect(result).toEqual(expectedUrl) - }) -}) diff --git a/core/modules/url/test/unit/helpers/parametrizeRouteData.spec.ts b/core/modules/url/test/unit/helpers/parametrizeRouteData.spec.ts deleted file mode 100644 index c10433d5ed..0000000000 --- a/core/modules/url/test/unit/helpers/parametrizeRouteData.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { parametrizeRouteData } from '@vue-storefront/core/modules/url/helpers'; -import { LocalizedRoute } from '@vue-storefront/core/lib/types' - -jest.mock('@vue-storefront/core/app', () => jest.fn()); -jest.mock('@vue-storefront/core/lib/router-manager', () => jest.fn()); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(), - localizedDispatcherRoute: jest.fn(), - localizedRoute: jest.fn() -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - once: (str) => jest.fn() -})) - -let expectedParametrizedRoute; -let routeData: LocalizedRoute; -let query; - -describe('parametrizeRouteData helper', () => { - beforeEach(() => { - jest.clearAllMocks(); - - query = { - slug: 'pants-18' - } - routeData = { - name: 'category', - params: { - slug: 'pants-18' - } - } - expectedParametrizedRoute = { - name: 'category', - params: { - slug: 'pants-18' - } - } - }) - - it('should return parametrizedRoute', () => { - const result = parametrizeRouteData(routeData, query, '') - expect(result).toEqual(expectedParametrizedRoute) - }) -}) diff --git a/core/modules/url/test/unit/helpers/preProcessDynamicRoutes.spec.ts b/core/modules/url/test/unit/helpers/preProcessDynamicRoutes.spec.ts deleted file mode 100644 index 157028c40a..0000000000 --- a/core/modules/url/test/unit/helpers/preProcessDynamicRoutes.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { preProcessDynamicRoutes } from '@vue-storefront/core/modules/url/helpers'; -import { LocalizedRoute } from '@vue-storefront/core/lib/types' -import { RouterManager } from '../../../../../lib/router-manager'; - -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '2', - localizedRoute: jest.fn() - })) -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})) -jest.mock('@vue-storefront/core/helpers/router', () => ({ - createRouter: jest.fn(), - createRouterProxy: jest.fn() -})) -jest.mock('@vue-storefront/core/lib/router-manager', () => ({ - RouterManager: { - findByName: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ - createApp: jest.fn(), - router: { - addRoutes: jest.fn() - } -})) -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => { - }), - debug: jest.fn(() => () => { - }), - warn: jest.fn(() => () => { - }), - error: jest.fn(() => () => { - }), - info: jest.fn(() => () => { - }) - } -})); - -let dispatcherMap: Record; -let expectedPreparedRoutes: LocalizedRoute[]; - -describe('preProcessDynamicRoutes helper', () => { - beforeEach(() => { - jest.clearAllMocks(); - - dispatcherMap = { - '/all-2': { - name: 'category', - params: { - slug: 'all-2' - } - } - } - expectedPreparedRoutes = [ - { - name: 'urldispatcher-/all-2', - params: { - slug: 'all-2' - }, - path: '/all-2', - pathToRegexpOptions: { - strict: true - } - } - ] - }) - - it('should return array with preparedRoutes', () => { - (RouterManager.findByName as jest.Mock).mockImplementationOnce(() => ({ - name: 'category', - path: '/all-2' - })); - - const result = preProcessDynamicRoutes(dispatcherMap, true) - - expect(result).toEqual(expectedPreparedRoutes) - }) - - it('should return blank array with null if it does not find user route', () => { - (RouterManager.findByName as jest.Mock).mockImplementationOnce(() => ({ - name: 'category', path: '/all-2' - })); - dispatcherMap = { - } - expectedPreparedRoutes = [] - - const result = preProcessDynamicRoutes(dispatcherMap, true) - - expect(result).toEqual(expectedPreparedRoutes) - }) -}) diff --git a/core/modules/url/test/unit/helpers/processDynamicRoute.spec.ts b/core/modules/url/test/unit/helpers/processDynamicRoute.spec.ts deleted file mode 100644 index 1636790085..0000000000 --- a/core/modules/url/test/unit/helpers/processDynamicRoute.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { processDynamicRoute } from '@vue-storefront/core/modules/url/helpers'; -import { LocalizedRoute } from '@vue-storefront/core/lib/types' -import { RouterManager } from '../../../../../lib/router-manager'; - -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '2', - localizedRoute: jest.fn() - })) -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - once: jest.fn() -})) -jest.mock('@vue-storefront/core/helpers/router', () => ({ - createRouter: jest.fn(), - createRouterProxy: jest.fn() -})) -jest.mock('@vue-storefront/core/lib/router-manager', () => ({ - RouterManager: { - findByName: jest.fn() - } -})); -jest.mock('@vue-storefront/core/app', () => ({ - createApp: jest.fn(), - router: { - addRoutes: jest.fn() - } -})) -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '2', - localizedRoute: jest.fn() - })) -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => { - }), - debug: jest.fn(() => () => { - }), - warn: jest.fn(() => () => { - }), - error: jest.fn(() => () => { - }), - info: jest.fn(() => () => { - }) - } -})); - -let expectedDynamicRoute; -let routeData: LocalizedRoute; - -describe('parametrizeRouteData helper', () => { - beforeEach(() => { - jest.clearAllMocks(); - - routeData = { - name: 'category', - params: { - slug: 'pants-18' - } - } - expectedDynamicRoute = { - name: 'urldispatcher-/men/bottoms-men/pants-men/pants-18', - params: { - slug: 'pants-18' - }, - path: '/men/bottoms-men/pants-men/pants-18', - pathToRegexpOptions: { - strict: true - } - } - }) - - it('should return parametrizedRoute from prepareDynamicRoute', () => { - (RouterManager.findByName as jest.Mock).mockImplementationOnce(() => ({ - name: 'category', - path: '/c/:slug' - })); - - const result = processDynamicRoute(routeData, '/men/bottoms-men/pants-men/pants-18', false) - - expect(result).toEqual(expectedDynamicRoute) - }) - - it('should return null from prepareDynamicRoute if it does not find route', () => { - routeData.name = 'test-test-test'; - routeData.params.slug = 'test-test-test'; - - const result = processDynamicRoute(routeData, '', false) - - expect(result).toEqual(null) - }) -}) diff --git a/core/modules/url/test/unit/store/actions.spec.ts b/core/modules/url/test/unit/store/actions.spec.ts deleted file mode 100644 index 74115fd8f6..0000000000 --- a/core/modules/url/test/unit/store/actions.spec.ts +++ /dev/null @@ -1,256 +0,0 @@ -import { cacheStorage } from '@vue-storefront/core/modules/recently-viewed/index'; -import { actions as urlActions } from '../../../store/actions'; -import { currentStoreView, removeStoreCodeFromRoute, localizedDispatcherRouteName } from '@vue-storefront/core/lib/multistore'; -import { normalizeUrlPath, parametrizeRouteData } from '../../../helpers'; -import { transformProductUrl } from '@vue-storefront/core/modules/url/helpers/transformUrl'; - -jest.mock('@vue-storefront/core/store', () => ({ Module: jest.fn() })) -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/modules/recently-viewed/index', () => ({ - cacheStorage: { - getItem: jest.fn(), - setItem: jest.fn(), - clear: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - init: jest.fn(), - get: jest.fn(() => cacheStorage), - getItem: jest.fn(), - initCacheStorage: jest.fn() - } -})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '', - localizedRoute: jest.fn(), - appendStoreCode: '' - })), - localizedDispatcherRouteName: jest.fn(), - removeStoreCodeFromRoute: jest.fn(() => '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html') -})); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => {}), - debug: jest.fn(() => () => {}), - warn: jest.fn(() => () => {}), - error: jest.fn(() => () => {}), - info: jest.fn(() => () => {}) - } -})); -jest.mock('@vue-storefront/core/modules/url/helpers', () => ({ - preProcessDynamicRoutes: jest.fn(), - parametrizeRouteData: jest.fn(), - removeStoreCodeFromRoute: jest.fn(), - normalizeUrlPath: jest.fn() -})); -jest.mock('@vue-storefront/core/lib/storeCodeFromRoute', () => - jest.fn(() => '') -); -jest.mock('config', () => ({})); -jest.mock('@vue-storefront/core/app', () => ({ - router: { - addRoutes: jest.fn() - } -})); -jest.mock('@vue-storefront/core/modules/url/helpers/transformUrl', () => ({ - transformProductUrl: jest.fn(), - transformCategoryUrl: jest.fn(), - transformCmsPageUrl: jest.fn() -})); - -let url: string; -let routeData: any; - -describe('Url actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - - url = 'https://www.example.com'; - routeData = 'routeData'; - }); - - describe('registerMapping action', () => { - it('should call register mapping mutation', async () => { - const contextMock = { - state: { - dispatcherMap: {} - } - }; - const result = await (urlActions as any).registerMapping(contextMock, { - url, - routeData - }); - - expect(result).toEqual(routeData); - }); - }); - - describe('registerDynamicRoutes action', () => { - it('should NOT call registerMapping action if dispatcherMap state is empty', async () => { - const contextMock = { - state: { - dispatcherMap: {} - }, - dispatch: jest.fn() - }; - const wrapper = (actions: any) => - actions.registerDynamicRoutes(contextMock); - - await wrapper(urlActions); - - expect(contextMock.dispatch).not.toBeCalledWith('registerMapping', { - url, - routeData - }); - }); - it('should call registerMapping action if dispatchetMap is not empty', async () => { - const contextMock = { - state: { - dispatcherMap: { - url: 'https://www.example.com' - } - }, - dispatch: jest.fn() - }; - routeData = contextMock.state.dispatcherMap.url; - url = 'url'; - - const wrapper = (actions: any) => - actions.registerDynamicRoutes(contextMock); - - await wrapper(urlActions); - - expect(contextMock.dispatch).toBeCalledWith('registerMapping', { - url, - routeData - }); - }); - }); - - describe('mapUrl action', () => { - beforeEach(() => { - (currentStoreView as jest.Mock).mockImplementation(() => ({ - storeCode: '' - })); - }); - - it('should return resolved promise with parametrizedRoute', () => { - url = '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html'; - - (normalizeUrlPath as jest.Mock).mockImplementationOnce(() => url); - (parametrizeRouteData as jest.Mock).mockImplementationOnce(() => ({ - name: 'configurable-product', - params: { - slug: 'troy-yoga-short-994', - parentSku: 'MSH09', - childSku: 'MSH09-32-Black' - } - })); - const contextMock = { - state: { - dispatcherMap: { - '/men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html': { - name: 'configurable-product', - params: { - slug: 'troy-yoga-short-994', - parentSku: 'MSH09', - childSku: 'MSH09-32-Black' - } - } - } - } - }; - const query = { childSku: 'MSH09-32-Black' }; - const expectedResult = new Promise(resolve => - resolve({ - name: 'configurable-product', - params: { - slug: 'troy-yoga-short-994', - parentSku: 'MSH09', - childSku: 'MSH09-32-Black' - } - }) - ); - const result = (urlActions as any).mapUrl(contextMock, { url, query }); - - expect(result).toEqual(expectedResult); - }); - }); - - describe('mapFallbackUrl action', () => { - beforeEach(() => { - (currentStoreView as jest.Mock).mockImplementation(() => ({ - storeCode: '', - appendStoreCode: '' - })); - }); - - it('should trigger fetch from url module', async () => { - url = 'men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html'; - (removeStoreCodeFromRoute as jest.Mock).mockImplementation(() => url); - - const contextMock = { - dispatch: jest.fn() - }; - - const wrapper = (actions: any) => actions.mapFallbackUrl(contextMock, { url }); - - await wrapper(urlActions); - - expect(contextMock.dispatch).toBeCalledWith('getFallbackByUrl', { url }) - }); - - it('should return page-not-found if missing record from ES', async () => { - url = 'men/bottoms-men/shorts-men/shorts-19/troy-yoga-short-994.html'; - (removeStoreCodeFromRoute as jest.Mock).mockImplementation(() => url); - - const contextMock = { - dispatch: jest.fn() - }; - - const wrapper = (actions: any) => actions.mapFallbackUrl(contextMock, { url }); - - const result = await wrapper(urlActions); - - expect(result).toEqual({ - name: 'page-not-found', - params: { - slug: 'page-not-found' - } - }) - }); - }); - - describe('transformFallback action', () => { - it('should call transformation function based on _type', async () => { - const contextMock = { - dispatch: jest.fn() - }; - - const wrapper = (actions: any) => actions.transformFallback(contextMock, { _type: 'product' }); - - await wrapper(urlActions); - - expect(transformProductUrl).toBeCalled() - }); - - it('should return by default page-not-found', async () => { - const contextMock = { - dispatch: jest.fn() - }; - - const wrapper = (actions: any) => actions.transformFallback(contextMock, { _type: 'xyz' }); - - const result = await wrapper(urlActions); - - expect(result).toEqual({ - name: 'page-not-found', - params: { - slug: 'page-not-found' - } - }) - }); - }); -}); diff --git a/core/modules/url/types/UrlState.ts b/core/modules/url/types/UrlState.ts deleted file mode 100644 index bc4d3138ce..0000000000 --- a/core/modules/url/types/UrlState.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Route } from 'vue-router'; -import { LocalizedRoute } from '@vue-storefront/core/lib/types' - -interface UrlModuleRoute extends Partial { - scrollPosition?: { - x: number, - y: number - }, - categoryPageSize?: number -} - -// This object should represent structure of your modules Vuex state -// It's a good practice is to name this interface accordingly to the KET (for example mailchimpState) -export interface UrlState { - dispatcherMap: { [path: string]: LocalizedRoute}, - currentRoute: UrlModuleRoute, - prevRoute: UrlModuleRoute, - isBackRoute: boolean -} diff --git a/core/modules/user/components/AccountButton.ts b/core/modules/user/components/AccountButton.ts deleted file mode 100644 index 282dfd856f..0000000000 --- a/core/modules/user/components/AccountButton.ts +++ /dev/null @@ -1,27 +0,0 @@ -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' - -export const AccountButton = { - name: 'AccountButton', - computed: { - isLoggedIn () { - return this.$store.getters['user/isLoggedIn'] - }, - user () { - return this.$store.state.user.current - } - }, - methods: { - goToAccount () { - if (this.currentUser) { - this.$router.push(this.localizedRoute('/my-account')) - } else { - this.$store.commit('ui/setAuthElem', 'login') - EventBus.$emit('modal-show', 'modal-signup') - } - }, - logout () { - EventBus.$emit('user-before-logout') - this.$router.push(this.localizedRoute('/')) - } - } -} diff --git a/core/modules/user/components/Login.ts b/core/modules/user/components/Login.ts deleted file mode 100644 index a67cc7de19..0000000000 --- a/core/modules/user/components/Login.ts +++ /dev/null @@ -1,41 +0,0 @@ -import i18n from '@vue-storefront/i18n' -import { Logger } from '@vue-storefront/core/lib/logger' - -export const Login = { - name: 'Login', - data () { - return { - remember: false, - email: '', - password: '' - } - }, - methods: { - callLogin () { - this.$bus.$emit('notification-progress-start', i18n.t('Authorization in progress ...')) - this.$store.dispatch('user/login', { username: this.email, password: this.password }).then((result) => { - this.$bus.$emit('notification-progress-stop', {}) - - if (result.code !== 200) { - this.onFailure(result) - } else { - this.onSuccess() - this.close() - } - }).catch(err => { - Logger.error(err, 'user')() - this.onFailure({ result: 'Unexpected authorization error. Check your Network conection.' }) - // TODO Move to theme - this.$bus.$emit('notification-progress-stop') - }) - }, - switchElem () { - // TODO Move to theme - this.$store.commit('ui/setAuthElem', 'register') - }, - callForgotPassword () { - // TODO Move to theme - this.$store.commit('ui/setAuthElem', 'forgot-pass') - } - } -} diff --git a/core/modules/user/components/Register.ts b/core/modules/user/components/Register.ts deleted file mode 100644 index e58d02a217..0000000000 --- a/core/modules/user/components/Register.ts +++ /dev/null @@ -1,60 +0,0 @@ -import i18n from '@vue-storefront/i18n' -import { Logger } from '@vue-storefront/core/lib/logger' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' - -export const Register = { - name: 'Register', - data () { - return { - email: '', - firstName: '', - lastName: '', - password: '', - rPassword: '', - conditions: false - } - }, - methods: { - switchElem () { - // TODO Move to theme - this.$store.commit('ui/setAuthElem', 'login') - }, - close () { - // TODO Move to theme - this.$bus.$emit('modal-hide', 'modal-signup') - }, - callRegister () { - // TODO Move to theme - this.$bus.$emit('notification-progress-start', i18n.t('Registering the account ...')) - this.$store.dispatch('user/register', { - email: this.email, - password: this.password, - firstname: this.firstName, - lastname: this.lastName, - storeId: currentStoreView().storeId - }).then((result) => { - Logger.debug(result, 'user')() - // TODO Move to theme - this.$bus.$emit('notification-progress-stop') - if (result.code !== 200) { - this.onFailure(result) - // If error includes a word 'password', focus on a corresponding field - if (result.result.includes('password')) { - this.$refs['password'].setFocus('password') - this.password = '' - this.rPassword = '' - } - } else { - this.$store.dispatch('user/login', { username: this.email, password: this.password }) - this.onSuccess() - this.close() - } - }).catch(err => { - // TODO Move to theme - this.onFailure({ result: 'Unexpected authorization error. Check your Network conection.' }) - this.$bus.$emit('notification-progress-stop') - Logger.error(err, 'user')() - }) - } - } -} diff --git a/core/modules/user/components/UserAccount.ts b/core/modules/user/components/UserAccount.ts deleted file mode 100644 index 9043640e95..0000000000 --- a/core/modules/user/components/UserAccount.ts +++ /dev/null @@ -1,230 +0,0 @@ -import toString from 'lodash-es/toString' -import pick from 'lodash-es/pick' -import config from 'config' -import { userHooks } from '@vue-storefront/core/modules/user/hooks' -const Countries = require('@vue-storefront/core/i18n/resource/countries.json') - -export const UserAccount = { - name: 'UserAccount', - data () { - return { - currentUser: Object.assign({}, this.$store.state.user.current), - userCompany: { - company: '', - street: '', - house: '', - city: '', - region: '', - country: '', - postcode: '', - taxId: '', - phone: '' - }, - countries: Countries, - changePassword: false, - oldPassword: '', - password: '', - rPassword: '', - addCompany: false, - isEdited: false, - remainInEditMode: false - } - }, - beforeMount () { - this.$bus.$on('user-after-loggedin', this.onLoggedIn) - this.$bus.$on('myAccount-before-remainInEditMode', block => { - if (block === 'MyProfile') { - this.remainInEditMode = true - } - }) - }, - beforeDestroy () { - this.$bus.$off('user-after-loggedin', this.onLoggedIn) - this.$bus.$off('myAccount-before-remainInEditMode') - }, - mounted () { - this.userCompany = this.getUserCompany() - if (this.userCompany.company) { - this.addCompany = true - } - }, - methods: { - onLoggedIn () { - this.currentUser = Object.assign({}, this.$store.state.user.current) - this.userCompany = this.getUserCompany() - if (this.userCompany.company) { - this.addCompany = true - } - }, - edit () { - this.isEdited = true - }, - objectsEqual (a, b, excludedFields = []) { - const aProps = Object.keys(a) - const bProps = Object.keys(b) - - if (aProps.length !== bProps.length) { - return false - } - - for (let i = 0; i < aProps.length; i++) { - let propName = aProps[i] - if (!b.hasOwnProperty(propName)) { - return false - } else { - if (a[propName] !== null && b[propName] !== null && a[propName] === 'object' && b[propName] === 'object') { - if (!this.objectsEqual(a[propName], b[propName])) { - return false - } - } else if (!excludedFields.includes(propName) && a[propName] !== b[propName]) { - return false - } - } - } - return true - }, - updateProfile () { - let updatedProfile - if (!this.objectsEqual(this.currentUser, this.$store.state.user.current, ['updated_at', 'addresses']) || - !this.objectsEqual(this.userCompany, this.getUserCompany()) || - (this.userCompany.company && !this.addCompany) - ) { - updatedProfile = JSON.parse(JSON.stringify(this.$store.state.user.current)) - updatedProfile.firstname = this.currentUser.firstname - updatedProfile.lastname = this.currentUser.lastname - updatedProfile.email = this.currentUser.email - if (updatedProfile.hasOwnProperty('default_billing')) { - let index - for (let i = 0; i < updatedProfile.addresses.length; i++) { - if (toString(updatedProfile.addresses[i].id) === toString(updatedProfile.default_billing)) { - index = i - } - } - if (index >= 0) { - if (this.addCompany) { - updatedProfile.addresses[index].firstname = this.currentUser.firstname || '' - updatedProfile.addresses[index].lastname = this.currentUser.lastname || '' - updatedProfile.addresses[index].company = this.userCompany.company || '' - updatedProfile.addresses[index].street = [this.userCompany.street, this.userCompany.house] || ['', ''] - updatedProfile.addresses[index].city = this.userCompany.city || '' - updatedProfile.addresses[index].region = { - region: this.userCompany.region ? this.userCompany.region : null - } - updatedProfile.addresses[index].country_id = this.userCompany.country || '' - updatedProfile.addresses[index].postcode = this.userCompany.postcode || '' - updatedProfile.addresses[index].vat_id = this.userCompany.taxId || '' - updatedProfile.addresses[index].telephone = this.userCompany.phone || '' - } else { - updatedProfile.addresses.splice(index, 1) - this.userCompany = { - company: '', - street: '', - house: '', - city: '', - region: '', - country: '', - postcode: '', - taxId: '', - phone: '' - } - } - } - } else if (this.addCompany) { - updatedProfile.addresses.push({ - firstname: this.currentUser.firstname, - lastname: this.currentUser.lastname, - company: this.userCompany.company, - street: [this.userCompany.street, this.userCompany.house], - city: this.userCompany.city, - ...(this.userCompany.region ? { region: { region: this.userCompany.region } } : {}), - country_id: this.userCompany.country, - postcode: this.userCompany.postcode, - vat_id: this.userCompany.taxId, - telephone: this.userCompany.phone, - default_billing: true - }) - } - } - if (this.password) { - this.$bus.$emit('myAccount-before-changePassword', { - currentPassword: this.oldPassword, - newPassword: this.password - }) - } - updatedProfile = pick(updatedProfile, config.users.allowModification) - this.exitSection(null, updatedProfile) - }, - exitSection (event, updatedProfile) { - this.$bus.$emit('myAccount-before-updateUser', updatedProfile) - userHooks.afterUserProfileUpdated(event => { - if (event.resultCode === 200) { - if (!updatedProfile) { - this.currentUser = Object.assign({}, this.$store.state.user.current) - this.userCompany = this.getUserCompany() - this.changePassword = false - this.oldPassword = '' - this.password = '' - this.rPassword = '' - if (!this.userCompany.company) { - this.addCompany = false - } - this.remainInEditMode = false - } - if (!this.remainInEditMode) { - this.isEdited = false - } - } else { - this.$store.dispatch('notification/spawnNotification', { - type: 'error', - message: this.$t(event.result.errorMessage || 'Something went wrong ...'), - action1: { label: this.$t('OK') } - }, { root: true }) - } - }) - }, - getUserCompany () { - let user = this.$store.state.user.current - if (user && user.hasOwnProperty('default_billing')) { - let index - for (let i = 0; i < this.currentUser.addresses.length; i++) { - if (toString(user.addresses[i].id) === toString(user.default_billing)) { - index = i - } - } - if (index >= 0) { - return { - company: user.addresses[index].company || '', - street: user.addresses[index].street[0] || '', - house: user.addresses[index].street[1] || '', - city: user.addresses[index].city || '', - region: user.addresses[index].region.region ? user.addresses[index].region.region : '', - country: user.addresses[index].country_id || '', - postcode: user.addresses[index].postcode || '', - taxId: user.addresses[index].vat_id || '', - phone: user.addresses[index].telephone || '' - } - } - } else { - return { - company: '', - street: '', - house: '', - city: '', - region: '', - country: '', - postcode: '', - taxId: '', - phone: '' - } - } - }, - getCountryName () { - for (let i = 0; i < this.countries.length; i++) { - if (this.countries[i].code === this.userCompany.country) { - return this.countries[i].name - } - } - return '' - } - } -} diff --git a/core/modules/user/components/UserShippingDetails.ts b/core/modules/user/components/UserShippingDetails.ts deleted file mode 100644 index ecd2e79218..0000000000 --- a/core/modules/user/components/UserShippingDetails.ts +++ /dev/null @@ -1,214 +0,0 @@ -import toString from 'lodash-es/toString' -import pick from 'lodash-es/pick' -import config from 'config' -import { userHooks } from '@vue-storefront/core/modules/user/hooks' -const Countries = require('@vue-storefront/i18n/resource/countries.json') - -export const UserShippingDetails = { - name: 'MyShippingDetails', - data () { - return { - shippingDetails: { - firstName: '', - lastName: '', - street: '', - house: '', - city: '', - postcode: '', - region: '', - country: '', - phone: '' - }, - countries: Countries, - useCompanyAddress: false, - currentUser: Object.assign({}, this.$store.state.user.current), - isEdited: false, - remainInEditMode: false - } - }, - beforeMount () { - this.$bus.$on('user-after-loggedin', this.onLoggedIn) - this.$bus.$on('myAccount-before-remainInEditMode', block => { - if (block === 'MyShippingDetails') { - this.remainInEditMode = true - } - }) - }, - beforeDestroy () { - this.$bus.$off('user-after-loggedin', this.onLoggedIn) - this.$bus.$off('myAccount-before-remainInEditMode') - }, - mounted () { - this.shippingDetails = this.getShippingDetails() - }, - watch: { - useCompanyAddress: { - handler () { - this.fillCompanyAddress() - } - } - }, - methods: { - onLoggedIn () { - this.currentUser = Object.assign({}, this.$store.state.user.current) - this.shippingDetails = this.getShippingDetails() - }, - edit () { - this.isEdited = true - }, - objectsEqual (a, b) { - const aProps = Object.keys(a) - const bProps = Object.keys(b) - - if (aProps.length !== bProps.length) { - return false - } - - for (let i = 0; i < aProps.length; i++) { - let propName = aProps[i] - if (!b.hasOwnProperty(propName)) { - return false - } else { - if (a[propName] !== null && b[propName] !== null && a[propName] === 'object' && b[propName] === 'object') { - if (!this.objectsEqual(a[propName], b[propName])) { - return false - } - } else if (a[propName] !== b[propName]) { - return false - } - } - } - return true - }, - updateDetails () { - let updatedShippingDetails - if (!this.objectsEqual(this.shippingDetails, this.getShippingDetails())) { - updatedShippingDetails = JSON.parse(JSON.stringify(this.$store.state.user.current)) - let updatedShippingDetailsAddress = { - firstname: this.shippingDetails.firstName, - lastname: this.shippingDetails.lastName, - street: [this.shippingDetails.street, this.shippingDetails.house], - city: this.shippingDetails.city, - ...(this.shippingDetails.region ? { region: { region: this.shippingDetails.region } } : {}), - country_id: this.shippingDetails.country, - postcode: this.shippingDetails.postcode, - ...(this.shippingDetails.phone ? { telephone: this.shippingDetails.phone } : {}) - } - if (this.currentUser.hasOwnProperty('default_shipping')) { - if (this.currentUser.addresses.length === 0) { - updatedShippingDetails = null - } else { - updatedShippingDetails.addresses = updatedShippingDetails.addresses.map((address) => - toString(address.id) === toString(this.currentUser.default_shipping) - ? { ...address, ...updatedShippingDetailsAddress } // update default address if already exist - : address - ) - } - } else { - // create default address - updatedShippingDetails.addresses.push({ - ...updatedShippingDetailsAddress, - default_shipping: true - }) - } - } - updatedShippingDetails = pick(updatedShippingDetails, config.users.allowModification) - this.exitSection(null, updatedShippingDetails) - }, - exitSection (event, updatedShippingDetails) { - this.$bus.$emit('myAccount-before-updateUser', updatedShippingDetails) - userHooks.afterUserProfileUpdated(event => { - if (event.resultCode === 200) { - if (!updatedShippingDetails) { - this.shippingDetails = this.getShippingDetails() - this.useCompanyAddress = false - this.remainInEditMode = false - } - if (!this.remainInEditMode) { - this.isEdited = false - } - } else { - this.$store.dispatch('notification/spawnNotification', { - type: 'error', - message: this.$t(event.result.errorMessage || 'Something went wrong ...'), - action1: { label: this.$t('OK') } - }, { root: true }) - } - }) - }, - fillCompanyAddress () { - if (this.useCompanyAddress) { - const companyAddress = this.currentUser.addresses.find((address) => toString(address.id) === toString(this.currentUser.default_billing)) - if (companyAddress) { - this.shippingDetails.firstName = companyAddress.firstname - this.shippingDetails.lastName = companyAddress.lastname - this.shippingDetails.street = companyAddress.street[0] - this.shippingDetails.house = companyAddress.street[1] - this.shippingDetails.city = companyAddress.city - this.shippingDetails.postcode = companyAddress.postcode - this.shippingDetails.region = companyAddress.region.region ? companyAddress.region.region : '' - this.shippingDetails.country = companyAddress.country_id - } - } else { - this.shippingDetails = this.getShippingDetails() - } - }, - readShippingDetailsFromCurrentUser (shippingDetails) { - for (let address of this.currentUser.addresses) { - if (toString(address.id) === toString(this.currentUser.default_shipping)) { - return { - firstName: address.firstname, - lastName: address.lastname, - street: address.street[0], - house: address.street[1], - city: address.city, - postcode: address.postcode, - region: address.region.region ? address.region.region : '', - country: address.country_id, - phone: address.hasOwnProperty('telephone') ? address.telephone : '' - } - } - } - return shippingDetails - }, - getShippingDetails () { - this.currentUser = Object.assign({}, this.$store.state.user.current) - let shippingDetails = { - firstName: '', - lastName: '', - street: '', - house: '', - city: '', - postcode: '', - region: '', - country: '', - phone: '' - } - if (this.currentUser) { - if (this.currentUser && this.currentUser.hasOwnProperty('default_shipping')) { - shippingDetails = this.readShippingDetailsFromCurrentUser(shippingDetails); - } else { - shippingDetails.firstName = this.currentUser.firstname - shippingDetails.lastName = this.currentUser.lastname - } - } - return shippingDetails; - }, - getCountryName () { - for (let i = 0; i < this.countries.length; i++) { - if (this.countries[i].code === this.shippingDetails.country) { - return this.countries[i].name - } - } - return '' - }, - hasBillingAddress () { - if (this.currentUser) { - if (this.currentUser.hasOwnProperty('default_billing')) { - return true - } - } - return false - } - } -} diff --git a/core/modules/user/hooks.ts b/core/modules/user/hooks.ts deleted file mode 100644 index d7919343b6..0000000000 --- a/core/modules/user/hooks.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { createListenerHook, createMutatorHook } from '@vue-storefront/core/lib/hooks' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import { UserProfile } from '@vue-storefront/core/modules/user/types/UserProfile'; - -// Authorize - -const { - hook: afterUserAuthorizeHook, - executor: afterUserAuthorizeExecutor -} = createListenerHook() - -// Unauthorize - -const { - hook: afterUserUnauthorizeHook, - executor: afterUserUnauthorizeExecutor -} = createListenerHook() - -const { - hook: afterUserProfileUpdatedHook, - executor: afterUserProfileUpdatedExecutor -} = createListenerHook() - -const { - hook: beforeUserProfileUpdateHook, - executor: beforeUserProfileUpdateExecutor -} = createMutatorHook() - -/** Only for internal usage in this module */ -const userHooksExecutors = { - afterUserAuthorize: afterUserAuthorizeExecutor, - afterUserUnauthorize: afterUserUnauthorizeExecutor, - afterUserProfileUpdated: afterUserProfileUpdatedExecutor, - beforeUserProfileUpdate: beforeUserProfileUpdateExecutor -} - -const userHooks = { - /** Hook is fired right after user is authenticated or auth fails. - * @param response result of user authentication containing status codes and user data - */ - afterUserAuthorize: afterUserAuthorizeHook, - /** Hook is fired right after user is logged out. - */ - afterUserUnauthorize: afterUserUnauthorizeHook, - /** - * Hook is fired after user profile is updated ('user/handleUpdateProfile') - */ - afterUserProfileUpdated: afterUserProfileUpdatedHook, - /** - * Hook is fired before user profile is send to update. This can be useful to add additional properties to user profile. - */ - beforeUserProfileUpdate: beforeUserProfileUpdateHook -} - -export { - userHooks, - userHooksExecutors -} diff --git a/core/modules/user/index.ts b/core/modules/user/index.ts deleted file mode 100644 index 7f88614cc9..0000000000 --- a/core/modules/user/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { userStore } from './store' -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { isServer } from '@vue-storefront/core/helpers' -import { Logger } from '@vue-storefront/core/lib/logger' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import * as types from './store/mutation-types' - -export const UserModule: StorefrontModule = async function ({ store }) { - StorageManager.init('user') - store.registerModule('user', userStore) - if (!isServer) { - EventBus.$on('user-before-logout', () => { - store.dispatch('user/logout', { silent: false }) - // TODO: Move it to theme - store.commit('ui/setSubmenu', { - depth: 0 - }) - }) - - EventBus.$on('user-after-loggedin', receivedData => { - // TODO: Make independent of checkout module - store.dispatch('checkout/savePersonalDetails', { - firstName: receivedData.firstname, - lastName: receivedData.lastname, - emailAddress: receivedData.email - }) - }) - - store.dispatch('user/startSession') - } - - store.subscribe((mutation, state) => { - const type = mutation.type - - if ( - type.endsWith(types.USER_INFO_LOADED) - ) { - StorageManager.get('user').setItem('current-user', state.user.current).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - } - - if ( - type.endsWith(types.USER_ORDERS_HISTORY_LOADED) - ) { - StorageManager.get('user').setItem('orders-history', state.user.orders_history).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - } - - if ( - type.endsWith(types.USER_TOKEN_CHANGED) - ) { - StorageManager.get('user').setItem('current-token', state.user.token).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - if (state.user.refreshToken) { - StorageManager.get('user').setItem('current-refresh-token', state.user.refreshToken).catch((reason) => { - Logger.error(reason)() // it doesn't work on SSR - }) // populate cache - } - } - }) -} diff --git a/core/modules/user/store/actions.ts b/core/modules/user/store/actions.ts deleted file mode 100644 index 217fcbfe65..0000000000 --- a/core/modules/user/store/actions.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import i18n from '@vue-storefront/i18n' -import RootState from '@vue-storefront/core/types/RootState' -import UserState from '../types/UserState' -import { Logger } from '@vue-storefront/core/lib/logger' -import { UserProfile } from '../types/UserProfile' -import { onlineHelper } from '@vue-storefront/core/helpers' -import { isServer } from '@vue-storefront/core/helpers' -import { UserService } from '@vue-storefront/core/data-resolver' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { userHooksExecutors, userHooks } from '../hooks' -import { isModuleRegistered } from '@vue-storefront/core/lib/modules' -import Task from '@vue-storefront/core/lib/sync/types/Task' -import uniqBy from 'lodash-es/uniqBy' - -const actions: ActionTree = { - async startSession ({ commit, dispatch, getters }) { - const usersCollection = StorageManager.get('user') - const userData = await usersCollection.getItem('current-user') - - if (isServer || getters.isLocalDataLoaded) return - commit(types.USER_LOCAL_DATA_LOADED, true) - - if (userData) { - commit(types.USER_INFO_LOADED, userData) - } - - commit(types.USER_START_SESSION) - const lastUserToken = await usersCollection.getItem('current-token') - - if (lastUserToken) { - commit(types.USER_TOKEN_CHANGED, { newToken: lastUserToken }) - await dispatch('sessionAfterAuthorized', {}) - - if (userData) { - dispatch('setUserGroup', userData) - } - } else { - EventBus.$emit('session-after-nonauthorized') - } - - EventBus.$emit('session-after-started') - }, - /** - * Send password reset link for specific e-mail - */ - resetPassword (context, { email }) { - return UserService.resetPassword(email) - }, - /** - * Create new password for provided email with resetToken - * We could receive resetToken by running user.resetPassword action - */ - createPassword (context, { email, newPassword, resetToken }) { - return UserService.createPassword(email, newPassword, resetToken) - }, - /** - * Login user and return user profile and current token - */ - async login ({ commit, dispatch }, { username, password }) { - await dispatch('resetUserInvalidation', {}, { root: true }) - - const resp = await UserService.login(username, password) - userHooksExecutors.afterUserAuthorize(resp) - - if (resp.code === 200) { - try { - commit(types.USER_TOKEN_CHANGED, { newToken: resp.result, meta: resp.meta }) // TODO: handle the "Refresh-token" header - await dispatch('sessionAfterAuthorized', { refresh: true, useCache: false }) - } catch (err) { - await dispatch('clearCurrentUser') - throw new Error(err) - } - } - - return resp - }, - /** - * Login user and return user profile and current token - */ - async register (context, { password, ...customer }) { - return UserService.register(customer, password) - }, - - /** - * Invalidate user token - */ - async refresh ({ commit }) { - const usersCollection = StorageManager.get('user') - const refreshToken = await usersCollection.getItem('current-refresh-token') - const newToken = await UserService.refreshToken(refreshToken) - - if (newToken) { - commit(types.USER_TOKEN_CHANGED, { newToken }) - } - - return newToken - }, - /** - * Update user groupToken and groupId in state - * @param context - * @param userData - */ - setUserGroup ({ commit }, userData) { - if (userData.groupToken) { - commit(types.USER_GROUP_TOKEN_CHANGED, userData.groupToken) - } - - if (userData.group_id) { - commit(types.USER_GROUP_CHANGED, userData.group_id) - } - }, - async restoreCurrentUserFromCache ({ commit, dispatch }) { - const usersCollection = StorageManager.get('user') - const currentUser = await usersCollection.getItem('current-user') - - if (currentUser) { - commit(types.USER_INFO_LOADED, currentUser) - await dispatch('setUserGroup', currentUser) - EventBus.$emit('user-after-loggedin', currentUser) - dispatch('cart/authorize', {}, { root: true }) - - return currentUser - } - - return null - }, - async refreshUserProfile ({ commit, dispatch }, { resolvedFromCache }) { - const resp = await UserService.getProfile() - - if (resp.resultCode === 200) { - commit(types.USER_INFO_LOADED, resp.result) // this also stores the current user to localForage - await dispatch('setUserGroup', resp.result) - } - - if (!resolvedFromCache && resp.resultCode === 200) { - EventBus.$emit('user-after-loggedin', resp.result) - await dispatch('cart/authorize', {}, { root: true }) - return resp - } - }, - /** - * Load current user profile - */ - async me ({ dispatch, getters }, { refresh = true, useCache = true } = {}) { - if (!getters.getToken) { - Logger.warn('No User token, user unauthorized', 'user')() - return - } - - let resolvedFromCache = false - - if (useCache) { - const currentUser = await dispatch('restoreCurrentUserFromCache') - - if (currentUser) { - resolvedFromCache = true - Logger.log('Current user served from cache', 'user')() - } - } - - if (refresh) { - return dispatch('refreshUserProfile', { resolvedFromCache }) - } - }, - /** - * Update user profile with data from My Account page - */ - async update (_, profile: UserProfile) { - profile = userHooksExecutors.beforeUserProfileUpdate(profile) - await UserService.updateProfile(profile, 'user/handleUpdateProfile') - }, - async handleUpdateProfile ({ dispatch }, event: Task) { - if (event.resultCode === 200) { - dispatch('notification/spawnNotification', { - type: 'success', - message: i18n.t('Account data has successfully been updated'), - action1: { label: i18n.t('OK') } - }, { root: true }) - dispatch('user/setCurrentUser', event.result, { root: true }) - } - userHooksExecutors.afterUserProfileUpdated(event) - }, - setCurrentUser ({ commit }, userData) { - commit(types.USER_INFO_LOADED, userData) - }, - /** - * Change user password - */ - async changePassword ({ dispatch, getters }, passwordData) { - if (!onlineHelper.isOnline) { - dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t('Reset password feature does not work while offline!'), - action1: { label: i18n.t('OK') } - }, { root: true }) - - return - } - - const resp = await UserService.changePassword(passwordData) - - if (resp.code === 200) { - await dispatch('notification/spawnNotification', { - type: 'success', - message: i18n.t('Password has successfully been changed'), - action1: { label: i18n.t('OK') } - }, { root: true }) - await dispatch('login', { - username: getters.getUserEmail, - password: passwordData.newPassword - }) - } else { - await dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t(resp.result.errorMessage), - action1: { label: i18n.t('OK') } - }, { root: true }) - } - }, - clearCurrentUser ({ commit, dispatch }) { - commit(types.USER_TOKEN_CHANGED, { newToken: null }) - commit(types.USER_GROUP_TOKEN_CHANGED, '') - commit(types.USER_GROUP_CHANGED, null) - commit(types.USER_INFO_LOADED, null) - if (isModuleRegistered('WishlistModule')) dispatch('wishlist/clear', null, { root: true }) - if (isModuleRegistered('CompareModule')) dispatch('compare/clear', null, { root: true }) - dispatch('checkout/savePersonalDetails', {}, { root: true }) - dispatch('checkout/saveShippingDetails', {}, { root: true }) - dispatch('checkout/savePaymentDetails', {}, { root: true }) - commit(types.USER_ORDERS_HISTORY_LOADED, {}) - StorageManager - .get('user') - .setItem('current-refresh-token', null) - .catch((reason) => { - Logger.error(reason)() - }) - }, - /** - * Logout user - */ - async logout ({ commit, dispatch }, { silent = false }) { - commit(types.USER_END_SESSION) - await dispatch('cart/disconnect', {}, { root: true }) - await dispatch('clearCurrentUser') - EventBus.$emit('user-after-logout') - // clear cart without sync, because after logout we don't want to clear cart on backend - // user should have items when he comes back - await dispatch('cart/clear', { sync: false }, { root: true }) - - if (!silent) { - await dispatch('notification/spawnNotification', { - type: 'success', - message: i18n.t("You're logged out"), - action1: { label: i18n.t('OK') } - }, { root: true }) - } - userHooksExecutors.afterUserUnauthorize() - }, - async loadOrdersFromCache ({ commit }) { - const ordersHistoryCollection = StorageManager.get('user') - const ordersHistory = await ordersHistoryCollection.getItem('orders-history') - - if (ordersHistory) { - commit(types.USER_ORDERS_HISTORY_LOADED, ordersHistory) - EventBus.$emit('user-after-loaded-orders', ordersHistory) - - return ordersHistory - } - }, - async appendOrdersHistory ({ commit, getters }, { pageSize = 20, currentPage = 1 }) { - const resp = await UserService.getOrdersHistory(pageSize, currentPage) - - if (resp.code === 200) { - const oldOrders = getters.getOrdersHistory; - let orders = resp.result; - if (oldOrders && orders.items) orders.items = uniqBy([...oldOrders, ...orders.items], 'increment_id') - - commit(types.USER_ORDERS_HISTORY_LOADED, orders) - EventBus.$emit('user-after-loaded-orders', orders) - return orders - } - }, - async refreshOrdersHistory ({ commit }, { resolvedFromCache, pageSize = 20, currentPage = 1 }) { - const resp = await UserService.getOrdersHistory(pageSize, currentPage) - - if (resp.code === 200) { - commit(types.USER_ORDERS_HISTORY_LOADED, resp.result) // this also stores the current user to localForage - EventBus.$emit('user-after-loaded-orders', resp.result) - } - - if (!resolvedFromCache) { - Promise.resolve(resp.code === 200 ? resp : null) - } - - return resp - }, - /** - * Load user's orders history - */ - async getOrdersHistory ({ dispatch, getters }, { refresh = true, useCache = true, pageSize = 20, currentPage = 1 }) { - if (!getters.getToken) { - Logger.debug('No User token, user unauthorized', 'user')() - return Promise.resolve(null) - } - let resolvedFromCache = false - - if (useCache) { - const ordersHistory = await dispatch('loadOrdersFromCache') - - if (ordersHistory) { - resolvedFromCache = true - Logger.log('Current user order history served from cache', 'user')() - } - } - - if (refresh) { - return dispatch('refreshOrdersHistory', { resolvedFromCache, pageSize, currentPage }) - } else { - if (!resolvedFromCache) { - Promise.resolve(null) - } - } - }, - async sessionAfterAuthorized ({ dispatch }, { refresh = onlineHelper.isOnline, useCache = true }) { - Logger.info('User session authorised ', 'user')() - await dispatch('me', { refresh, useCache }) - await dispatch('getOrdersHistory', { refresh, useCache }) - } -} - -export default actions diff --git a/core/modules/user/store/getters.ts b/core/modules/user/store/getters.ts deleted file mode 100644 index 4b60ec718c..0000000000 --- a/core/modules/user/store/getters.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import UserState from '../types/UserState' - -const getters: GetterTree = { - isLoggedIn (state) { - return state.current !== null - }, - isLocalDataLoaded: state => state.local_data_loaded, - getUserToken (state) { - return state.token - }, - getOrdersHistory (state) { - return state.orders_history ? state.orders_history.items : [] - }, - getToken (state) { - return state.token - }, - getUserEmail (state, getters) { - return getters.isLoggedIn ? state.current.email : null - } -} - -export default getters diff --git a/core/modules/user/store/index.ts b/core/modules/user/store/index.ts deleted file mode 100644 index eca304504f..0000000000 --- a/core/modules/user/store/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import UserState from '../types/UserState' - -export const userStore: Module = { - namespaced: true, - state: { - token: '', - refreshToken: '', - groupToken: '', - groupId: null, - current: null, - current_storecode: '', - session_started: new Date(), - orders_history: null, - local_data_loaded: false - }, - getters, - actions, - mutations -} diff --git a/core/modules/user/store/mutation-types.ts b/core/modules/user/store/mutation-types.ts deleted file mode 100644 index cd840d4acc..0000000000 --- a/core/modules/user/store/mutation-types.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const SN_USER = 'user' -export const USER_NEWSLETTER_SIGNUP = SN_USER + '/NEWSLETTER_SIGNUP' -export const USER_TOKEN_CHANGED = SN_USER + '/TOKEN_CHANGED' -export const USER_INFO_LOADED = SN_USER + '/INFO_LOADED' -export const USER_ORDERS_HISTORY_LOADED = SN_USER + '/ORDERS_HISTORY_LOADED' -export const USER_START_SESSION = SN_USER + '/START_SESSION' -export const USER_END_SESSION = SN_USER + '/END_SESSION' -export const USER_UPDATE_PREFERENCES = SN_USER + '/UPDATE_PREFERENCES' -export const USER_GROUP_TOKEN_CHANGED = SN_USER + '/GROUP_TOKEN_CHANGED' -export const USER_GROUP_CHANGED = SN_USER + '/GROUP_ID_CHANGED' -export const USER_LOCAL_DATA_LOADED = SN_USER + '/LOCAL_DATA_LOADED' diff --git a/core/modules/user/store/mutations.ts b/core/modules/user/store/mutations.ts deleted file mode 100644 index 8c4cbcdcbb..0000000000 --- a/core/modules/user/store/mutations.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import UserState from '../types/UserState' -import { Logger } from '@vue-storefront/core/lib/logger' - -const mutations: MutationTree = { - [types.USER_TOKEN_CHANGED] (state, payload) { - state.token = payload.newToken - if (payload.meta && payload.meta.refreshToken) { - state.refreshToken = payload.meta.refreshToken // store the refresh token - Logger.log('Refresh token is set to' + state.refreshToken, 'user')() - } - }, - [types.USER_START_SESSION] (state) { - state.session_started = new Date() - }, - [types.USER_GROUP_TOKEN_CHANGED] (state, token) { - state.groupToken = token - }, - [types.USER_GROUP_CHANGED] (state, groupId) { - state.groupId = groupId - }, - [types.USER_INFO_LOADED] (state, currentUser) { - state.current = currentUser - }, - [types.USER_ORDERS_HISTORY_LOADED] (state, ordersHistory) { - state.orders_history = ordersHistory - }, - [types.USER_END_SESSION] (state) { - state.token = '' - state.current = null - state.session_started = null - }, - [types.USER_LOCAL_DATA_LOADED] (state, readed = false) { - state.local_data_loaded = readed - } -} - -export default mutations diff --git a/core/modules/user/test/unit/store/actions.spec.ts b/core/modules/user/test/unit/store/actions.spec.ts deleted file mode 100644 index ddb5432bbb..0000000000 --- a/core/modules/user/test/unit/store/actions.spec.ts +++ /dev/null @@ -1,615 +0,0 @@ -import * as types from '../../../store/mutation-types' -import * as data from './data' -import userActions from '../../../store/actions' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import { UserService } from '@vue-storefront/core/data-resolver' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { isModuleRegistered } from '@vue-storefront/core/lib/modules' - -jest.mock('@vue-storefront/i18n', () => ({ t: jest.fn(str => str) })); -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => { - }), - debug: jest.fn(() => () => { - }), - warn: jest.fn(() => () => { - }), - error: jest.fn(() => () => { - }), - info: jest.fn(() => () => { - }) - } -})); -jest.mock('@vue-storefront/core/lib/multistore', () => ({ - currentStoreView: jest.fn(() => ({ - storeCode: '2', - currentStoreView: jest.fn(), - localizedRoute: jest.fn() - })) -})); -jest.mock('@vue-storefront/core/helpers', () => ({ - get isServer () { - return false - }, - onlineHelper: { - isOnline: true - } -})); -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); -jest.mock('@vue-storefront/core/data-resolver', () => ({ - UserService: { - login: jest.fn(), - register: jest.fn(), - resetPassword: jest.fn(), - refreshToken: jest.fn(), - updateProfile: jest.fn(), - changePassword: jest.fn(), - getOrdersHistory: jest.fn() - } -})); -EventBus.$emit = jest.fn() -jest.mock('@vue-storefront/core/lib/modules', () => ({ - isModuleRegistered: jest.fn(() => false) -})); - -describe('User actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('startSession action', () => { - it('should NOT set user info', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (data.user) - })); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: true } - }; - const wrapper = (actions: any) => actions.startSession(contextMock); - - await wrapper(userActions); - - expect(contextMock.commit).not.toBeCalledWith(types.USER_INFO_LOADED, data.user) - }) - it('should star user session', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (data.user) - })); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const wrapper = (actions: any) => actions.startSession(contextMock); - - await wrapper(userActions); - - expect(contextMock.commit).toBeCalledWith(types.USER_LOCAL_DATA_LOADED, true) - expect(contextMock.commit).toBeCalledWith(types.USER_INFO_LOADED, data.user) - expect(contextMock.commit).toBeCalledWith(types.USER_START_SESSION) - }) - it('should set user token', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (data.lastUserToken) - })); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const wrapper = (actions: any) => actions.startSession(contextMock); - - await wrapper(userActions); - - expect(contextMock.commit).toBeCalledWith(types.USER_TOKEN_CHANGED, { newToken: data.lastUserToken }) - }) - it('should call setUserGroup action', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (data.user) - })); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const wrapper = (actions: any) => actions.startSession(contextMock); - - await wrapper(userActions); - - expect(contextMock.dispatch).toBeCalledWith('setUserGroup', data.user) - }) - it('should emit session-after-nonauthorized', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (null) - })); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const wrapper = (actions: any) => actions.startSession(contextMock); - - await wrapper(userActions); - - expect(EventBus.$emit).toBeCalledWith('session-after-nonauthorized') - }) - }); - - describe('resetPassword action', () => { - it('should return response from resetPassword', () => { - (UserService.resetPassword as jest.Mock).mockImplementation(() => - (data.responseOb) - ); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const email = data.email; - const result = (userActions as any).resetPassword(contextMock, { email }); - - expect(result).toEqual(data.responseOb) - }) - }); - - describe('login action', () => { - it('should return login response', async () => { - (UserService.login as jest.Mock).mockImplementation(async () => - (data.responseOb) - ); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const refreshValue = data.refresh; - const useCacheValue = !data.useCache; - const rootValue = true; - const username = data.username; - const password = data.password; - const result = await (userActions as any).login(contextMock, { username, password }); - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'resetUserInvalidation', {}, { root: rootValue }) - expect(contextMock.commit).toHaveBeenCalledWith(types.USER_TOKEN_CHANGED, { - newToken: data.responseOb.result, - meta: data.responseOb.meta - }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'sessionAfterAuthorized', { - refresh: refreshValue, - useCache: useCacheValue - }) - expect(contextMock.dispatch).not.toBeCalledWith('clearCurrentUser'); - expect(result).toEqual(data.responseOb) - }) - }); - - describe('register action', () => { - it('should return response from register', async () => { - (UserService.register as jest.Mock).mockImplementation(async () => - (data.responseOb) - ); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const password = data.password; - const customer = data.customer; - const result = await (userActions as any).register(contextMock, { password, customer }); - - expect(result).toEqual(data.responseOb) - }) - }); - - describe('refresh action', () => { - it('should update user token', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (data.lastUserToken) - })); - (UserService.refreshToken as jest.Mock).mockImplementation(async () => - (data.lastUserToken) - ) - - const contextMock = { - commit: jest.fn() - } - const newToken = data.lastUserToken - const result = await (userActions as any).refresh(contextMock) - - expect(contextMock.commit).toBeCalledWith(types.USER_TOKEN_CHANGED, { newToken }); - expect(result).toEqual(newToken) - }) - }); - - describe('setUserGroup action', () => { - it('should update user groupToken and groupId in state', () => { - const contextMock = { - commit: jest.fn() - } - const wrapper = (actions: any) => actions.setUserGroup(contextMock, data.user); - - wrapper(userActions); - - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.USER_GROUP_TOKEN_CHANGED, data.user.groupToken) - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.USER_GROUP_CHANGED, data.user.group_id) - }) - }); - - describe('restoreCurrentUserFromCache', () => { - it('should restore current user from cache', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (data.user) - })); - - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const result = await (userActions as any).restoreCurrentUserFromCache(contextMock) - - expect(contextMock.commit).toHaveBeenCalledWith(types.USER_INFO_LOADED, data.user) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'setUserGroup', data.user) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'cart/authorize', {}, { root: true }) - expect(result).toEqual(data.user) - }) - it('should return null if is not cached', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { isLocalDataLoaded: false } - }; - const result = await (userActions as any).restoreCurrentUserFromCache(contextMock) - - expect(result).toEqual(data.user) - }) - }); - - describe('me action', () => { - it('should NOT dispatch restoreCurrentUserFromCache', async () => { - const contextMock = { - dispatch: jest.fn(), - getters: jest.fn() - } - - await (userActions as any).me(contextMock) - - expect(contextMock.dispatch).not.toBeCalledWith('restoreCurrentUserFromCache') - }) - it('should load current user profile if getToken is not empty', async () => { - const contextMock = { - dispatch: jest.fn(), - getters: { getToken: data.lastUserToken } - } - const resolvedFromCache = !data.resolvedFromCache - - await (userActions as any).me(contextMock) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'restoreCurrentUserFromCache') - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'refreshUserProfile', { resolvedFromCache }) - }) - }); - - describe('update action', () => { - it('should set current result if resultCode from response is 200', async () => { - const responseOb = { - resultCode: 200, - result: 200 - }; - const contextMock = { - dispatch: jest.fn() - } - - await (userActions as any).handleUpdateProfile(contextMock, responseOb) - - expect(contextMock.dispatch).toHaveBeenCalledWith('user/setCurrentUser', responseOb.result, { root: true }) - }) - }); - - describe('setCurrentUser action', () => { - it('should set current user', () => { - const contextMock = { - commit: jest.fn() - }; - - (userActions as any).setCurrentUser(contextMock, data.user) - - expect(contextMock.commit).toBeCalledWith(types.USER_INFO_LOADED, data.user) - }) - }); - - describe('changePassword action', () => { - it('should call login action if response code is 200', async () => { - (UserService.changePassword as jest.Mock).mockImplementation(async () => - (data.responseOb) - ); - - const contextMock = { - dispatch: jest.fn(), - getters: { getUserEmail: data.email } - } - const passwordData = { - currentPassword: data.password, - newPassword: 'newPassword1' - } - - await (userActions as any).changePassword(contextMock, passwordData) - - expect(contextMock.dispatch).toBeCalledWith('login', { - username: data.user.email, - password: passwordData.newPassword - }) - }) - it('should call spawnNotification if response code is not 200', async () => { - const responseOb = { - code: 400, - result: { - errorMessage: 'Error' - } - }; - (UserService.changePassword as jest.Mock).mockImplementation(async () => - (responseOb) - ); - - const contextMock = { - dispatch: jest.fn() - } - const passwordData = { - currentPassword: data.password, - newPassword: 'newPassword1' - } - - await (userActions as any).changePassword(contextMock, passwordData) - - expect(contextMock.dispatch).toBeCalledWith('notification/spawnNotification', { - type: 'error', - message: responseOb.result.errorMessage, - action1: { label: 'OK' } - }, { root: true }) - }) - }); - - describe('clearCurrentUser action', () => { - it('should clear current user', () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn() - }; - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - setItem: async () => {} - })); - - (userActions as any).clearCurrentUser(contextMock) - - expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.USER_TOKEN_CHANGED, { newToken: null }) - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.USER_GROUP_TOKEN_CHANGED, '') - expect(contextMock.commit).toHaveBeenNthCalledWith(3, types.USER_GROUP_CHANGED, null) - expect(contextMock.commit).toHaveBeenNthCalledWith(4, types.USER_INFO_LOADED, null) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'checkout/savePersonalDetails', {}, { root: true }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'checkout/saveShippingDetails', {}, { root: true }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'checkout/savePaymentDetails', {}, { root: true }) - }) - it('should clear additional modules when they are registered', () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn() - }; - ;(isModuleRegistered as jest.Mock).mockImplementation(() => true); - (userActions as any).clearCurrentUser(contextMock) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'wishlist/clear', null, { root: true }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'compare/clear', null, { root: true }) - }) - }); - - describe('logout action', () => { - it('should logout user', async () => { - const contextMock = { - commit: jest.fn(), - dispatch: jest.fn() - }; - const silent = false - - await (userActions as any).logout(contextMock, silent) - - expect(contextMock.commit).toBeCalledWith(types.USER_END_SESSION) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'cart/disconnect', {}, { root: true }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'clearCurrentUser') - expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(4, 'notification/spawnNotification', { - type: 'success', - message: "You're logged out", - action1: { label: 'OK' } - }, { root: true }) - }) - }); - - describe('loadOrdersFromCache action', () => { - it('should return ordersHistory', async () => { - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: async () => (data.ordersHistory) - })); - - const contextMock = { - commit: jest.fn() - } - - const result = await (userActions as any).loadOrdersFromCache(contextMock) - - expect(contextMock.commit).toBeCalledWith(types.USER_ORDERS_HISTORY_LOADED, data.ordersHistory) - expect(result).toBe(data.ordersHistory) - }) - }); - - describe('appendOrdersHistory action', () => { - it('should append order to orders history', async () => { - const responseOb = { - result: data.ordersHistory, - code: 200 - }; - (UserService.getOrdersHistory as jest.Mock).mockImplementation(async () => responseOb); - const contextMock = { - commit: jest.fn(), - getters: { - getOrdersHistory: responseOb.result - } - }; - const pageSize = data.pageSize; - const currentPage = data.currentPage; - - const result = await (userActions as any).appendOrdersHistory(contextMock, { - pageSize, - currentPage - }) - - expect(contextMock.commit).toBeCalledWith(types.USER_ORDERS_HISTORY_LOADED, responseOb.result); - expect(EventBus.$emit).toBeCalledWith('user-after-loaded-orders', result); - expect(result).toBe(responseOb.result) - }) - - it('returns orders with unique increment_id', async () => { - const oldOrders = [ - { name: 'a', increment_id: 0 }, - { name: 'b', increment_id: 1 } - ] - const orders = { - items: [ - { name: 'a', increment_id: 0 }, - { name: 'c', increment_id: 2 } - ] - } - const responseOb = { - result: orders, - code: 200 - }; - const contextMock = { - commit: jest.fn(), - getters: { - getOrdersHistory: oldOrders - } - }; - const pageSize = data.pageSize; - const currentPage = data.currentPage; - - (UserService.getOrdersHistory as jest.Mock).mockImplementation(async () => responseOb); - const result = await (userActions as any).appendOrdersHistory(contextMock, { - pageSize, - currentPage - }) - const expectedResult = { - items: [ - { name: 'a', increment_id: 0 }, - { name: 'b', increment_id: 1 }, - { name: 'c', increment_id: 2 } - ] - } - - expect(result).toEqual(expectedResult); - }) - }) - - describe('refreshOrderHistory action', () => { - it('should refresh orders history', async () => { - const responseOb = { - result: data.ordersHistory, - code: 200 - }; - (UserService.getOrdersHistory as jest.Mock).mockImplementation(async () => - (responseOb) - ); - - const contextMock = { - commit: jest.fn() - } - const resolvedFromCache = data.resolvedFromCache; - const pageSize = data.pageSize; - const currentPage = data.currentPage; - const result = await (userActions as any).refreshOrdersHistory(contextMock, { - resolvedFromCache, - pageSize, - currentPage - }) - - expect(contextMock.commit).toBeCalledWith(types.USER_ORDERS_HISTORY_LOADED, responseOb.result) - expect(result).toBe(responseOb) - }) - }); - - describe('getOrdersHistory action', () => { - it('should return null from the resolved promise if getToken is empty', async () => { - const contextMock = { - dispatch: jest.fn(), - getters: { - getToken: null - } - } - const refresh = data.refresh; - const useCache = data.useCache; - const pageSize = data.pageSize; - const currentPage = data.currentPage; - const result = await (userActions as any).getOrdersHistory(contextMock, { - refresh, - useCache, - pageSize, - currentPage - }) - - expect(result).toBe(null) - }) - it('should dispatch loadOrdersFromCache if useCache is set to true and refreshOrdersHistory action if refresh is set to true', async () => { - const contextMock = { - dispatch: jest.fn(), - getters: { - dispatch: jest.fn(), - getToken: data.lastUserToken - } - } - const refresh = data.refresh; - const useCache = data.useCache; - const pageSize = data.pageSize; - const resolvedFromCache = data.resolvedFromCache; - const currentPage = data.currentPage; - - contextMock.dispatch.mockImplementationOnce(() => Promise.resolve(data.ordersHistory)) - - await (userActions as any).getOrdersHistory(contextMock, { refresh, useCache, pageSize, currentPage }) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'loadOrdersFromCache') - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'refreshOrdersHistory', { - resolvedFromCache, - pageSize, - currentPage - }) - }) - }); - - describe('sessionAfterAuthorized action', () => { - it('should call me and getOrdersHistory after authorized', async () => { - const contextMock = { - dispatch: jest.fn() - } - const refresh = data.refresh; - const useCache = data.useCache; - - await (userActions as any).sessionAfterAuthorized(contextMock, { refresh, useCache }) - - expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'me', { refresh, useCache }) - expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'getOrdersHistory', { refresh, useCache }) - }) - }) -}) diff --git a/core/modules/user/test/unit/store/data.ts b/core/modules/user/test/unit/store/data.ts deleted file mode 100644 index 0ee35070ac..0000000000 --- a/core/modules/user/test/unit/store/data.ts +++ /dev/null @@ -1,82 +0,0 @@ -let user = { - id: 58, - group_id: 1, - groupToken: 'group-three', - default_billing: '62', - default_shipping: '48', - created_at: '2018-01-23 15:30:00', - updated_at: '2018-03-04 06:39:28', - created_in: 'Default Store View', - email: 'examplename@example.com', - firstname: 'ExampleFirstName', - lastname: 'ExampleLastName', - store_id: 1, - website_id: 1, - addresses: [ - { - id: 48, - customer_id: 58, - region: { - region_code: null, - region: null, - region_id: 0 - }, - region_id: 0, - country_id: 'CountryId', - street: ['Street', '12'], - telephone: '', - postcode: '51-169', - city: 'City', - firstname: 'ExampleFirstName', - lastname: 'ExampleLastName', - default_shipping: true - }, - { - id: 62, - customer_id: 58, - region: { - region_code: null, - region: null, - region_id: 0 - }, - region_id: 0, - country_id: 'CountryId', - street: ['Street', '12'], - company: 'example', - telephone: '', - postcode: '51-169', - city: 'City', - firstname: 'ExampleFirstName', - lastname: 'ExampleLastName', - vat_id: 'vatidhere42342', - default_billing: true - } - ], - 'disable_auto_group_change': 0 -}; -let lastUserToken = 'current-refresh-token'; -let responseOb = { - code: 200, - result: lastUserToken, - meta: 'meta' -}; -let email = 'examplename@example.com'; -let username = 'username'; -let password = 'Password456'; -let customer = { - email: 'examplename@example.com', - firstname: 'ExampleFirstName', - lastname: 'ExampleLastName', - addresses: 'addr' -}; -let ordersHistory = 'orders-history'; -let refresh = true; -let useCache = true; -let resolvedFromCache = true; -let pageSize = 20; -let currentPage = 1; - -export { - user, lastUserToken, responseOb, email, username, password, customer, - ordersHistory, refresh, useCache, resolvedFromCache, pageSize, currentPage -} diff --git a/core/modules/user/test/unit/store/mutations.spec.ts b/core/modules/user/test/unit/store/mutations.spec.ts deleted file mode 100644 index 6fd20ec91a..0000000000 --- a/core/modules/user/test/unit/store/mutations.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -import * as types from '../../../store/mutation-types' -import userMutations from '../../../store/mutations' -import * as data from './data' - -jest.mock('@vue-storefront/core/lib/logger', () => ({ - Logger: { - log: jest.fn(() => () => { - }) - } -})); - -describe('User mutations', () => { - beforeEach(() => { - jest.clearAllMocks() - }) - - describe('USER_TOKEN_CHANGED', () => { - it('should assign new user token', () => { - const stateMock = { - token: '' - } - const expectedState = { - token: data.lastUserToken - } - const wrapper = (mutations: any) => mutations[types.USER_TOKEN_CHANGED](stateMock, { newToken: data.lastUserToken }) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - it('should assign new token and new refreshToken', () => { - const stateMock = { - token: '', - refreshToken: '' - } - const expectedState = { - token: data.lastUserToken, - refreshToken: 'refresh-token' - } - const wrapper = (mutations: any) => mutations[types.USER_TOKEN_CHANGED](stateMock, { - newToken: data.lastUserToken, - meta: { refreshToken: 'refresh-token' } - }) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('USER_START_SESSION', () => { - it('should assign session_started', () => { - jest.isolateModules(() => { - let dateTest = new Date(Date.now()); - jest - .spyOn(global, 'Date') - .mockImplementationOnce(() => dateTest.toDateString()); - - const stateMock = { - session_started: new Date() - } - const expectedState = { - session_started: new Date() - } - const wrapper = (mutations: any) => mutations[types.USER_START_SESSION](stateMock) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - }) - - describe('USER_GROUP_TOKEN_CHANGED', () => { - it('should assign token to groupToken', () => { - const stateMock = { - groupToken: '' - } - const expectedState = { - groupToken: data.user.groupToken - } - const wrapper = (mutations: any) => mutations[types.USER_GROUP_TOKEN_CHANGED](stateMock, data.user.groupToken) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('USER_GROUP_CHANGED', () => { - it('should assign groupid', () => { - const stateMock = { - groupId: null - } - const expectedState = { - groupId: data.user.group_id - } - const wrapper = (mutations: any) => mutations[types.USER_GROUP_CHANGED](stateMock, data.user.group_id) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('USER_INFO_LOADED', () => { - it('should assign current user', () => { - const stateMock = { - current: null - } - const expectedState = { - current: data.user - } - const wrapper = (mutations: any) => mutations[types.USER_INFO_LOADED](stateMock, data.user) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('USER_ORDERS_HISTORY_LOADED', () => { - it('should assign orders history', () => { - const stateMock = { - orders_history: null - } - const expectedState = { - orders_history: data.ordersHistory - } - const wrapper = (mutations: any) => mutations[types.USER_ORDERS_HISTORY_LOADED](stateMock, data.ordersHistory) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('USER_END_SESSION', () => { - it('should clear current user token, current user info and session_started', () => { - const stateMock = { - token: data.lastUserToken, - current: data.user, - session_started: new Date() - } - const expectedState = { - token: '', - current: null, - session_started: null - } - const wrapper = (mutations: any) => mutations[types.USER_END_SESSION](stateMock) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) - - describe('USER_LOCAL_DATA_LOADED', () => { - it('should assign readed boolean value to local_data_loaded', () => { - const stateMock = { - local_data_loaded: false - } - const expectedState = { - local_data_loaded: true - } - const wrapper = (mutations: any) => mutations[types.USER_LOCAL_DATA_LOADED](stateMock, true) - - wrapper(userMutations) - - expect(stateMock).toEqual(expectedState) - }) - }) -}) diff --git a/core/modules/user/types/UserProfile.ts b/core/modules/user/types/UserProfile.ts deleted file mode 100644 index 61885a5991..0000000000 --- a/core/modules/user/types/UserProfile.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface UserProfile { - customer: { - email: string, - firstname: string, - lastname: string, - website_id?: number | string, - addresses?: { - firstname: string, - lastname: string, - street: (string | number)[], - city: string, - region?: { - region: string | null, - [k: string]: any - }, - country_id: string | null, - postcode: string | null, - company?: string, - telephone?: string, - default_billing?: boolean, - default_shipping?: boolean, - [k: string]: any - }[], - custom_attributes?: { - attribute_code: string, - value: string | null, - [k: string]: any - }[], - [k: string]: any - } -} diff --git a/core/modules/user/types/UserState.ts b/core/modules/user/types/UserState.ts deleted file mode 100644 index d9729c6ce3..0000000000 --- a/core/modules/user/types/UserState.ts +++ /dev/null @@ -1,13 +0,0 @@ -export default interface UserState { - token: string, - refreshToken: string, - groupToken: string, - groupId: any, - current: { - email: string - } | null, - current_storecode: string, - session_started: Date, - orders_history: any, - local_data_loaded: boolean -} diff --git a/core/modules/wishlist/components/AddToWishlist.ts b/core/modules/wishlist/components/AddToWishlist.ts deleted file mode 100644 index 8064358f52..0000000000 --- a/core/modules/wishlist/components/AddToWishlist.ts +++ /dev/null @@ -1,23 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import { WishlistModule } from '../' -import wishlistMountedMixin from '@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin' -import { registerModule } from '@vue-storefront/core/lib/modules'; - -export const AddToWishlist = { - name: 'AddToWishlist', - mixins: [wishlistMountedMixin], - props: { - product: { - required: true, - type: Object - } - }, - created () { - registerModule(WishlistModule) - }, - methods: { - addToWishlist (product: Product) { - return this.$store.state['wishlist'] ? this.$store.dispatch('wishlist/addItem', product) : false - } - } -} diff --git a/core/modules/wishlist/components/IsOnWishlist.ts b/core/modules/wishlist/components/IsOnWishlist.ts deleted file mode 100644 index db8d650496..0000000000 --- a/core/modules/wishlist/components/IsOnWishlist.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { WishlistModule } from '../' -import wishlistMountedMixin from '@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin' -import { registerModule } from '@vue-storefront/core/lib/modules'; - -export const IsOnWishlist = { - name: 'isOnWishlist', - mixins: [wishlistMountedMixin], - props: { - product: { - required: true, - type: Object - } - }, - created () { - registerModule(WishlistModule) - }, - computed: { - isOnWishlist () { - return this.$store.getters['wishlist/isOnWishlist'](this.product) - } - } -} diff --git a/core/modules/wishlist/components/Product.ts b/core/modules/wishlist/components/Product.ts deleted file mode 100644 index cc2af11e2a..0000000000 --- a/core/modules/wishlist/components/Product.ts +++ /dev/null @@ -1,23 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import wishlistMountedMixin from '@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin' - -export const WishlistProduct = { - name: 'Product', - mixins: [wishlistMountedMixin], - props: { - product: { - type: Object, - required: true - } - }, - computed: { - thumbnail () { - return this.getThumbnail(this.product.image, 150, 150) - } - }, - methods: { - removeFromWishlist (product: Product) { - return this.$store.state['wishlist'] ? this.$store.dispatch('wishlist/removeItem', product) : false - } - } -} diff --git a/core/modules/wishlist/components/RemoveFromWishlist.ts b/core/modules/wishlist/components/RemoveFromWishlist.ts deleted file mode 100644 index a359e02663..0000000000 --- a/core/modules/wishlist/components/RemoveFromWishlist.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Product from '@vue-storefront/core/modules/catalog/types/Product' -import { WishlistModule } from '../' -import wishlistMountedMixin from '@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin' -import { registerModule } from '@vue-storefront/core/lib/modules'; - -export const RemoveFromWishlist = { - name: 'RemoveFromWishlist', - mixins: [wishlistMountedMixin], - props: { - product: { - required: true, - type: Object - } - }, - methods: { - removeFromWishlist (product: Product) { - registerModule(WishlistModule) - this.$store.dispatch('wishlist/removeItem', product) - } - } -} diff --git a/core/modules/wishlist/components/Wishlist.ts b/core/modules/wishlist/components/Wishlist.ts deleted file mode 100644 index 0fbb366eef..0000000000 --- a/core/modules/wishlist/components/Wishlist.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { WishlistModule } from '../' -import wishlistMountedMixin from '@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin' -import { registerModule } from '@vue-storefront/core/lib/modules'; - -export const Wishlist = { - name: 'Wishlist', - mixins: [wishlistMountedMixin], - created () { - registerModule(WishlistModule) - }, - computed: { - isWishlistOpen () { - return this.$store.state.ui.wishlist - }, - productsInWishlist () { - return this.$store.state.wishlist.items - } - }, - methods: { - closeWishlist () { - this.$store.dispatch('ui/toggleWishlist') - } - } -} diff --git a/core/modules/wishlist/components/WishlistButton.ts b/core/modules/wishlist/components/WishlistButton.ts deleted file mode 100644 index 3475856afd..0000000000 --- a/core/modules/wishlist/components/WishlistButton.ts +++ /dev/null @@ -1,14 +0,0 @@ -import wishlistMountedMixin from '@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin' -import { mapGetters } from 'vuex' - -export const WishlistButton = { - mixins: [wishlistMountedMixin], - computed: { - ...mapGetters('wishlist', ['getWishlistItemsCount']) - }, - methods: { - toggleWishlist () { - this.$store.dispatch('ui/toggleWishlist') - } - } -} diff --git a/core/modules/wishlist/index.ts b/core/modules/wishlist/index.ts deleted file mode 100644 index 0b54107bb3..0000000000 --- a/core/modules/wishlist/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { wishlistStore } from './store' -import whishListPersistPlugin from './store/whishListPersistPlugin' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -export const WishlistModule: StorefrontModule = function ({ store }) { - StorageManager.init('wishlist') - store.registerModule('wishlist', wishlistStore) - store.subscribe(whishListPersistPlugin) -} diff --git a/core/modules/wishlist/mixins/wishlistMountedMixin.js b/core/modules/wishlist/mixins/wishlistMountedMixin.js deleted file mode 100644 index c73cd7feb2..0000000000 --- a/core/modules/wishlist/mixins/wishlistMountedMixin.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This is mixin for all module components. - * Invoke here actions, which were not invoked on server-side render, like reading LocalStorage or checking window size. - */ - -export default { - mounted () { - this.$store.dispatch('wishlist/load') - } -} diff --git a/core/modules/wishlist/store/actions.ts b/core/modules/wishlist/store/actions.ts deleted file mode 100644 index 3ab1218165..0000000000 --- a/core/modules/wishlist/store/actions.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState' -import WishlistState from '../types/WishlistState' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -const actions: ActionTree = { - clear (context) { - context.commit(types.WISH_DEL_ALL_ITEMS, []) - }, - async load ({ commit, getters, dispatch }, force: boolean = false) { - if (!force && getters.isWishlistLoaded) return - commit(types.SET_WISHLIST_LOADED) - const storedItems = await dispatch('loadFromCache') - commit(types.WISH_LOAD_WISH, storedItems) - }, - loadFromCache () { - const wishlistStorage = StorageManager.get('wishlist') - return wishlistStorage.getItem('current-wishlist') - }, - addItem ({ commit }, product) { - commit(types.WISH_ADD_ITEM, { product }) - }, - removeItem ({ commit }, product) { - commit(types.WISH_DEL_ITEM, { product }) - } -} - -export default actions diff --git a/core/modules/wishlist/store/getters.ts b/core/modules/wishlist/store/getters.ts deleted file mode 100644 index f7708ca2a9..0000000000 --- a/core/modules/wishlist/store/getters.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import WishlistState from '../types/WishlistState' - -const getters: GetterTree = { - isOnWishlist: state => product => - state.items.some(p => p.sku === product.sku), - isWishlistLoaded: state => state.loaded, - getWishlistItemsCount: state => state.items.length -} - -export default getters diff --git a/core/modules/wishlist/store/index.ts b/core/modules/wishlist/store/index.ts deleted file mode 100644 index ff63f9ac3c..0000000000 --- a/core/modules/wishlist/store/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from 'vuex' -import actions from './actions' -import mutations from './mutations' -import getters from './getters' -import RootState from '@vue-storefront/core/types/RootState' -import WishlistState from '../types/WishlistState' - -export const wishlistStore: Module = { - namespaced: true, - state: { - loaded: false, - items: [] - }, - actions, - mutations, - getters -} diff --git a/core/modules/wishlist/store/mutation-types.ts b/core/modules/wishlist/store/mutation-types.ts deleted file mode 100644 index 324b607a7b..0000000000 --- a/core/modules/wishlist/store/mutation-types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const SN_WISHLIST = 'wishlist' -export const WISH_ADD_ITEM = SN_WISHLIST + '/ADD' -export const WISH_DEL_ITEM = SN_WISHLIST + '/DEL' -export const WISH_DEL_ALL_ITEMS = SN_WISHLIST + '/DEL_ALL' -export const WISH_LOAD_WISH = SN_WISHLIST + '/LOAD' -export const SET_WISHLIST_LOADED = `${SN_WISHLIST}/SET_WISHLIST_LOADED` diff --git a/core/modules/wishlist/store/mutations.ts b/core/modules/wishlist/store/mutations.ts deleted file mode 100644 index 547cc78d26..0000000000 --- a/core/modules/wishlist/store/mutations.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import WishlistState from '../types/WishlistState' - -const mutations: MutationTree = { - [types.WISH_ADD_ITEM] (state, { product }) { - const record = state.items.find(p => p.sku === product.sku) - if (!record) { - state.items.push({ - ...product, - qty: 1 - }) - } - }, - [types.WISH_DEL_ITEM] (state, { product }) { - state.items = state.items.filter(p => p.sku !== product.sku) - }, - [types.WISH_LOAD_WISH] (state, storedItems = []) { - state.items = storedItems || [] - }, - [types.WISH_DEL_ALL_ITEMS] (state) { - state.items = [] - }, - [types.SET_WISHLIST_LOADED] (state, isLoaded: boolean = true) { - state.loaded = isLoaded - } -} - -export default mutations diff --git a/core/modules/wishlist/store/whishListPersistPlugin.ts b/core/modules/wishlist/store/whishListPersistPlugin.ts deleted file mode 100644 index 2815a2d2b0..0000000000 --- a/core/modules/wishlist/store/whishListPersistPlugin.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as types from './mutation-types' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' - -const mutationToWatch = [types.WISH_ADD_ITEM, types.WISH_DEL_ITEM, types.WISH_DEL_ALL_ITEMS] - .map(m => `wishlist/${m}`) - -const whishListPersistPlugin = (mutation, state) => { - const whishListStorage = StorageManager.get('wishlist') - - if (mutationToWatch.includes(mutation.type)) { - whishListStorage.setItem('current-wishlist', state.wishlist.items) - } -} - -export default whishListPersistPlugin diff --git a/core/modules/wishlist/test/unit/components/AddToWishlist.spec.ts b/core/modules/wishlist/test/unit/components/AddToWishlist.spec.ts deleted file mode 100644 index baf86b3a0a..0000000000 --- a/core/modules/wishlist/test/unit/components/AddToWishlist.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { registerModule } from '@vue-storefront/core/lib/modules'; -import { WishlistModule } from '@vue-storefront/core/modules/wishlist'; -import { AddToWishlist } from '@vue-storefront/core/modules/wishlist/components/AddToWishlist'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({})); -jest.mock('@vue-storefront/core/lib/modules', () => ({ registerModule: jest.fn() })); -jest.mock('@vue-storefront/core/helpers', () => ({ once: () => ({}) })); -jest.mock('@vue-storefront/core/modules/wishlist/store', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/store/whishListPersistPlugin', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin', () => ({})); - -describe('AddToWishlist', () => { - let product; - - beforeEach(() => { - jest.clearAllMocks(); - product = { - sku: 'example_sku', - image: 'example_image' - }; - }); - - it('creates a component', () => { - const wrapper = mountMixin(AddToWishlist, { - propsData: { product } - }); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('component has been registered in "created" hook', () => { - mountMixin(AddToWishlist, { - propsData: { product } - }); - - expect(registerModule).toHaveBeenCalledWith(WishlistModule); - }); - - it('addToWishList method dispatches wishlist/addItem action', () => { - const mockStore = { - modules: { - wishlist: { - actions: { - addItem: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(AddToWishlist, mockStore, { - propsData: { product } - }); - - (wrapper.vm as any).addToWishlist(product); - - expect(mockStore.modules.wishlist.actions.addItem).toHaveBeenCalledWith(expect.anything(), product); - }); -}); diff --git a/core/modules/wishlist/test/unit/components/IsOnWishlist.spec.ts b/core/modules/wishlist/test/unit/components/IsOnWishlist.spec.ts deleted file mode 100644 index dcd3b54f68..0000000000 --- a/core/modules/wishlist/test/unit/components/IsOnWishlist.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { registerModule } from '@vue-storefront/core/lib/modules'; -import { WishlistModule } from '@vue-storefront/core/modules/wishlist'; -import { IsOnWishlist } from '@vue-storefront/core/modules/wishlist/components/IsOnWishlist'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({})); -jest.mock('@vue-storefront/core/lib/modules', () => ({ registerModule: jest.fn() })); -jest.mock('@vue-storefront/core/helpers', () => ({ once: () => ({}) })); -jest.mock('@vue-storefront/core/modules/wishlist/store', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/store/whishListPersistPlugin', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin', () => ({})); - -describe('IsOnWishlist', () => { - let product; - - beforeEach(() => { - jest.clearAllMocks(); - product = { - sku: 'example_sku', - image: 'example_image' - }; - }); - - it('creates a component', () => { - const wrapper = mountMixin(IsOnWishlist, { - propsData: { product } - }); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('component has been registered in "created" hook', () => { - mountMixin(IsOnWishlist, { - propsData: { product } - }); - - expect(registerModule).toHaveBeenCalledWith(WishlistModule); - }); - - it('isOnWishlist computed property calls wishlist/isOnWishlist getter with product from prop', () => { - const isOnWishlistGetter = jest.fn(() => true); - const mockStore = { - modules: { - wishlist: { - getters: { - isOnWishlist: () => isOnWishlistGetter - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(IsOnWishlist, mockStore, { - propsData: { product } - }); - - const isOnWishlist = (wrapper.vm as any).isOnWishlist; - - expect(isOnWishlistGetter).toHaveBeenCalledWith(product); - expect(isOnWishlist).toBe(true); - }); -}); diff --git a/core/modules/wishlist/test/unit/components/Product.spec.ts b/core/modules/wishlist/test/unit/components/Product.spec.ts deleted file mode 100644 index 207cc9e8af..0000000000 --- a/core/modules/wishlist/test/unit/components/Product.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { WishlistProduct } from '@vue-storefront/core/modules/wishlist/components/Product'; - -jest.mock('@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin', () => ({})); - -describe('Product', () => { - let product; - - beforeEach(() => { - jest.clearAllMocks(); - product = { - sku: 'example_sku', - image: 'example_image' - }; - }); - - it('creates a component', () => { - const wrapper = mountMixin(WishlistProduct, { - propsData: { product } - }); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('thumbnail computed property calls getThumbnail method', () => { - const getThumbnail = jest.fn(() => 'thumbnail'); - const wrapper = mountMixin(WishlistProduct, { - propsData: { product }, - methods: { getThumbnail } - }); - - const thumbnail = (wrapper.vm as any).thumbnail; - - expect(getThumbnail).toHaveBeenCalledWith(product.image, 150, 150); - expect(thumbnail).toBe('thumbnail'); - }); - - it('removeFromWishlist method dispatches wishlist/removeItem action', () => { - const mockStore = { - modules: { - wishlist: { - actions: { - removeItem: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(WishlistProduct, mockStore, { - propsData: { product } - }); - - (wrapper.vm as any).removeFromWishlist(product); - - expect(mockStore.modules.wishlist.actions.removeItem).toHaveBeenCalledWith(expect.anything(), product); - }); -}); diff --git a/core/modules/wishlist/test/unit/components/RemoveFromWishlist.spec.ts b/core/modules/wishlist/test/unit/components/RemoveFromWishlist.spec.ts deleted file mode 100644 index f12cfb67ee..0000000000 --- a/core/modules/wishlist/test/unit/components/RemoveFromWishlist.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { registerModule } from '@vue-storefront/core/lib/modules'; -import { WishlistModule } from '@vue-storefront/core/modules/wishlist'; -import { RemoveFromWishlist } from '@vue-storefront/core/modules/wishlist/components/RemoveFromWishlist'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({})); -jest.mock('@vue-storefront/core/lib/modules', () => ({ registerModule: jest.fn() })); -jest.mock('@vue-storefront/core/helpers', () => ({ once: () => ({}) })); -jest.mock('@vue-storefront/core/modules/wishlist/store', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/store/whishListPersistPlugin', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin', () => ({})); - -describe('RemoveFromWishlist', () => { - let product; - - beforeEach(() => { - jest.clearAllMocks(); - product = { - sku: 'example_sku', - image: 'example_image' - }; - }); - - it('creates a component', () => { - const wrapper = mountMixin(RemoveFromWishlist, { - propsData: { product } - }); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('removeFromWishlist method registers component and dispatches wishlist/removeItem action', () => { - const mockStore = { - modules: { - wishlist: { - actions: { - removeItem: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(RemoveFromWishlist, mockStore, { - propsData: { product } - }); - - (wrapper.vm as any).removeFromWishlist(product); - - expect(registerModule).toHaveBeenCalledWith(WishlistModule); - expect(mockStore.modules.wishlist.actions.removeItem).toHaveBeenCalledWith(expect.anything(), product); - }); -}); diff --git a/core/modules/wishlist/test/unit/components/Wishlist.spec.ts b/core/modules/wishlist/test/unit/components/Wishlist.spec.ts deleted file mode 100644 index e23505b283..0000000000 --- a/core/modules/wishlist/test/unit/components/Wishlist.spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { registerModule } from '@vue-storefront/core/lib/modules'; -import { WishlistModule } from '@vue-storefront/core/modules/wishlist'; -import { Wishlist } from '@vue-storefront/core/modules/wishlist/components/Wishlist'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({})); -jest.mock('@vue-storefront/core/lib/modules', () => ({ registerModule: jest.fn() })); -jest.mock('@vue-storefront/core/helpers', () => ({ once: () => ({}) })); -jest.mock('@vue-storefront/core/modules/wishlist/store', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/store/whishListPersistPlugin', () => ({})); -jest.mock('@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin', () => ({})); - -describe('Wishlist', () => { - let product; - - beforeEach(() => { - jest.clearAllMocks(); - product = { - sku: 'example_sku', - image: 'example_image' - }; - }); - - it('creates a component', () => { - const wrapper = mountMixin(Wishlist, { - propsData: { product } - }); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('component has been registered in "created" hook', () => { - mountMixin(Wishlist, { - propsData: { product } - }); - - expect(registerModule).toHaveBeenCalledWith(WishlistModule); - }); - - it('isWishlistOpen computed property returns ui/wishlist state', () => { - const mockStore = { - modules: { - ui: { - state: { - wishlist: true - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Wishlist, mockStore, { - propsData: { product } - }); - - const result = (wrapper.vm as any).isWishlistOpen; - - expect(result).toBe(true); - }); - - it('productsInWishlist computed property returns wishlist/items state', () => { - const wishlistItems = [{ sku: 1 }, { sku: 2 }, { sku: 3 }]; - const mockStore = { - modules: { - wishlist: { - state: { - items: wishlistItems - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Wishlist, mockStore, { - propsData: { product } - }); - - const result = (wrapper.vm as any).productsInWishlist; - - expect(result).toBe(wishlistItems); - }); - - it('closeWishlist method dispatches ui/toggleWishlist action', () => { - const mockStore = { - modules: { - ui: { - actions: { - toggleWishlist: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(Wishlist, mockStore, { - propsData: { product } - }); - - (wrapper.vm as any).closeWishlist(); - - expect(mockStore.modules.ui.actions.toggleWishlist).toHaveBeenCalled(); - }); -}); diff --git a/core/modules/wishlist/test/unit/components/WishlistButton.spec.ts b/core/modules/wishlist/test/unit/components/WishlistButton.spec.ts deleted file mode 100644 index 8ceb929a4c..0000000000 --- a/core/modules/wishlist/test/unit/components/WishlistButton.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { mountMixin, mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import { WishlistButton } from '@vue-storefront/core/modules/wishlist/components/WishlistButton'; - -jest.mock('@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin', () => ({})); - -describe('WishlistButton', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('creates a component', () => { - const wrapper = mountMixin(WishlistButton); - - expect(wrapper.exists()).toBe(true); - expect(wrapper.isVueInstance()).toBe(true); - }); - - it('getWishlistItemsCount computed property calls wishlist/getWishlistItemsCount getter', () => { - const getWishlistItemsCountGetter = jest.fn(() => 42); - const mockStore = { - modules: { - wishlist: { - getters: { - getWishlistItemsCount: getWishlistItemsCountGetter - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(WishlistButton, mockStore); - - const getWishlistItemsCount = (wrapper.vm as any).getWishlistItemsCount; - - expect(getWishlistItemsCountGetter).toHaveBeenCalled(); - expect(getWishlistItemsCount).toBe(42); - }); - - it('toggleWishlist method dispatches ui/toggleWishlist action', () => { - const mockStore = { - modules: { - ui: { - actions: { - toggleWishlist: jest.fn() - }, - namespaced: true - } - } - }; - - const wrapper = mountMixinWithStore(WishlistButton, mockStore); - - (wrapper.vm as any).toggleWishlist(); - - expect(mockStore.modules.ui.actions.toggleWishlist).toHaveBeenCalled(); - }); -}); diff --git a/core/modules/wishlist/test/unit/mixins/wishlistMountedMixin.spec.ts b/core/modules/wishlist/test/unit/mixins/wishlistMountedMixin.spec.ts deleted file mode 100644 index b966e4159d..0000000000 --- a/core/modules/wishlist/test/unit/mixins/wishlistMountedMixin.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { mountMixinWithStore } from '@vue-storefront/unit-tests/utils'; -import wishlistMountedMixin from '@vue-storefront/core/modules/wishlist/mixins/wishlistMountedMixin'; - -describe('wishlistMountedMixin', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('dispatches wishlist/load action on mount', () => { - const mockStore = { - modules: { - wishlist: { - actions: { - load: jest.fn() - }, - namespaced: true - } - } - }; - - mountMixinWithStore(wishlistMountedMixin, mockStore); - - expect(mockStore.modules.wishlist.actions.load).toHaveBeenCalled(); - }); -}); diff --git a/core/modules/wishlist/test/unit/store/actions.spec.ts b/core/modules/wishlist/test/unit/store/actions.spec.ts deleted file mode 100644 index f6eb698a3c..0000000000 --- a/core/modules/wishlist/test/unit/store/actions.spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -import * as types from '@vue-storefront/core/modules/wishlist/store/mutation-types'; -import wishlistActions from '@vue-storefront/core/modules/wishlist/store/actions'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); - -describe('Wishlist actions', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('clear', () => { - it('should delete all items', () => { - const mockContext = { - commit: jest.fn() - }; - - (wishlistActions as any).clear(mockContext); - - expect(mockContext.commit).toHaveBeenCalledWith(types.WISH_DEL_ALL_ITEMS, []); - }); - }); - - describe('load', () => { - let wishlist; - - beforeEach(() => { - wishlist = [{ sku: 1 }, { sku: 2 }, { sku: 3 }]; - }); - - it('should not load wishlist if it is already loaded', () => { - const mockContext = { - commit: jest.fn(), - dispatch: jest.fn(), - getters: { - isWishlistLoaded: true - } - }; - - (wishlistActions as any).load(mockContext); - - expect(mockContext.commit).not.toHaveBeenCalled(); - expect(mockContext.dispatch).not.toHaveBeenCalled(); - }); - - it('should load wishlist if it is not loaded', async () => { - const mockContext = { - commit: jest.fn(), - dispatch: jest.fn(() => { - return new Promise(resolve => resolve(wishlist)); - }), - getters: { - isWishlistLoaded: false - } - }; - - await (wishlistActions as any).load(mockContext); - - expect(mockContext.commit).toHaveBeenCalledTimes(2); - expect(mockContext.commit).toHaveBeenNthCalledWith(1, types.SET_WISHLIST_LOADED); - expect(mockContext.commit).toHaveBeenNthCalledWith(2, types.WISH_LOAD_WISH, wishlist); - expect(mockContext.dispatch).toHaveBeenCalledWith('loadFromCache'); - }); - - it('should load wishlist with "force" argument even if it is already loaded', async () => { - const mockContext = { - commit: jest.fn(), - dispatch: jest.fn(() => { - return new Promise(resolve => resolve(wishlist)); - }), - getters: { - isWishlistLoaded: true - } - }; - - await (wishlistActions as any).load(mockContext, true); - - expect(mockContext.commit).toHaveBeenCalledTimes(2); - expect(mockContext.commit).toHaveBeenNthCalledWith(1, types.SET_WISHLIST_LOADED); - expect(mockContext.commit).toHaveBeenNthCalledWith(2, types.WISH_LOAD_WISH, wishlist); - expect(mockContext.dispatch).toHaveBeenCalledWith('loadFromCache'); - }); - }); - - describe('loadFromCache', () => { - it('should load wishlist from cache', () => { - const mockGetItem = jest.fn(() => ({})); - - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - getItem: mockGetItem - })); - - const wishlistStorage = (wishlistActions as any).loadFromCache(); - - expect(StorageManager.get).toHaveBeenCalledWith('wishlist'); - expect(mockGetItem).toHaveBeenCalledWith('current-wishlist'); - expect(wishlistStorage).toEqual({}); - }); - }); - - describe('addItem', () => { - it('should add product to wishlist', () => { - const product = { sku: 1 }; - const mockContext = { - commit: jest.fn() - }; - - (wishlistActions as any).addItem(mockContext, product); - - expect(mockContext.commit).toHaveBeenCalledWith(types.WISH_ADD_ITEM, { product }); - }); - }); - - describe('removeItem', () => { - it('should remove product from wishlist', () => { - const product = { sku: 1 }; - const mockContext = { - commit: jest.fn() - }; - - (wishlistActions as any).removeItem(mockContext, product); - - expect(mockContext.commit).toHaveBeenCalledWith(types.WISH_DEL_ITEM, { product }); - }); - }); -}); diff --git a/core/modules/wishlist/test/unit/store/getters.spec.ts b/core/modules/wishlist/test/unit/store/getters.spec.ts deleted file mode 100644 index 9bf2512ea8..0000000000 --- a/core/modules/wishlist/test/unit/store/getters.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import wishlistGetters from '@vue-storefront/core/modules/wishlist/store/getters'; - -describe('Wishlist getters', () => { - it('should inform if given product is on wishlist', () => { - const mockState = { - items: [ - { sku: 1 }, { sku: 2 }, { sku: 3 } - ] - }; - - const productExists = (wishlistGetters as any).isOnWishlist(mockState)({ sku: 1 }); - const productDoesNotExist = (wishlistGetters as any).isOnWishlist(mockState)({ sku: 123 }); - - expect(productExists).toBe(true); - expect(productDoesNotExist).toBe(false); - }); - - it('should inform if wishlist is loaded', () => { - const wishlistIsLoaded = (wishlistGetters as any).isWishlistLoaded({ loaded: true }); - const wishlistIsNotLoaded = (wishlistGetters as any).isWishlistLoaded({ loaded: false }); - - expect(wishlistIsLoaded).toBe(true); - expect(wishlistIsNotLoaded).toBe(false); - }); - - it('should return number of products in wishlist', () => { - const mockState = { - items: [ - { sku: 1 }, { sku: 2 }, { sku: 3 } - ] - }; - - const numberOfProducts = (wishlistGetters as any).getWishlistItemsCount(mockState); - - expect(numberOfProducts).toBe(mockState.items.length); - }); -}); diff --git a/core/modules/wishlist/test/unit/store/mutations.spec.ts b/core/modules/wishlist/test/unit/store/mutations.spec.ts deleted file mode 100644 index bba28e6b4f..0000000000 --- a/core/modules/wishlist/test/unit/store/mutations.spec.ts +++ /dev/null @@ -1,147 +0,0 @@ -import * as types from '../../../store/mutation-types'; -import wishlistMutations from '../../../store/mutations' - -describe('Wishlist mutations', () => { - let product1; - let product2; - let product3; - - beforeEach(() => { - product1 = { - sku: 'example-product-id1', - qty: 123 - }; - - product2 = { - sku: 'example-product-id2', - qty: 456 - }; - - product3 = { - sku: 'example-product-id3', - qty: 789 - }; - }); - - describe('WISH_ADD_ITEM', () => { - it('should add exactly one product to wishlist if it does not exist there', () => { - const mockState = { - items: [] - }; - - const expectedState = { - items: [{ ...product1, qty: 1 }] - }; - - (wishlistMutations as any)[types.WISH_ADD_ITEM](mockState, { product: product1 }); - - expect(mockState).toEqual(expectedState); - }); - - it('should not add product to wishlist if it exists there', () => { - const mockState = { - items: [{ ...product1 }] - }; - - const expectedState = { - items: [{ ...product1 }] - }; - - (wishlistMutations as any)[types.WISH_ADD_ITEM](mockState, { product: product1 }); - - expect(mockState).toEqual(expectedState); - }); - }); - - describe('WISH_DEL_ITEM', () => { - it('should remove existing product from wishlist', () => { - const mockState = { - items: [{ ...product1 }, { ...product2 }, { ...product3 }] - }; - - const expectedState = { - items: [{ ...product1 }, { ...product2 }] - }; - - (wishlistMutations as any)[types.WISH_DEL_ITEM](mockState, { product: product3 }); - - expect(mockState).toEqual(expectedState); - }); - - it('should not modify wishlist if product does not exist there', () => { - const mockState = { - items: [{ ...product1 }, { ...product2 }] - }; - - const expectedState = { - items: [{ ...product1 }, { ...product2 }] - }; - - (wishlistMutations as any)[types.WISH_DEL_ITEM](mockState, { product: product3 }); - - expect(mockState).toEqual(expectedState); - }); - }); - - describe('WISH_LOAD_WISH', () => { - it('should init wishlist', () => { - const mockState = { - items: [{ ...product1 }] - }; - - const expectedState = { - items: [{ ...product2 }, { ...product3 }] - }; - - (wishlistMutations as any)[types.WISH_LOAD_WISH](mockState, [{ ...product2 }, { ...product3 }]); - - expect(mockState).toEqual(expectedState); - }); - - it('should init wishlist with empty array if loaded wishlist is falsy', () => { - const mockState = { - items: [{ ...product1 }] - }; - - const expectedState = { - items: [] - }; - - (wishlistMutations as any)[types.WISH_LOAD_WISH](mockState, null); - - expect(mockState).toEqual(expectedState); - }); - }); - - describe('WISH_DEL_ALL_ITEMS', () => { - it('should delete all products from wishlist', () => { - const mockState = { - items: [{ ...product1 }, { ...product2 }, { ...product3 }] - }; - - const expectedState = { - items: [] - }; - - (wishlistMutations as any)[types.WISH_DEL_ALL_ITEMS](mockState); - - expect(mockState).toEqual(expectedState); - }); - }); - - describe('SET_WISHLIST_LOADED', () => { - it('should set loaded state for wishlist', () => { - const mockState = { - loaded: false - }; - - const expectedState = { - loaded: true - }; - - (wishlistMutations as any)[types.SET_WISHLIST_LOADED](mockState); - - expect(mockState).toEqual(expectedState); - }); - }); -}); diff --git a/core/modules/wishlist/test/unit/store/whishListPersistPlugin.spec.ts b/core/modules/wishlist/test/unit/store/whishListPersistPlugin.spec.ts deleted file mode 100644 index bc69e2365c..0000000000 --- a/core/modules/wishlist/test/unit/store/whishListPersistPlugin.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as types from '@vue-storefront/core/modules/wishlist/store/mutation-types'; -import whishListPersistPlugin from '@vue-storefront/core/modules/wishlist/store/whishListPersistPlugin'; -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; - -jest.mock('@vue-storefront/core/lib/storage-manager', () => ({ - StorageManager: { - get: jest.fn() - } -})); - -describe('whishListPersistPlugin', () => { - let mockSetItem; - let mockState; - - beforeEach(() => { - mockSetItem = jest.fn(); - - (StorageManager.get as jest.Mock).mockImplementation(() => ({ - setItem: mockSetItem - })); - - mockState = { - wishlist: { - items: [ - { sku: 1 }, { sku: 2 }, { sku: 3 } - ] - } - }; - - jest.clearAllMocks(); - }); - - it('should store wishlist in cache for supported mutations', () => { - const mutations = [ - { type: `wishlist/${types.WISH_ADD_ITEM}` }, - { type: `wishlist/${types.WISH_DEL_ITEM}` }, - { type: `wishlist/${types.WISH_DEL_ALL_ITEMS}` } - ]; - - mutations.forEach(mutation => whishListPersistPlugin(mutation, mockState)); - - expect(StorageManager.get).toHaveBeenCalledTimes(mutations.length); - expect(StorageManager.get).toHaveBeenCalledWith('wishlist'); - expect(mockSetItem).toHaveBeenCalledTimes(mutations.length); - expect(mockSetItem).toHaveBeenCalledWith('current-wishlist', mockState.wishlist.items); - }); - - it('should not store wishlist in cache for unsupported mutations', () => { - const mutations = [ - { type: 'a/b/c' }, - { type: types.WISH_ADD_ITEM }, - { type: types.WISH_DEL_ITEM }, - { type: types.WISH_DEL_ALL_ITEMS }, - { type: `wishlist/${types.WISH_LOAD_WISH}` }, - { type: `wishlist/${types.SET_WISHLIST_LOADED}` } - ]; - - mutations.forEach(mutation => whishListPersistPlugin(mutation, mockState)); - - expect(StorageManager.get).toHaveBeenCalledTimes(mutations.length); - expect(StorageManager.get).toHaveBeenCalledWith('wishlist'); - expect(mockSetItem).not.toHaveBeenCalled(); - }); -}); diff --git a/core/modules/wishlist/types/WishlistState.ts b/core/modules/wishlist/types/WishlistState.ts deleted file mode 100644 index 38e55fe9c3..0000000000 --- a/core/modules/wishlist/types/WishlistState.ts +++ /dev/null @@ -1,7 +0,0 @@ -export default interface WishlistState { - /** - * Informs if wishlist is already loaded from local cache. - */ - loaded: boolean, - items: any[] -} diff --git a/core/package.json b/core/package.json deleted file mode 100644 index fd0b333e1c..0000000000 --- a/core/package.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "name": "@vue-storefront/core", - "version": "1.12.2", - "description": "Vue Storefront Core", - "license": "MIT", - "main": "app.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "dependencies": { - "bodybuilder": "2.2.21", - "compression": "^1.7.4", - "config": "^1.30.0", - "express": "^4.14.0", - "helmet": "^3.23.3", - "html-minifier": "^4.0.0", - "lean-he": "^2.0.0", - "localforage": "^1.7.2", - "lodash-es": "^4.17", - "lru-cache": "^5.1.1", - "query-string": "6.13.2", - "redis-tag-cache": "^1.2.1", - "remove-accents": "^0.4.2", - "uuid": "^7.0.2", - "vue": "^2.6.11", - "vue-carousel": "^0.18", - "vue-i18n": "^8.0.0", - "vue-lazyload": "^1.2.6", - "vue-meta": "^2.3.3", - "vue-observe-visibility": "^0.4.1", - "vue-offline": "^1.0.8", - "vue-router": "3.1.6", - "vue-server-renderer": "^2.6.11", - "vuelidate": "^0.7.5", - "vuex": "^3.1.2", - "vuex-router-sync": "^5.0.0" - }, - "devDependencies": { - "@vue/test-utils": "^1.0.0-beta.19", - "app-root-path": "^2.0.1", - "autoprefixer": "^8.6.2", - "babel-core": "^6.26.0", - "babel-loader": "^8.0.6", - "babel-preset-env": "^1.6.x", - "babel-preset-stage-2": "^6.13.0", - "case-sensitive-paths-webpack-plugin": "^2.1.2", - "command-exists": "^1.2.2", - "commander": "^2.18.0", - "cross-env": "^3.1.4", - "css-loader": "^1.0.0", - "d3-dsv": "^1.0.8", - "detect-installed": "^2.0.4", - "empty-dir": "^1.0.0", - "eslint": "^4.16.0", - "eslint-config-standard": "^11.0.0", - "eslint-friendly-formatter": "^4.0.1", - "eslint-loader": "^2.0.0", - "eslint-plugin-import": "^2.12.0", - "eslint-plugin-node": "^6.0.1", - "eslint-plugin-promise": "^3.7.0", - "eslint-plugin-standard": "^3.1.0", - "eslint-plugin-vue": "^4.5.0", - "eslint-plugin-vue-storefront": "^0.0.1", - "file-loader": "^1.1.11", - "fs-exists-sync": "^0.1.0", - "html-webpack-plugin": "^3.2.0", - "inquirer": "^3.3.0", - "is-windows": "^1.0.1", - "jsonfile": "^4.0.0", - "memory-fs": "^0.4.1", - "mkdirp": "^0.5.1", - "node-sass": "^4.12.0", - "phantomjs-prebuilt": "^2.1.10", - "postcss-flexbugs-fixes": "^3.3.1", - "postcss-loader": "^2.1.5", - "print-message": "^2.1.0", - "sass-loader": "^7.0.3", - "shelljs": "^0.8.1", - "sw-precache-webpack-plugin": "^0.11.5", - "url-loader": "^1.1.2", - "url-parse": "^1.4.4", - "vue-eslint-parser": "^2.0.3", - "vue-loader": "^15.5.1", - "vue-ssr-webpack-plugin": "^3.0.0", - "vue-template-compiler": "^2.6.11", - "webpack": "^4.25.1", - "webpack-cli": "^3.3.11", - "webpack-dev-middleware": "^3.4.0", - "webpack-hot-middleware": "^2.24.3", - "webpack-merge": "^4.2.2" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/core/pages/Category.js b/core/pages/Category.js deleted file mode 100644 index b89817ca4d..0000000000 --- a/core/pages/Category.js +++ /dev/null @@ -1,289 +0,0 @@ -import Vue from 'vue' -import toString from 'lodash-es/toString' -import config from 'config' -import i18n from '@vue-storefront/i18n' -import store from '@vue-storefront/core/store' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { baseFilterProductsQuery, buildFilterProductsQuery, isServer } from '@vue-storefront/core/helpers' -import { htmlDecode } from '@vue-storefront/core/filters/html-decode' -import { currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore' -import Composite from '@vue-storefront/core/mixins/composite' -import { Logger } from '@vue-storefront/core/lib/logger' -import { mapGetters, mapActions } from 'vuex' -import onBottomScroll from '@vue-storefront/core/mixins/onBottomScroll' - -export default { - name: 'Category', - mixins: [Composite, onBottomScroll], - data () { - return { - pagination: { - perPage: 50, - current: 0, - enabled: false - }, - lazyLoadProductsOnscroll: true - } - }, - computed: { - ...mapGetters('category', ['getCurrentCategory', 'getCurrentCategoryProductQuery', 'getAllCategoryFilters', 'getCategoryBreadcrumbs', 'getCurrentCategoryPath']), - ...mapGetters('tax', ['getIsUserGroupedTaxActive']), - products () { - return this.$store.getters['product/list'] - }, - productsCounter () { - return this.products ? this.products.length : 0 - }, - productsTotal () { - return this.$store.state.product.list.total - }, - currentQuery () { - return this.getCurrentCategoryProductQuery - }, - isCategoryEmpty () { - return (!(this.products) || this.products.length === 0) - }, - category () { - return this.getCurrentCategory - }, - categoryName () { - return this.getCurrentCategory ? this.getCurrentCategory.name : '' - }, - categoryId () { - return this.getCurrentCategory ? this.getCurrentCategory.id : '' - }, - filters () { - return this.getAllCategoryFilters - }, - breadcrumbs () { - return this.getCategoryBreadcrumbs - } - }, - preAsyncData ({ store, route }) { - Logger.log('preAsyncData query setup')() - const currentProductQuery = store.getters['category/getCurrentCategoryProductQuery'] - const sort = currentProductQuery && currentProductQuery.sort ? currentProductQuery.sort : config.entities.productList.sort - store.dispatch('category/setSearchOptions', { - populateAggregations: true, - store: store, - route: route, - current: 0, - perPage: 50, - sort, - filters: config.products.defaultFilters, - includeFields: config.entities.optimize && isServer ? config.entities.productList.includeFields : null, - excludeFields: config.entities.optimize && isServer ? config.entities.productList.excludeFields : null, - append: false - }) - }, - async asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - Logger.info('Entering asyncData in Category Page (core)')() - try { - if (context) context.output.cacheTags.add(`category`) - const defaultFilters = config.products.defaultFilters - store.dispatch('category/resetFilters') - EventBus.$emit('filter-reset') - const parentCategory = await store.dispatch('category/single', { key: config.products.useMagentoUrlKeys ? 'url_key' : 'slug', value: route.params.slug }) - let query = store.getters['category/getCurrentCategoryProductQuery'] - if (!query.searchProductQuery) { - store.dispatch('category/mergeSearchOptions', { - searchProductQuery: baseFilterProductsQuery(parentCategory, defaultFilters) - }) - } - const subloaders = await store.dispatch('category/products', query) - if (subloaders) { - await Promise.all(subloaders) - await EventBus.$emitFilter('category-after-load', { store: store, route: route }) - } else { - throw new Error('Category query returned empty result') - } - } catch (err) { - Logger.error(err)() - throw err - } - }, - async beforeRouteEnter (to, from, next) { - if (!isServer && !from.name) { // Loading category products to cache on SSR render - next(vm => { - const defaultFilters = config.products.defaultFilters - let parentCategory = store.getters['category/getCurrentCategory'] - let query = store.getters['category/getCurrentCategoryProductQuery'] - if (!query.searchProductQuery) { - store.dispatch('category/mergeSearchOptions', { - searchProductQuery: baseFilterProductsQuery(parentCategory, defaultFilters), - cacheOnly: true// this is cache only request - }) - } - store.dispatch('category/products', query) - }) - } else { - next() - } - }, - beforeMount () { - this.$bus.$on('filter-changed-category', this.onFilterChanged) - this.$bus.$on('list-change-sort', this.onSortOrderChanged) - if (config.usePriceTiers || this.getIsUserGroupedTaxActive) { - this.$bus.$on('user-after-loggedin', this.onUserPricesRefreshed) - this.$bus.$on('user-after-logout', this.onUserPricesRefreshed) - } - }, - beforeDestroy () { - this.$bus.$off('list-change-sort', this.onSortOrderChanged) - this.$bus.$off('filter-changed-category', this.onFilterChanged) - if (config.usePriceTiers || this.getIsUserGroupedTaxActive) { - this.$bus.$off('user-after-loggedin', this.onUserPricesRefreshed) - this.$bus.$off('user-after-logout', this.onUserPricesRefreshed) - } - }, - beforeRouteUpdate (to, from, next) { - this.validateRoute(to) - next() - }, - methods: { - ...mapActions('category', ['mergeSearchOptions']), - onBottomScroll () { - this.pullMoreProducts() - }, - bottomVisible () { - const scrollY = Math.ceil(window.scrollY) - const visible = window.innerHeight - const pageHeight = document.documentElement.scrollHeight - const bottomOfPage = visible + scrollY >= pageHeight - return bottomOfPage || pageHeight < visible - }, - pullMoreProducts () { - if (typeof navigator !== 'undefined' && !navigator.onLine) return - let current = this.getCurrentCategoryProductQuery.current + this.getCurrentCategoryProductQuery.perPage - this.mergeSearchOptions({ - append: true, - route: this.$route, - store: this.$store, - current - }) - this.pagination.current = this.getCurrentCategoryProductQuery.current - this.pagination.perPage = this.getCurrentCategoryProductQuery.perPage - if (this.getCurrentCategoryProductQuery.current <= this.productsTotal) { - this.mergeSearchOptions({ - searchProductQuery: buildFilterProductsQuery(this.category, this.filters.chosen) - }) - return this.$store.dispatch('category/products', this.getCurrentCategoryProductQuery) - } - }, - onFilterChanged (filterOption) { - this.pagination.current = 0 - if (this.filters.chosen[filterOption.attribute_code] && ((toString(filterOption.id) === toString(this.filters.chosen[filterOption.attribute_code].id)) || filterOption.id === this.filters.chosen[filterOption.attribute_code].id)) { // for price filter it's a string - Vue.delete(this.filters.chosen, filterOption.attribute_code) - } else { - Vue.set(this.filters.chosen, filterOption.attribute_code, filterOption) - } - - let filterQr = buildFilterProductsQuery(this.category, this.filters.chosen) - - const filtersConfig = Object.assign({}, this.filters.chosen) // create a copy because it will be used asynchronously (take a look below) - this.mergeSearchOptions({ - populateAggregations: false, - searchProductQuery: filterQr, - current: this.pagination.current, - perPage: this.pagination.perPage, - configuration: filtersConfig, - append: false, - includeFields: null, - excludeFields: null - }) - this.$store.dispatch('category/products', this.getCurrentCategoryProductQuery).then((res) => { - }) // because already aggregated - }, - onSortOrderChanged (param) { - this.pagination.current = 0 - if (param.attribute) { - const filtersConfig = Object.assign({}, this.filters.chosen) // create a copy because it will be used asynchronously (take a look below) - let filterQr = buildFilterProductsQuery(this.category, this.filters.chosen) - this.mergeSearchOptions({ - sort: param.attribute, - searchProductQuery: filterQr, - current: this.pagination.current, - perPage: this.pagination.perPage, - configuration: filtersConfig, - append: false, - includeFields: null, - excludeFields: null - }) - this.$store.dispatch('category/products', this.getCurrentCategoryProductQuery).then((res) => { - }) - } else { - this.notify() - } - }, - validateRoute (route = this.$route) { - this.$store.dispatch('category/resetFilters') - this.$bus.$emit('filter-reset') - - this.$store.dispatch('category/single', { key: config.products.useMagentoUrlKeys ? 'url_key' : 'slug', value: route.params.slug }).then(category => { - if (!category) { - this.$router.push(this.localizedRoute('/')) - } else { - this.pagination.current = 0 - let searchProductQuery = baseFilterProductsQuery(this.getCurrentCategory, config.products.defaultFilters) - this.$bus.$emit('current-category-changed', this.getCurrentCategoryPath) - this.mergeSearchOptions({ // base prototype from the asyncData is being used here - current: this.pagination.current, - perPage: this.pagination.perPage, - store: this.$store, - route: this.$route, - append: false, - populateAggregations: true - }) - if (!this.getCurrentCategoryProductQuery.searchProductQuery) { - this.mergeSearchOptions({ - searchProductQuery - }) - } - this.$store.dispatch('category/products', this.getCurrentCategoryProductQuery) - this.$bus.$emitFilter('category-after-load', { store: this.$store, route: route }) - } - }).catch(err => { - if (err.message.indexOf('query returned empty result') > 0) { - this.$store.dispatch('notification/spawnNotification', { - type: 'error', - message: i18n.t('The product, category or CMS page is not available in Offline mode. Redirecting to Home.'), - action1: { label: i18n.t('OK') } - }) - this.$router.push(localizedRoute('/', currentStoreView().storeCode)) - } - }) - }, - onUserPricesRefreshed () { - const defaultFilters = config.products.defaultFilters - this.$store.dispatch('category/single', { - key: config.products.useMagentoUrlKeys ? 'url_key' : 'slug', - value: this.$route.params.slug - }).then((parentCategory) => { - if (!this.getCurrentCategoryProductQuery.searchProductQuery) { - this.mergeSearchOptions({ - searchProductQuery: baseFilterProductsQuery(parentCategory, defaultFilters), - skipCache: true - }) - } - this.$store.dispatch('category/products', this.getCurrentCategoryProductQuery) - }) - } - }, - metaInfo () { - const storeView = currentStoreView() - return { - /* link: [ - { rel: 'amphtml', - href: this.$router.resolve(localizedRoute({ - name: 'category-amp', - params: { - slug: this.category.slug - } - }, storeView.storeCode)).href - } - ], */ - title: htmlDecode(this.category.meta_title || this.categoryName), - meta: this.category.meta_description ? [{ vmid: 'description', name: 'description', content: htmlDecode(this.category.meta_description) }] : [] - } - } -} diff --git a/core/pages/Checkout.js b/core/pages/Checkout.js deleted file mode 100644 index 0dc2c893fb..0000000000 --- a/core/pages/Checkout.js +++ /dev/null @@ -1,350 +0,0 @@ -import Vue from 'vue' -import i18n from '@vue-storefront/i18n' -import config from 'config' -import VueOfflineMixin from 'vue-offline/mixin' -import { mapGetters } from 'vuex' -import { StorageManager } from '@vue-storefront/core/lib/storage-manager' -import Composite from '@vue-storefront/core/mixins/composite' -import { currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore' -import { isServer } from '@vue-storefront/core/helpers' -import { Logger } from '@vue-storefront/core/lib/logger' - -export default { - name: 'Checkout', - mixins: [Composite, VueOfflineMixin], - data () { - return { - stockCheckCompleted: false, - stockCheckOK: false, - confirmation: null, // order confirmation from server - activeSection: { - personalDetails: true, - shipping: false, - payment: false, - orderReview: false - }, - order: {}, - personalDetails: {}, - shipping: {}, - shippingMethod: {}, - payment: {}, - orderReview: {}, - cartSummary: {}, - validationResults: { - personalDetails: { $invalid: true }, - shipping: { $invalid: true }, - payment: { $invalid: true } - }, - focusedField: null - } - }, - computed: { - ...mapGetters({ - isVirtualCart: 'cart/isVirtualCart', - isThankYouPage: 'checkout/isThankYouPage' - }) - }, - async beforeMount () { - await this.$store.dispatch('checkout/load') - this.$bus.$emit('checkout-after-load') - this.$store.dispatch('checkout/setModifiedAt', Date.now()) - // TODO: Use one event with name as apram - this.$bus.$on('cart-after-update', this.onCartAfterUpdate) - this.$bus.$on('cart-after-delete', this.onCartAfterUpdate) - this.$bus.$on('checkout-after-personalDetails', this.onAfterPersonalDetails) - this.$bus.$on('checkout-after-shippingDetails', this.onAfterShippingDetails) - this.$bus.$on('checkout-after-paymentDetails', this.onAfterPaymentDetails) - this.$bus.$on('checkout-after-cartSummary', this.onAfterCartSummary) - this.$bus.$on('checkout-before-placeOrder', this.onBeforePlaceOrder) - this.$bus.$on('checkout-do-placeOrder', this.onDoPlaceOrder) - this.$bus.$on('checkout-before-edit', this.onBeforeEdit) - this.$bus.$on('order-after-placed', this.onAfterPlaceOrder) - this.$bus.$on('checkout-before-shippingMethods', this.onBeforeShippingMethods) - this.$bus.$on('checkout-after-shippingMethodChanged', this.onAfterShippingMethodChanged) - this.$bus.$on('checkout-after-validationError', this.focusField) - if (!this.isThankYouPage) { - this.$store.dispatch('cart/load', { forceClientState: true }).then(() => { - if (this.$store.state.cart.cartItems.length === 0) { - this.notifyEmptyCart() - this.$router.push(this.localizedRoute('/')) - } else { - this.stockCheckCompleted = false - const checkPromises = [] - for (let product of this.$store.state.cart.cartItems) { // check the results of online stock check - if (product.onlineStockCheckid) { - checkPromises.push(new Promise((resolve, reject) => { - StorageManager.get('syncTasks').getItem(product.onlineStockCheckid, (err, item) => { - if (err || !item) { - if (err) Logger.error(err)() - resolve(null) - } else { - product.stock = item.result - resolve(product) - } - }) - })) - } - } - Promise.all(checkPromises).then((checkedProducts) => { - this.stockCheckCompleted = true - this.stockCheckOK = true - for (let chp of checkedProducts) { - if (chp && chp.stock) { - if (!chp.stock.is_in_stock) { - this.stockCheckOK = false - chp.errors.stock = i18n.t('Out of stock!') - this.notifyOutStock(chp) - } - } - } - }) - } - }) - } - const storeView = currentStoreView() - let country = this.$store.state.checkout.shippingDetails.country - if (!country) country = storeView.i18n.defaultCountry - this.$bus.$emit('checkout-before-shippingMethods', country) - }, - beforeDestroy () { - this.$store.dispatch('checkout/setModifiedAt', 0) // exit checkout - this.$bus.$off('cart-after-update', this.onCartAfterUpdate) - this.$bus.$off('cart-after-delete', this.onCartAfterUpdate) - this.$bus.$off('checkout-after-personalDetails', this.onAfterPersonalDetails) - this.$bus.$off('checkout-after-shippingDetails', this.onAfterShippingDetails) - this.$bus.$off('checkout-after-paymentDetails', this.onAfterPaymentDetails) - this.$bus.$off('checkout-after-cartSummary', this.onAfterCartSummary) - this.$bus.$off('checkout-before-placeOrder', this.onBeforePlaceOrder) - this.$bus.$off('checkout-do-placeOrder', this.onDoPlaceOrder) - this.$bus.$off('checkout-before-edit', this.onBeforeEdit) - this.$bus.$off('order-after-placed', this.onAfterPlaceOrder) - this.$bus.$off('checkout-before-shippingMethods', this.onBeforeShippingMethods) - this.$bus.$off('checkout-after-shippingMethodChanged', this.onAfterShippingMethodChanged) - this.$bus.$off('checkout-after-validationError', this.focusField) - }, - watch: { - '$route': 'activateHashSection', - 'OnlineOnly': 'onNetworkStatusCheck' - }, - methods: { - onCartAfterUpdate (payload) { - if (this.$store.state.cart.cartItems.length === 0) { - this.notifyEmptyCart() - this.$router.push(this.localizedRoute('/')) - } - }, - async onAfterShippingMethodChanged (payload) { - await this.$store.dispatch('cart/syncTotals', { forceServerSync: true, methodsData: payload }) - this.shippingMethod = payload - }, - onBeforeShippingMethods (country) { - this.$store.dispatch('checkout/updatePropValue', ['country', country]) - this.$store.dispatch('cart/syncTotals', { forceServerSync: true }) - this.$forceUpdate() - }, - async onAfterPlaceOrder (payload) { - this.confirmation = payload.confirmation - this.$store.dispatch('checkout/setThankYouPage', true) - this.$store.dispatch('user/getOrdersHistory', { refresh: true, useCache: true }) - Logger.debug(payload.order)() - }, - onBeforeEdit (section) { - this.activateSection(section) - }, - onBeforePlaceOrder (payload) { - }, - onAfterCartSummary (receivedData) { - this.cartSummary = receivedData - }, - onDoPlaceOrder (additionalPayload) { - if (this.$store.state.cart.cartItems.length === 0) { - this.notifyEmptyCart() - this.$router.push(this.localizedRoute('/')) - } else { - this.payment.paymentMethodAdditional = additionalPayload - this.placeOrder() - } - }, - onAfterPaymentDetails (receivedData, validationResult) { - this.payment = receivedData - this.validationResults.payment = validationResult - this.activateSection('orderReview') - this.savePaymentDetails() - }, - onAfterShippingDetails (receivedData, validationResult) { - this.shipping = receivedData - this.validationResults.shipping = validationResult - this.activateSection('payment') - this.saveShippingDetails() - - const storeView = currentStoreView() - storeView.tax.defaultCountry = this.shipping.country - }, - onAfterPersonalDetails (receivedData, validationResult) { - this.personalDetails = receivedData - this.validationResults.personalDetails = validationResult - - if (this.isVirtualCart === true) { - this.activateSection('payment') - } else { - this.activateSection('shipping') - } - this.savePersonalDetails() - this.focusedField = null - }, - onNetworkStatusCheck (isOnline) { - this.checkConnection(isOnline) - }, - checkStocks () { - let isValid = true - for (let child of this.$children) { - if (child.hasOwnProperty('$v')) { - if (child.$v.$invalid) { - // Check if child component is Personal Details. - // If so, then ignore validation of account creation fields. - if (child.$v.hasOwnProperty('personalDetails')) { - if (child.$v.personalDetails.$invalid) { - isValid = false - break - } - } else { - isValid = false - break - } - } - } - } - - if (typeof navigator !== 'undefined' && navigator.onLine) { - if (this.stockCheckCompleted) { - if (!this.stockCheckOK) { - isValid = false - this.notifyNotAvailable() - } - } else { - this.notifyStockCheck() - isValid = false - } - } - return isValid - }, - activateHashSection () { - if (!isServer) { - var urlStep = window.location.hash.replace('#', '') - if (this.activeSection.hasOwnProperty(urlStep) && this.activeSection[urlStep] === false) { - this.activateSection(urlStep) - } else if (urlStep === '') { - this.activateSection('personalDetails') - } - } - }, - checkConnection (isOnline) { - if (!isOnline) { - this.notifyNoConnection() - } - }, - activateSection (sectionToActivate) { - for (let section in this.activeSection) { - this.activeSection[section] = false - } - this.activeSection[sectionToActivate] = true - if (!isServer) window.location.href = window.location.origin + window.location.pathname + '#' + sectionToActivate - }, - // This method checks if there exists a mapping of chosen payment method to one of Magento's payment methods. - getPaymentMethod () { - let paymentMethod = this.payment.paymentMethod - if (config.orders.payment_methods_mapping.hasOwnProperty(paymentMethod)) { - paymentMethod = config.orders.payment_methods_mapping[paymentMethod] - } - return paymentMethod - }, - prepareOrder () { - this.order = { - user_id: this.$store.state.user.current ? this.$store.state.user.current.id.toString() : '', - cart_id: this.$store.state.cart.cartServerToken ? this.$store.state.cart.cartServerToken.toString() : '', - products: this.$store.state.cart.cartItems, - addressInformation: { - billingAddress: { - region: this.payment.state, - region_id: this.payment.region_id ? this.payment.region_id : 0, - country_id: this.payment.country, - street: [this.payment.streetAddress, this.payment.apartmentNumber], - company: this.payment.company, - telephone: this.payment.phoneNumber, - postcode: this.payment.zipCode, - city: this.payment.city, - firstname: this.payment.firstName, - lastname: this.payment.lastName, - email: this.personalDetails.emailAddress, - region_code: this.payment.region_code ? this.payment.region_code : '', - vat_id: this.payment.taxId - }, - shipping_method_code: this.shippingMethod.method_code ? this.shippingMethod.method_code : this.shipping.shippingMethod, - shipping_carrier_code: this.shippingMethod.carrier_code ? this.shippingMethod.carrier_code : this.shipping.shippingCarrier, - payment_method_code: this.getPaymentMethod(), - payment_method_additional: this.payment.paymentMethodAdditional, - shippingExtraFields: this.shipping.extraFields - } - } - if (!this.isVirtualCart) { - this.order.addressInformation.shippingAddress = { - region: this.shipping.state, - region_id: this.shipping.region_id ? this.shipping.region_id : 0, - country_id: this.shipping.country, - street: [this.shipping.streetAddress, this.shipping.apartmentNumber], - company: '', - telephone: this.shipping.phoneNumber, - postcode: this.shipping.zipCode, - city: this.shipping.city, - firstname: this.shipping.firstName, - lastname: this.shipping.lastName, - email: this.personalDetails.emailAddress, - region_code: this.shipping.region_code ? this.shipping.region_code : '' - } - } - return this.order - }, - placeOrder () { - this.checkConnection({ online: typeof navigator !== 'undefined' ? navigator.onLine : true }) - if (this.checkStocks()) { - this.$store.dispatch('checkout/placeOrder', { order: this.prepareOrder() }) - } else { - this.notifyNotAvailable() - } - }, - savePersonalDetails () { - this.$store.dispatch('checkout/savePersonalDetails', this.personalDetails) - }, - saveShippingDetails () { - this.$store.dispatch('checkout/saveShippingDetails', this.shipping) - }, - savePaymentDetails () { - this.$store.dispatch('checkout/savePaymentDetails', this.payment) - }, - focusField (fieldName) { - if (fieldName === 'password') { - window.scrollTo(0, 0) - this.activateSection('personalDetails') - this.focusedField = fieldName - } - if (fieldName === 'email-address') { - window.scrollTo(0, 0) - this.activateSection('personalDetails') - this.focusedField = fieldName - } - } - }, - metaInfo () { - return { - title: this.$route.meta.title || i18n.t('Checkout'), - meta: this.$route.meta.description ? [{ vmid: 'description', name: 'description', content: this.$route.meta.description }] : [] - } - }, - asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - return new Promise((resolve, reject) => { - if (context) context.output.cacheTags.add(`checkout`) - if (context) context.server.response.redirect(localizedRoute('/')) - resolve() - }) - } -} diff --git a/core/pages/CmsPage.js b/core/pages/CmsPage.js deleted file mode 100644 index 058794da07..0000000000 --- a/core/pages/CmsPage.js +++ /dev/null @@ -1,54 +0,0 @@ -import { htmlDecode } from '@vue-storefront/core/filters/html-decode' -import Composite from '@vue-storefront/core/mixins/composite' -import { Logger } from '@vue-storefront/core/lib/logger' - -export default { - name: 'CmsPage', - mixins: [Composite], - computed: { - pageTitle () { - return this.$store.state.cmsPage.current ? this.$store.state.cmsPage.current.meta_title || this.$store.state.cmsPage.current.title : '' - }, - pageDescription () { - return this.$store.state.cmsPage.current ? this.$store.state.cmsPage.current.meta_description : '' - }, - pageKeywords () { - return this.$store.state.cmsPage.current ? this.$store.state.cmsPage.current.meta_keywords : '' - } - }, - watch: { - '$route': 'validateRoute' - }, - asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - return new Promise((resolve, reject) => { - if (context) context.output.cacheTags.add(`cmsPage`) - store.dispatch('cmsPage/single', { - value: route.params.slug, - setCurrent: true - }).then(page => { - resolve(page) - }).catch(err => { - Logger.error(err)() - reject(err) - }) - }) - }, - methods: { - validateRoute () { - this.$store.dispatch('cmsPage/single', { value: this.$route.params.slug, setCurrent: true }).then(cmsPage => { - if (!cmsPage) { - this.$router.push(this.localizedRoute('/')) - } - }) - } - }, - metaInfo () { - return { - title: htmlDecode(this.pageTitle || this.$route.meta.title), - meta: [ - { vmid: 'description', name: 'description', content: htmlDecode(this.pageDescription || this.$route.meta.description) }, - { vmid: 'keywords', name: 'keywords', content: htmlDecode(this.pageKeywords) } - ] - } - } -} diff --git a/core/pages/Compare.js b/core/pages/Compare.js deleted file mode 100644 index 31ecaed2d1..0000000000 --- a/core/pages/Compare.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Compare } from '@vue-storefront/core/modules/compare/components/Compare.ts' - -export default { - name: 'Compare', - mixins: [Compare], - computed: { - all_comparable_attributes () { - // Computed Property renamed to 'allComparableAttributes' - return this.allComparableAttributes - } - } -} diff --git a/core/pages/Compilation.js b/core/pages/Compilation.js deleted file mode 100644 index 8d42237910..0000000000 --- a/core/pages/Compilation.js +++ /dev/null @@ -1,76 +0,0 @@ -module.exports = ` - - - Compiling - - - - -
-
Waiting for compilation...
- -` diff --git a/core/pages/Error.js b/core/pages/Error.js deleted file mode 100644 index 5f5bfc3832..0000000000 --- a/core/pages/Error.js +++ /dev/null @@ -1,21 +0,0 @@ -import i18n from '@vue-storefront/i18n' -import { Logger } from '@vue-storefront/core/lib/logger' - -export default { - name: 'Error', - asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - return new Promise((resolve, reject) => { - Logger.log('Calling asyncData for Error page ' + new Date())() - if (context) { - context.output.cacheTags.add(`error`) - } - resolve() - }) - }, - metaInfo () { - return { - title: this.$route.meta.title || i18n.t('Internal Server Error 500'), - meta: this.$route.meta.description ? [{ vmid: 'description', name: 'description', content: this.$route.meta.description }] : [] - } - } -} diff --git a/core/pages/Home.js b/core/pages/Home.js deleted file mode 100644 index 2f82029f99..0000000000 --- a/core/pages/Home.js +++ /dev/null @@ -1,37 +0,0 @@ -import { mapGetters } from 'vuex' - -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import i18n from '@vue-storefront/i18n' - -import Composite from '@vue-storefront/core/mixins/composite' -import { Logger } from '@vue-storefront/core/lib/logger' - -export default { - name: 'Home', - mixins: [Composite], - computed: { - ...mapGetters('category', ['getCategories']), - rootCategories () { - return this.getCategories - } - }, - async asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - if (context) context.output.cacheTags.add(`home`) - Logger.info('Calling asyncData in Home Page (core)')() - try { - await EventBus.$emitFilter('home-after-load', { store: store, route: route }) - } catch (e) { - Logger.error(e)() - throw e - } - }, - beforeMount () { - this.$store.dispatch('category/reset') - }, - metaInfo () { - return { - title: this.$route.meta.title || i18n.t('Home Page'), - meta: this.$route.meta.description ? [{ vmid: 'description', name: 'description', content: this.$route.meta.description }] : [] - } - } -} diff --git a/core/pages/MyAccount.js b/core/pages/MyAccount.js deleted file mode 100644 index e069a198fb..0000000000 --- a/core/pages/MyAccount.js +++ /dev/null @@ -1,69 +0,0 @@ -import i18n from '@vue-storefront/i18n' - -import Composite from '@vue-storefront/core/mixins/composite' -import { Logger } from '@vue-storefront/core/lib/logger' -import { currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore' - -export default { - name: 'MyAccount', - mixins: [Composite], - props: { - activeBlock: { - type: String, - default: 'MyProfile' - } - }, - data () { - return { - navigation: [], - returnEditMode: false - } - }, - beforeMount () { - this.$bus.$on('myAccount-before-updateUser', this.onBeforeUpdateUser) - this.$bus.$on('myAccount-before-changePassword', this.onBeforeChangePassword) - this.$bus.$on('user-after-logout', this.afterUserIsLogout) - }, - async mounted () { - await this.$store.dispatch('user/startSession') - if (!this.$store.getters['user/isLoggedIn']) { - localStorage.setItem('redirect', this.$route.path) - this.$router.push(localizedRoute('/', currentStoreView().storeCode)) - } - }, - beforeDestroy () { - this.$bus.$off('myAccount-before-updateUser', this.onBeforeUpdateUser) - this.$bus.$off('myAccount-before-changePassword', this.onBeforeChangePassword) - this.$bus.$off('user-after-logout', this.afterUserIsLogout) - }, - methods: { - onBeforeChangePassword (passwordData) { - this.$store.dispatch('user/changePassword', passwordData) - }, - onBeforeUpdateUser (updatedData) { - if (updatedData) { - try { - this.$store.dispatch('user/update', { customer: updatedData }) - } catch (err) { - this.$bus.$emit('myAccount-before-remainInEditMode', this.$props.activeBlock) - Logger.error(err)() - } - } - }, - afterUserIsLogout () { - this.$router.push(localizedRoute('/', currentStoreView().storeCode)) - } - }, - metaInfo () { - return { - title: this.$route.meta.title || i18n.t('My Account'), - meta: this.$route.meta.description ? [{ vmid: 'description', name: 'description', content: this.$route.meta.description }] : [] - } - }, - asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - return new Promise((resolve, reject) => { - if (context) context.output.cacheTags.add(`my-account`) - resolve() - }) - } -} diff --git a/core/pages/PageNotFound.js b/core/pages/PageNotFound.js deleted file mode 100644 index a55fda111d..0000000000 --- a/core/pages/PageNotFound.js +++ /dev/null @@ -1,36 +0,0 @@ -import { prepareQuery } from '@vue-storefront/core/modules/catalog/queries/common' - -import i18n from '@vue-storefront/i18n' -import Composite from '@vue-storefront/core/mixins/composite' -import { Logger } from '@vue-storefront/core/lib/logger' - -export default { - name: 'PageNotFound', - mixins: [Composite], - async asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - Logger.log('Entering asyncData for PageNotFound ' + new Date())() - if (context) { - context.output.cacheTags.add(`page-not-found`) - context.server.response.statusCode = 404 - } - let ourBestsellersQuery = prepareQuery({ queryConfig: 'bestSellers' }) - const { items } = await store.dispatch('product/findProducts', { - query: ourBestsellersQuery, - size: 8, - sort: 'created_at:desc', - options: { - populateRequestCacheTags: false, - prefetchGroupProducts: false - } - }) - if (items.length) { - store.state.homepage.bestsellers = items - } - }, - metaInfo () { - return { - title: this.$route.meta.title || i18n.t('404 Page Not Found'), - meta: this.$route.meta.description ? [{ vmid: 'description', name: 'description', content: this.$route.meta.description }] : [] - } - } -} diff --git a/core/pages/Product.js b/core/pages/Product.js deleted file mode 100644 index 8f885521eb..0000000000 --- a/core/pages/Product.js +++ /dev/null @@ -1,246 +0,0 @@ -import { mapGetters } from 'vuex' -import config from 'config' - -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' -import { htmlDecode } from '@vue-storefront/core/filters' -import { currentStoreView, localizedRoute } from '@vue-storefront/core/lib/multistore' -import { CompareProduct } from '@vue-storefront/core/modules/compare/components/Product.ts' -import { AddToCompare } from '@vue-storefront/core/modules/compare/components/AddToCompare.ts' -import { ProductOption } from '@vue-storefront/core/modules/catalog/components/ProductOption.ts' -import omit from 'lodash-es/omit' -import Composite from '@vue-storefront/core/mixins/composite' -import { Logger } from '@vue-storefront/core/lib/logger' -import { formatProductLink } from '@vue-storefront/core/modules/url/helpers' - -export default { - name: 'Product', - mixins: [Composite, AddToCompare, CompareProduct, ProductOption], - data () { - return { - loading: false - } - }, - computed: { - ...mapGetters({ - product: 'product/getCurrentProduct', - originalProduct: 'product/getOriginalProduct', - parentProduct: 'product/getParentProduct', - attributesByCode: 'attribute/getAttributeListByCode', - attributesById: 'attribute/getAttributeListById', - breadcrumbs: 'category-next/getBreadcrumbs', - configuration: 'product/getCurrentProductConfiguration', - options: 'product/getCurrentProductOptions', - category: 'category/getCurrentCategory', - gallery: 'product/getProductGallery', - isUserGroupedTaxActive: 'tax/getIsUserGroupedTaxActive' - }), - productName () { - return this.product ? this.product.name : '' - }, - productId () { - return this.product ? this.product.id : '' - }, - offlineImage () { - return { - src: this.getThumbnail(this.product.image, config.products.thumbnails.width, config.products.thumbnails.height), - error: this.getThumbnail(this.product.image, config.products.thumbnails.width, config.products.thumbnails.height), - loading: this.getThumbnail(this.product.image, config.products.thumbnails.width, config.products.thumbnails.height) - } - }, - image () { - return this.gallery.length ? this.gallery[0] : false - }, - customAttributes () { - return Object.values(this.attributesByCode).filter(a => { - return a.is_visible && a.is_user_defined && (parseInt(a.is_visible_on_front) || a.is_visible_on_front === true) && this.product[a.attribute_code] - }) - }, - currentStore () { - return currentStoreView() - } - }, - asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - EventBus.$emit('product-before-load', { store: store, route: route }) - if (context) context.output.cacheTags.add(`product`) - return store.dispatch('product/loadProduct', { parentSku: route.params.parentSku, childSku: route && route.params && route.params.childSku ? route.params.childSku : null }) - }, - beforeRouteUpdate (to, from, next) { - this.validateRoute(to) // TODO: remove because client-entry.ts is executing `asyncData` anyway - next() - }, - // Move busses to mixin which is directly imported in Project.vue - beforeDestroy () { - this.$bus.$off('product-after-removevariant') - this.$bus.$off('filter-changed-product') - this.$bus.$off('product-after-priceupdate', this.onAfterPriceUpdate) - this.$bus.$off('product-after-customoptions') - this.$bus.$off('product-after-bundleoptions') - if (config.usePriceTiers || this.isUserGroupedTaxActive) { - this.$bus.$off('user-after-loggedin', this.onUserPricesRefreshed) - this.$bus.$off('user-after-logout', this.onUserPricesRefreshed) - } - }, - beforeMount () { - this.$bus.$on('product-after-removevariant', this.onAfterVariantChanged) - this.$bus.$on('product-after-priceupdate', this.onAfterPriceUpdate) // moved to catalog module - this.$bus.$on('filter-changed-product', this.onAfterFilterChanged) // moved to catalog module - this.$bus.$on('product-after-customoptions', this.onAfterCustomOptionsChanged) // moved to catalog module - this.$bus.$on('product-after-bundleoptions', this.onAfterBundleOptionsChanged) // moved to catalog module - if (config.usePriceTiers || this.isUserGroupedTaxActive) { // moved to catalog module - this.$bus.$on('user-after-loggedin', this.onUserPricesRefreshed) - this.$bus.$on('user-after-logout', this.onUserPricesRefreshed) - } - this.onStateCheck() - this.$store.dispatch('recently-viewed/addItem', this.product) - }, - methods: { - validateRoute (route = this.$route) { - if (!this.loading) { - this.loading = true - this.$store.dispatch('product/loadProduct', { parentSku: route.params.parentSku, childSku: route && route.params && route.params.childSku ? route.params.childSku : null }).then(res => { - this.loading = false - this.defaultOfflineImage = this.product.image - this.onStateCheck() - this.$store.dispatch('recently-viewed/addItem', this.product) - }).catch((err) => { - this.loading = false - Logger.error(err)() - this.notifyOutStock() - this.$router.back() - }) - } else { - Logger.error('Error with loading = true in Product.vue; Reload page')() - } - }, - addToWishlist (product) { - return this.$store.state['wishlist'] ? this.$store.dispatch('wishlist/addItem', product) : false - }, - removeFromWishlist (product) { - return this.$store.state['wishlist'] ? this.$store.dispatch('wishlist/removeItem', product) : false - }, - addToList (list) { - // Method renamed to 'addToCompare(product)', product is an Object - AddToCompare.methods.addToCompare.call(this, this.product) - }, - removeFromList (list) { - // Method renamed to 'removeFromCompare(product)', product is an Object - CompareProduct.methods.removeFromCompare.call(this, this.product) - }, - onAfterCustomOptionsChanged (payload) { - let priceDelta = 0 - let priceDeltaInclTax = 0 - for (const optionValue of Object.values(payload.optionValues)) { - if (typeof optionValue === 'object' && parseInt(optionValue.option_type_id) > 0) { - if (optionValue.price_type === 'fixed' && optionValue.price !== 0) { - priceDelta += optionValue.price - priceDeltaInclTax += optionValue.price - } - if (optionValue.price_type === 'percent' && optionValue.price !== 0) { - priceDelta += ((optionValue.price / 100) * this.originalProduct.price) - priceDeltaInclTax += ((optionValue.price / 100) * this.originalProduct.price_incl_tax) - } - } - } - this.product.price = this.originalProduct.price + priceDelta - this.product.price_incl_tax = this.originalProduct.price_incl_tax + priceDeltaInclTax - }, - onAfterBundleOptionsChanged (payload) { - let priceDelta = 0 - let priceDeltaInclTax = 0 - for (const optionValue of Object.values(payload.optionValues)) { - if (typeof optionValue.value.product !== 'undefined' && parseInt(optionValue.qty) >= 0) { - priceDelta += optionValue.value.product.price * parseInt(optionValue.qty) - priceDeltaInclTax += optionValue.value.product.price_incl_tax * parseInt(optionValue.qty) - } - } - if (priceDelta > 0) { - this.product.price = priceDelta - this.product.price_incl_tax = priceDeltaInclTax - } - }, - onStateCheck () { - if (this.parentProduct && this.parentProduct.id !== this.product.id) { - Logger.log('Redirecting to parent, configurable product', this.parentProduct.sku)() - const parentUrl = formatProductLink(this.parentProduct, currentStoreView().storeCode) - this.$router.replace(parentUrl) - } - }, - onAfterPriceUpdate (product) { - if (product.sku === this.product.sku) { - // join selected variant object to the store - this.$store.dispatch('product/setCurrent', omit(product, ['name'])) - .catch(err => Logger.error({ - info: 'Dispatch product/setCurrent in Product.vue', - err - })) - } - }, - onAfterVariantChanged (payload) { - this.$store.dispatch('product/setProductGallery', { product: this.product }) - this.$forceUpdate() - }, - onAfterFilterChanged (filterOption) { - this.$bus.$emit('product-before-configure', { filterOption: filterOption, configuration: this.configuration }) - const prevOption = this.configuration[filterOption.attribute_code] - let changedConfig = Object.assign({}, this.configuration, { [filterOption.attribute_code]: filterOption }) - this.$forceUpdate() // this is to update the available options regarding current selection - this.$store.dispatch('product/configure', { - product: this.product, - configuration: changedConfig, - selectDefaultVariant: true, - fallbackToDefaultWhenNoAvailable: false, - setProductErorrs: true - }).then((selectedVariant) => { - if (config.products.setFirstVarianAsDefaultInURL) { - this.$router.push({ params: { childSku: selectedVariant.sku } }) - } - if (!selectedVariant) { - if (typeof prevOption !== 'undefined' && prevOption) { - this.configuration[filterOption.attribute_code] = prevOption - } else { - delete this.configuration[filterOption.attribute_code] - } - this.notifyWrongAttributes() - } - }).catch(err => Logger.error({ - info: 'Dispatch product/configure in Product.vue', - err - })) - }, - /** - * Reload product to get correct prices (including tier prices for group) - */ - onUserPricesRefreshed () { - if (this.$route.params.parentSku) { - this.$store.dispatch('product/reset') - this.$bus.$emit('product-before-load', { store: this.$store, route: this.$route }) - this.$store.dispatch('product/single', { - options: { - sku: this.$route.params.parentSku, - childSku: this.$route && this.$route.params && this.$route.params.childSku ? this.$route.params.childSku : null - }, - skipCache: true - }) - } - } - }, - metaInfo () { - const storeView = currentStoreView() - return { - /* link: [ - { rel: 'amphtml', - href: this.$router.resolve(localizedRoute({ - name: this.product.type_id + '-product-amp', - params: { - parentSku: this.product.parentSku ? this.product.parentSku : this.product.sku, - slug: this.product.slug, - childSku: this.product.sku - } - }, storeView.storeCode)).href - } - ], */ - title: htmlDecode(this.product.meta_title || this.productName), - meta: this.product.meta_description ? [{ vmid: 'description', name: 'description', content: htmlDecode(this.product.meta_description) }] : [] - } - } -} diff --git a/core/scripts/all.js b/core/scripts/all.js deleted file mode 100644 index a7f6a39306..0000000000 --- a/core/scripts/all.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict' - -const shell = require('shelljs') -const jsonFile = require('jsonfile') -const installer = require('./installer') - -class Manager extends installer.Manager { - /** - * {@inheritDoc} - */ - initBackend () { - if (Manager.isBackendInstalledLocally()) { - return this.backend.goToDirectory(Manager.getBackendDirectory()) - .then(this.backend.dockerComposeUp.bind(this.backend)) - .then(this.backend.runDevEnvironment.bind(this.backend)) - } else { - return Promise.resolve() - } - } - - /** - * {@inheritDoc} - */ - initStorefront () { - return this.storefront.goToDirectory() - .then(this.storefront.depBuild.bind(this.storefront)) - .then(this.storefront.runDevEnvironment.bind(this.storefront)) - } - - /** - * {@inheritDoc} - */ - static showWelcomeMessage () { - installer.Message.greeting([ - 'Hi, seat, relax...', - 'I\'ll start everything for you ;)' - ]) - } - - /** - * {@inheritDoc} - */ - showGoodbyeMessage () { - return new Promise((resolve, reject) => { - installer.Message.greeting([ - 'Congratulations!', - '', - 'You\'ve just successfully started vue-storefront.', - 'All required servers are running in the background', - '', - 'Storefront: http://localhost:3000', - 'Backend: ' + (Manager.isBackendInstalledLocally() ? 'http://localhost:8080' : installer.STOREFRONT_REMOTE_BACKEND_URL), - '', - 'Good Luck!' - ], true) - - resolve() - }) - } - - /** - * Check if backend was installed locally - * - * @returns {boolean} - */ - static isBackendInstalledLocally () { - if (typeof installer.Abstract.wasLocalBackendInstalled === 'undefined') { - let config = jsonFile.readFileSync(installer.TARGET_BACKEND_CONFIG_FILE) - - installer.Abstract.wasLocalBackendInstalled = Boolean(config.install.is_local_backend) - } - - return Boolean(installer.Abstract.wasLocalBackendInstalled) - } - - /** - * Return backend directory - * - * @returns {string} - */ - static getBackendDirectory () { - if (typeof installer.Abstract.backendDir === 'undefined') { - let config = jsonFile.readFileSync(installer.TARGET_BACKEND_CONFIG_FILE) - - installer.Abstract.backendDir = config.install.backend_dir - } - - return installer.Abstract.backendDir - } -} - -/** - * Predefine class static variables - */ -installer.Abstract.wasLocalBackendInstalled = undefined -installer.Abstract.backendDir = undefined - -/** - * Pre-loading staff - */ -Manager.checkUserOS() -Manager.showWelcomeMessage(); - -/** - * This is where all the magic happens - */ -(async function () { - let manager = new Manager() - - await manager.tryToCreateLogFiles() - .then(manager.initBackend.bind(manager)) - .then(manager.initStorefront.bind(manager)) - .then(manager.showGoodbyeMessage.bind(manager)) - .catch(installer.Message.error) - - shell.exit(0) -})() diff --git a/core/scripts/cache.js b/core/scripts/cache.js deleted file mode 100644 index 4038647ad9..0000000000 --- a/core/scripts/cache.js +++ /dev/null @@ -1,41 +0,0 @@ -const program = require('commander') -const config = require('config') -const cache = require('./utils/cache-instance') - -program - .command('clear') - .option('-t|--tag ', 'tag name, available tags: ' + config.server.availableCacheTags.join(', '), '*') - .action((cmd) => { // TODO: add parallel processing - if (!cmd.tag) { - console.error('error: tag must be specified') - process.exit(1) - } else { - console.log(`Clear cache request for [${cmd.tag}]`) - let tags = [] - if (cmd.tag === '*') { - tags = config.server.availableCacheTags - } else { - tags = cmd.tag.split(',') - } - const subPromises = [] - tags.forEach(tag => { - if (config.server.availableCacheTags.indexOf(tag) >= 0 || config.server.availableCacheTags.find(t => { - return tag.indexOf(t) === 0 - })) { - subPromises.push(cache.invalidate(tag).then(() => { - console.log(`Tags invalidated successfully for [${tag}]`) - })) - } else { - console.error(`Invalid tag name ${tag}`) - } - }) - Promise.all(subPromises).then(r => { - console.log(`All tags invalidated successfully [${cmd.tag}]`) - process.exit(0) - }).catch(error => { - console.error(error) - }) - } - }) - -program.parse(process.argv) diff --git a/core/scripts/generate-files.ts b/core/scripts/generate-files.ts deleted file mode 100644 index 5dd33d8e58..0000000000 --- a/core/scripts/generate-files.ts +++ /dev/null @@ -1,35 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import config from 'config'; - -fs.writeFileSync( - path.resolve(__dirname, '../build/config.json'), - JSON.stringify(config) -) - -const csvDirectories = [ - path.resolve(__dirname, '../../node_modules/@vue-storefront/i18n/resource/i18n/') -] - -const moduleRoot = path.resolve(__dirname, '../../src/modules') -fs.readdirSync(moduleRoot).forEach(directory => { - const dirName = moduleRoot + '/' + directory + '/resource/i18n' - - if (fs.existsSync(dirName)) { - csvDirectories.push(dirName); - } -}); - -const themeRoot = require('../build/theme-path'); -const themeResources = themeRoot + '/resource' -csvDirectories.push(path.resolve(__dirname, themeResources + '/i18n/')); - -const translationPreprocessor = require('@vue-storefront/i18n/scripts/translation.preprocessor.js') -translationPreprocessor(csvDirectories, config) - -const tsconfig = require('../../tsconfig.json'); -tsconfig.compilerOptions.paths['theme/*'] = [ - `${themeRoot}/*` -]; - -require('fs').writeFileSync('tsconfig.json', JSON.stringify(tsconfig, null, 2)); diff --git a/core/scripts/generate.ts b/core/scripts/generate.ts deleted file mode 100644 index 9f1ffa859e..0000000000 --- a/core/scripts/generate.ts +++ /dev/null @@ -1,186 +0,0 @@ -import generator from './utils/page-generator' -import { path as rootPath } from 'app-root-path' -import * as path from 'path' -import * as ssr from './utils/ssr-renderer' -import config from 'config' -import themeRoot from '../build/theme-path' -import { search } from './utils/catalog-client' -import bodybuilder from 'bodybuilder' -import program from 'commander' -const resolve = file => path.resolve(rootPath, file) - -// eslint-disable-next-line @typescript-eslint/no-use-before-define -const { renderer, templatesCache, destPath } = _prepareRenderer(); - -async function _renderItems (itemsSource, pageFrom, pageSize, useRelativePaths = false, urlToFileNameMapper = (destPath, item) => { - return (path.join(destPath, item._source.url_path)) -}) { - let recordsProcessed = 0 - let results = null - do { - results = await itemsSource(pageFrom, pageSize) - console.log(`Processing records - pageSize: ${pageSize} from: ${pageFrom}`) - if (results.hits && results.hits.hits.length > 0) { - results.hits.hits.forEach(async item => { - console.log(`Generating static page for ${item._source.url_path} - ${item._source.name}`) - const urlToRender = item._source.url_path - const res = { redirect: (url) => {} } - const req = { url: urlToRender } - const context = ssr.initSSRRequestContext(null, req, res, config) - try { - let output = await renderer.renderToString(context) - const outputFilename = urlToFileNameMapper(destPath, item) - output = ssr.applyAdvancedOutputProcessing(context, output, templatesCache, true, useRelativePaths, destPath, outputFilename); - generator.saveRenderedPage(outputFilename, output) - } catch (err) { - console.error(`Error rendering item: ${item._source.name}: ${err}`) - } - }); - recordsProcessed += results.hits.hits.length - } else { - console.log(`Done! Total number of items processed: ${recordsProcessed}`) - } - pageFrom = pageFrom + pageSize - } while (results.hits && results.hits.hits.length > 0) -} - -// TODO: all, prepare, clear commands + relative paths as an option -const _cmdGenerateProducts = async (cmd) => { - const getProductsPage = (from, size) => search({ - size: size, - from: from, - sort: 'id:desc', - type: 'product', - searchQuery: bodybuilder().filter('terms', 'visibility', [2, 3, 4]).andFilter('term', 'status', 1).build() - }, config, config /* TODO: add support for different storeviews */) - let pageFrom = parseInt(cmd.from) - let pageSize = parseInt(cmd.size) - - await _renderItems(getProductsPage, pageFrom, pageSize, cmd.relative) -} -program - .command('products') - .option('-r|--relative ', 'use relative paths', false) - .option('-f|--from ', 'from - starting record', 0) - .option('-s|--size ', 'size - batch size', 20) - .action(_cmdGenerateProducts) - -const _cmdGenerateCategories = async (cmd) => { - const getCategoriesPage = (from, size) => search({ - size: size, - from: from, - sort: 'id:desc', - type: 'category', - searchQuery: bodybuilder().filter('term', 'is_active', true).build() - }, config, config /* TODO: add support for different storeviews */) - let pageFrom = parseInt(cmd.from) - let pageSize = parseInt(cmd.size) - - await _renderItems(getCategoriesPage, pageFrom, pageSize, cmd.relative, (destPath, item) => { - return (path.join(destPath, `${item._source.url_path}/index.html`)) - }) -} -program - .command('categories') - .option('-r|--relative ', 'use relative paths', false) - .option('-f|--from ', 'from - starting record', 0) - .option('-s|--size ', 'size - batch size', 20) - .action(_cmdGenerateCategories) - -const _cmdGenerateCms = async (cmd) => { - const getCmsPage = (from, size) => search({ - size: size, - from: from, - sort: 'id:desc', - type: 'cms_page', - searchQuery: bodybuilder().build() - }, config, config /* TODO: add support for different storeviews */).then(results => { - if (results.hits && results.hits.hits.length > 0) { - results.hits.hits.map(page => { page._source.url_path = `/i/${page._source.identifier}` }) - } - return results - }) - let pageFrom = parseInt(cmd.from) - let pageSize = parseInt(cmd.size) - - await _renderItems(getCmsPage, pageFrom, pageSize, cmd.relative) -} -program - .command('cms') - .option('-r|--relative ', 'use relative paths', false) - .option('-f|--from ', 'from - starting record', 0) - .option('-s|--size ', 'size - batch size', 20) - .action(_cmdGenerateCms) - -const _cmdPrepare = async (cmd) => { - await generator.clearAll(destPath); - await generator.saveScripts(resolve(''), destPath); - await generator.saveSW(resolve(''), destPath); - await generator.saveAssets(themeRoot, destPath); -} -program - .command('prepare') - .action(_cmdPrepare) - -const _cmdAll = async (cmd) => { - await _cmdPrepare(cmd) - // render home page - await _renderItems(async (from, to) => { - if (from === 0) { - return { - hits: { - hits: [ - { - _source: { - name: 'Home page', - output_file_name: 'index.html', - url_path: '/' // to render home page - } - }, - { - _source: { - name: 'Page not found', - output_file_name: 'page-not-found', - url_path: '/page-not-found' // to render home page - } - } - ] - } - } - } else { - return { hits: null } - } - }, 0, 50, cmd.relative, (destPath, item) => { - return path.join(destPath, item._source.output_file_name) - }) - await _cmdGenerateCategories(cmd) - await _cmdGenerateProducts(cmd) - await _cmdGenerateCms(cmd) -} -program - .command('all') - .option('-r|--relative ', 'use relative paths', false) - .option('-f|--from ', 'from - starting record', 0) - .option('-s|--size ', 'size - batch size', 20) - .action(_cmdAll) - -function _prepareRenderer () { - const compileOptions = { - escape: /{{([^{][\s\S]+?[^}])}}/g, - interpolate: /{{{([\s\S]+?)}}}/g - }; - const templatesCache = ssr.initTemplatesCache(config, compileOptions); - // In production: create server renderer using server bundle and index HTML - // template from real fs. - // The server bundle is generated by vue-ssr-webpack-plugin. - const clientManifest = require(resolve('dist/vue-ssr-client-manifest.json')); - const bundle = require(resolve('dist/vue-ssr-bundle.json')); - // src/index.template.html is processed by html-webpack-plugin to inject - // build assets and output as dist/index.html. - // TODO: Add dynamic templates loading from (config based?) list - const renderer = ssr.createRenderer(bundle, clientManifest); - const destPath = resolve(config.staticPages.destPath); - return { renderer, templatesCache, destPath }; -} - -program.parse(process.argv) diff --git a/core/scripts/installer.js b/core/scripts/installer.js deleted file mode 100644 index 0afe988764..0000000000 --- a/core/scripts/installer.js +++ /dev/null @@ -1,946 +0,0 @@ -'use strict' - -const path = require('path') -const shell = require('shelljs') -const mkdirp = require('mkdirp') -const exists = require('fs-exists-sync') -const message = require('print-message') -const inquirer = require('inquirer') -const jsonFile = require('jsonfile') -const urlParser = require('url-parse') -const isWindows = require('is-windows') -const isEmptyDir = require('empty-dir') -const commandExists = require('command-exists') -const program = require('commander') -const { createThemeTasks, createThemePrompt } = require('./../../packages/cli/themeTasks') - -const SAMPLE_DATA_PATH = 'var/magento2-sample-data' -const TARGET_FRONTEND_CONFIG_FILE = 'config/local.json' -const SOURCE_FRONTEND_CONFIG_FILE = 'config/default.json' - -const TARGET_BACKEND_CONFIG_FILE = 'config/local.json' -const SOURCE_BACKEND_CONFIG_FILE = 'config/default.json' - -const STOREFRONT_BACKEND_GIT_URL = 'https://github.com/vuestorefront/vue-storefront-api' -const MAGENTO_SAMPLE_DATA_GIT_URL = 'https://github.com/magento/magento2-sample-data.git' -const STOREFRONT_REMOTE_BACKEND_URL = 'https://demo.vuestorefront.io' - -const STOREFRONT_DIRECTORY = shell.pwd() - -const LOG_DIR = `${STOREFRONT_DIRECTORY}/var/log` -const INSTALL_LOG_FILE = `${STOREFRONT_DIRECTORY}/var/log/install.log` -const VUE_STOREFRONT_LOG_FILE = `${STOREFRONT_DIRECTORY}/var/log/vue-storefront.log` -const VUE_STOREFRONT_BACKEND_LOG_FILE = `${STOREFRONT_DIRECTORY}/var/log/vue-storefront-api.log` - -/** - * Abstract class for field initialization - */ -class Abstract { - /** - * Constructor - * - * Initialize fields - */ - constructor (answers) { - this.answers = answers - } -} - -/** - * Message management - */ -class Message { - /** - * Renders informative message - * - * @param text - */ - static info (text) { - text = Array.isArray(text) ? text : [text] - - message([ - ...text - ], { color: 'blue', border: false, marginTop: 1 }) - } - - /** - * Renders error message - * - * @param text - * @param logFile - */ - static error (text, logFile = INSTALL_LOG_FILE) { - text = Array.isArray(text) ? text : [text] - - // show trace if exception occurred - if (text[0] instanceof Error) { - text = text[0].stack.split('\n') - } - - let logDetailsInfo = `Please check log file for details: ${logFile}` - - if (!Abstract.logsWereCreated) { - logDetailsInfo = 'Try to fix problem with logs to see the error details.' - } - - message([ - 'ERROR', - '', - ...text, - '', - logDetailsInfo - ], { borderColor: 'red', marginBottom: 1 }) - - shell.exit(1) - } - - /** - * Render warning message - * - * @param text - */ - static warning (text) { - text = Array.isArray(text) ? text : [text] - - message([ - 'WARNING:', - ...text - ], { color: 'yellow', border: false, marginTop: 1 }) - } - - /** - * Render block info message - * - * @param text - * @param isLastMessage - */ - static greeting (text, isLastMessage = false) { - text = Array.isArray(text) ? text : [text] - - message([ - ...text - ], Object.assign(isLastMessage ? { marginTop: 1 } : {}, { borderColor: 'green', marginBottom: 1 })) - } -} - -/** - * Scripts for initialization backend - */ -class Backend extends Abstract { - /** - * Clone API repository - * - * @returns {Promise} - */ - cloneRepository () { - return new Promise((resolve, reject) => { - const backendDir = path.normalize(this.answers.backend_dir) - const gitPath = path.normalize(this.answers.git_path) - - Message.info(`Cloning backend into '${backendDir}'...`) - - if (shell.exec(`${gitPath} clone ${STOREFRONT_BACKEND_GIT_URL} '${backendDir}' > ${Abstract.infoLogStream} 2>&1`).code !== 0) { - reject(new Error(`Can't clone backend into '${backendDir}'.`)) - } - - resolve() - }) - } - - /** - * Go to backend directory - * - * @returns {Promise} - */ - goToDirectory (backendDir = null) { - return new Promise((resolve, reject) => { - const dir = this.answers ? this.answers.backend_dir : backendDir - - Message.info(`Trying change directory to '${dir}'...`) - - if (shell.cd(path.normalize(dir)).code !== 0) { - reject(new Error(`Can't change directory to '${dir}'.`)) - } - - Message.info(`Working in directory '${shell.pwd()}'...`) - - resolve() - }) - } - - /** - * Run 'yarn install' in backend directory - * - * @returns {Promise} - */ - depInstall () { - return new Promise((resolve, reject) => { - Message.info('Installing backend dep...') - - if (shell.exec(`yarn >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { - reject(new Error('Can\'t install backend dep.')) - } - - resolve() - }) - } - - /** - * Run 'docker-compose up' in background - * - * @returns {Promise} - */ - dockerComposeUp () { - return new Promise((resolve, reject) => { - Message.info('Starting Docker in background...') - - if (shell.exec(`docker-compose up -d > /dev/null 2>&1`).code !== 0) { - reject(new Error('Can\'t start Docker in background.')) - } - // Adding 20sec timer for ES to get up and running - // before starting restoration and migration processes - setTimeout(() => { resolve() }, 20000) - }) - } - - /** - * Validate Magento integration settings. - * - * @returns {Promise} - */ - validateM2Integration () { - return new Promise((resolve, reject) => { - const Magento2Client = require('magento2-rest-client').Magento2Client - - Message.info(`Validating Magento integration configuration...`) - - let m2Url = urlParser(this.answers.m2_url).href - let apiUrl = urlParser(this.answers.m2_api_url).href - - if (!m2Url.length) { - reject(new Error('Invalid Magento URL supplied.')) - } - if (!apiUrl.length) { - reject(new Error('Invalid Magento rest API URL supplied.')) - } - - let options = { - 'url': apiUrl, - 'consumerKey': this.answers.m2_api_consumer_key, - 'consumerSecret': this.answers.m2_api_consumer_secret, - 'accessToken': this.answers.m2_api_access_token, - 'accessTokenSecret': this.answers.m2_api_access_token_secret - } - let client = Magento2Client(options) - - client.categories.list() - .then((categories) => { - resolve() - }).catch((e) => { - reject(new Error('Invalid Magento integration settings. Original error: ' + e)) - }) - }) - } - - /** - * Creating backend config/local.json - * - * @returns {Promise} - */ - createConfig () { - return new Promise((resolve, reject) => { - let config - - Message.info(`Creating backend config '${TARGET_BACKEND_CONFIG_FILE}'...`) - - try { - config = jsonFile.readFileSync(SOURCE_BACKEND_CONFIG_FILE) - let host = urlParser(this.answers.images_endpoint).hostname - - if (!host.length) { - throw new Error() - } - - config.imageable.whitelist.allowedHosts.push(host) - - config.magento2.url = urlParser(this.answers.m2_url).href - config.magento2.imgUrl = this.answers.m2_url ? urlParser(this.answers.m2_url).href + '/pub/media/catalog/product' : config.magento2.imgUrl - config.magento2.api.url = urlParser(this.answers.m2_api_url).href || config.magento2.api.url - config.magento2.api.consumerKey = this.answers.m2_api_consumer_key || config.magento2.api.consumerKey - config.magento2.api.consumerSecret = this.answers.m2_api_consumer_secret || config.magento2.api.consumerSecret - config.magento2.api.accessToken = this.answers.m2_api_access_token || config.magento2.api.accessToken - config.magento2.api.accessTokenSecret = this.answers.m2_api_access_token_secret || config.magento2.api.accessTokenSecret - - jsonFile.writeFileSync(TARGET_BACKEND_CONFIG_FILE, config, { spaces: 2 }) - } catch (e) { - reject(new Error('Can\'t create backend config. Original error: ' + e)) - } - - resolve() - }) - } - - /** - * Run 'yarn restore' - * - * @returns {Promise} - */ - restoreElasticSearch () { - return new Promise((resolve, reject) => { - Message.info('Restoring data for ElasticSearch...') - - if (shell.exec(`yarn restore >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { - reject(new Error('Can\'t restore data for ElasticSearch.')) - } - - resolve() - }) - } - - /** - * Run 'yarn migrate' - * - * @returns {Promise} - */ - migrateElasticSearch () { - return new Promise((resolve, reject) => { - Message.info('Migrating data into ElasticSearch...') - - if (shell.exec(`yarn migrate >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { - reject(new Error('Can\'t migrate data into ElasticSearch.')) - } - - resolve() - }) - } - - /** - * Run 'yarn mage2vs import' - * - * @returns {Promise} - */ - importElasticSearch () { - return new Promise((resolve, reject) => { - Message.info('Importing data from Magento into ElasticSearch...') - - if (shell.exec(`yarn mage2vs import >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { - reject(new Error('Can\'t import data into ElasticSearch.')) - } - - resolve() - }) - } - - /** - * Cloning Magento sample data - * - * @returns {Promise} - */ - cloneMagentoSampleData () { - return new Promise((resolve, reject) => { - Message.info(`Cloning Magento 2 Sample Data into '${SAMPLE_DATA_PATH}'...`) - - if (shell.exec(`${this.answers.git_path} clone ${MAGENTO_SAMPLE_DATA_GIT_URL} ${SAMPLE_DATA_PATH} >> ${Abstract.infoLogStream} 2>&1`).code !== 0) { - reject(new Error(`Can't clone Magento 2 Sample Data into '${SAMPLE_DATA_PATH}'...`)) - } - - resolve() - }) - } - - /** - * Start 'yarn dev' in background - * - * @returns {Promise} - */ - runDevEnvironment () { - return new Promise((resolve, reject) => { - Message.info('Starting backend server...') - - if (isWindows()) { - if (shell.exec(`start /min yarn dev > ${Abstract.backendLogStream} 2>&1 &`).code !== 0) { - reject(new Error('Can\'t start dev server.', VUE_STOREFRONT_BACKEND_LOG_FILE)) - } - } else { - if (shell.exec(`nohup yarn dev > ${Abstract.backendLogStream} 2>&1 &`).code !== 0) { - reject(new Error('Can\'t start dev server.', VUE_STOREFRONT_BACKEND_LOG_FILE)) - } - } - - resolve() - }) - } -} - -/** - * Scripts for initialization storefront - */ -class Storefront extends Abstract { - /** - * Go to storefront directory - * - * @returns {Promise} - */ - goToDirectory () { - return new Promise((resolve, reject) => { - if (Abstract.wasLocalBackendInstalled) { - Message.info(`Trying change directory to '${STOREFRONT_DIRECTORY}'...`) - - if (shell.cd(STOREFRONT_DIRECTORY).code !== 0) { - reject(new Error(`Can't change directory to '${STOREFRONT_DIRECTORY}'.`)) - } - - Message.info(`Working in directory '${STOREFRONT_DIRECTORY}'...`) - } - - resolve() - }) - } - - /** - * Creating storefront config/local.json - * - * @returns {Promise} - */ - createConfig () { - return new Promise((resolve, reject) => { - let config - - Message.info(`Creating storefront config '${TARGET_FRONTEND_CONFIG_FILE}'...`) - - try { - config = jsonFile.readFileSync(SOURCE_FRONTEND_CONFIG_FILE) - - let backendPath - let graphQlHost - let graphQlPort = 8080 - - if (Abstract.wasLocalBackendInstalled) { - graphQlHost = 'localhost' - backendPath = 'http://localhost:8080' - } else { - backendPath = STOREFRONT_REMOTE_BACKEND_URL - graphQlHost = backendPath.replace('https://', '').replace('http://', '') - } - - config.api.url = backendPath - config.graphql.host = graphQlHost - config.graphql.port = graphQlPort - config.elasticsearch.host = `${backendPath}/api/catalog` - config.orders.endpoint = `${backendPath}/api/order` - config.products.endpoint = `${backendPath}/api/product` - config.users.loginAfterCreatePassword = true - config.users.endpoint = `${backendPath}/api/user` - config.users.history_endpoint = `${backendPath}/api/user/order-history?token={{token}}&pageSize={{pageSize}}¤tPage={{currentPage}}` - config.users.resetPassword_endpoint = `${backendPath}/api/user/reset-password` - config.users.createPassword_endpoint = `${backendPath}/api/user/create-password` - config.users.changePassword_endpoint = `${backendPath}/api/user/change-password?token={{token}}` - config.users.login_endpoint = `${backendPath}/api/user/login` - config.users.create_endpoint = `${backendPath}/api/user/create` - config.users.me_endpoint = `${backendPath}/api/user/me?token={{token}}` - config.users.refresh_endpoint = `${backendPath}/api/user/refresh` - config.stock.endpoint = `${backendPath}/api/stock` - config.cart.create_endpoint = `${backendPath}/api/cart/create?token={{token}}` - config.cart.updateitem_endpoint = `${backendPath}/api/cart/update?token={{token}}&cartId={{cartId}}` - config.cart.deleteitem_endpoint = `${backendPath}/api/cart/delete?token={{token}}&cartId={{cartId}}` - config.cart.pull_endpoint = `${backendPath}/api/cart/pull?token={{token}}&cartId={{cartId}}` - config.cart.totals_endpoint = `${backendPath}/api/cart/totals?token={{token}}&cartId={{cartId}}` - config.cart.paymentmethods_endpoint = `${backendPath}/api/cart/payment-methods?token={{token}}&cartId={{cartId}}` - config.cart.shippingmethods_endpoint = `${backendPath}/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}` - config.cart.shippinginfo_endpoint = `${backendPath}/api/cart/shipping-information?token={{token}}&cartId={{cartId}}` - config.cart.collecttotals_endpoint = `${backendPath}/api/cart/collect-totals?token={{token}}&cartId={{cartId}}` - config.cart.deletecoupon_endpoint = `${backendPath}/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}` - config.cart.applycoupon_endpoint = `${backendPath}/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}` - config.reviews.create_endpoint = `${backendPath}/api/review/create?token={{token}}` - - config.newsletter.endpoint = `${backendPath}/api/ext/mailchimp-subscribe/subscribe` - config.mailer.endpoint.send = `${backendPath}/api/ext/mail-service/send-email` - config.mailer.endpoint.token = `${backendPath}/api/ext/mail-service/get-token` - config.images.baseUrl = this.answers.images_endpoint - config.cms.endpoint = `${backendPath}/api/ext/cms-data/cms{{type}}/{{cmsId}}` - config.cms.endpointIdentifier = `${backendPath}/api/ext/cms-data/cms{{type}}Identifier/{{cmsIdentifier}}/storeId/{{storeId}}` - - if (this.answers.ssr_endpoints) { - if (Abstract.wasLocalBackendInstalled) { - graphQlHost = 'localhost' - backendPath = 'http://localhost:8080' - } else { - backendPath = STOREFRONT_REMOTE_BACKEND_URL - graphQlHost = backendPath.replace('https://', '').replace('http://', '') - } - - // Do we really need protocol_ssr in a different place than GraphQL? - config.server.protocol_ssr = 'http' - config.api.url_ssr = backendPath - config.graphql.host_ssr = graphQlHost - config.graphql.port_ssr = graphQlPort - config.elasticsearch.host_ssr = `${backendPath}/api/catalog` - config.orders.endpoint_ssr = `${backendPath}/api/order` - config.products.endpoint_ssr = `${backendPath}/api/product` - config.users.endpoint_ssr = `${backendPath}/api/user` - config.users.history_endpoint_ssr = `${backendPath}/api/user/order-history?token={{token}}` - config.users.resetPassword_endpoint_ssr = `${backendPath}/api/user/reset-password` - config.users.changePassword_endpoint_ssr = `${backendPath}/api/user/change-password?token={{token}}` - config.users.login_endpoint_ssr = `${backendPath}/api/user/login` - config.users.create_endpoint_ssr = `${backendPath}/api/user/create` - config.users.me_endpoint_ssr = `${backendPath}/api/user/me?token={{token}}` - config.users.refresh_endpoint_ssr = `${backendPath}/api/user/refresh` - config.stock.endpoint_ssr = `${backendPath}/api/stock` - config.cart.create_endpoint_ssr = `${backendPath}/api/cart/create?token={{token}}` - config.cart.updateitem_endpoint_ssr = `${backendPath}/api/cart/update?token={{token}}&cartId={{cartId}}` - config.cart.deleteitem_endpoint_ssr = `${backendPath}/api/cart/delete?token={{token}}&cartId={{cartId}}` - config.cart.pull_endpoint_ssr = `${backendPath}/api/cart/pull?token={{token}}&cartId={{cartId}}` - config.cart.totals_endpoint_ssr = `${backendPath}/api/cart/totals?token={{token}}&cartId={{cartId}}` - config.cart.paymentmethods_endpoint_ssr = `${backendPath}/api/cart/payment-methods?token={{token}}&cartId={{cartId}}` - config.cart.shippingmethods_endpoint_ssr = `${backendPath}/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}` - config.cart.shippinginfo_endpoint_ssr = `${backendPath}/api/cart/shipping-information?token={{token}}&cartId={{cartId}}` - config.cart.collecttotals_endpoint_ssr = `${backendPath}/api/cart/collect-totals?token={{token}}&cartId={{cartId}}` - config.cart.deletecoupon_endpoint_ssr = `${backendPath}/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}` - config.cart.applycoupon_endpoint_ssr = `${backendPath}/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}` - config.reviews.create_endpoint_ssr = `${backendPath}/api/review/create?token={{token}}` - - // Probably pointless (only CS) - // config.newsletter.endpoint_ssr = `${backendPath}/api/ext/mailchimp-subscribe/subscribe` - config.mailer.endpoint.send_ssr = `${backendPath}/api/ext/mail-service/send-email` - config.mailer.endpoint.token_ssr = `${backendPath}/api/ext/mail-service/get-token` - // Probably pointless (only CS) - // config.images.baseUrl_ssr = this.answers.images_endpoint - config.cms.endpoint_ssr = `${backendPath}/api/ext/cms-data/cms{{type}}/{{cmsId}}` - config.cms.endpointIdentifier_ssr = `${backendPath}/api/ext/cms-data/cms{{type}}Identifier/{{cmsIdentifier}}/storeId/{{storeId}}` - } - - config.install = { - is_local_backend: Abstract.wasLocalBackendInstalled, - backend_dir: this.answers.backend_dir || false - } - - jsonFile.writeFileSync(TARGET_FRONTEND_CONFIG_FILE, config, { spaces: 2 }) - } catch (e) { - reject(new Error('Can\'t create storefront config.')) - } - - resolve() - }) - } - - /** - * Run 'yarn build' on storefront - * - * @returns {Promise} - */ - depBuild () { - return new Promise((resolve, reject) => { - Message.info('Build storefront dep...') - - if (shell.exec(`yarn build > ${Abstract.storefrontLogStream} 2>&1`).code !== 0) { - reject(new Error('Can\'t build storefront dep.', VUE_STOREFRONT_LOG_FILE)) - } - - resolve() - }) - } - - /** - * Start 'yarn dev' in background - * - * @returns {Promise} - */ - runDevEnvironment (answers) { - return new Promise((resolve, reject) => { - Message.info('Starting storefront server...') - - if (isWindows()) { - if (shell.exec(`start /min yarn dev >> ${Abstract.storefrontLogStream} 2>&1 &`).code !== 0) { - reject(new Error('Can\'t start storefront server.', VUE_STOREFRONT_LOG_FILE)) - } - } else { - if (shell.exec(`nohup yarn dev >> ${Abstract.storefrontLogStream} 2>&1 &`).code !== 0) { - reject(new Error('Can\'t start storefront server.', VUE_STOREFRONT_LOG_FILE)) - } - } - - resolve(answers) - }) - } - - /** - * Handles all tasks needed to make theme installation - */ - async themeInstallation () { - // get theme tasks - const { installDeps, cloneTheme, configureTheme } = createThemeTasks(STOREFRONT_DIRECTORY.toString()) - - // put tasks in order - const tasks = [ - cloneTheme, - installDeps, - configureTheme - ] - - for (let { title, task, skip } of tasks) { - Message.info(title) - - const skipAnswer = skip ? await skip(this.answers) : '' - - if (skipAnswer) { - Message.warning(skipAnswer) - } else { - await task(this.answers) - } - } - } -} - -class Manager extends Abstract { - /** - * {@inheritDoc} - * - * Assign backend and storefront entities - */ - constructor (answers) { - super(answers) - - this.backend = new Backend(answers) - this.storefront = new Storefront(answers) - } - - /** - * Trying to create log files - * If is impossible - warning shows - * - * @returns {Promise} - */ - tryToCreateLogFiles () { - return new Promise((resolve, reject) => { - Message.info('Trying to create log files...') - - try { - mkdirp.sync(LOG_DIR, { mode: parseInt('0755', 8) }) - - let logFiles = [ - INSTALL_LOG_FILE, - VUE_STOREFRONT_BACKEND_LOG_FILE, - VUE_STOREFRONT_LOG_FILE - ] - - for (let logFile of logFiles) { - if (shell.touch(logFile).code !== 0 || !exists(logFile)) { - throw new Error() - } - } - - Abstract.logsWereCreated = true - Abstract.infoLogStream = INSTALL_LOG_FILE - Abstract.storefrontLogStream = VUE_STOREFRONT_LOG_FILE - Abstract.backendLogStream = VUE_STOREFRONT_BACKEND_LOG_FILE - } catch (e) { - Message.warning('Can\'t create log files.') - } - - resolve() - }) - } - - /** - * Initialize all processes for backend (if selected) - * - * @returns {Promise} - */ - initBackend () { - if (this.answers.is_remote_backend === false) { - Abstract.wasLocalBackendInstalled = true - if (this.answers.m2_api_oauth2 === true) { - return this.backend.validateM2Integration() - .then(this.backend.cloneRepository.bind(this.backend)) - .then(this.backend.goToDirectory.bind(this.backend)) - .then(this.backend.depInstall.bind(this.backend)) - .then(this.backend.createConfig.bind(this.backend)) - .then(this.backend.dockerComposeUp.bind(this.backend)) - .then(this.backend.importElasticSearch.bind(this.backend)) - .then(this.backend.runDevEnvironment.bind(this.backend)) - } else { - return this.backend.cloneRepository() - .then(this.backend.goToDirectory.bind(this.backend)) - .then(this.backend.depInstall.bind(this.backend)) - .then(this.backend.createConfig.bind(this.backend)) - .then(this.backend.dockerComposeUp.bind(this.backend)) - .then(this.backend.restoreElasticSearch.bind(this.backend)) - .then(this.backend.migrateElasticSearch.bind(this.backend)) - .then(this.backend.cloneMagentoSampleData.bind(this.backend)) - .then(this.backend.runDevEnvironment.bind(this.backend)) - } - } else { - return Promise.resolve() - } - } - - /** - * Initialize all processes for storefront - * - * @returns {Promise} - */ - initStorefront () { - return this.storefront.goToDirectory() - .then(this.storefront.createConfig.bind(this.storefront)) - .then(this.storefront.themeInstallation.bind(this.storefront)) - .then(this.storefront.depBuild.bind(this.storefront)) - .then(this.storefront.runDevEnvironment.bind(this.storefront)) - } - - /** - * Shows message rendered on the very beginning - */ - static showWelcomeMessage () { - Message.greeting([ - 'Hi, welcome to the vue-storefront installation.', - 'Let\'s configure it together :)' - ]) - } - - /** - * Shows details about successful installation finish - * - * @returns {Promise} - */ - showGoodbyeMessage () { - return new Promise((resolve, reject) => { - Message.greeting([ - 'Congratulations!', - '', - 'You\'ve just successfully installed vue-storefront.', - 'All required servers are running in background', - '', - 'Storefront: http://localhost:3000', - 'Backend: ' + (Abstract.wasLocalBackendInstalled ? 'http://localhost:8080' : STOREFRONT_REMOTE_BACKEND_URL), - '', - Abstract.logsWereCreated ? `Logs: ${LOG_DIR}/` : 'You don\'t have log files created.', - '', - 'Good Luck!' - ], true) - - resolve() - }) - } -} - -/** - * Here we configure questions - * - * @type {[Object,Object,Object,Object]} - */ -let questions = [ - { - type: 'confirm', - name: 'is_remote_backend', - message: `Would you like to use ${STOREFRONT_REMOTE_BACKEND_URL} as the backend?`, - default: true - }, - { - type: 'input', - name: 'git_path', - message: 'Please provide Git path (if it\'s not globally installed)', - default: 'git', - when: function (answers) { - return answers.is_remote_backend === false - }, - validate: function (value) { - if (!commandExists.sync(value)) { - return 'Invalid git path. Try again ;)' - } - - return true - } - }, - { - type: 'input', - name: 'backend_dir', - message: 'Please provide path for installing backend locally', - default: '../vue-storefront-api', - when: function (answers) { - return answers.is_remote_backend === false - }, - validate: function (value) { - try { - mkdirp.sync(value, { mode: parseInt('0755', 8) }) - - if (!isEmptyDir.sync(value)) { - return 'Please provide path to empty directory.' - } - } catch (error) { - return 'Can\'t access to write in this directory. Try again ;)' - } - - return true - } - }, - { - type: 'list', - name: 'images_endpoint', - message: 'Choose path for images endpoint', - choices: [ - `${STOREFRONT_REMOTE_BACKEND_URL}/img/`, - 'http://localhost:8080/img/', - 'Custom url' - ], - when: function (answers) { - return answers.is_remote_backend === false - } - }, - { - type: 'input', - name: 'images_endpoint', - message: 'Please provide path for images endpoint', - default: `${STOREFRONT_REMOTE_BACKEND_URL}/img/`, - when: function (answers) { - let isProvideByYourOwn = answers.images_endpoint === 'Custom url' - - return isProvideByYourOwn || answers.is_remote_backend === true - }, - filter: function (url) { - let prefix = 'http://' - let prefixSsl = 'https://' - - url = url.trim() - - // add http:// if no protocol set - if (url.substr(0, prefix.length) !== prefix && url.substr(0, prefixSsl.length) !== prefixSsl) { - url = prefix + url - } - - // add extra slash as suffix if was not set - return url.slice(-1) === '/' ? url : `${url}/` - } - }, - { - type: 'input', - name: 'm2_url', - message: 'Please provide your Magento url', - default: 'http://demo-magento2.vuestorefront.io', - when: function (answers) { - return answers.is_remote_backend === false - } - }, - { - type: 'confirm', - name: 'm2_api_oauth2', - message: `Would You like to perform initial data import from Magento2 instance?`, - default: false, - when: function (answers) { - return answers.is_remote_backend === false - } - }, - { - type: 'input', - name: 'm2_api_url', - message: 'Please provide the URL to your Magento rest API', - default: 'http://demo-magento2.vuestorefront.io/rest', - when: function (answers) { - return answers.m2_api_oauth2 === true - }, - filter: function (url) { - let prefix = 'http://' - let prefixSsl = 'https://' - - url = url.trim() - - // add http:// if no protocol set - if (url.substr(0, prefix.length) !== prefix && url.substr(0, prefixSsl.length) !== prefixSsl) { - url = prefix + url - } - - return url - } - }, - { - type: 'input', - name: 'm2_api_consumer_key', - message: 'Please provide your consumer key', - default: 'byv3730rhoulpopcq64don8ukb8lf2gq', - when: function (answers) { - return answers.m2_api_oauth2 === true - } - }, - { - type: 'input', - name: 'm2_api_consumer_secret', - message: 'Please provide your consumer secret', - default: 'u9q4fcobv7vfx9td80oupa6uhexc27rb', - when: function (answers) { - return answers.m2_api_oauth2 === true - } - }, - { - type: 'input', - name: 'm2_api_access_token', - message: 'Please provide your access token', - default: '040xx3qy7s0j28o3q0exrfop579cy20m', - when: function (answers) { - return answers.m2_api_oauth2 === true - } - }, - { - type: 'input', - name: 'm2_api_access_token_secret', - message: 'Please provide your access token secret', - default: '7qunl3p505rubmr7u1ijt7odyialnih9', - when: function (answers) { - return answers.m2_api_oauth2 === true - } - }, - { - type: 'confirm', - name: 'ssr_endpoints', - message: `Would You like to create fields for SSR endpoints?`, - default: false - }, - ...createThemePrompt(STOREFRONT_DIRECTORY.toString()) -] - -async function processAnswers (answers) { - let manager = new Manager(answers) - - await manager.tryToCreateLogFiles() - .then(manager.initBackend.bind(manager)) - .then(manager.initStorefront.bind(manager)) - .then(manager.showGoodbyeMessage.bind(manager)) - .catch(Message.error) - - shell.exit(0) -} - -/** - * Predefine class static variables - */ -Abstract.wasLocalBackendInstalled = false -Abstract.logsWereCreated = false -Abstract.infoLogStream = '/dev/null' -Abstract.storefrontLogStream = '/dev/null' -Abstract.backendLogStream = '/dev/null' - -if (require.main.filename === __filename) { - /** - * This is where all the magic happens - */ - - program - .option('--default-config', 'Run with default configuration') - .parse(process.argv) - - if (program.defaultConfig) { - const defaultConfig = {} - questions.forEach(question => { - defaultConfig[question.name] = question.default - }) - processAnswers(defaultConfig) - } else { - Manager.showWelcomeMessage() - inquirer.prompt(questions).then(answers => processAnswers(answers)) - } -} else { - module.exports.Message = Message - module.exports.Manager = Manager - module.exports.Abstract = Abstract - module.exports.STOREFRONT_REMOTE_BACKEND_URL = STOREFRONT_REMOTE_BACKEND_URL - module.exports.TARGET_FRONTEND_CONFIG_FILE = TARGET_FRONTEND_CONFIG_FILE - module.exports.TARGET_BACKEND_CONFIG_FILE = TARGET_BACKEND_CONFIG_FILE -} diff --git a/core/scripts/resolvers/resolveGraphQL.js b/core/scripts/resolvers/resolveGraphQL.js deleted file mode 100644 index 79a6f4e98d..0000000000 --- a/core/scripts/resolvers/resolveGraphQL.js +++ /dev/null @@ -1,73 +0,0 @@ -import { server, graphql } from 'config' -import Vue from 'vue' -import { Logger } from '@vue-storefront/core/lib/logger' -import { once, isServer } from '@vue-storefront/core/helpers' - -export const getApolloProvider = async () => { - if (server.api === 'graphql') { - const ApolloModule = await import(/* webpackChunkName: "vsf-graphql" */ 'vue-apollo') - const VueApollo = ApolloModule.default - - once('__VUE_EXTEND_GQL__', () => { - Vue.use(VueApollo) - }) - - const HttpLinkModule = await import(/* webpackChunkName: "vsf-graphql" */ 'apollo-link-http') - const HttpLink = HttpLinkModule.HttpLink - - let uri - if (isServer && (graphql.host_ssr || graphql.port_ssr)) { - const host = graphql.host_ssr || graphql.host - const port = graphql.port_ssr || graphql.port - - uri = host.indexOf('://') >= 0 - ? host - : (server.protocol + '://' + host + ':' + port + '/graphql') - } else { - uri = graphql.host.indexOf('://') >= 0 - ? graphql.host - : (server.protocol + '://' + graphql.host + ':' + graphql.port + '/graphql') - } - - const httpLink = new HttpLink({ - uri - }) - - const ApolloClientModule = await import(/* webpackChunkName: "vsf-graphql" */ 'apollo-client') - const ApolloClient = ApolloClientModule.default - const inMemoryCacheModule = await import(/* webpackChunkName: "vsf-graphql" */ 'apollo-cache-inmemory') - const InMemoryCache = inMemoryCacheModule.InMemoryCache - - const apolloClient = new ApolloClient({ - link: httpLink, - cache: new InMemoryCache(), - connectToDevTools: true - }) - - let loading = 0 - - const apolloProvider = new VueApollo({ - clients: { - a: apolloClient - }, - defaultClient: apolloClient, - defaultOptions: { - // $loadingKey: 'loading', - }, - watchLoading (state, mod) { - loading += mod - Logger.log('Global loading', loading, mod)() - }, - errorHandler (error) { - Logger.log('Global error handler')() - Logger.error(error)() - } - }) - - return apolloProvider - } else return null -} - -export default { - getApolloProvider -} diff --git a/core/scripts/server.ts b/core/scripts/server.ts deleted file mode 100755 index 15e6c86ab5..0000000000 --- a/core/scripts/server.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { serverHooksExecutors } from '@vue-storefront/core/server/hooks' - -const config = require('config') -const path = require('path') -const glob = require('glob') -const fs = require('fs') -const rootPath = require('app-root-path').path -const resolve = file => path.resolve(rootPath, file) -const serverExtensions = glob.sync('src/modules/*/server.{ts,js}') -const configProviders: Function[] = [] - -serverExtensions.map(serverModule => { - const module = require(resolve(serverModule)) - if (module.configProvider && typeof module.configProvider === 'function') { - configProviders.push(module.configProvider) - } -}) - -serverHooksExecutors.afterProcessStarted(config.server) -const express = require('express') -const ms = require('ms') -const request = require('request'); -const helmet = require('helmet') - -const cache = require('./utils/cache-instance') -const apiStatus = require('./utils/api-status') -const HTMLContent = require('../pages/Compilation') -const ssr = require('./utils/ssr-renderer') - -const compileOptions = { - escape: /{{([^{][\s\S]+?[^}])}}/g, - interpolate: /{{{([\s\S]+?)}}}/g -} -const NOT_ALLOWED_SSR_EXTENSIONS_REGEX = new RegExp(`^.*\\.(${config.server.ssrDisabledFor.extensions.join('|')})$`) - -const isProd = process.env.NODE_ENV === 'production' -process['noDeprecation'] = true - -const app = express() - -serverHooksExecutors.afterApplicationInitialized({ app, config: config.server, isProd }) - -const templatesCache = ssr.initTemplatesCache(config, compileOptions) - -let renderer - -if (isProd) { - // In production: create server renderer using server bundle and index HTML - // template from real fs. - // The server bundle is generated by vue-ssr-webpack-plugin. - const clientManifest = require(resolve('dist/vue-ssr-client-manifest.json')) - const bundle = require(resolve('dist/vue-ssr-bundle.json')) - // src/index.template.html is processed by html-webpack-plugin to inject - // build assets and output as dist/index.html. - // TODO: Add dynamic templates loading from (config based?) list - renderer = ssr.createRenderer(bundle, clientManifest) -} else { - // In development: setup the dev server with watch and hot-reload, - // and create a new renderer on bundle / index template update. - require(resolve('core/build/dev-server'))(app, (bundle, template) => { - templatesCache['default'] = ssr.compileTemplate(template, compileOptions) // Important Notice: template switching doesn't work with dev server because of the HMR - renderer = ssr.createRenderer(bundle) - }) -} - -function invalidateCache (req, res) { - if (config.server.useOutputCache) { - if (req.query.tag && req.query.key) { // clear cache pages for specific query tag - if (req.query.key !== config.server.invalidateCacheKey) { - console.error('Invalid cache invalidation key') - apiStatus(res, 'Invalid cache invalidation key', 500) - return - } - console.log(`Clear cache request for [${req.query.tag}]`) - let tags = [] - if (req.query.tag === '*') { - tags = config.server.availableCacheTags - } else { - tags = req.query.tag.split(',') - } - const subPromises = [] - - serverHooksExecutors.beforeCacheInvalidated({ tags, req }) - - tags.forEach(tag => { - if (config.server.availableCacheTags.indexOf(tag) >= 0 || config.server.availableCacheTags.find(t => { - return tag.indexOf(t) === 0 - })) { - subPromises.push(cache.invalidate(tag).then(() => { - console.log(`Tags invalidated successfully for [${tag}]`) - })) - } else { - console.error(`Invalid tag name ${tag}`) - } - }) - - Promise.all(subPromises).then(r => { - apiStatus(res, `Tags invalidated successfully [${req.query.tag}]`, 200) - }).catch(error => { - apiStatus(res, error, 500) - console.error(error) - }).finally(() => { - serverHooksExecutors.afterCacheInvalidated({ tags, req }) - }) - - if (config.server.invalidateCacheForwarding) { // forward invalidate request to the next server in the chain - if (!req.query.forwardedFrom && config.server.invalidateCacheForwardUrl) { // don't forward forwarded requests - request(config.server.invalidateCacheForwardUrl + req.query.tag + '&forwardedFrom=vs', {}, (err, res, body) => { - if (err) { console.error(err); } - try { - if (body && JSON.parse(body).code !== 200) console.log(body); - } catch (e) { - console.error('Invalid Cache Invalidation response format', e) - } - }); - } - } - } else { - apiStatus(res, 'Invalid parameters for Clear cache request', 500) - console.error('Invalid parameters for Clear cache request') - } - } else { - apiStatus(res, 'Cache invalidation is not required, output cache is disabled', 200) - } -} - -const serve = (path, cache, options?) => express.static(resolve(path), Object.assign({ - fallthrough: false, - setHeaders: cache && isProd ? function (res, path) { - const mimeType = express.static.mime.lookup(path); - let maxAge = config.expireHeaders.default; - if (config.expireHeaders.hasOwnProperty(mimeType)) { - maxAge = config.expireHeaders.get(mimeType); - } - res.setHeader('Cache-Control', 'public, max-age=' + ms(maxAge) / 1000); - } : null -}, options)) - -const themeRoot = require('../build/theme-path') - -if (config.server.helmet && config.server.helmet.enabled && isProd) { - app.use(helmet(config.server.helmet.config)) -} - -app.use('/dist', serve('dist', true)) -app.use('/assets', serve(themeRoot + '/assets', true)) -app.use('/service-worker.js', serve('dist/service-worker.js', false, { - setHeaders: function (res, path, stat) { - res.set('Content-Type', 'text/javascript; charset=UTF-8') - } -})) - -app.post('/invalidate', invalidateCache) -app.get('/invalidate', invalidateCache) - -function cacheVersion (req, res) { - res.send(fs.readFileSync(resolve('core/build/cache-version.json'))) -} - -app.get('/cache-version.json', cacheVersion) - -let globalContextConfig: any = null; - -app.get('*', async (req, res, next) => { - if (NOT_ALLOWED_SSR_EXTENSIONS_REGEX.test(req.url)) { - apiStatus(res, 'Vue Storefront: Resource is not found', 404) - return - } - - const s = Date.now() - const errorHandler = err => { - if (err && err.code === 404) { - if (NOT_ALLOWED_SSR_EXTENSIONS_REGEX.test(req.url)) { - console.error(`Resource is not found : ${req.url}`) - return apiStatus(res, 'Vue Storefront: Resource is not found', 404) - } else { - console.error(`Redirect for resource not found : ${req.url}`) - return res.redirect('/page-not-found') - } - } else { - console.error(`Error during render : ${req.url}`) - console.error(err) - serverHooksExecutors.ssrException({ err, req, isProd }) - return res.redirect('/error') - } - } - - const site = req.headers['x-vs-store-code'] || 'main' - const cacheKey = `page:${site}:${req.url}` - - const dynamicRequestHandler = (renderer, config) => { - if (!renderer) { - res.setHeader('Content-Type', 'text/html') - res.status(202).end(HTMLContent) - return next() - } - const context = ssr.initSSRRequestContext(app, req, res, config) - renderer.renderToString(context).then(output => { - if (!res.get('content-type')) { - res.setHeader('Content-Type', 'text/html') - } - let tagsArray = [] - if (config.server.useOutputCacheTagging && context.output.cacheTags && context.output.cacheTags.size > 0) { - tagsArray = Array.from(context.output.cacheTags) - const cacheTags = tagsArray.join(' ') - res.setHeader('X-VS-Cache-Tags', cacheTags) - console.log(`cache tags for the request: ${cacheTags}`) - } - - const beforeOutputRenderedResponse = serverHooksExecutors.beforeOutputRenderedResponse({ - req, - res, - context, - output, - isProd - }) - - if (typeof beforeOutputRenderedResponse.output === 'string') { - output = beforeOutputRenderedResponse.output - } else if (typeof beforeOutputRenderedResponse === 'string') { - output = beforeOutputRenderedResponse - } - - output = ssr.applyAdvancedOutputProcessing(context, output, templatesCache, isProd); - if (config.server.useOutputCache && cache) { - cache.set( - cacheKey, - { headers: res.getHeaders(), body: output, httpCode: res.statusCode }, - tagsArray - ).catch(errorHandler) - } - - const afterOutputRenderedResponse = serverHooksExecutors.afterOutputRenderedResponse({ - req, - res, - context, - output, - isProd - }) - - if (typeof afterOutputRenderedResponse === 'string') { - res.end(afterOutputRenderedResponse) - } else if (typeof afterOutputRenderedResponse.output === 'string') { - res.end(afterOutputRenderedResponse.output) - } else { - res.end(output) - } - - console.log(`whole request [${req.url}]: ${Date.now() - s}ms`) - next() - }).catch(errorHandler) - .finally(() => { - ssr.clearContext(context) - }) - } - - const dynamicCacheHandler = (config) => { - if (config.server.useOutputCache && cache) { - cache.get( - cacheKey - ).then(output => { - if (output !== null) { - if (output.headers) { - for (const header of Object.keys(output.headers)) { - res.setHeader(header, output.headers[header]) - } - } - res.setHeader('X-VS-Cache', 'Hit') - - if (output.httpCode) { - res.status(output.httpCode) - } - - if (output.body) { - res.end(output.body) - } else { - res.setHeader('Content-Type', 'text/html') - res.end(output) - } - console.log(`cache hit [${req.url}], cached request: ${Date.now() - s}ms`) - next() - } else { - res.setHeader('X-VS-Cache', 'Miss') - console.log(`cache miss [${req.url}], request: ${Date.now() - s}ms`) - dynamicRequestHandler(renderer, config) // render response - } - }).catch(errorHandler) - } else { - dynamicRequestHandler(renderer, config) - } - } - - let requestContextConfig: any = config.util.extendDeep({}, config); - - if (config.server.dynamicConfigReload && !globalContextConfig) { - if (configProviders.length > 0) { - const configPromises = []; - configProviders.forEach(configProvider => { - if (typeof configProvider === 'function') { - configPromises.push(configProvider(req).then(loadedConfig => { - requestContextConfig = config.util.extendDeep(requestContextConfig, loadedConfig) - }).catch(() => { - if (!config.server.dynamicConfigContinueOnError) { - if (req.url !== '/error') { - res.redirect('/error') - } - } - })) - } - }) - await Promise.all(configPromises) - - if (!config.server.dynamicConfigReloadWithEachRequest) { - globalContextConfig = config.util.extendDeep({}, requestContextConfig) - } - } - } else if (globalContextConfig) { - requestContextConfig = config.util.extendDeep({}, globalContextConfig) - } - - dynamicCacheHandler(requestContextConfig) -}) - -let port = process.env.PORT || config.server.port -const host = process.env.HOST || config.server.host -const start = () => { - const server = app.listen(port, host) - server.on('listening', () => { - console.log(`\n\n----------------------------------------------------------`) - console.log('| |') - console.log(`| Vue Storefront Server started at http://${host}:${port} |`) - console.log('| |') - console.log(`----------------------------------------------------------\n\n`) - - serverHooksExecutors.httpServerIsReady({ server, config: config.server, isProd }) - }).on('error', (e) => { - if (e.code === 'EADDRINUSE') { - port = parseInt(port) + 1 - console.log(`The port is already in use, trying ${port}`) - start() - } - }) -} -start() diff --git a/core/scripts/static-server.ts b/core/scripts/static-server.ts deleted file mode 100644 index f4a820c489..0000000000 --- a/core/scripts/static-server.ts +++ /dev/null @@ -1,42 +0,0 @@ -import express from 'express' -import config from 'config' -import path from 'path' -const app = express() - -const rootPath = require('app-root-path').path -const resolve = file => path.resolve(rootPath, file) -const isProd = true - -const serve = (path, cache, options) => express.static(resolve(path), Object.assign({ - maxAge: cache && isProd ? 2592000000 : 0, // 1 month in milliseconds = 1000 * 60 * 60 * 24 * 30 = 2592000000 - fallthrough: false -}, options)) - -app.use('/', serve(resolve(config.staticPages.destPath), true, { - setHeaders: function (res, path, stat) { - if (path.endsWith('.svg')) { - res.set('Content-Type', 'image/svg+xml; charset=UTF-8') - } else { - if (!path.endsWith('.js')) { - res.set('Content-Type', 'text/html; charset=UTF-8') - } - }// TODO: add better mime type guessing - } -})) - -let port = process.env.PORT || config.server.port -const host = process.env.HOST || config.server.host -const start = () => { - app.listen(port, host) - .on('listening', () => { - console.log(`Vue Storefront Static Server started at http://${host}:${port}`) - }) - .on('error', (e: { code: string }) => { - if (e.code === 'EADDRINUSE') { - port = parseInt(port) + 1 - console.log(`The port is already in use, trying ${port}`) - start() - } - }) -} -start() diff --git a/core/scripts/utils/api-status.js b/core/scripts/utils/api-status.js deleted file mode 100644 index 53a6305f6e..0000000000 --- a/core/scripts/utils/api-status.js +++ /dev/null @@ -1,13 +0,0 @@ -/** Creates a API status call and sends it through to Express SearchResponse object. - * @param {expressserver.response} res Express HTTP SearchResponse - * @param {number} [code=200] Status code to send on success - * @param {json} [result='OK'] Text message or result information object - */ -module.exports = (res, result = 'OK', code = 200, meta = null) => { - let apiResult = { code: code, result: result } - if (meta !== null) { - apiResult.meta = meta - } - res.status(code).json(apiResult) - return result -} diff --git a/core/scripts/utils/cache-instance.js b/core/scripts/utils/cache-instance.js deleted file mode 100644 index f9126c62e8..0000000000 --- a/core/scripts/utils/cache-instance.js +++ /dev/null @@ -1,25 +0,0 @@ -const fs = require('fs') -const path = require('path') -const TagCache = require('redis-tag-cache').default -const config = require('config') -let cache = false - -if (config.server.useOutputCache) { - const cacheVersionPath = path.resolve(path.join('core', 'build', 'cache-version.json')) - let cacheKey = '' - try { - cacheKey = JSON.parse(fs.readFileSync(cacheVersionPath) || '') - } catch (err) { - console.error(err) - } - const redisConfig = Object.assign(config.redis, { keyPrefix: cacheKey }) - - console.log('Redis cache set', redisConfig) - - cache = new TagCache({ - redis: redisConfig, - defaultTimeout: config.server.outputCacheDefaultTtl - }) -} - -module.exports = cache diff --git a/core/scripts/utils/catalog-client.ts b/core/scripts/utils/catalog-client.ts deleted file mode 100644 index 8d2a1f8023..0000000000 --- a/core/scripts/utils/catalog-client.ts +++ /dev/null @@ -1,58 +0,0 @@ -import queryString from 'query-string' -import fetch from 'isomorphic-fetch' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -export const processURLAddress = (url: string = '', config: any) => { - if (url.startsWith('/')) return `${getApiEndpointUrl(config.api, 'url')}${url}` - return url -} -export async function search (request, storeView, config) { - const elasticsearchQueryBody = request.searchQuery - if (!request.index) request.index = storeView.elasticsearch.index - let url = processURLAddress(getApiEndpointUrl(storeView.elasticsearch, 'host'), config) - - const httpQuery: { - size: number, - from: number, - sort: string, - _source_exclude?: string[], - _source_include?: string[], - q?: string, - request?: string - } = { - size: request.size, - from: request.from, - sort: request.sort - } - - if (request._sourceExclude) { - httpQuery._source_exclude = request._sourceExclude.join(',') - } - if (request._sourceInclude) { - httpQuery._source_include = request._sourceInclude.join(',') - } - if (request.q) { - httpQuery.q = request.q - } - - if (!request.index || !request.type) { - throw new Error('Query.index and Query.type are required arguments for executing ElasticSearch query') - } - if (config.elasticsearch.queryMethod === 'GET') { - httpQuery.request = JSON.stringify(elasticsearchQueryBody) - } - url = url + '/' + encodeURIComponent(request.index) + '/' + encodeURIComponent(request.type) + '/_search' - url = url + '?' + queryString.stringify(httpQuery) - return fetch(url, { method: config.elasticsearch.queryMethod, - mode: 'cors', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - body: config.elasticsearch.queryMethod === 'POST' ? JSON.stringify(elasticsearchQueryBody) : null - }) - .then(resp => { return resp.json() }) - .catch(error => { - throw new Error('FetchError in request to ES: ' + error.toString()) - }) -} diff --git a/core/scripts/utils/page-generator.ts b/core/scripts/utils/page-generator.ts deleted file mode 100644 index ec577b19ff..0000000000 --- a/core/scripts/utils/page-generator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import fs from 'fs-extra' -import path from 'path' -function saveRenderedPage (destPath, output) { - return fs.outputFile(destPath, output) -} -function saveScripts (basePath, destPath) { - return fs.copy(path.join(basePath, 'dist'), path.join(destPath, 'dist')) -} -function saveAssets (basePath, destPath) { - return fs.copy(path.join(basePath, 'assets'), path.join(destPath, 'assets')) -} -function saveIndex (basePath, destPath) { -} -function saveSW (basePath, destPath) { - return fs.copy(path.join(basePath, 'dist', 'service-worker.js'), path.join(destPath, 'service-worker.js')) -} -function clearAll (destPath) { - return fs.removeSync(destPath) -} -export default { - saveRenderedPage, - saveAssets, - saveIndex, - saveSW, - saveScripts, - clearAll -} diff --git a/core/scripts/utils/sort-translations.js b/core/scripts/utils/sort-translations.js deleted file mode 100644 index 0a915b878b..0000000000 --- a/core/scripts/utils/sort-translations.js +++ /dev/null @@ -1,19 +0,0 @@ -const { readFileSync, writeFileSync } = require('fs'); -const { EOL } = require('os'); - -const fixTranslation = (filename) => { - const content = readFileSync(filename, 'utf8'); - - writeFileSync( - filename, - content - .split(EOL) - .sort() - .filter(line => line.length > 0) - .join(EOL) + EOL - ); -} - -process.argv.map(filename => { - filename.split('.').pop() === 'csv' && fixTranslation(filename); -}); diff --git a/core/scripts/utils/ssr-renderer.ts b/core/scripts/utils/ssr-renderer.ts deleted file mode 100644 index c64f177a9b..0000000000 --- a/core/scripts/utils/ssr-renderer.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { Context } from './types'; -const fs = require('fs') -const path = require('path') -const compile = require('lodash.template') -const rootPath = require('app-root-path').path -const resolve = file => path.resolve(rootPath, file) -const omit = require('lodash/omit') -const set = require('lodash/set') -const get = require('lodash/get') -const config = require('config') -const minify = require('html-minifier').minify - -function createRenderer (bundle, clientManifest, template?) { - let shouldPreload = () => {} - let shouldPrefetch = () => {} - try { - const scripts = require('../../modules/initial-resources/serverResourcesFilter') - shouldPreload = scripts.shouldPreload - shouldPrefetch = scripts.shouldPrefetch - } catch (err) { - if (config.initialResources) { - console.error(err) - } - } - const LRU = require('lru-cache') - // https://github.com/vuejs/vue/blob/dev/packages/vue-server-renderer/README.md#why-use-bundlerenderer - return require('vue-server-renderer').createBundleRenderer(bundle, { - clientManifest, - // runInNewContext: false, - cache: new LRU({ - max: 1000, - maxAge: 1000 * 60 * 15 - }), - shouldPreload, - shouldPrefetch - }) -} - -function getFieldsToFilter () { - const fields = [ - ...(config.ssr && (config.ssr.initialStateFilter || [])), - ...(config.ssr && (config.ssr.lazyHydrateFor || [])) - ] - - return fields -} - -function filterState (context) { - if (!config.ssr.useInitialStateFilter) { - return context - } - - for (const field of getFieldsToFilter()) { - const newValue = get(context.initialState, field, null) - set(context.state, field, newValue) - } - - if (!config.server.dynamicConfigReload) { - context.state = omit(context.state, ['config']) - } - - return omit(context, ['initialState']) -} - -function applyAdvancedOutputProcessing (context, output, templatesCache, isProd = true, relatvePaths = false, destDir = '', outputFilename = '') { - context = filterState(context) - const contentPrepend = (typeof context.output.prepend === 'function') ? context.output.prepend(context) : ''; - const contentAppend = (typeof context.output.append === 'function') ? context.output.append(context) : ''; - output = contentPrepend + output + contentAppend; - if (context.output.template) { // case when we've got the template name back from vue app - if (!isProd) { context.output.template = 'default'; } // in dev mode we can not use pre-rendered HTML templates - if (templatesCache[context.output.template]) { // please look at: https://github.com/vuejs/vue/blob/79cabadeace0e01fb63aa9f220f41193c0ca93af/src/server/template-renderer/index.js#L87 for reference - output = templatesCache[context.output.template](context).replace('', output); - } else { - throw new Error(`The given template name ${context.output.template} does not exist`); - } - } - if (relatvePaths) { - const relativePath = path.relative(outputFilename, destDir).replace('../', '') - output = output.replace(new RegExp('/dist', 'g'), `${relativePath}/dist`) - output = output.replace(new RegExp('/assets', 'g'), `${relativePath}/dist`) - output = output.replace(new RegExp('href="/', 'g'), `href="${relativePath}/`) - } - - if (config.server.useHtmlMinifier) { - console.debug('HTML Minifier is enabled') - output = minify(output, config.server.htmlMinifierOptions) - } - - if ((typeof context.output.filter === 'function')) { - output = context.output.filter(output, context) - } - - return output; -} - -function initTemplatesCache (config, compileOptions) { - const templatesCache = {} - for (const tplName of Object.keys(config.ssr.templates)) { - const fileName = resolve(config.ssr.templates[tplName]); - if (fs.existsSync(fileName)) { - const template = fs.readFileSync(fileName, 'utf-8'); - templatesCache[tplName] = compile(template, compileOptions); - } - } - return templatesCache -} - -function initSSRRequestContext (app, req, res, config): Context { - return { - url: decodeURI(req.url), - output: { - prepend: (context) => { return ''; }, - append: (context) => { return ''; }, - filter: (output, context) => { return output }, - appendHead: (context) => { return ''; }, - template: 'default', - cacheTags: new Set() - }, - server: { - app: app, - response: res, - request: req - }, - meta: null, - vs: { - config: config, - storeCode: typeof req.header === 'function' ? (req.header('x-vs-store-code') ? req.header('x-vs-store-code') : process.env.STORE_CODE) : process.env.STORE_CODE - } - }; -} - -function clearContext (context) { - Object.keys(context.server).forEach(key => delete context.server[key]) - delete context.output['cacheTags'] - delete context['meta'] -} - -export { - createRenderer, - initTemplatesCache, - initSSRRequestContext, - applyAdvancedOutputProcessing, - compile as compileTemplate, - clearContext -} diff --git a/core/scripts/utils/types/index.ts b/core/scripts/utils/types/index.ts deleted file mode 100644 index 373620cac3..0000000000 --- a/core/scripts/utils/types/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Express } from 'express' - -export interface Context { - url: string, - output: { - prepend: (context: any) => string, - append: (context: any) => string, - filter: (output: T, context: any) => T, - appendHead: (context: any) => string, - template: string, - cacheTags: Set - }, - server: { - app: Express, - response: Express.Response, - request: Express.Request - }, - meta: any|null, - vs: { - config: Record, - storeCode: string - } -} diff --git a/core/server-entry.ts b/core/server-entry.ts deleted file mode 100755 index 0f4103e2df..0000000000 --- a/core/server-entry.ts +++ /dev/null @@ -1,107 +0,0 @@ -import union from 'lodash-es/union' -import { createApp } from '@vue-storefront/core/app' -import { HttpError } from '@vue-storefront/core/helpers/internal' -import storeCodeFromRoute from '@vue-storefront/core/lib/storeCodeFromRoute' -import omit from 'lodash-es/omit' -import pick from 'lodash-es/pick' -import buildTimeConfig from 'config' -import { AsyncDataLoader } from '@vue-storefront/core/lib/async-data-loader' -import config from 'config' -import { Logger } from '@vue-storefront/core/lib/logger' -import { RouterManager } from './lib/router-manager'; -import queryString from 'query-string' -import purgeConfig from './build/purge-config/purgeConfig'; - -function _commonErrorHandler (err, reject) { - if (err.message.indexOf('query returned empty result') > 0) { - reject(new HttpError(err.message, 404)) - } else { - reject(new Error(err.stack)) - } -} - -function _ssrHydrateSubcomponents (components, store, router, resolve, reject, app, context) { - Promise.all(components.map(SubComponent => { - if (SubComponent.asyncData) { - return SubComponent.asyncData({ - store, - route: router.currentRoute, - context - }) - } else { - return Promise.resolve(null) - } - })).then(() => { - AsyncDataLoader.flush({ store, route: router.currentRoute, context } /* AsyncDataLoaderActionContext */).then((r) => { - context.state = store.state - if (buildTimeConfig.server.dynamicConfigReload) { - const excludeFromConfig = buildTimeConfig.server.dynamicConfigExclude - const includeFromConfig = buildTimeConfig.server.dynamicConfigInclude - // console.log(excludeFromConfig, includeFromConfig) - if (includeFromConfig && includeFromConfig.length > 0) { - context.state.config = pick(context.state.config, includeFromConfig) - } - if (excludeFromConfig && excludeFromConfig.length > 0) { - context.state.config = omit(context.state.config, excludeFromConfig) - } - context.state.config = purgeConfig({ - ...context.state.config, - purgeConfig: buildTimeConfig.purgeConfig - }) - } - resolve(app) - }).catch(err => { - _commonErrorHandler(err, reject) - }) - }).catch(err => { - _commonErrorHandler(err, reject) - }) -} - -function getHostFromHeader (headers: string[]): string { - return headers['x-forwarded-host'] !== undefined ? headers['x-forwarded-host'] : headers['host'] -} - -export default async context => { - let storeCode = context.vs.storeCode - if (config.storeViews.multistore === true) { - if (!storeCode) { // this is from url - const currentRoute = Object.assign({ path: queryString.parseUrl(context.url).url/* this gets just the url path part */, host: getHostFromHeader(context.server.request.headers) }) - storeCode = storeCodeFromRoute(currentRoute) - } - } - const { app, router, store, initialState } = await createApp(context, context.vs && context.vs.config ? context.vs.config : buildTimeConfig, storeCode) - - RouterManager.flushRouteQueue() - context.initialState = initialState - return new Promise((resolve, reject) => { - const meta = (app as any).$meta() - router.push(context.url) - context.meta = meta - router.onReady(() => { - const matchedComponents = router.getMatchedComponents() - if (!matchedComponents.length || !matchedComponents[0]) { - return reject(new HttpError('No components matched', 404)) // TODO - don't redirect if already on page-not-found - } - store.dispatch('url/setCurrentRoute', { to: router.currentRoute }) - Promise.all(matchedComponents.map((Component: any) => { - const components = Component.mixins ? Array.from(Component.mixins) : [] - union(components, [Component]).map(SubComponent => { - if (SubComponent.preAsyncData) { - SubComponent.preAsyncData({ store, route: router.currentRoute }) - } - }) - if (Component.asyncData) { - Component.asyncData({ store, route: router.currentRoute, context: context }).then((result) => { // always execute the asyncData() from the top most component first - Logger.debug('Top-most asyncData executed')() - _ssrHydrateSubcomponents(components, store, router, resolve, reject, app, context) - }).catch((err) => { - _commonErrorHandler(err, reject) - }) - } else { - _ssrHydrateSubcomponents(components, store, router, resolve, reject, app, context) - } - })) - }, reject) - }) -} diff --git a/core/server/hooks.ts b/core/server/hooks.ts deleted file mode 100644 index 0fa31db24b..0000000000 --- a/core/server/hooks.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { createListenerHook, createMutatorHook } from '@vue-storefront/core/lib/hooks' -import { Express, Request } from 'express' -import http from 'http' - -// To add like tracing which needs to be done as early as possible - -const { - hook: afterProcessStartedHook, - executor: afterProcessStartedExecutor -} = createListenerHook() - -interface BeforeCacheInvalidatedParamter { - tags: string[], - req: Request -} - -interface AfterCacheInvalidatedParamter { - tags: string[], - req: Request -} - -const { - hook: beforeCacheInvalidatedHook, - executor: beforeCacheInvalidatedExecutor -} = createListenerHook() - -const { - hook: afterCacheInvalidatedHook, - executor: afterCacheInvalidatedExecutor -} = createListenerHook() - -// beforeStartApp -interface Extend { - app: Express, - config: any, - isProd: boolean -} - -const { - hook: afterApplicationInitializedHook, - executor: afterApplicationInitializedExecutor -} = createListenerHook() - -interface Server { - server: http.Server, - config: any, - isProd: boolean -} - -const { - hook: httpServerIsReadyHook, - executor: httpServerIsReadyExecutor -} = createListenerHook() - -interface Exception { - err: Exception, - req: Request, - isProd: boolean -} - -const { - hook: ssrExceptionHook, - executor: ssrExceptionExecutor -} = createListenerHook() - -const { - hook: beforeOutputRenderedResponseHook, - executor: beforeOutputRenderedResponseExecutor -} = createMutatorHook() - -const { - hook: afterOutputRenderedResponseHook, - executor: afterOutputRenderedResponseExecutor -} = createMutatorHook() - -/** Only for internal usage in this module */ -const serverHooksExecutors = { - afterProcessStarted: afterProcessStartedExecutor, - afterApplicationInitialized: afterApplicationInitializedExecutor, - httpServerIsReady: httpServerIsReadyExecutor, - ssrException: ssrExceptionExecutor, - beforeOutputRenderedResponse: beforeOutputRenderedResponseExecutor, - afterOutputRenderedResponse: afterOutputRenderedResponseExecutor, - beforeCacheInvalidated: beforeCacheInvalidatedExecutor, - afterCacheInvalidated: afterCacheInvalidatedExecutor -} - -const serverHooks = { - /** Hook is fired right at the start of the app. - * @param void - */ - afterProcessStarted: afterProcessStartedHook, - /** - * - */ - afterApplicationInitialized: afterApplicationInitializedHook, - httpServerIsReady: httpServerIsReadyHook, - ssrException: ssrExceptionHook, - beforeOutputRenderedResponse: beforeOutputRenderedResponseHook, - afterOutputRenderedResponse: afterOutputRenderedResponseHook, - beforeCacheInvalidated: beforeCacheInvalidatedHook, - afterCacheInvalidated: afterCacheInvalidatedHook -} - -export { - serverHooks, - serverHooksExecutors -} diff --git a/core/service-worker/index.js b/core/service-worker/index.js deleted file mode 100644 index af83f90dfe..0000000000 --- a/core/service-worker/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import '../modules/offline-order/extends/service-worker.js' -import 'theme/service-worker/index.js' - -// core service worker, all service worker related features are placed here. diff --git a/core/service-worker/registration.js b/core/service-worker/registration.js deleted file mode 100644 index 0d74296127..0000000000 --- a/core/service-worker/registration.js +++ /dev/null @@ -1,27 +0,0 @@ -import { register } from 'register-service-worker' -import { server } from 'config' -import { Logger } from '@vue-storefront/core/lib/logger' - -if (process.env.NODE_ENV === 'production' || server.devServiceWorker) { - register(`/service-worker.js`, { - ready () { - Logger.log( - 'App is being served from cache by a service worker.' - ) - }, - cached () { - Logger.log('Content has been cached for offline use.')() - }, - updated (registration) { - Logger.log('New content is available, please refresh.')() - }, - offline () { - Logger.log( - 'No internet connection found. App is running in offline mode.' - ) - }, - error (error) { - Logger.error('Error during service worker registration:', error)() - } - }) -} diff --git a/core/store/actions.ts b/core/store/actions.ts deleted file mode 100644 index 84c5d69eda..0000000000 --- a/core/store/actions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ActionTree } from 'vuex' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState' - -const actions: ActionTree = { - async resetUserInvalidateLock ({ commit }) { - commit(types.USER_TOKEN_INVALIDATE_LOCK_CHANGED, 0) - }, - async resetUserInvalidation ({ commit }) { - commit(types.RESET_USER_TOKEN_INVALIDATION) - } -} - -export default actions diff --git a/core/store/getters.ts b/core/store/getters.ts deleted file mode 100644 index 4a2ef32672..0000000000 --- a/core/store/getters.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { GetterTree } from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' - -const getters: GetterTree = { - getCurrentStoreView: state => state.storeView -} - -export default getters diff --git a/core/store/index.ts b/core/store/index.ts deleted file mode 100644 index 2112be7d9c..0000000000 --- a/core/store/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import Vue from 'vue' -import Vuex from 'vuex' -import RootState from '@vue-storefront/core/types/RootState' -import { once } from '@vue-storefront/core/helpers' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' - -once('__VUE_EXTEND_VUEX__', () => { - Vue.use(Vuex) -}) - -const state: any = { - version: '', - __DEMO_MODE__: false, - config: {}, - storeView: {}, - twoStageCachingDelta1: 0, - twoStageCachingDelta2: 0, - twoStageCachingDisabled: false, - userTokenInvalidated: null, - userTokenInvalidateAttemptsCount: 0, - userTokenInvalidateLock: 0 -} - -let rootStore = new Vuex.Store({ - // TODO: refactor it to return just the constructor to avoid event-bus and i18n shenanigans; challenge: the singleton management OR add i18n and eventBus here to rootStore instance? modules: { - state, - actions, - getters, - mutations -}) - -export default rootStore diff --git a/core/store/mutation-types.ts b/core/store/mutation-types.ts deleted file mode 100644 index 26c171a542..0000000000 --- a/core/store/mutation-types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const SN_ROOT = 'root' -export const USER_TOKEN_INVALIDATE_LOCK_CHANGED = SN_ROOT + '/USER_TOKEN_INVALIDATE_LOCK_CHANGED' -export const RESET_USER_TOKEN_INVALIDATION = SN_ROOT + '/RESET_USER_TOKEN_INVALIDATION' diff --git a/core/store/mutations.ts b/core/store/mutations.ts deleted file mode 100644 index 9d9d351bc7..0000000000 --- a/core/store/mutations.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { MutationTree } from 'vuex' -import * as types from './mutation-types' -import RootState from '@vue-storefront/core/types/RootState' - -const mutations: MutationTree = { - [types.USER_TOKEN_INVALIDATE_LOCK_CHANGED] (state, payload) { - state.userTokenInvalidateLock = payload - }, - [types.RESET_USER_TOKEN_INVALIDATION] (state) { - state.userTokenInvalidateLock = 0 - state.userTokenInvalidated = null - state.userTokenInvalidateAttemptsCount = 0 - } - -} - -export default mutations diff --git a/core/test/unit/helpers/buildFilterProductsQuery.spec.ts b/core/test/unit/helpers/buildFilterProductsQuery.spec.ts deleted file mode 100644 index 77d35cbe9e..0000000000 --- a/core/test/unit/helpers/buildFilterProductsQuery.spec.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { buildFilterProductsQuery } from '@vue-storefront/core/helpers' - -jest.mock('remove-accents', () => jest.fn()); -jest.mock('@vue-storefront/core/modules/url/helpers', () => jest.fn()); -jest.mock('@vue-storefront/core/lib/multistore', () => jest.fn()); -jest.mock('config', () => ({ - products: { - 'defaultFilters': ['color', 'size', 'price', 'erin_recommends'] - } -})); -jest.mock('@vue-storefront/core/store', () => ({})); - -describe('buildFilterProductsQuery method', () => { - let currentCategory - - beforeEach(() => { - currentCategory = { - 'path': '1/2/20', - 'is_active': true, - 'level': 2, - 'product_count': 0, - 'children_count': '8', - 'parent_id': 2, - 'name': 'Women', - 'id': 20, - 'url_path': 'women/women-20', - 'url_key': 'women-20', - 'children_data': [ - { - 'id': 21, - 'children_data': [ - { - 'id': 23 - }, - { - 'id': 24 - }, - { - 'id': 25 - }, - { - 'id': 26 - } - ] - }, - { - 'id': 22, - 'children_data': [ - { - 'id': 27 - }, - { - 'id': 28 - } - ] - } - ], - '_score': null, - 'slug': 'women-20' - } - }); - - it('should build default query', () => { - const result = buildFilterProductsQuery(currentCategory) - const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'category_ids') - expect(categoryFilter).toBeDefined() - expect(categoryFilter.value.in).toEqual([20, 21, 23, 24, 25, 26, 22, 27, 28]) - }); - - it('should build query with color filters', () => { - const filters = { - color: [ - { - 'id': '49', - 'label': 'Black', - 'type': 'color', - 'attribute_code': 'color' - }, - { - 'id': '50', - 'label': 'Blue', - 'type': 'color', - 'attribute_code': 'color' - } - ] - } - const result = buildFilterProductsQuery(currentCategory, filters) - const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'color') - expect(categoryFilter).toBeDefined() - expect(categoryFilter.value.in).toEqual(['49', '50']) - }); - - it('should build query with single price from 0 filter', () => { - const filters = { - price: { - 'id': '0.0-50.0', - 'type': 'price', - 'from': 0, - 'to': 50, - 'label': '< $50', - 'attribute_code': 'price' - } - } - const result = buildFilterProductsQuery(currentCategory, filters) - const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'price') - expect(categoryFilter).toBeDefined() - expect(categoryFilter.value.lte).toEqual(50) - }); - - it('should build query with single price between 50-100 filter', () => { - const filters = { - price: { - 'id': '50.0-100.0', - 'type': 'price', - 'from': 50, - 'to': 100, - 'label': '$50 - 100', - 'attribute_code': 'price' - } - } - const result = buildFilterProductsQuery(currentCategory, filters) - const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'price') - expect(categoryFilter).toBeDefined() - expect(categoryFilter.value.gte).toEqual(50) - expect(categoryFilter.value.lte).toEqual(100) - }); - - it('should build query with price filters', () => { - const filters = { - price: [{ - 'id': '0.0-50.0', - 'type': 'price', - 'from': 0, - 'to': 50, - 'label': '< $50', - 'attribute_code': 'price' - }] - } - const result = buildFilterProductsQuery(currentCategory, filters) - const categoryFilter = result.getAppliedFilters().find(filter => filter.attribute === 'price') - expect(categoryFilter).toBeDefined() - expect(categoryFilter.value.lte).toEqual(50) - }); - - it('should build query with color and erin_recommends filters', () => { - const filters = { - color: [ - { - 'id': '49', - 'label': 'Black', - 'type': 'color', - 'attribute_code': 'color' - }, - { - 'id': '50', - 'label': 'Blue', - 'type': 'color', - 'attribute_code': 'color' - } - ], - erin_recommends: [ - { - 'id': '1', - 'label': 'Yes', - 'type': 'erin_recommends', - 'attribute_code': 'erin_recommends' - } - ] - } - const result = buildFilterProductsQuery(currentCategory, filters) - const colorFilter = result.getAppliedFilters().find(filter => filter.attribute === 'color') - expect(colorFilter).toBeDefined() - expect(colorFilter.value.in).toEqual(['49', '50']) - const erinFilter = result.getAppliedFilters().find(filter => filter.attribute === 'erin_recommends') - expect(erinFilter).toBeDefined() - expect(erinFilter.value.in).toEqual(['1']) - }); -}); diff --git a/core/types/RootState.ts b/core/types/RootState.ts deleted file mode 100644 index 017ed7f7b4..0000000000 --- a/core/types/RootState.ts +++ /dev/null @@ -1,37 +0,0 @@ -export default interface RootState { - version: string, - __DEMO_MODE__: boolean, - config: any, - cart: any, - checkout: any, - cms: any, - compare: any, - product: any, - shipping: any, - user: any, - wishlist: any, - attribute: any, - ui: any, - newsletter: any, - category: { - current_path: string, - current_product_query: any, - current: { - slug: string, - name: string - }, - filters: any - }, - stock: { - cache: any - }, - storeView: any, - twoStageCachingDelta1: number, - twoStageCachingDelta2: number, - twoStageCachingDisabled: boolean, - userTokenInvalidated: string | null, - userTokenInvalidateAttemptsCount: number, - userTokenInvalidateLock: number, - route?: any, - url: any -} diff --git a/core/types/asyncData.d.ts b/core/types/asyncData.d.ts deleted file mode 100644 index d4dc961edc..0000000000 --- a/core/types/asyncData.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import Vue from 'vue'; -import { Route } from 'vue-router'; -import { Context } from '@vue-storefront/core/scripts/utils/types' - -interface AsyncDataParameter { - store: any, - route: Route, - context?: Context -} - -declare module 'vue/types/options' { - interface ComponentOptions { - asyncData?: ({ store, route, context }: AsyncDataParameter) => Promise - } -} diff --git a/core/types/isomorphic-fetch.d.ts b/core/types/isomorphic-fetch.d.ts deleted file mode 100644 index 5a7458ed47..0000000000 --- a/core/types/isomorphic-fetch.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare const fet: typeof fetch; - -declare module 'isomorphic-fetch' { - export const fetch: typeof fet; - export default fetch; -} diff --git a/core/types/search/HttpQuery.ts b/core/types/search/HttpQuery.ts deleted file mode 100644 index bcbab4bd47..0000000000 --- a/core/types/search/HttpQuery.ts +++ /dev/null @@ -1,11 +0,0 @@ -export default interface HttpQuery { - q?: string, - size: number, - from: number, - sort: string, - request?: string, - request_format?: string, - response_format?: string, - _source_exclude?: string, - _source_include?: string -} diff --git a/core/types/search/SearchRequest.ts b/core/types/search/SearchRequest.ts deleted file mode 100644 index 99cb325d84..0000000000 --- a/core/types/search/SearchRequest.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface SearchRequest { - store: any, - type: string, - searchQuery: any, - size: number, - groupId: any, - groupToken: any, - from: number, - sort: string, - _sourceExclude?: string, - _sourceInclude?: string -} diff --git a/core/types/search/SearchResponse.ts b/core/types/search/SearchResponse.ts deleted file mode 100644 index 49880d1c40..0000000000 --- a/core/types/search/SearchResponse.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface SearchResponse { - items: any[], - total: number, - start: number, - perPage: number, - aggregations: any, - offline?: boolean, - cache?: boolean, - noresults?: boolean, - suggestions: any, - attributeMetadata?: any -} diff --git a/cypress.json b/cypress.json index 51f68f789a..d5fd315327 100644 --- a/cypress.json +++ b/cypress.json @@ -1,10 +1,6 @@ { - "baseUrl": "http://localhost:3000", - "projectId": "xrqwrm", - "fixturesFolder": "test/e2e/fixtures", - "integrationFolder": "test/e2e/integration", - "pluginsFile": "test/e2e/plugins/index.js", - "supportFile": "test/e2e/support/index.js", - "viewportWidth": 1280, - "viewportHeight": 1024 -} + "baseUrl": "https://lovecrafts-demo.storefrontcloud.io/", + "viewportWidth": 1280, + "viewportHeight": 1024 + } + \ No newline at end of file diff --git a/dev/docker/Dockerfile b/dev/docker/Dockerfile index 528a855b84..76d567cf7a 100644 --- a/dev/docker/Dockerfile +++ b/dev/docker/Dockerfile @@ -1,17 +1,17 @@ -FROM node:10 +FROM node:12 -ENV NODE_CONFIG_ENV=docker PM2_ARGS=--no-daemon BIND_HOST=0.0.0.0 VS_ENV=prod +# ENV NODE_CONFIG_ENV=docker PM2_ARGS=--no-daemon BIND_HOST=0.0.0.0 VS_ENV=prod + +ARG COMMIT +ENV LAST_COMMIT=${COMMIT} WORKDIR /var/www COPY . . -# Should be yarn install --production -RUN apt update && apt install -y git \ - && yarn install \ - && yarn build +RUN yarn install --network-concurrency 1 && yarn build:ct && yarn cache clean --all COPY dev/docker/vue-storefront.sh /usr/local/bin/ -RUN chmod a+x /usr/local/bin/vue-storefront.sh ENTRYPOINT ["vue-storefront.sh"] + diff --git a/dev/docker/vue-storefront.sh b/dev/docker/vue-storefront.sh old mode 100644 new mode 100755 index 4413dc3c06..547235e1b5 --- a/dev/docker/vue-storefront.sh +++ b/dev/docker/vue-storefront.sh @@ -2,7 +2,7 @@ set -e if [ "$VS_ENV" = 'dev' ]; then - yarn dev + yarn start:ct else - yarn start + yarn start:ct fi diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 97ade3f72c..0000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,30 +0,0 @@ -version: '2.0' -services: - app: - # image: divante/vue-storefront:latest - build: - context: . - dockerfile: docker/vue-storefront/Dockerfile - env_file: docker/vue-storefront/default.env - environment: - VS_ENV: dev - network_mode: host - volumes: - - './babel.config.js:/var/www/babel.config.js' - - './config:/var/www/config' - - './core:/var/www/core' - - './ecosystem.json:/var/www/ecosystem.json' - - './.eslintignore:/var/www/.eslintignore' - - './.eslintrc.js:/var/www/.eslintrc.js' - - './lerna.json:/var/www/lerna.json' - - './tsconfig.json:/var/www/tsconfig.json' - - './tsconfig-build.json:/var/www/tsconfig-build.json' - - './shims.d.ts:/var/www/shims.d.ts' - - './package.json:/var/www/package.json' - - './src:/var/www/src' - - './var:/var/www/var' - - './packages:/var/www/packages' - tmpfs: - - /var/www/dist - ports: - - '3000:3000' diff --git a/docker/vue-storefront/Dockerfile b/docker/vue-storefront/Dockerfile deleted file mode 100644 index cfdaa30cc3..0000000000 --- a/docker/vue-storefront/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM node:10-alpine - -ENV VS_ENV prod - -WORKDIR /var/www - -COPY package.json ./ -COPY yarn.lock ./ - -RUN apk add --no-cache --virtual .build-deps ca-certificates wget python make g++ \ - && apk add --no-cache git \ - && yarn install --no-cache \ - && apk del .build-deps - -COPY docker/vue-storefront/vue-storefront.sh /usr/local/bin/ - -CMD ["vue-storefront.sh"] diff --git a/docker/vue-storefront/default.env b/docker/vue-storefront/default.env deleted file mode 100644 index 7753cee5b6..0000000000 --- a/docker/vue-storefront/default.env +++ /dev/null @@ -1,4 +0,0 @@ -NODE_CONFIG_ENV=docker -VS_ENV=prod -BIND_HOST=0.0.0.0 -PM2_ARGS=--no-daemon diff --git a/docker/vue-storefront/vue-storefront.sh b/docker/vue-storefront/vue-storefront.sh deleted file mode 100755 index babf04e10c..0000000000 --- a/docker/vue-storefront/vue-storefront.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -set -e - -yarn install || exit $? - -if [ "$VS_ENV" = 'dev' ]; then - yarn dev -else - yarn build || exit $? - yarn start -fi diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js deleted file mode 100644 index 3eb45095ac..0000000000 --- a/docs/.vuepress/config.js +++ /dev/null @@ -1,159 +0,0 @@ -const GTM_TAG = 'GTM-WMDC3CP'; - -module.exports = { - base: '/v1/', - port: 8081, - markdown: { - toc: { - includeLevel: [2] - } - }, - head: [ - ['link', { rel: 'icon', href: '/favicon.png' }], - ['script', { src: 'https://cdnjs.cloudflare.com/ajax/libs/diff2html/2.12.1/diff2html.min.js'}], - // HubSpot - ['script', { async: true, defer: true, src: 'https://js.hs-scripts.com/8443671.js', id: 'hs-script-loader' }], - // Google Tag Manager - ['script', {}, [` - (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': - new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], - j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= - 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); - })(window,document,'script','dataLayer','${GTM_TAG}'); - `]] - ], - themeConfig: { - GTM_TAG, - repo: 'vuestorefront/vue-storefront', - docsDir: 'docs', - editLinks: true, - sidebarDepth: 3, - nav: [ - { - text: 'YouTube', - link: 'https://www.youtube.com/channel/UCkm1F3Cglty3CE1QwKQUhhg', - }, - { - text: 'Blog', - link: 'https://blog.vuestorefront.io/', - }, - ], - sidebar: { - '/guide/': [ - { - title : 'General Information', - collapsable: false, - children: [ - 'general/introduction' - ] - }, - 'upgrade-notes/', - 'security/', - { - title: 'Cookbook', - collapsable: false, - children: [ - 'cookbook/data-import', - 'cookbook/elastic', - 'cookbook/setup', - 'cookbook/module', - 'cookbook/theme', - 'cookbook/checklist' - ], - }, - { - title: 'Installation', - collapsable: false, - children: [ - 'installation/theme', - 'installation/production-setup', - ], - }, - { - title: 'Basics', - collapsable: false, - children: [ - 'basics/release-cycle', - 'basics/project-structure', - 'basics/configuration', - 'basics/contributing', - 'basics/feature-list', - 'basics/recipes', - 'basics/ssr-cache', - 'basics/static-generator', - 'basics/e2e', - 'basics/url' - ], - }, - { - title: 'Core and themes', - collapsable: false, - children: [ - 'core-themes/themes', - 'core-themes/layouts', - 'core-themes/core-components', - 'core-themes/ui-store', - 'core-themes/translations', - 'core-themes/service-workers', - 'core-themes/webpack', - 'core-themes/plugins', - 'core-themes/stylesheets', - ], - }, - { - title: 'Data in Vue Storefront', - collapsable: false, - children: [ - 'data/data', - 'data/elasticsearch', - 'data/elastic-queries', - 'data/database-tool', - 'data/static-data', - 'data/data-loader' - ], - }, - { - title: 'Integrations', - collapsable: false, - children: [ - 'integrations/integrations', - 'integrations/reviews', - 'integrations/payment-gateway', - 'integrations/paypal-payments', - 'integrations/direct-prices-sync', - 'integrations/tier-prices-sync', - 'integrations/totals-sync', - 'integrations/multistore', - ], - }, - { - title: 'Data Resolvers', - collapsable: false, - children: [ - 'data-resolvers/introduction', - 'data-resolvers/category-service', - 'data-resolvers/user-service', - ] - }, - { - title: 'Archives', - collapsable: true, - children: [ - 'archives/modules', - 'archives/extensions', - 'archives/components', - 'archives/vuex', - 'archives/cookbook', - 'archives/graphql', - 'archives/amp', - 'archives/typescript', - 'archives/migration', - 'archives/entity_type' - ], - }, - ], - }, - }, - title: 'Vue Storefront', - description: 'Headless PWA for eCommerce', -}; diff --git a/docs/.vuepress/public/Apple_iPhone_X.png b/docs/.vuepress/public/Apple_iPhone_X.png deleted file mode 100644 index 65a84df374..0000000000 Binary files a/docs/.vuepress/public/Apple_iPhone_X.png and /dev/null differ diff --git a/docs/.vuepress/public/Fil-Rakowski-VS-Demo-Youtube.png b/docs/.vuepress/public/Fil-Rakowski-VS-Demo-Youtube.png deleted file mode 100644 index 66787b1efd..0000000000 Binary files a/docs/.vuepress/public/Fil-Rakowski-VS-Demo-Youtube.png and /dev/null differ diff --git a/docs/.vuepress/public/GitHub-Architecture-VS.png b/docs/.vuepress/public/GitHub-Architecture-VS.png deleted file mode 100644 index 2a5bf55767..0000000000 Binary files a/docs/.vuepress/public/GitHub-Architecture-VS.png and /dev/null differ diff --git a/docs/.vuepress/public/Vue-storefront-architecture-proxy-requests.png b/docs/.vuepress/public/Vue-storefront-architecture-proxy-requests.png deleted file mode 100644 index d1561e3849..0000000000 Binary files a/docs/.vuepress/public/Vue-storefront-architecture-proxy-requests.png and /dev/null differ diff --git a/docs/.vuepress/public/Vue-storefront-architecture.png b/docs/.vuepress/public/Vue-storefront-architecture.png deleted file mode 100644 index d9d27aeee2..0000000000 Binary files a/docs/.vuepress/public/Vue-storefront-architecture.png and /dev/null differ diff --git a/docs/.vuepress/public/cart-localstorage.png b/docs/.vuepress/public/cart-localstorage.png deleted file mode 100644 index eba2da24e4..0000000000 Binary files a/docs/.vuepress/public/cart-localstorage.png and /dev/null differ diff --git a/docs/.vuepress/public/cart-sync.png b/docs/.vuepress/public/cart-sync.png deleted file mode 100644 index f3afb0a740..0000000000 Binary files a/docs/.vuepress/public/cart-sync.png and /dev/null differ diff --git a/docs/.vuepress/public/categories-localstorage.png b/docs/.vuepress/public/categories-localstorage.png deleted file mode 100644 index 3be79872e8..0000000000 Binary files a/docs/.vuepress/public/categories-localstorage.png and /dev/null differ diff --git a/docs/.vuepress/public/chrome-dev-console.png b/docs/.vuepress/public/chrome-dev-console.png deleted file mode 100644 index 707b5c5f41..0000000000 Binary files a/docs/.vuepress/public/chrome-dev-console.png and /dev/null differ diff --git a/docs/.vuepress/public/favicon.png b/docs/.vuepress/public/favicon.png deleted file mode 100644 index 9071dc96e9..0000000000 Binary files a/docs/.vuepress/public/favicon.png and /dev/null differ diff --git a/docs/.vuepress/public/logo.png b/docs/.vuepress/public/logo.png deleted file mode 100644 index 9071dc96e9..0000000000 Binary files a/docs/.vuepress/public/logo.png and /dev/null differ diff --git a/docs/.vuepress/public/magento_1.png b/docs/.vuepress/public/magento_1.png deleted file mode 100644 index e322bbf1f5..0000000000 Binary files a/docs/.vuepress/public/magento_1.png and /dev/null differ diff --git a/docs/.vuepress/public/magento_2.png b/docs/.vuepress/public/magento_2.png deleted file mode 100644 index 200a11fdc5..0000000000 Binary files a/docs/.vuepress/public/magento_2.png and /dev/null differ diff --git a/docs/.vuepress/public/magento_3.png b/docs/.vuepress/public/magento_3.png deleted file mode 100644 index 2b3051e762..0000000000 Binary files a/docs/.vuepress/public/magento_3.png and /dev/null differ diff --git a/docs/.vuepress/public/o2m-output.png b/docs/.vuepress/public/o2m-output.png deleted file mode 100644 index b52b063280..0000000000 Binary files a/docs/.vuepress/public/o2m-output.png and /dev/null differ diff --git a/docs/.vuepress/public/orders-collection.png b/docs/.vuepress/public/orders-collection.png deleted file mode 100644 index 9c05578b57..0000000000 Binary files a/docs/.vuepress/public/orders-collection.png and /dev/null differ diff --git a/docs/.vuepress/public/orders-localstorage.png b/docs/.vuepress/public/orders-localstorage.png deleted file mode 100644 index 34c1ffa548..0000000000 Binary files a/docs/.vuepress/public/orders-localstorage.png and /dev/null differ diff --git a/docs/.vuepress/public/partners/develo.png b/docs/.vuepress/public/partners/develo.png deleted file mode 100644 index 191256c313..0000000000 Binary files a/docs/.vuepress/public/partners/develo.png and /dev/null differ diff --git a/docs/.vuepress/public/partners/macopedia.svg b/docs/.vuepress/public/partners/macopedia.svg deleted file mode 100644 index 9df910d251..0000000000 --- a/docs/.vuepress/public/partners/macopedia.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - diff --git a/docs/.vuepress/public/paypal.svg b/docs/.vuepress/public/paypal.svg deleted file mode 100644 index 787ed4a114..0000000000 --- a/docs/.vuepress/public/paypal.svg +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - /execute - - - - - - - - - - - - YOUR SERVER - - - - - - - - - - - - - - - - - - API - - - - - - - Pay Now - - - - - - - - - - - - - - - - 1 - - - 2 - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - 4 - - - diff --git a/docs/.vuepress/public/storefront.png b/docs/.vuepress/public/storefront.png deleted file mode 100644 index 5c94d43070..0000000000 Binary files a/docs/.vuepress/public/storefront.png and /dev/null differ diff --git a/docs/.vuepress/public/syncTasks-example.png b/docs/.vuepress/public/syncTasks-example.png deleted file mode 100644 index e33d6a2619..0000000000 Binary files a/docs/.vuepress/public/syncTasks-example.png and /dev/null differ diff --git a/docs/.vuepress/public/video-webcast-1.png b/docs/.vuepress/public/video-webcast-1.png deleted file mode 100644 index f2d79190a4..0000000000 Binary files a/docs/.vuepress/public/video-webcast-1.png and /dev/null differ diff --git a/docs/.vuepress/public/video-webcast-2.png b/docs/.vuepress/public/video-webcast-2.png deleted file mode 100644 index f2d79190a4..0000000000 Binary files a/docs/.vuepress/public/video-webcast-2.png and /dev/null differ diff --git a/docs/.vuepress/public/vs-video.png b/docs/.vuepress/public/vs-video.png deleted file mode 100644 index eca5e4493b..0000000000 Binary files a/docs/.vuepress/public/vs-video.png and /dev/null differ diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl deleted file mode 100644 index 2c43fac7ec..0000000000 --- a/docs/.vuepress/styles/index.styl +++ /dev/null @@ -1,370 +0,0 @@ - h4 { - font-weight: 400; - } - /* - * - * Diff to HTML (diff2html.css) - * Author: rtfpessoa - * - */ - -.d2h-wrapper { - text-align: left; -} - -.d2h-file-header { - height: 35px; - padding: 5px 10px; - border-bottom: 1px solid #d8d8d8; - background-color: #f7f7f7; -} - -.d2h-file-stats { - display: flex; - margin-left: auto; - font-size: 14px; -} - -.d2h-lines-added { - text-align: right; - border: 1px solid #b4e2b4; - border-radius: 5px 0 0 5px; - color: #399839; - padding: 2px; - vertical-align: middle; -} - -.d2h-lines-deleted { - text-align: left; - border: 1px solid #e9aeae; - border-radius: 0 5px 5px 0; - color: #c33; - padding: 2px; - vertical-align: middle; - margin-left: 1px; -} - -.d2h-file-name-wrapper { - display: flex; - align-items: center; - width: 100%; - font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 15px; -} - -.d2h-file-name { - white-space: nowrap; - text-overflow: ellipsis; - overflow-x: hidden; -} - -.d2h-file-wrapper { - border: 1px solid #ddd; - border-radius: 3px; - margin-bottom: 1em; -} - -.d2h-diff-table { - width: 100%; - border-collapse: collapse; - font-family: "Menlo", "Consolas", monospace; - font-size: 13px; -} - -.d2h-files-diff { - display: block; - width: 100%; - height: 100%; -} - -.d2h-file-diff { - overflow-y: hidden; -} - -.d2h-file-side-diff { - display: inline-block; - overflow-x: scroll; - overflow-y: hidden; - width: 50%; - margin-right: -4px; - margin-bottom: -8px; -} - -.d2h-code-line { - display: inline-block; - white-space: nowrap; - /* Compensate for the absolute positioning of the line numbers */ - padding: 0 8em; -} - -.d2h-code-side-line { - display: inline-block; - white-space: nowrap; - /* Compensate for the absolute positioning of the line numbers */ - padding: 0 4.5em; -} - -.d2h-code-line del, -.d2h-code-side-line del { - display: inline-block; - margin-top: -1px; - text-decoration: none; - background-color: #ffb6ba; - border-radius: 0.2em; -} - -.d2h-code-line ins, -.d2h-code-side-line ins { - display: inline-block; - margin-top: -1px; - text-decoration: none; - background-color: #97f295; - border-radius: 0.2em; - text-align: left; -} - -.d2h-code-line-prefix { - display: inline; - background: none; - padding: 0; - word-wrap: normal; - white-space: pre; -} - -.d2h-code-line-ctn { - display: inline; - background: none; - padding: 0; - word-wrap: normal; - white-space: pre; -} - -.line-num1 { - box-sizing: border-box; - float: left; - width: 3.5em; - overflow: hidden; - text-overflow: ellipsis; - padding: 0 0.5em 0 0.5em; - margin:0 -11px; -} - -.line-num2 { - box-sizing: border-box; - float: right; - width: 3.5em; - overflow: hidden; - text-overflow: ellipsis; - padding: 0 0.5em 0 0.5em; - margin:0 -11px; -} - -.d2h-code-linenumber { - box-sizing: border-box; - width: 7.5em; - /* Keep the numbers fixed on line contents scroll */ - position: absolute; - display: inline-block; - background-color: #fff; - color: rgba(0, 0, 0, 0.3); - text-align: right; - border: solid #eeeeee; - border-width: 0 1px 0 1px; - cursor: pointer; -} - -.d2h-code-linenumber:after { - content: '\200b'; -} - -.d2h-code-side-linenumber { - /* Keep the numbers fixed on line contents scroll */ - position: absolute; - display: inline-block; - box-sizing: border-box; - width: 4em; - background-color: #fff; - color: rgba(0, 0, 0, 0.3); - text-align: right; - border: solid #eeeeee; - border-width: 0 1px 0 1px; - cursor: pointer; - overflow: hidden; - text-overflow: ellipsis; -} - -.d2h-code-side-linenumber:after { - content: '\200b'; -} - -.d2h-code-side-emptyplaceholder, -.d2h-emptyplaceholder { - background-color: #f1f1f1; - border-color: #e1e1e1; -} - -/* - * Changes Highlight - */ - -.d2h-del { - background-color: #fee8e9; - border-color: #e9aeae; -} - -.d2h-ins { - background-color: #dfd; - border-color: #b4e2b4; -} - -.d2h-info { - background-color: #f8fafd; - color: rgba(0, 0, 0, 0.3); - border-color: #d5e4f2; -} - -.d2h-file-diff .d2h-del.d2h-change { - background-color: #fdf2d0; -} - -.d2h-file-diff .d2h-ins.d2h-change { - background-color: #ded; -} - -/* - * File Summary List - */ - -.d2h-file-list-wrapper { - margin-bottom: 10px; -} - -.d2h-file-list-wrapper a { - text-decoration: none; - color: #3572b0; -} - -.d2h-file-list-wrapper a:visited { - color: #3572b0; -} - -.d2h-file-list-header { - text-align: left; -} - -.d2h-file-list-title { - font-weight: bold; -} - -.d2h-file-list-line { - display: flex; - text-align: left; -} - -.d2h-file-list { - display: block; - list-style: none; - padding: 0; - margin: 0; -} - -.d2h-file-list > li { - border-bottom: #ddd solid 1px; - padding: 5px 10px; - margin: 0; -} - -.d2h-file-list > li:last-child { - border-bottom: none; -} - -.d2h-file-switch { - display: none; - font-size: 10px; - cursor: pointer; -} - -.d2h-icon { - vertical-align: middle; - margin-right: 10px; - fill: currentColor; -} - -.d2h-deleted { - color: #c33; -} - -.d2h-added { - color: #399839; -} - -.d2h-changed { - color: #d0b44c; -} - -.d2h-moved { - color: #3572b0; -} - -.d2h-tag { - display: flex; - font-size: 10px; - margin-left: 5px; - padding: 0 2px; - background-color: #fff; -} - -.d2h-deleted-tag { - border: #c33 1px solid; -} - -.d2h-added-tag { - border: #399839 1px solid; -} - -.d2h-changed-tag { - border: #d0b44c 1px solid; -} - -.d2h-moved-tag { - border: #3572b0 1px solid; -} - -/* - * Selection util. - */ - -.selecting-left .d2h-code-line, -.selecting-left .d2h-code-line *, -.selecting-right td.d2h-code-linenumber, -.selecting-right td.d2h-code-linenumber *, -.selecting-left .d2h-code-side-line, -.selecting-left .d2h-code-side-line *, -.selecting-right td.d2h-code-side-linenumber, -.selecting-right td.d2h-code-side-linenumber * { - -webkit-touch-callout: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.selecting-left .d2h-code-line::-moz-selection, -.selecting-left .d2h-code-line *::-moz-selection, -.selecting-right td.d2h-code-linenumber::-moz-selection, -.selecting-left .d2h-code-side-line::-moz-selection, -.selecting-left .d2h-code-side-line *::-moz-selection, -.selecting-right td.d2h-code-side-linenumber::-moz-selection, -.selecting-right td.d2h-code-side-linenumber *::-moz-selection { - background: transparent; -} - -.selecting-left .d2h-code-line::selection, -.selecting-left .d2h-code-line *::selection, -.selecting-right td.d2h-code-linenumber::selection, -.selecting-left .d2h-code-side-line::selection, -.selecting-left .d2h-code-side-line *::selection, -.selecting-right td.d2h-code-side-linenumber::selection, -.selecting-right td.d2h-code-side-linenumber *::selection { - background: transparent; -} \ No newline at end of file diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 51fee44682..0000000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -docs.vuestorefront.io \ No newline at end of file diff --git a/docs/Dockerfile b/docs/Dockerfile deleted file mode 100644 index 33f4c7ce22..0000000000 --- a/docs/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM node:12 AS build - -WORKDIR /var/www - -COPY . . - -RUN npm install \ - && npm run docs:build - -FROM nginx - -COPY --from=build /var/www/.vuepress/dist /usr/share/nginx/html/v1 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index eb1f059fac..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -home: true -heroImage: /vuelogo.jpg -heroText: Documentation -tagline: Bodiless frontend framework for headless commerce (Magento 2) -actionText: Introduction → -actionLink: /guide/general/introduction.html - ---- - -

How to get started

Basic Information

Cookbook

Themes and Frontend

Data Structure and Modules

Integration

- - - - - - - - - - diff --git a/docs/deploy.sh b/docs/deploy.sh deleted file mode 100644 index 97a0fbb4fa..0000000000 --- a/docs/deploy.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env sh - -# abort on errors -set -e - -# build -npm run docs:build - -# navigate into the build output directory -cd .vuepress/dist - -# if you are deploying to a custom domain -# echo 'www.example.com' > CNAME - -git init -git add -A -git commit -m 'deploy' - -# if you are deploying to https://.github.io -git push -f git@github.com:/.github.io.git master - -# if you are deploying to https://.github.io/ -git push -f git@github.com:DivanteLtd/vue-storefront.git master:gh-pages - -cd - diff --git a/docs/guide/README.md b/docs/guide/README.md deleted file mode 100644 index c0fa8c842b..0000000000 --- a/docs/guide/README.md +++ /dev/null @@ -1,125 +0,0 @@ - -# Introduction to Vue Storefront - -Vue Storefront is a rather complex solution with a lot of possibilities. Learning all of them can take some time. In this introduction, we will learn all of its crucial concepts in a few minutes, which is enough to start playing with [Vue Storefront](https://www.vuestorefront.io/). - - -## What is Vue Storefront -![Vue Storefront](https://cdn-images-1.medium.com/max/1600/0*X7cXhVkWidbWFrbM) - -Vue Storefront is a headless and backend-agnostic eCommerce [Progressive Web App (PWA)](https://developers.google.com/web/progressive-web-apps/) written in Vue.js. The fact that it's using headless architecture allows Vue Storefront to connect with any eCommerce platform so it can be a frontend PWA for Magento, Shopify, BigCommerce, WooCommerce and etc. - - It's a very popular [Open Source project](https://github.com/vuestorefront/vue-storefront) with a strong and growing community. - -**Key features of Vue Storefront:** -- Platform-agnostic -- Focus on performance -- Mobile-first approach -- Cutting-edge tech -- No limitations in theming and customization -- Open Source with MIT license -- Exciting developer experience -- Out-of-the-box Server Side Rendering (for SEO) -- Offline mode - - -## How does it connect with backend platforms? -Vue Storefront manages to be platform-agnostic thanks to the [vue-storefront-api](https://github.com/vuestorefront/vue-storefront-api) and [dedicated API connectors](https://github.com/vuestorefront/vue-storefront#integrations) for eCommerce backend platforms. The data format in vue-storefront-api is always the same for any platform, which means no matter what eCommerce backend you use, your frontend remains the same without any change. - -It's a great strategy for migrations since you can easily migrate from one platform to another (or one version to another, e.g. Magento 1 to 2) without touching your frontend. - - -![Architecture diagram](https://raw.githubusercontent.com/DivanteLtd/vue-storefront/master/docs/.vuepress/public/GitHub-Architecture-VS.png) - -The API connector works in two phases: -- **data pump** ([mage2nosql](https://github.com/vuestorefront/mage2vuestorefront) in the image) is pulling static data (catalog, orders, etc.) from your eCommerce platform to Vue Storefront Elasticsearch and changes its format to the one consumed by vue-storefront-api. Once finished pulling the data, you can display the product catalog in Vue Storefront. After pumping the data into Elasticsearch is done, it will stay in sync with changes made on the backend platform and update its content accordingly. - -- **worker pool** is a synchronization process of so-called dynamic calls (user sessions, cart rules, etc.) that couldn't be stored in the database and need to be called by vue-storefront-api directly from the backend platform. - -VueStorefront works seamlessly with your backend platform while two integration phases are managed as above. - -Some of the most popular backend platforms already have their integrations ([Magento 2](https://github.com/vuestorefront/magento2-vsbridge-indexer), [Magento 1](https://github.com/divanteLtd/magento1-vsbridge-indexer), [CoreShop](https://github.com/divanteLtd/coreshop-vsbridge), [BigCommerce](https://github.com/divanteLtd/bigcommerce2vuestorefront), [WooCommerce](https://github.com/divanteLtd/woocommerce2vuestorefront)), but you can easily make your own with the [integration boilerplate](https://github.com/divanteLtd/vue-storefront-integration-boilerplate). - -The blue parts on the diagram are responsible for offline cache and will be explained later in the article. - -## How does it work? - -There are 3 concepts you need to be familiar with while working with Vue Storefront. - -- **Vue Storefront Core** ( `core` folder) is the glue for all the features that allow Vue Storefront to work. It contains all the entry points, SSR behavior, build process, in-app libs and helpers. You shouldn't touch this folder directly when building your own implementations in order to stay up-to-date with its features and security. - -- **Vue Storefront Modules** ( `core/modules` and `src/modules` ) are the eCommerce features. Each module is one encapsulated feature (like cart, wishlist, catalog, and some third-party integrations). You can add/remove/edit these modules as you wish and compose your Vue Storefront shop with only the features that you need. They are also used for 3rd-party extensions. - -- **Vue Storefront Themes** ( `src/themes` ) are the actual shop implementation. In themes, you can use and extend all the logic from registered modules / core and add your HTML markup and styles. Vue Storefront provides a fully customizable default theme. - -To summarize: Your shop is basically a Vue Storefront theme that uses features provided by modules. Vue Storefront Core glues it all together. - -Knowing these 3 concepts allows you to confidently work with Vue Storefront and make your own shops. - -Useful materials: [Vue Storefront project structure](/guide/basics/project-structure.html) - -## Installing Vue Storefront -When you want to play with Vue Storefront, there are three options: - -![install](https://cdn-images-1.medium.com/max/1200/0*dz-mwiEQ_Qkzpd5H) -*This is everything you need to have VS working with our demo backend.* - -- You can set up the frontend connected to our demo backend platform (best for trying out Vue Storefront). -- You can set up frontend with your own `vue-storefront-api` and database dumped from the demo. -- You can set up frontend with `vue-storefront-api` connected to your eCommerce backend. - -To do any of this, simply type `yarn installer` in the root of the project and answer the questions in the console. Once the installation is done, type `yarn dev` to run your project (by default, on port `3000`). No matter what option you choose, you can change the settings in the config file later. - -## Vue Storefront config file - -Most of the Vue Storefront configuration (like the active theme, backend API addresses, multistore setup, etc.) is done through its [config](/guide/basics/configuration.html) file that can be found under the `config` folder. The `default.json` file contains all the default setup. - -For your own implementation you should create a `local.json` file in the same directory and include fields from `default.json` that you want to override. These two files will be merged in favor of `local.json` during the build process. If you use the installer to set up your Vue Storefront instance, it'll generate proper config files. - -## Building themes in Vue Storefront -![themes structure](https://cdn-images-1.medium.com/max/1200/1*jMel95nhs5UTIi2DQdeq4Q.png) - -While making themes in Vue Storefront, in most cases, all you need to do is create your own HTML and CSS markup. All the required business logic is exposed by the core with its core modules and can be easily injected into any of the theme components. -![biz-logic](https://cdn-images-1.medium.com/max/1200/1*tMwC0smduKIwKh82jTiJmw.png) -*The business logic from the core component can be easily injected into any theme component as a Vue.js mixin.* - -The mechanism of injecting core business logic into themes is ridiculously simple. We are using [Vue.js mixins](https://vuejs.org/v2/guide/mixins.html) to maintain business logic upgradable in the core. - -So assume we have a core Microcart component with business logic as above (left side), we can easily inject it into any of our theme components (right side) just by importing it and adding as a mixin `mixins: [Microcart]`. This is all you need to make use of core business logic inside your theme. With this approach, we can easily ship updates to all core components without breaking your shop. - -[Check how to create theme based on our official themes](/guide/installation/theme.html). - -## Offline mode and cache -Vue Storefront still works even while the user is offline. - -We managed to do this by making extensive use of the browser cache.  -- **For the static assets** (only prod) we use the [sw-precache](https://github.com/GoogleChromeLabs/sw-precache) plugin (config can be found in `core/build/webpack.prod.sw.config.js` ). They are cached in Service Worker and can be inspected under the `Application/Cache Storage` tab of your Developer Tools. - -![cache](https://cdn-images-1.medium.com/max/1200/1*BHVzt7oCIxcM3bNPZriKmw.png) -*Here you can find cached static assets. Please notice that Service Worker works only in production mode.* - -:::warning -Please note that Service Worker works only in production mode. -::: - -- **For the catalog and store-data cache** we use IndexedDB and Local Storage. We also prefetch products from visited categories so once you enter one, all of its products are available offline. The mechanism of offline storage is located under `core/lin./storage`. - -We use some of the cached data even while the user is online to display the content instantly. This explains why Vue Storefront is lightning fast. - - -## What else -You may not believe me but this is all you need to know to start working with Vue Storefront! Once you are done wrapping your head around the basics, just look around docs and visit community [slack](https://vuestorefront.slack.com) to dig deeper into the project. - -## Useful Links - -- [Documentation](https://docs.vuestorefront.io/) -- [Community slack invitation link](https://join.slack.com/t/vuestorefront/shared_invite/enQtOTUwNjQyNjY5MDI0LWFmYzE4NTYxNDBhZDRlMjM5MDUzY2RiMjU0YTRjYWQ3YzdkY2YzZjZhZDZmMDUwMWQyOWRmZjQ3NDgwZGQ3NTk) -- [Project structure explained](https://docs.vuestorefront.io/guide/basics/project-structure.html) -- [Configuration file explained](https://docs.vuestorefront.io/guide/basics/configuration.html) -- [Extending Vue Storefront](https://docs.vuestorefront.io/guide/extensions/introduction.html) -- [How to contribute](https://docs.vuestorefront.io/guide/basics/contributing.html#branches) - -## Video with training -You can also watch a video recording from 4th Vue Storefront hackathon with free introduction training - -[![0.jpg](http://img.youtube.com/vi/IL2HMtvf_hw/0.jpg)](https://youtu.be/IL2HMtvf_hw) diff --git a/docs/guide/archives/amp.md b/docs/guide/archives/amp.md deleted file mode 100644 index fcd7008dfd..0000000000 --- a/docs/guide/archives/amp.md +++ /dev/null @@ -1,17 +0,0 @@ -# Google Accelerated Mobile Pages - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.12` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - - -Google Accelerated Mobile pages are available with Vue Storefront 1.6. By default, the Product and Category pages do support AMP. To get the AMP page, you should use the following URL: -`http://localhost:3000/c/hoodies-and-sweatshirts-24` -> `http://localhost:3000/amp/c/hoodies-and-sweatshirts-24`. -The discovery links are added as ` -
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
- -``` - -#### Available events: - -```html - - - -``` - -#### Available props - -| Prop | Type | Required | Default | Description | -| ----- | ------ | -------- | ------- | --------------------- | -| name | String | true | | Unique name of modal | -| delay | Number | false | 300 | Timeout to show modal | - -#### Available Methods - -| Method | Argument | Description | -| ------ | -------------- | ---------------------------------------------------------- | -| toggle | state: Boolean | Manually toggles a modal | -| close | | Alias for manually hides a modal. Helpful for Close button | - -#### Styles - -Core component doesn't have CSS styles. If you want to see an example of our implementation please look [here](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/Modal.vue) - - - -## Events List - -To keep track and make debugging of `$bus.$emit` events across components easier, here is a list of such events that are triggered by components of the default theme. - -### ForgotPass - -On component close: -- [`modal-hide`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L80) - -On send email action: -- [`notification-progress-start`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L95) -- [`notification-progress-stop`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L97) - -On error handler of email send action: -- [`notification-progress-stop`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L109) - -### OrderConfirmation - -On mounted lifecycle hook: -- [`modal-show`, `modal-order-confirmation`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue#L65) - -On order confirmation: -- [`modal-hide`, `modal-order-confirmation`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue#L71) - -On order cancelling: -- [`modal-show`, `modal-order-confirmation`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue#L75) - -### OrderReview - -On 'Term and conditions' link click: -- [`modal-toggle`, `modal-terms`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderReview.vue#L51) - -### PersonalDetails - -On 'Term and conditions' link click: -- [`modal-toggle`, `modal-terms`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/PersonalDetails.vue#L151) - -### Newsletter - -On newsletter popup show: -- [`modal-show`, `modal-newsletter`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Footer/Newsletter.vue#L49) - -### Header - -On 'Login to your account' link click: -- [`modal-toggle`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Header/Header.vue#L122) - -### Reviews - -On 'Login to your account' link click: -- [`modal-show`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Reviews/Reviews.vue#L155) - -### SidebarMenu - -On 'Login to your account' link click: -- [`modal-show`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/SidebarMenu/SidebarMenu.vue#L201) - -### SubCategory - -On user logout: -- [`user-before-logout`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/SidebarMenu/SubCategory.vue#L131) - -### Language - -On mounted lifecycle hook: -- [`modal-show`, `modal-switcher`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Switcher/Language.vue#L55) - -On component close: -- [`modal-hide`, `modal-switcher`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Switcher/Language.vue#L60) - -### LanguageSwitcher - -On showing language popup: -- [`modal-show`, `modal-switcher`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/LanguageSwitcher.vue#L30) - -### NewsletterPopup - -On showing newsletter popup: -- [`modal-show`, `modal-newsletter`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/NewsletterPopup.vue#L54) - -On hiding newsletter popup: -- [`modal-hide`, `modal-newsletter`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/NewsletterPopup.vue#L67) - -### Onboard - -On component close: -- [`modal-hide`, `modal-onboard`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/theme/blocks/Home/Onboard.vue#L45) - -### Home - -On beforeMount lifecycle hook: -- [`modal-toggle`, `modal-onboard`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/pages/Home.vue#L74) diff --git a/docs/guide/archives/cookbook.md b/docs/guide/archives/cookbook.md deleted file mode 100644 index 11a527acfe..0000000000 --- a/docs/guide/archives/cookbook.md +++ /dev/null @@ -1,1331 +0,0 @@ -# Cookbook - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.12` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - -## Install - -In this chapter, we will cover : - -[[toc]] - - -### 0. Introduction -Now you are definitely interested in **Vue Storefront**. That's why you are here. You've come across the line. You made a choice. You will have something in return, which is great. Be it developers, entrepreneurs or even marketing managers that they may want to try something new for better products in hopes of enhancing their clients or customers' experience. You chose the right path. We will explore anything you need to get you started at all with [**Vue Storefront** infrastructure](https://github.com/vuestorefront). - - -### 1. Install with Docker -Docker has been arguably the most sought-after, brought to the market which took the community by storm ever since its introduction. Although it's yet controversial whether it's the best choice among its peers, I have never seen such an unanimous enthusiasm over one tech product throughout the whole developers community. - -Then, why so? In modern computer engineering, products are so complex with an endless list of dependencies intertwined with each other. Building such dependencies in place for every occasion where it's required is one hell of a job, not to mention glitches from all the version variation. That's where Docker steps in to make you achieve **infrastructure automation**. This concept was conceived to help you focus on your business logic rather than having you stuck with hassles of lower level tinkering. - -Luckily, we already have been through all this for you, got our hands dirty. All you need is run a set of docker commands to get you up and running from scratch. Without further ado, let's get started! - - -#### 1. Preparation -- You need [`docker`](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04) and [`docker-compose`](https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04) installed. - -- You need [`git`](https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-18-04) installed. - -:::tip NOTE -We will walk you with docker on *Linux*. (Specifically *Ubuntu 18.04* if needed) - -There is only one bias for Docker before using it; *Run it on Linux*. Docker is native Linux, was created using a Linux technology; LXC (linux container) in the first place. Even though there were many attempts made to make it available to other platforms as it does on Linux, and it has definitely been on a progress, however, using Docker on Linux is the solidest way to deal with the technology. - -That being sad, there are tips for using other platforms for docker at [Chef's Secrets](#_4-chef-s-secret-protip) as well. -::: - -#### 2. Recipe -1. First, start with backend, download [**Vue Storefront API**](https://github.com/vuestorefront/vue-storefront-api) from github. -```bash -git clone https://github.com/vuestorefront/vue-storefront-api.git vue-storefront-api -cd vue-storefront-api -``` - -2. Copy `./config/default.json` to `./config/local.json` -```bash -cp config/default.json config/local.json -``` -Then edit `local.json` to your need. -We will look into this in greater detail at [Chef's secret](#_4-chef-s-secret-protip) -:::tip TIP -This step can be skipped if you are OK with values of `default.json` since it follows the [files load order](https://github.com/lorenwest/node-config/wiki/Configuration-Files#file-load-order) of [node-config](https://github.com/lorenwest/node-config) - -::: - -3. Run the following Docker command : -```bash -docker-compose -f docker-compose.yml -f docker-compose.nodejs.yml up -d -``` - -Then the result would look something like this : -```bash -Building app -Step 1/8 : FROM node:10-alpine - ---> 9dfa73010b19 -Step 2/8 : ENV VS_ENV prod - ---> Using cache - ---> 4d0a83421665 -Step 3/8 : WORKDIR /var/www - ---> Using cache - ---> e3871c8db7f3 -Step 4/8 : RUN apk add --no-cache curl git - ---> Using cache - ---> 49e996f0f6cb -Step 5/8 : COPY package.json ./ - ---> 14ed18d76efc -Step 6/8 : RUN apk add --no-cache --virtual .build-deps ca-certificates wget && yarn install --no-cache && apk del .build-deps - ---> Running in 3d6f91acc2fe -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz -(1/2) Installing wget (1.20.3-r0) -(2/2) Installing .build-deps (0) -Executing busybox-1.29.3-r10.trigger -OK: 22 MiB in 26 packages -yarn install v1.16.0 -info No lockfile found. -[1/4] Resolving packages... -warning @babel/node > @babel/polyfill@7.4.4: 🚨 As of Babel 7.4.0, this -package has been deprecated in favor of directly -including core-js/stable (to polyfill ECMAScript -features) and regenerator-runtime/runtime -(needed to use transpiled generator functions): - - > import "core-js/stable"; - > import "regenerator-runtime/runtime"; -warning eslint > file-entry-cache > flat-cache > circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor. -[2/4] Fetching packages... - -# ... abridged - - -``` -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/7XT5CWP4ynrPItattiP3on6wS) - -:::tip TIP -`-f` flag allows you to use the following docker-compose file. Without this flag, it will use the default file that is `docker-compose.yml` - -`-d` flag allows you to run the command in `detach mode` which means *running background*. -::: -3. In order to verify, run `docker ps` to show which containers are up -```bash -docker ps -``` - -Then, -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -53a47d5a6440 vuestorefrontapi_kibana "/bin/bash /usr/loca…" 31 seconds ago Up 29 seconds 0.0.0.0:5601->5601/tcp vuestorefrontapi_kibana_1 -7d8f6328601b vuestorefrontapi_app "docker-entrypoint.s…" 31 seconds ago Up 27 seconds 0.0.0.0:8080->8080/tcp vuestorefrontapi_app_1 -165ae945dbe5 vuestorefrontapi_es1 "/bin/bash bin/es-do…" 8 days ago Up 30 seconds 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch -8dd144746cef redis:4-alpine "docker-entrypoint.s…" 11 days ago Up 31 seconds 0.0.0.0:6379->6379/tcp vuestorefrontapi_redis_1 -``` -The ports number will be used later in the frontend configuration. In fact, they are already set in as default values. - -You will see 4 containers are running, which is : -| Container | Port | -|------------------------|---------------------| -| Vue Storefront API app | :8080 | -| Elasticsearch | :9200 | -| Kibana | :5601 | -| Redis | :6379 | - - -4. Now that backend part is done, let's work on frontend part, that is download [**Vue Storefront**](https://github.com/vuestorefront/vue-storefront) - -```bash -git clone --single-branch --branch master https://github.com/vuestorefront/vue-storefront.git vue-storefront -cd vue-storefront -``` - -5. Prepare the config file at `./config/local.json`. There is `default.json` file in the same folder which is a default set of configuration. Copy it as follows : -```bash -cp config/default.json config/local.json -``` -Then fix the value as you need it in the `local.json` file. -In `local.json`, you may change values for information of backend family. But if you followed this recipe verbatim, you don't have to, because it's already there with the default value. Should you study the contents, please see to [Chef's secret](#secret-1-study-in-local-json) - -6. Finally run the following Docker command : -```bash -docker-compose up -d -``` -The result should be something like this : -```bash -Building app -Step 1/8 : FROM node:10-alpine - ---> 9dfa73010b19 -Step 2/8 : ENV VS_ENV prod - ---> Using cache - ---> 4d0a83421665 -Step 3/8 : WORKDIR /var/www - ---> Using cache - ---> e3871c8db7f3 -Step 4/8 : COPY package.json ./ - ---> 0eab68a8f13a -Step 5/8 : COPY yarn.lock ./ - ---> ac1f5e4a1831 -Step 6/8 : RUN apk add --no-cache --virtual .build-deps ca-certificates wget git && yarn install --no-cache && apk del .build-deps - ---> Running in 1ca7bc7782e3 -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz -(1/9) Installing ca-certificates (20190108-r0) -(2/9) Installing wget (1.20.3-r0) -(3/9) Installing nghttp2-libs (1.35.1-r0) -(4/9) Installing libssh2 (1.8.2-r0) -(5/9) Installing libcurl (7.64.0-r2) -(6/9) Installing expat (2.2.6-r0) -(7/9) Installing pcre2 (10.32-r1) -(8/9) Installing git (2.20.1-r0) -(9/9) Installing .build-deps (0) -Executing busybox-1.29.3-r10.trigger -Executing ca-certificates-20190108-r0.trigger -OK: 22 MiB in 25 packages -yarn install v1.16.0 -[1/5] Validating package.json... -[2/5] Resolving packages... -[3/5] Fetching packages... -info fsevents@1.2.4: The platform "linux" is incompatible with this module. - -# ... abridged - -``` - -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/JZYI9ZE6DHeC7N2keBNoFUWjQ) - -7. In order to verify, run `docker ps`, there should be another container added to the list. -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -88d758bc24d0 vuestorefront_app "docker-entrypoint.s…" 2 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp vuestorefront_app_1 -de560221fdaf vuestorefrontapi_kibana "/bin/bash /usr/loca…" 8 hours ago Up 23 minutes 0.0.0.0:5601->5601/tcp vuestorefrontapi_kibana_1 -5576cd9963a1 vuestorefrontapi_app "docker-entrypoint.s…" 8 hours ago Up 23 minutes 0.0.0.0:8080->8080/tcp vuestorefrontapi_app_1 -88f5db9486da vuestorefrontapi_es1 "/bin/bash bin/es-do…" 8 hours ago Up 24 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch -d46c1e0a22af redis:4-alpine "docker-entrypoint.s…" 8 hours ago Up 24 minutes 0.0.0.0:6379->6379/tcp vuestorefrontapi_redis_1 - -``` -8. Open your browser and visit [http://localhost:3000/](http://localhost:3000/) - -After compiling, *Voila!* - -![vs_home_intro_borderline](../images/home-vuestorefront.png) - -#### 3. Peep into the kitchen (what happens internally) -We used `docker-compose` for setting up the entire environment of Vue Storefront. It was more than enough to launch the machines behind for running the shop. - -It was possible because `docker` encapsulated the whole bunch of infrastructure into a linear set of declarative definition for the desired state. - -We had 2 steps of `docker-compose` one of which is for backend **Vue Storefront API**, the other for frontend **Vue Storefront**. - -The first `docker-compose` had two `yml` files for input. The first input file `docker-compose.yml` describe its base requirement all but **Vue Storefront API** itself; that is, **Elasticsearch** as data store, **Redis** for cache and **Kibana** for helping you grab your data visually (a pair of Elasticsearch). -```yaml -# docker-compose.yml -version: '3.0' -services: - es1: - container_name: elasticsearch - build: docker/elasticsearch/ - volumes: - - ./docker/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro - - ./docker/elasticsearch/data:/usr/share/elasticsearch/data - ports: - - '9200:9200' - - '9300:9300' - environment: - ES_JAVA_OPTS: "-Xmx512m -Xms512m" - - kibana: - build: docker/kibana/ - volumes: - - ./docker/kibana/config/:/usr/share/kibana/config:ro - ports: - - '5601:5601' - depends_on: - - es1 - - redis: - image: 'redis:4-alpine' - ports: - - '6379:6379' - -volumes: - esdat1: -``` -:::tip NOTE -Once a term explained, it will be ignored thereafter for consecutive occurrence. -::: -`version` denotes which version of `docker-compose` this file uses. - -`services` describe containers. It codifies how they should run. In other words, it codifies option flags used with `docker run ...` - -`es1` contains information of data store *Elasticsearch* container. -- `build` denotes build path of container. -- `volumes` contains the mount path of volumes shared between host and container as *host:container* -- `ports` connect ports between host and container as in *host:container* -- `environment` allows you to add environment variables. `Xmx512m` means JVM will take up to maximum 512MB memory. `Xms512m` means minimum memory. Combining them, there will be no memory resize, it will just stick to 512MB from start to end throughout its life cycle. - -`kibana` contains information of *Kibana* application container. -- `depends_on` creates dependency for a container of other containers. So, this container is dependent on `es1` that's just described above. -- `volumes` mean volumes shared, `:ro` creates the volume in `read-only` mode for the container. - -`redis` contains information of *Redis* cache application container. - -- `image` node contains the name of image this container is based on. - -`volumes` in top level can be used as a reference to be used across multiple services(containers). -
-
- -The second input file `docker-compose.nodejs.yml` deals with **Vue Storefront API** node application. -```yaml -version: '3.0' -services: - app: - # image: divante/vue-storefront-api:latest - build: - context: . - dockerfile: docker/vue-storefront-api/Dockerfile - depends_on: - - es1 - - redis - env_file: docker/vue-storefront-api/default.env - environment: - VS_ENV: dev - volumes: - - './config:/var/www/config' - - './ecosystem.json:/var/www/ecosystem.json' - - './migrations:/var/www/migrations' - - './package.json:/var/www/package.json' - - './babel.config.js:/var/www/babel.config.js' - - './scripts:/var/www/scripts' - - './src:/var/www/src' - - './var:/var/www/var' - tmpfs: - - /var/www/dist - ports: - - '8080:8080' -``` -`app` contains information of *Vue Storefront API* application. -- `build` is path for build information. If the value is string, it's a plain path. When it's object, you may have a few options to add. `context` is relative path or git repo url where `Dockerfile` is located. `dockerfile` node may change the path/name of `Dockerfile`. [more info](https://docs.docker.com/compose/compose-file/#build) -- `depends_on` tells us this container is based on `es1` and `redis` containers we created above. -- `env_file` helps you add environment values from files. It's relative path from the `docker-compose` file that is in the process, in this case, it's `docker-compose.nodejs.yml` -- `environment` is to set `VS_ENV` as `dev` so that environment will be setup for developer mode. -- `tmpfs` denotes temporary volumes that are only available to host memory. Unlike `volumes`, this `tmpfs` will be gone once the container stops. This option is only available to *Linux*. - -
-
- -The second `docker-compose` step handles **Vue Storefront** frontend. -``` yaml -version: '2.0' -services: - app: - # image: divante/vue-storefront:latest - build: - context: . - dockerfile: docker/vue-storefront/Dockerfile - env_file: docker/vue-storefront/default.env - environment: - VS_ENV: dev - network_mode: host - volumes: - - './babel.config.js:/var/www/babel.config.js' - - './config:/var/www/config' - - './core:/var/www/core' - - './ecosystem.json:/var/www/ecosystem.json' - - './.eslintignore:/var/www/.eslintignore' - - './.eslintrc.js:/var/www/.eslintrc.js' - - './lerna.json:/var/www/lerna.json' - - './tsconfig.json:/var/www/tsconfig.json' - - './tsconfig-build.json:/var/www/tsconfig-build.json' - - './shims.d.ts:/var/www/shims.d.ts' - - './package.json:/var/www/package.json' - - './src:/var/www/src' - - './var:/var/www/var' - tmpfs: - - /var/www/dist - ports: - - '3000:3000' -``` -This looks like by and large the same with *Vue Storefront API* with a few changes. - -`app` service describes options for *Vue Storefront* frontend application. -- `network_mode` allows you to modify values for `--network` option of docker client. `host` option allows your designated container to open to host network. For example, if you bind your container in host's `80` port, then the container will be accessible at host's `:80` from the internet. In other words, the container is not isolated. [more info](https://docs.docker.com/network/host/) - -If you take a closer look inside `Dockerfile`s, you will notice they install all the dependencies of the project from `package.json` not to mention required OS features including `git`, `wget` and certificates. You don't have to worry what to do because we made it do for you. - -Next, you might want to import your goods data. Please jump to [Data imports](./data-import.md) if you don't want to stop. - -#### 4. Chef's secret (protip) -##### Secret 1. Study in `local.json` for *Vue Storefront API* -Starting point of customization is `default.json` or its copy `local.json` where the platform seeks configuration values. -:::tip NOTE -If you want to modify `default.json`, don't edit it directly but copy the whole file into `local.json` and start editing it in that file. Why it should be done that way is explained later at [Secret 3. Why use node-config?](#secret-3-why-use-node-config) -::: -We have 2 `local.json` files, one of which is for backend here, and we will look at [Secret 2](#secret-2-study-in-local-json-for-vue-storefront), the other for frontend . - -At [`vue-storefront-api/config/default.json`](https://github.com/vuestorefront/vue-storefront-api/blob/master/config/default.json) for **backend** : -```json - "server": { - "host": "localhost", - "port": 8080, - "searchEngine": "elasticsearch" - }, -``` -- This is where your API backend is defined. The server will listen `server.host`:`server.port` unless it's defined otherwise in environment variables. - -- `server.searchEngine` is used in the integration with `graphql` so please don't change it. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/resolvers.js#L6) -```json - "orders": { - "useServerQueue": false - }, - "catalog": { - "excludeDisabledProducts": false - }, -``` -- `orders.useServerQueue` allows you to use queue process when `order` API is used to create an order. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/order.js#L65) - -- `catalog.excludeDisabledProducts` allows you to skip disabled products when importing products using `mage2vs`. -[jump to code](https://github.com/vuestorefront/mage2vuestorefront/blob/master/src/adapters/magento/product.js#L166) - -```json - "elasticsearch": { - "host": "localhost", - "port": 9200, - "protocol": "http", - "user": "elastic", - "password": "changeme", - "min_score": 0.01, - "indices": [ - "vue_storefront_catalog", - "vue_storefront_catalog_de", - "vue_storefront_catalog_it" - ], - "indexTypes": [ - "product", - "category", - "cms", - "attribute", - "taxrule", - "review" - ], - "apiVersion": "5.6" - }, -``` -- `elasticsearch` element is used widely across the whole platform. Considering `elasticsearch` works as a data store (database), it's natural. - - - `host`, `port`, `protocol` defines `elasticsearch` connect information. -- `user`, `password` is default credentials of `elasticsearch`. If you changed the credentials of `elasticsearch`, please change this accordingly. [more info](https://www.elastic.co/guide/en/x-pack/current/security-getting-started.html) - - `min_score` sets a `min_score` when building a query for `elasticsearch`. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/elasticsearch/queryBuilder.js#L172) - :::tip TIP - `min_score` helps you exclude documents with `_score` less than `min_score` value. - ::: - - `indices` may contain one or multiple indexes. Each index acts as a data store for a storefront. You may add entries to the array with arbitrary names or remove entries from it. - :::warning CAUTION ! - However, the index name should match the one you will use for [data pump](data-import.md#_2-2-recipe-b-using-on-premise). - ::: - The default values for `indices` assume you have 2 additional stores(`de`, `it`) plus the default store. - - `indexTypes` contains values for mapping. You can consider it as `table` if you take `indices` as database. - - `apiVersion` defines the `elasticsearch` version it uses. - -```json - "redis": { - "host": "localhost", - "port": 6379, - "db": 0 - }, - "kue": {}, -``` -- `redis` contains `redis` server connect information. -- `kue` contains `kue` application options. [jump to code for options](https://github.com/Automattic/kue/blob/master/lib/kue.js#L88) - -```json - "availableStores": [ - "de", - "it" - ], -``` -- `availableStores` contains additional stores code name. If this value is an empty array, it means you only have one default store. - -```json -"storeViews": { - "multistore": true, - "mapStoreUrlsFor": [ - "de", - "it" - ], - "de": { - "storeCode": "de", - "storeId": 3, - "name": "German Store", - "url": "/de", - "elasticsearch": { - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog_de" - }, - "tax": { - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true, - "sourcePriceIncludesTax": false - }, - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - }, - "it": { - "storeCode": "it", - "storeId": 4, - "name": "Italian Store", - "url": "/it", - "elasticsearch": { - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog_it" - }, - "tax": { - "defaultCountry": "IT", - "defaultRegion": "", - "calculateServerSide": true, - "sourcePriceIncludesTax": false - }, - "i18n": { - "fullCountryName": "Italy", - "fullLanguageName": "Italian", - "defaultCountry": "IT", - "defaultLanguage": "IT", - "defaultLocale": "it-IT", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - } - }, - -``` -- `storeViews` element contains the whole information of ***additional*** stores. The default store information doesn't exist here, it exists on top level. -- `multistore` is supposed to tell the platform if it has multiple stores to consider. For example, it is used to configure `tax` values of additional store. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L14) -- `mapStoreUrlsFor` is used for building url routes in frontend. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/multistore.ts#L85) -- `de` element contains detailed information of `de` store. You need to have this kind of element for all the additional stores you added to `availableStores` with `storeCode` as the key. `de` and `it` in the `default.json` exhibits an example you can copy & paste for other stores you need to add. - - `storeCode` denotes store code for the store. - - `storeId` denotes store ID of the store. - - `name` denotes the store name. - - `url` denotes URL for the store. - - `elasticsearch` contains information for the store. This information may override the default one defined above. - - `host` is where your *Elasticsearch* listens on. - - `index` is the name of the index for the store. - - `tax` contains tax information of the store. - - `defaultCountry` is the code name of the country on which tax is calculated for the store. - - `defaultRegion` is default region. - - `calculateServerSide` determines if price is fetched with(`true`)/without(`false`) tax calculated. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/product.js#L48) - - `sourcePriceIncludesTax` determines whether price is stored with tax applied (`true`) or tax calculated on runtime (`false`). [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L12) - - `i18n` connotes *internationalization*. [more info](https://en.wikipedia.org/wiki/Internationalization_and_localization) - - `fullCountryName` is the full name of the country this `i18n` is applied to. - - `fullLanguageName` is the full name of the language this `i18n` is applied to. - - `defaultCountry` is the abbreviated name of the country this `i18n` is applied to by default. - - `defaultLanguage` is the abbreviated name of the language this `i18n` is applied to by default. - - `defaultLocale` is the default locale this `i18n` uses. - - `currencyCode` is the currency code this store uses. - - `currencySign` is the currency sign this store uses. - - `dateFormat` is the date format this store uses. - - - ```json - "authHashSecret": "__SECRET_CHANGE_ME__", - "objHashSecret": "__SECRET_CHANGE_ME__", - ``` -- `authHashSecret` is used to encode & decode JWT for API use. -- `objHashSecret` is 1) fallback secret hash for `authHashSecret`, 2) used for hashing in tax calculation. - -```json - "cart": { - "setConfigurableProductOptions": false - }, - "tax": { - "defaultCountry": "PL", - "defaultRegion": "", - "calculateServerSide": true, - "alwaysSyncPlatformPricesOver": false, - "usePlatformTotals": true, - "setConfigurableProductOptions": true, - "sourcePriceIncludesTax": false - }, -``` -- `cart` - - `setConfigurableProductOptions` flag determines to show either the parent item or the child item (aka selected option item) in the cart context. `true` shows parent item instead of the option item selected. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/o2m.js#L94) -- `tax` - - `alwaysSyncPlatformPricesOver` [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/order.js#L49) - - `usePlatformTotals` - These two options are used to determine whether to fetch prices from data source on the fly or not. If you set `alwaysSyncPlatformPricesOver` true, then it skips checking the checksum for cart items based on price. - -```json - "bodyLimit": "100kb", - "corsHeaders": [ - "Link" - ], -``` -- `bodyLimit` limits how big a request can be for your application. -- `corsHeaders` allows you to add entries to `Access-Control-Expose-Headers` - -```json - "platform": "magento2", -``` -- `platform` defines which e-commerce platform is used as a source. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/order.js#L13) - -```json - "registeredExtensions": [ - "mailchimp-subscribe", - "example-magento-api", - "cms-data", - "mail-service" - ], - "extensions": { - "mailchimp": { - "listId": "e06875a7e1", - "apiKey": "a9a3318ea7d30f5c5596bd4a78ae0985-us3", - "apiUrl": "https://us3.api.mailchimp.com/3.0" - }, - "mailService": { - "transport": { - "host": "smtp.gmail.com", - "port": 465, - "secure": true, - "user": "vuestorefront", - "pass": "vuestorefront.io" - }, - "targetAddressWhitelist": ["contributors@vuestorefront.io"], - "secretString": "__THIS_IS_SO_SECRET__" - } - }, -``` -- `registeredExtensions` element contains the list of supported extensions, it bootstraps entry points for those extensions [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/index.js#L45) - -- `extensions` contains additional configuration for extensions. [jump to code](https://github.com/vuestorefront/vue-storefront-api/tree/master/src/api/extensions) - - `mailchimp` provides `POST`, `DELETE` APIs for *Mailchimp* `subscribe` method. - - `listId` is the ID of list you are publishing. - - `apiKey` is API key you are assigned. - - `apiUrl` is API base url for *Mailchimp* service. - - `mailService` is used to send emails from Vue Storefront via *Gmail*. - - `transport` contains basic information for *Gmail* service. - - `host` is where your mail is sent en route. - - `port` is the port number used for the service. - - `secure` determines to use SSL connection. - - `user` is `username` for the service. - - `pass` is `password` for the service. - - `targetAddressWhitelist` checks if an user confirmed his/her email address *and* source email is white-listed. - - `secretString` is used for hashing. - -```json - "magento2": { - "url": "http://demo-magento2.vuestorefront.io/", - "imgUrl": "http://demo-magento2.vuestorefront.io/media/catalog/product", - "assetPath": "/../var/magento2-sample-data/pub/media", - "magentoUserName": "", - "magentoUserPassword": "", - "httpUserName": "", - "httpUserPassword": "", - "api": { - "url": "http://demo-magento2.vuestorefront.io/rest", - "consumerKey": "byv3730rhoulpopcq64don8ukb8lf2gq", - "consumerSecret": "u9q4fcobv7vfx9td80oupa6uhexc27rb", - "accessToken": "040xx3qy7s0j28o3q0exrfop579cy20m", - "accessTokenSecret": "7qunl3p505rubmr7u1ijt7odyialnih9" - } - }, - "magento1": { - "url": "http://magento-demo.local", - "imgUrl": "http://magento-demo.local/media/catalog/product", - "magentoUserName": "", - "magentoUserPassword": "", - "httpUserName": "", - "httpUserPassword": "", - "api": { - "url": "http://magento-demo.local/vsbridge", - "consumerKey": "", - "consumerSecret": "", - "accessToken": "", - "accessTokenSecret": "" - } - }, -``` -- `magento2` is used to integrate with Magento 2 as a data source. - - - `imgUrl` is base image url. [jump to code](https://github.com/kkdg/vue-storefront-api/blob/master/src/api/img.js#L38) - - - `assetPath` is used for the `media` path. [jump to code](https://github.com/kkdg/vue-storefront-api/blob/master/src/index.js#L22) - - - `api` contains API credentials for integration. - - - `url` is base url for Magento 2 instance. - - `consumerKey` See **TIP** - - `consumerSecret` - - `accessToken` - - `accessTokenSecret` - - - - :::tip TIP - - These 4 nodes above is the required credentials for integration with Magento 2. [how to get them](data-import.html#_2-2-recipe-b-using-on-premise) - - ::: - -`magento1` has just the same structure with `magento2`. - - - -```json - "imageable": { - "namespace": "", - "maxListeners": 512, - "imageSizeLimit": 1024, - "whitelist": { - "allowedHosts": [ - ".*divante.pl", - ".*vuestorefront.io" - ] - }, - "cache": { - "memory": 50, - "files": 20, - "items": 100 - }, - "concurrency": 0, - "counters": { - "queue": 2, - "process": 4 - }, - "simd": true, - "keepDownloads": true - }, -``` -- `imageable` deals with everything you need to configure when it comes to your storefront images, especially product images. - - - `maxListeners` limits maximum listeners to request's socket. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/img.js#L21) - - `imageSizeLimit` limits maximum image size. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/img.js#L56) - - `whitelist` contains a white-list of image source domains - - - `allowedHosts` contains the array of white-list - - :::warning DON'T FORGET - - You should include your source domain in `allowedHosts` or your request for product images will fail. [more info](data-import.html#secret-1-product-image-is-not-synced) - - ::: - - :::tip NOTE - - From `cache` to `simd` they are used to configure [Sharp](https://github.com/lovell/sharp) library. *Sharp* is a popular library for image processing in *Node.js*. [jump to option docs](https://sharp.dimens.io/en/stable/api-utility/#cache) - - ::: - - - `cache` limits `libvips` operation cache from *Sharp*. Values hereunder are default values. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/lib/image.js#L5) - - - `memory` is the maximum memory in MB to use for the cache. - - `files` is the maximum number of files to hold open. - - `items` is the maximum number of operations to cache. - - - `concurrency` is the number of threads for processing each image. - - - `counters` provides access to internal task counters. - - - `queue` is the number of tasks in queue for *libuv* to provide a worker thread. - - `process` limits the number of resize tasks concurrently processed. - - - `simd` to use SIMD vector unit of the CPU in order to enhance the performance. - - - -```json - "entities": { - "category": { - "includeFields": [ "children_data", "id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key" ] - }, - "attribute": { - "includeFields": [ "attribute_code", "id", "entity_type_id", "options", "default_value", "is_user_defined", "frontend_label", "attribute_id", "default_frontend_label", "is_visible_on_front", "is_visible", "is_comparable" ] - }, - "productList": { - "sort": "", - "includeFields": [ "type_id", "sku", "product_links", "tax_class_id", "special_price", "special_to_date", "special_from_date", "name", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "url_key" ], - "excludeFields": [ "configurable_children", "description", "configurable_options", "sgn" ] - }, - "productListWithChildren": { - "includeFields": [ "type_id", "sku", "name", "tax_class_id", "special_price", "special_to_date", "special_from_date", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax", "configurable_children.color", "configurable_children.size", "product_links", "url_key"], - "excludeFields": [ "description", "sgn"] - }, - "product": { - "excludeFields": [ "updated_at", "created_at", "attribute_set_id", "status", "visibility", "tier_prices", "options_container", "msrp_display_actual_price_type", "has_options", "stock.manage_stock", "stock.use_config_min_qty", "stock.use_config_notify_stock_qty", "stock.stock_id", "stock.use_config_backorders", "stock.use_config_enable_qty_inc", "stock.enable_qty_increments", "stock.use_config_manage_stock", "stock.use_config_min_sale_qty", "stock.notify_stock_qty", "stock.use_config_max_sale_qty", "stock.use_config_max_sale_qty", "stock.qty_increments", "small_image"], - "includeFields": null, - "filterFieldMapping": { - "category.name": "category.name.keyword" - } - } - }, -``` -- `entities` is used to integrate with *GraphQL* in **Vue Storefront API**. - - `category` - - `includeFields` contains an array of fields to be added as `sourceInclude` [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/elasticsearch/category/resolver.js#L10) - - `product` - - `filterFieldMapping` adds a field mapping to apply a filter in a query [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/elasticsearch/mapping.js#L19) - - `category.name` - -```json - "usePriceTiers": false, - "boost": { - "name": 3, - "category.name": 1, - "short_description": 1, - "description": 1, - "sku": 1, - "configurable_children.sku": 1 - } -``` -- `usePriceTiers` determines whether to use price tiers for customers in groups -- `boost` is used to give weighted values to fields for a query to *Elasticsearch*, the bigger, the heavier. - - `name` field has the value *3* so that matching query with the `name` has the highest priority. - - `category.name` ,`short_description`, `description`, `sku`, `configurable_children.sku ` the rest of fields have the default value; 1. - - -
-
- -#### Secret 2. Study in `local.json` for *Vue Storefront* - -At [`vue-storefront/config/default.json`](https://github.com/vuestorefront/vue-storefront/blob/master/config/default.json) for **frontend** : - -```json -"server": { - "host": "localhost", - "port": 3000, - "protocol": "http", - "api": "api", - "devServiceWorker": false, - "useOutputCacheTagging": false, - "useOutputCache": false, - "outputCacheDefaultTtl": 86400, - "availableCacheTags": ["product", "category", "home", "checkout", "page-not-found", "compare", "my-account", "P", "C", "error"], - "invalidateCacheKey": "aeSu7aip", - "dynamicConfigReload": false, - "dynamicConfigContinueOnError": false, - "dynamicConfigExclude": ["ssr", "storeViews", "entities", "localForage", "shipping", "boost", "query"], - "dynamicConfigInclude": [], - "elasticCacheQuota": 4096 -}, -``` - -- `server` contains information of various features related to *frontend* server. - - - `host` is the host address in which your *Vue Storefront* instance starts at. - - - `port` is the port number in which your *Vue Storefront* instance listens to. - - - `protocol` is used for *GraphQL* integration. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/search/adapter/graphql/searchAdapter.ts#L48) - - - `api` determines API mode between `api` and `graphql`. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/resolvers/resolveGraphQL.js#L7) - - :::tip TIP - - You may take a look at [*GraphQL Action Plan*](/guide/basics/graphql.html) guide to help yourself make a decision which mode you should take. - ::: - - - `devServiceWorker` enables *service worker* in `develop` mode. The *service worker* is normally enabled by default for `production` mode, but not for `develop` mode. Setting this flag *true* forces to use *service worker* in `develop` mode too. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/service-worker/registration.js#L5) - :::tip TIP - - You may take a look at [Working with Service Workers](/guide/core-themes/service-workers.html) for better understanding. - ::: - - - `useOutputCacheTagging` determines to allow *Output Cache Tags*. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L168) - - - `useOutputCache` determines to allow *Output Cache*. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L64) - - - `outputCacheDefaultTtl` defines the default timeout for *Redis Tag Cache*. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/utils/cache-instance.js#L16) - - - `availableCacheTags` contains a list of available cache tags. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/cache.js#L7) - - - `invalidateCacheKey` is the key used for checking validity of invalidation. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L66) - :::tip TIP - - You may take a look at [SSR Cache](/guide/basics/ssr-cache.html) in order to grab the idea of *Output Cache* in *Vue Storefront* - ::: - - - `dynamicConfigReload` enables to reload `config.json` on the fly with each server request. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L232) - - `dynamicConfigReloadWithEachRequest` enables to reload `config.json` on the fly with each server request. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L313) - - `dynamicConfigContinueOnError` allows to skip errors during configuration merge on the fly. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L240) - - `dynamicConfigExclude` - - `dynamicConfigInclude` - - `elasticCacheQuota` - - -```json -"seo": { - "useUrlDispatcher": true -}, -"console": { - "showErrorOnProduction" : true, - "verbosityLevel": "display-everything" -}, -"redis": { - "host": "localhost", - "port": 6379, - "db": 0 -}, -"graphql":{ - "host": "localhost", - "port": 8080 -}, -"api": { - "url": "http://localhost:8080" -}, -``` - -- `seo` -- `console` -- `redis` -- `graphql` -- `api` - -```json -"elasticsearch": { - "httpAuth": "", - "host": "/api/catalog", - "index": "vue_storefront_catalog", - "min_score": 0.02, - "csrTimeout": 5000, - "ssrTimeout": 1000, - "queryMethod": "GET", - "disableLocalStorageQueriesCache": true, - "searchScoring": { - "attributes": { - "attribute_code": { - "scoreValues": { "attribute_value": { "weight": 1 } } - } - }, - "fuzziness": 2, - "cutoff_frequency": 0.01, - "max_expansions": 3, - "minimum_should_match": "75%", - "prefix_length": 2, - "boost_mode": "multiply", - "score_mode": "multiply", - "max_boost": 100, - "function_min_score": 1 - }, - "searchableAttributes": { - "name": { - "boost": 4 - }, - "sku": { - "boost": 2 - }, - "category.name": { - "boost": 1 - } - } -}, -``` -`elasticsearch` ... - -```json -"ssr": { - "templates": { - "default": "dist/index.html", - "minimal": "dist/index.minimal.html", - "basic": "dist/index.basic.html", - "amp": "dist/index.amp.html" - }, - "executeMixedinAsyncData": true, - "initialStateFilter": ["__DEMO_MODE__", "version", "storeView"], - "useInitialStateFilter": true -}, -``` -- `ssr` - - `templates` - - `default` - -```json -"defaultStoreCode": "", -"storeViews": { - "multistore": false, - "commonCache": false, - "mapStoreUrlsFor": ["de", "it"], - "de": { - "storeCode": "de", - "storeId": 3, - "name": "German Store", - "url": "/de", - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_de" - }, - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - }, - "it": { - "storeCode": "it", - "storeId": 4, - "name": "Italian Store", - "url": "/it", - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_it" - }, - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "IT", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "fullCountryName": "Italy", - "fullLanguageName": "Italian", - "defaultCountry": "IT", - "defaultLanguage": "IT", - "defaultLocale": "it-IT", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - } -}, -``` -- `defaultStoreCode` - -```json -"entities": { - "optimize": true, - "twoStageCaching": true, - "optimizeShoppingCart": true, - "category": { - "includeFields": [ "id", "*.children_data.id", "*.id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key", "url_path", "product_count", "path"], - "excludeFields": [ "sgn" ], - "categoriesRootCategorylId": 2, - "categoriesDynamicPrefetchLevel": 2, - "categoriesDynamicPrefetch": true - }, - "attribute": { - "includeFields": [ "attribute_code", "id", "entity_type_id", "options", "default_value", "is_user_defined", "frontend_label", "attribute_id", "default_frontend_label", "is_visible_on_front", "is_visible", "is_comparable", "tier_prices", "frontend_input" ] - }, - "productList": { - "sort": "", - "includeFields": [ "type_id", "sku", "product_links", "tax_class_id", "special_price", "special_to_date", "special_from_date", "name", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "url_path", "url_key", "status", "tier_prices", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax" ], - "excludeFields": [ "description", "configurable_options", "sgn", "*.sgn", "msrp_display_actual_price_type", "*.msrp_display_actual_price_type", "required_options" ] - }, - "productListWithChildren": { - "includeFields": [ "type_id", "sku", "name", "tax_class_id", "special_price", "special_to_date", "special_from_date", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax", "configurable_children.color", "configurable_children.size", "configurable_children.id", "configurable_children.tier_prices", "product_links", "url_path", "url_key", "status", "tier_prices"], - "excludeFields": [ "description", "sgn", "*.sgn", "msrp_display_actual_price_type", "*.msrp_display_actual_price_type", "required_options"] - }, - "review": { - "excludeFields": ["review_entity", "review_status"] - }, - "product": { - "excludeFields": [ "*.msrp_display_actual_price_type", "required_options", "updated_at", "created_at", "attribute_set_id", "options_container", "msrp_display_actual_price_type", "has_options", "stock.manage_stock", "stock.use_config_min_qty", "stock.use_config_notify_stock_qty", "stock.stock_id", "stock.use_config_backorders", "stock.use_config_enable_qty_inc", "stock.enable_qty_increments", "stock.use_config_manage_stock", "stock.use_config_min_sale_qty", "stock.notify_stock_qty", "stock.use_config_max_sale_qty", "stock.use_config_max_sale_qty", "stock.qty_increments", "small_image", "sgn", "*.sgn"], - "includeFields": null, - "useDynamicAttributeLoader": true, - "standardSystemFields": [ - "description", - "configurable_options", - "tsk", - "custom_attributes", - "size_options", - "regular_price", - "final_price", - "price", - "color_options", - "id", - "links", - "gift_message_available", - "category_ids", - "sku", - "stock", - "image", - "thumbnail", - "visibility", - "type_id", - "tax_class_id", - "media_gallery", - "url_key", - "url_path", - "max_price", - "minimal_regular_price", - "special_price", - "minimal_price", - "name", - "configurable_children", - "max_regular_price", - "category", - "status", - "priceTax", - "priceInclTax", - "specialPriceTax", - "specialPriceInclTax", - "_score", - "slug", - "errors", - "info", - "erin_recommends", - "special_from_date", - "news_from_date", - "custom_design_from", - "originalPrice", - "originalPriceInclTax", - "parentSku", - "options", - "product_option", - "qty", - "is_configured" - ] - } -}, -"cart": { - "serverMergeByDefault": true, - "serverSyncCanRemoveLocalItems": false, - "serverSyncCanModifyLocalItems": false, - "synchronize": true, - "synchronize_totals": true, - "setCustomProductOptions": true, - "setConfigurableProductOptions": true, - "askBeforeRemoveProduct": true, - "displayItemDiscounts": true, - "minicartCountType": "quantities", - "create_endpoint": "http://localhost:8080/api/cart/create?token={{token}}", - "updateitem_endpoint": "http://localhost:8080/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "http://localhost:8080/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "http://localhost:8080/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "http://localhost:8080/api/cart/totals?token={{token}}&cartId={{cartId}}", - "paymentmethods_endpoint": "http://localhost:8080/api/cart/payment-methods?token={{token}}&cartId={{cartId}}", - "shippingmethods_endpoint": "http://localhost:8080/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}", - "shippinginfo_endpoint": "http://localhost:8080/api/cart/shipping-information?token={{token}}&cartId={{cartId}}", - "collecttotals_endpoint": "http://localhost:8080/api/cart/collect-totals?token={{token}}&cartId={{cartId}}", - "deletecoupon_endpoint": "http://localhost:8080/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}", - "applycoupon_endpoint": "http://localhost:8080/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}" -}, -"products": { - "useShortCatalogUrls": false, - "useMagentoUrlKeys": true, - "setFirstVarianAsDefaultInURL": false, - "configurableChildrenStockPrefetchStatic": false, - "configurableChildrenStockPrefetchDynamic": false, - "configurableChildrenStockPrefetchStaticPrefetchCount": 8, - "filterUnavailableVariants": false, - "listOutOfStockProducts": true, - "preventConfigurableChildrenDirectAccess": true, - "alwaysSyncPlatformPricesOver": false, - "clearPricesBeforePlatformSync": false, - "waitForPlatformSync": false, - "setupVariantByAttributeCode": true, - "endpoint": "http://localhost:8080/api/product", - "defaultFilters": ["color", "size", "price", "erin_recommends"], - "systemFilterNames": ["sort"], - "filterFieldMapping": { - "category.name": "category.name.keyword" - }, - "colorMappings": { - "Melange graphite": "#eeeeee" - }, - "sortByAttributes": { - "Latest": "updated_at", - "Price: Low to high":"final_price", - "Price: High to low":"final_price:desc" - }, - "gallery": { - "variantsGroupAttribute": "color", - "mergeConfigurableChildren": true, - "imageAttributes": ["image","thumbnail","small_image"], - "width": 600, - "height": 744 - }, - "filterAggregationSize": { - "default": 10, - "size": 10, - "color": 10 - } -}, -"orders": { - "directBackendSync": true, - "endpoint": "http://localhost:8080/api/order", - "payment_methods_mapping": { - }, - "offline_orders": { - "automatic_transmission_enabled": false, - "notification" : { - "enabled": true, - "title" : "Order waiting!", - "message": "Click here to confirm the order that you made offline.", - "icon": "/assets/logo.png" - } - } -}, -"localForage": { - "defaultDrivers": { - "user": "LOCALSTORAGE", - "cmspage": "LOCALSTORAGE", - "cmsblock": "LOCALSTORAGE", - "carts": "LOCALSTORAGE", - "orders": "LOCALSTORAGE", - "wishlist": "LOCALSTORAGE", - "categories": "LOCALSTORAGE", - "attributes": "LOCALSTORAGE", - "products": "INDEXEDDB", - "elasticCache": "LOCALSTORAGE", - "claims": "LOCALSTORAGE", - "syncTasks": "LOCALSTORAGE", - "ordersHistory": "LOCALSTORAGE", - "checkoutFieldValues": "LOCALSTORAGE" - } -}, -"reviews": { - "create_endpoint": "http://localhost:8080/api/review/create" -}, -"users": { - "autoRefreshTokens": true, - "endpoint": "http://localhost:8080/api/user", - "history_endpoint": "http://localhost:8080/api/user/order-history?token={{token}}", - "resetPassword_endpoint": "http://localhost:8080/api/user/reset-password", - "changePassword_endpoint": "http://localhost:8080/api/user/change-password?token={{token}}", - "login_endpoint": "http://localhost:8080/api/user/login", - "create_endpoint": "http://localhost:8080/api/user/create", - "me_endpoint": "http://localhost:8080/api/user/me?token={{token}}", - "refresh_endpoint": "http://localhost:8080/api/user/refresh" -}, -"stock": { - "synchronize": true, - "allowOutOfStockInCart": true, - "endpoint": "http://localhost:8080/api/stock" -}, -"images": { - "useExactUrlsNoProxy": false, - "baseUrl": "https://demo.vuestorefront.io/img/", - "productPlaceholder": "/assets/placeholder.jpg" -}, -"install": { - "is_local_backend": true, - "backend_dir": "../vue-storefront-api" -}, -"demomode": false, -"tax": { - "defaultCountry": "US", - "defaultRegion": "", - "sourcePriceIncludesTax": false, - "calculateServerSide": true, - "userGroupId": null, - "useOnlyDefaultUserGroupId": false, - "deprecatedPriceFieldsSupport": true, - "finalPriceIncludesTax": false -}, -``` - - `tax`: ... - - `defaultCountry` is the code name of the country on which tax is calculated for the store. - - `defaultRegion` is default region. - - `sourcePriceIncludesTax` determines whether price is stored with tax applied (`true`) or tax calculated on runtime (`false`). [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L12) - - `calculateServerSide` determines if price is fetched with(`true`)/without(`false`) tax calculated. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/product.js#L48) - - `userGroupId`: null, - - `useOnlyDefaultUserGroupId`: false, - - `deprecatedPriceFieldsSupport`: true, - - `finalPriceIncludesTax`: false -```json -"shipping": { - "methods": [ - { - "method_title": "DPD Courier", - "method_code": "flatrate", - "carrier_code": "flatrate", - "amount": 4, - "price_incl_tax": 5, - "default": true, - "offline": true - } - ] -}, -"i18n": { - "defaultCountry": "US", - "defaultLanguage": "EN", - "availableLocale": ["en-US","de-DE","fr-FR","es-ES","nl-NL", "ja-JP", "ru-RU", "it-IT", "pt-BR", "pl-PL", "cs-CZ"], - "defaultLocale": "en-US", - "currencyCode": "USD", - "currencySign": "$", - "currencyDecimal": null, - "currencyGroup": null, - "fractionDigits": 2, - "priceFormat": "{sign}{amount}", - "dateFormat": "HH:mm D/M/YYYY", - "fullCountryName": "United States", - "fullLanguageName": "English", - "bundleAllStoreviewLanguages": true -}, -"mailchimp": { - "endpoint": "http://localhost:8080/api/ext/mailchimp-subscribe/subscribe" -}, -"mailer": { - "endpoint": { - "send": "http://localhost:8080/api/ext/mail-service/send-email", - "token": "http://localhost:8080/api/ext/mail-service/get-token" - }, - "contactAddress": "contributors@vuestorefront.io", - "sendConfirmation": true -}, -"theme": "@vue-storefront/theme-default", -"analytics": { - "id": false -}, -"hotjar": { - "id": false -}, -"cms": { - "endpoint": "http://localhost:8080/api/ext/cms-data/cms{{type}}/{{cmsId}}", - "endpointIdentifier": "http://localhost:8080/api/ext/cms-data/cms{{type}}Identifier/{{cmsIdentifier}}/storeId/{{storeId}}" -}, -"cms_block": { - "max_count": 500 -}, -"cms_page": { - "max_count": 500 -}, -"usePriceTiers": false, -"useZeroPriceProduct": true, -"query": { - "inspirations": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Performance Fabrics" } - } - ] - }, - "newProducts": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Tees" } - } - ] - }, - "bestSellers": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Tees" } - } - ] - } -} - -``` diff --git a/docs/guide/archives/entity_type.md b/docs/guide/archives/entity_type.md deleted file mode 100644 index 4818721cec..0000000000 --- a/docs/guide/archives/entity_type.md +++ /dev/null @@ -1,113 +0,0 @@ -# Data Entity Types - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.12` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - -Vue Storefront uses multiple data-entity types to cover the whole scope of the storefront. Default entity types are: - -- Product -- Category -- Attribute -- Taxrule - -These entity types were hardcoded and there was no ability to easily use another custom entity type required for customization. - -Now, Vue Storefront has a new logic to work with entities in the data-fetching perspective: Entity Types. - -Each search adapter should register an entity type to cover a search feature. Default API and new GraphQL search adapters are updated to register all required existing entity types, but developers can also inject custom entity types to work with some other custom entity type data (for example, to get a list of offline stores or something else). - -To use it, an internal GraphQL server should be updated with adding a corresponding resolver for the new entity type. Also, you can use some other external GraphQL server that already has implemented a resolver for this entity type. - -To register such an entity type, you should use the `searchAdapter.registerEntityTypeByQuery` method like shown in the example below: - -```js -const factory = new SearchAdapterFactory(); -let searchAdapter = factory.getSearchAdapter('graphql'); -searchAdapter.registerEntityTypeByQuery('testentity', { - url: 'http://localhost:8080/graphql/', - query: require('./queries/testentity.gql'), - queryProcessor: query => { - // function that can modify the query each time before it's being executed - return query; - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted'); - } - if (resp.hasOwnProperty('data')) { - return processESResponseType(resp.data.testentity, start, size); - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)); - } else { - throw new Error( - "Unknown error with graphQl result in resultProcessor for entity type 'category'", - ); - } - } - }, -}); -``` - -The sample extension `sample-custom-entity-graphql` was added to illustrate how it can be used. It injects a custom entity type `testentity` and sets a custom GraphQL server URL (it is the same as a default API host in the example, because a resolver for this `testentity` was added there for testing. But please notice it was removed there). - -To test a sample extension with resolver, you can add a GraphQL schema file and resolver file in the separate `src/graphql/elastcisearch/testentity` folder in the Vue Storefront API. - -`schema.graphqls` file: - -```graphql -type Query { - testentity(filter: TestInput): ESResponse -} -input TestInput - @doc( - description: "TaxRuleInput specifies the tax rules information to search" - ) { - id: FilterTypeInput - @doc(description: "An ID that uniquely identifies the tax rule") - code: FilterTypeInput - @doc( - description: "The unique identifier for an tax rule. This value should be in lowercase letters without spaces." - ) - priority: FilterTypeInput @doc(description: "Priority of the tax rule") - position: FilterTypeInput @doc(description: "Position of the tax rule") - customer_tax_class_ids: FilterTypeInput - @doc(description: "Cunstomer tax class ids of the tax rule") - product_tax_class_ids: FilterTypeInput - @doc(description: "Products tax class ids of the tax rule") - tax_rate_ids: FilterTypeInput - @doc(description: "Tax rates ids of the tax rule") - calculate_subtotal: FilterTypeInput - @doc(description: "Calculating subtotals of the tax rule") - rates: FilterTypeInput @doc(description: "Rates of the tax rule") -} -``` - -Resolver file `resolver.js`: - -```js -import config from 'config'; -import client from '../client'; -import { buildQuery } from '../queryBuilder'; - -async function testentity(filter) { - let query = buildQuery({ filter, pageSize: 150, type: 'taxrule' }); - - const response = await client.search({ - index: config.elasticsearch.indices[0], - type: config.elasticsearch.indexTypes[4], - body: query, - }); - - return response; -} - -const resolver = { - Query: { - testentity: (_, { filter }) => testentity(filter), - }, -}; - -export default resolver; -``` diff --git a/docs/guide/archives/extensions.md b/docs/guide/archives/extensions.md deleted file mode 100644 index e193b3f2cb..0000000000 --- a/docs/guide/archives/extensions.md +++ /dev/null @@ -1,241 +0,0 @@ -# Extensions - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.11` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - - -## Introduction - -### What do Vue Storefront extensions look like? - -Depending on your needs, Vue Storefront extensions can have two parts: -- **Client-side part,** which is just a [Vue Storefront module](https://github.com/vuestorefront/vue-storefront/blob/master/docs/guide/modules/introduction.md). It covers most of the use cases. - -- **Server-side part** which is a [Vue Storefront API extension](https://github.com/vuestorefront/vue-storefront/blob/master/docs/guide/extensions/extending-api.md) and should be used if you want to add some endpoints to `vue-storefront-api` or interact with Elasticsearch. - -### Where extensions are located -- On the client side, extension modules should be placed in `src/modules` folder of `vue-storefront` or installed via NPM cli and registered in `src/modules/index.ts` -- On the server side, extensions should be placed in `src/api/extensions` folder of `vue-storefront-api` and registered in config file - -### Writing extensions -If you are writing a VS extension as an NPM module, start the package name with a `vsf-` prefix so it can be transpiled with other VS code and ship it as a raw es6/typescript module. If you don't use the prefix, you need to handle transpilation by yourself. We are currently building an extension boilerplate to make it easier to develop one. - -Here, you can find two articles explaining how to create custom Vue Storefront extensions: -- [How to create an Instagram Feed module for Vue Storefront](https://itnext.io/how-to-create-an-instagram-feed-module-for-vue-storefront-eaa03019b288) by Javier Villanueva -- [Developing a Vue Storefront payment module](https://www.develodesign.co.uk/news/development-of-the-paypal-module-for-vue-storefront/#.XCoa2h2Mmmo.twitter) by Dmitry Schegolikhin from [Develo Design](https://www.develodesign.co.uk/) - -**IMPORTANT** If you are an extension developer, please join `#extension-dev` channel on our Slack to receive information about important API updates and new features. - -### Extensions list -You can find a curated list of VS extensions in [Awesome Vue Storefront](https://github.com/frqnck/awesome-vue-storefront) list. - - -## Extending the API - -Some extensions need to have additional API methods to get some data directly from Magento/other CMS or just from custom Elasticsearch data collections. - -You may add new ES collections [using the Migration mechanism](../data/data-migrations.md) - -Then you may extend the [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api) to add your custom API methods. Please take a look at: [mailchimp-subscribe](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/extensions/mailchimp-subscribe/index.js) for reference. - -To add the API extension to `vue-storefront-api`: - -1. Create the folder within `src/api/extensions` for example 'custom_extension`. -2. Then add the `index.js` file and put the API methods code inside. We're using Express.js. Here is a boilerplate/example for the extension code: - -```js -import { apiStatus } from '../../../lib/util'; -import { Router } from 'express'; - -module.exports = ({ config, db }) => { - let mcApi = Router(); - - /** - * POST create an user - */ - mcApi.post('/subscribe', (req, res) => { - let userData = req.body; - if (!userData.email) { - apiStatus(res, 'Invalid e-mail provided!', 500); - return; - } - - let request = require('request'); - request( - { - url: - config.extensions.mailchimp.apiUrl + - '/lists/' + - encodeURIComponent(config.extensions.mailchimp.listId) + - '/members', - method: 'POST', - headers: { - Authorization: 'apikey ' + config.extensions.mailchimp.apiKey, - }, - json: true, - body: { email_address: userData.email, status: 'subscribed' }, - }, - function(error, response, body) { - if (error) { - apiStatus(res, error, 500); - } else { - apiStatus(res, body, 200); - } - }, - ); - }); - - /** - * DELETE delete an user - */ - mcApi.delete('/subscribe', (req, res) => { - let userData = req.body; - if (!userData.email) { - apiStatus(res, 'Invalid e-mail provided!', 500); - return; - } - - let request = require('request'); - request( - { - url: - config.extensions.mailchimp.apiUrl + - '/lists/' + - encodeURIComponent(config.extensions.mailchimp.listId), - method: 'POST', - headers: { - Authorization: 'apikey ' + config.extensions.mailchimp.apiKey, - }, - json: true, - body: { - members: [{ email_address: userData.email, status: 'unsubscribed' }], - update_existing: true, - }, - }, - function(error, response, body) { - if (error) { - apiStatus(res, error, 500); - } else { - apiStatus(res, body, 200); - } - }, - ); - }); - return mcApi; -}; -``` - -3. Add the extension to `config/local.json`: - -```json - "registeredExtensions": ["mailchimp-subscribe"], -``` - -4. Restart the `vue-storefront-api` -5. Your new API method is available on `localhost:8080/api/ext//` for example: `localhost:8080/api/ext/mailchimp-subscribe/subscribe` - - -## Extending Express.js server-side routes - -From Vue Storefront 1.4.0, you can add your own custom server-side routes without Vue.js SSR context. These routes may be used, for example, for generating large, unbuffered files like XML maps, binary files, etc. - -You can add numerous, custom Express js middlewares and routes by simply modifying the `src/server/index.js`: - -```js -// You can extend Vue Storefront server routes by binding to the Express.js (expressApp) in here -module.exports.registerUserServerRoutes = expressApp => { - require('./example/generator')(expressApp); -}; -``` - -The example route handler looks like this: - -```js -module.exports = expressApp => { - /** - * This is an example on how You can bind Your own Express.js server routes to SSR server running Vue Storefront. - * It may be usefull to avoid all the Vue.js processing and context - and useful for example for large XML/binary file generation - */ - expressApp.get('/vue-storefront.xml', (req, res) => { - res.end('Vue Storefront custom XML generator example'); - }); -}; -``` - -### Data operations inside Express routes - -Unfortunately, as you may have seen above in the `core/scripts/server.js`, all modules used by the script (including the dynamic routes) can not use ES modules (`import ... from ...` type of statements). By this limitation, you can't currently use `@vue-storefront`modules inside the custom Express.js routes as they're not compiled to the CommonJS. This is likely to be fixed. To get the data, you may execute `fetch()` requests to the `vue-storefront-api` endpoints. You can still use `const config = require('config')` to read the endpoints, URLs, etc. - - -## Using extensions to Modify Elasticsearch results - -Vue Storefront API has a built-in processor for calculating taxes and adding that data to product search results. API extensions can also add their own processors for modifying Elasticsearch results. - -Some possible use cases for this could be: -- Replacing Magento product descriptions with data from a CMS. -- Cleaning Magento "WYSIWYG" data. -- Adding product ratings or other data from third-party systems. - -Here is an example of creating a custom result processor to replace Product descriptions with Prismic CMS data: - -1. Create the extension folder in `src/api/extensions`. -2. The extension folder must contain another folder called `processors`. -3. Add the processor file, for example `src/api/extensions/example-extension/processors/prismic-product.js`. -```js -import Prismic from 'prismic-javascript' -import PrismicDOM from 'prismic-dom' - -class ProductPrismic { - constructor (config, request) { - this._request = request - this._config = config - } - - process (productList) { - const skus = productList.map( prod => { - return prod._source.sku.toLowerCase() - }) - return Prismic.getApi(this._config.extensions['example-extension'].baseUrl).then((api) => { - return api.query(Prismic.Predicates.in('my.product.uid', skus)) - }).then((result) => { - for (const item of result.results) { - const product = productList.find( prod => { - return prod._source.sku.toLowerCase() === item.uid - }) - if (product) { - try { - product._source.description = PrismicDOM.RichText.asHtml(item.data.description) - } - catch(error) { - console.log(error) - } - } - } - return productList - }).catch(err => { - console.log(err) - }) - } -} - -module.exports = ProductPrismic -``` -4. Add the extension to `config/local.json` and declare the custom processor in the extension settings. It needs to be in this structure: -```json - "registeredExtensions": ["example-extension"], - "extensions": { - "example-extension": { - "baseUrl": "https://my_account.cdn.prismic.io/api/v2", - "resultProcessors": { - "product": "prismic-product" - } - } - } -``` - -That's it. In Prismic, create documents with a uid matching each product SKU, and a description field. Those description will then appear in Vue Storefront product listings. The data update instantly, whenever a document is published in Prismic. - -Note: This example uses Prismic and PrismicDOM, so they'll need to be added to your dependencies in package.json - -Note 2: See `src/platform/magento2/tax.js` for another example of a results processor. diff --git a/docs/guide/archives/graphql.md b/docs/guide/archives/graphql.md deleted file mode 100644 index 73c942f582..0000000000 --- a/docs/guide/archives/graphql.md +++ /dev/null @@ -1,71 +0,0 @@ -# GraphQL - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.12` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - -## GraphQL Action Plan (Deprecated) - -Starting with Vue Storefront 1.4.0, we're supporting two ways of getting data from the backend: - -- existing `api` mode, which is using ElasticSearch DSL as a query language -- new `graphql` mode, which is using GraphQL queries. - -You can set the desired API format in the `config/local.json` and `vue-storefront-api` is supporting both of them, however [the default is still set to `api`](https://github.com/vuestorefront/vue-storefront/blob/4cbf866ca93f917b04461d3ae139a2d26ddf552a/config/default.json#L6). - -We've introduced an abstract [`SearchQuery`](https://github.com/vuestorefront/vue-storefront/tree/develop/core/store/lib/search) interface with switchable Query Adapters to provide the abstraction layer. This is an ultra-cool feature, especially when you're integrating Vue Storefront with a custom backend application—you're able [to create your own adapter](https://github.com/vuestorefront/vue-storefront/tree/develop/core/lib/search/adapter) to customize the way data is gathered from the backend. - -From now on the **bodybuilder** package is **deprecated** and you should start using the `SearchQuery` interface to build the search queries that will be translated to GraphQL / API queries. - -Here is an example of how to build the Query: - -```js -export function prepareRelatedQuery(key, sku) { - let relatedProductsQuery = new SearchQuery(); - - relatedProductsQuery = relatedProductsQuery.applyFilter({ - key: key, - value: { in: sku }, - }); - - relatedProductsQuery = relatedProductsQuery - .applyFilter({ key: 'visibility', value: { in: [2, 3, 4] } }) - .applyFilter({ key: 'status', value: { in: [0, 1, 2] } }); // @TODO Check if status 2 (disabled) was set not by occasion here - - if (config.products.listOutOfStockProducts === false) { - relatedProductsQuery = relatedProductsQuery.applyFilter({ - key: 'stock.is_in_stock', - value: { eq: true }, - }); - } - - return relatedProductsQuery; -} - -let relatedProductsQuery = prepareRelatedQuery(key, sku); - -this.$store - .dispatch('product/list', { - query: relatedProductsQuery, - size: 8, - prefetchGroupProducts: false, - updateState: false, - }) - .then(response => { - if (response) { - this.$store.dispatch('product/related', { - key: this.type, - items: response.items, - }); - this.$forceUpdate(); - } - }); -``` - -[More information on how to query the data](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/data/elastic-queries.md). - -**Bodybuilder** queries are still supported by our backward-compatibility mode, so if you've used bodybuilder in your theme, it's fine as long as you're using the `api` mode for the backend queries. - -The **legacy queries** using bodybuilder will still work - and [here is an example](https://github.com/pkarw/vue-storefront/blob/28feb8e5dc30ec216353ef87a859212379901c57/src/extensions/template/index.js#L36). - -You can also use direct **ApolloQuery** GraphQL queries thanks to `vue-apollo` support. Please find the example [in here](https://github.com/vuestorefront/vue-storefront/blob/4cbf866ca93f917b04461d3ae139a2d26ddf552a/src/themes/default/components/core/blocks/SearchPanel/SearchPanel.gql.vue#L21). diff --git a/docs/guide/archives/migration.md b/docs/guide/archives/migration.md deleted file mode 100644 index 39b6fddf0d..0000000000 --- a/docs/guide/archives/migration.md +++ /dev/null @@ -1,73 +0,0 @@ -# Data Migrations for Elacticsearch - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.12` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - -Vue Storefront uses Elasticsearch as a primary data store. We're using Redis as a cache layer and Kue for queue processing. - -Although all of these data stores are basically schema-free, some mappings and meta data should be used for setting ES indices and so forth. - -Vue Storefront uses a data-migration mechanism based on [node-migrate](https://github.com/tj/node-migrate). - -## Migration tool - -We use node-migrate, which is pre-configured with npm, so we're using the following alias: - -```bash -yarn migrate -``` - -which runs the migrations against `migrations` folder. - -## How to add new migration? - -You can add a new migration by simply adding a file to the `migrations` directory (not recommended) or using the command line tool: - -```bash -yarn migrate create name-of-my-migration -``` - -The tool automatically generates the file under the `migrations` folder. - -## Examples - -The example migrations show how to manipulate products and mappings. Let's take a look at the mapping modification: - -```js -// Migration scripts use: https://github.com/tj/node-migrate -'use strict'; - -let config = require('config'); -let common = require('./.common'); - -module.exports.up = function(next) { - // example of adding a field to the schema - // other examples: https://stackoverflow.com/questions/22325708/elasticsearch-create-index-with-mappings-using-javascript, - common.db.indices - .putMapping({ - index: config.elasticsearch.indices[0], - type: 'product', - body: { - properties: { - slug: { type: 'string' }, // add slug field - suggest: { - type: 'completion', - analyzer: 'simple', - search_analyzer: 'simple', - }, - }, - }, - }) - .then(res => { - console.dir(res, { depth: null, colors: true }); - next(); - }); -}; - -module.exports.down = function(next) { - next(); -}; -``` - -... and that's it :) diff --git a/docs/guide/archives/modules.md b/docs/guide/archives/modules.md deleted file mode 100644 index 0114207755..0000000000 --- a/docs/guide/archives/modules.md +++ /dev/null @@ -1,1342 +0,0 @@ -# Modules - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.11` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - -## Introduction -### Table of contents - -**Introduction and motivation** -- [What are VS Modules](#what-are-vs-modules) -- [Motivation](#motivation) -- [What is the purpose of VS modules?](#what-is-the-purpose-of-vs-modules) - -**Technical part** -- [Module config and its capabilities](#module-config-and-capabilities) -- [Module file structure](#module-file-structure) -- [Module registration](#module-registration) - -**Patterns and good practices for common use cases** -- [General rules and good practices](#general-rules-and-good-practices) -- [Adding new features as VS modules](#adding-new-features-as-vs-modules) -- [Extending and overriding Vue Storefront modules](#extending-and-overriding-vue-storefront-modules) -- [Creating third party modules](#Creating-3rd-party-modules) - - -### What are VS modules? - -You can think about each module as a one, independent feature available in Vue Storefront with all its logic and dependencies inside. This *one feature* however is a common denominator that links all the features inside. For example, the common denominator for adding a product to the cart, receiving a list of items that is in the cart or applying a cart coupon is obviously a `cart` and `cart` is not a feature of anything bigger than itself (its common denominator is the shop) so it should be a module. Wishlist, Reviews or Newsletter are also good examples of the module as we intuitively think about them as standalone features. - -#### Motivation - -I believe that an obvious metaphor can clearly describe the problem, at the same time, the solution. - -To better illustrate the whole concept I'll try to explain it with lego bricks. - -Let's say we have a box with 90 lego bricks that we can use to build some fancy things like Towers, Castles, or Helicopters. Unfortunately due to some stupid EU regulations we can only have 3 different colors of bricks in our box. As we all know, not every color is accurate for every structure that can be built so we need to swap one color with another in a shop from time to time in order to have bricks in colors that are best-suited for our next lego project. - -Cool, but there is one problem - since we have all our bricks in one box they look more or less as follows : - -![lego](../images/pile_of_legos.png) - -When we want to replace the green bricks with, let's say, the black ones we need to look for each green brick separately among all the others which can take a lot of time... and there is still a chance that we will miss some of them! Not to mention that finding the particular green brick that we need to finish the palm tree we are building (this one!) will require looking for it among all the other bricks which can make this task extremely difficult and time-consuming. - -This is obviously not a situation that we want to end up in with our small lego empire. Neither do we want it with Vue Storefront since it's meant to be easily extendable so you can replace your green bricks (or current user cart feature/cms provider/cms content provider) with the black ones (different cart feature with multiple carts, WordPress instead of Prismic for content etc) without hustles and bustles looking for each of them among all the bricks and without worries that you will miss some of them or EU will confiscate all the bricks that you have! We also want to make it easier to find the correct brick that we want right now to finish this damn palm tree! - -So how do we make this horrible situation better? - -Introducing... (drums build up in the background) **_bricks grouped by colors_**! (wows in the background) - -![lego2](../images/organized_lego_bricks.jpeg) - -When we have our bricks grouped by their colors (and in separate boxes - modules) it's much easier to find this green brick that we needed for a palm tree since we only need to search in a small subset of all bricks. Moreover when we want to replace green bricks with the black ones, then instead of looking for all the green representatives one by one we are just replacing their box with the one containing black bricks. We also don't need to worry if something was left behind since we know that all the green bricks were in the box. - -This is the modularity and extendability we are looking for in Vue Storefront and the architecture we are currently rewriting it into. - -### What is the purpose of VS modules? - -The purpose is well described in [this discussion](https://github.com/vuestorefront/vue-storefront/issues/1213). It can be summarized to: - -- **Better extendability**: We can extend each module or replace it completely with the new one. For example, we may want to replace our Cart module with the one that allows to have multiple carts. With module approach, we can just detach the current Cart module and replace it with the new one. Another example can be using different modules for different content CMSes integration etc. -- **Better developer experience**: Along with the modules we are introducing many features focused on delivering better and easier experience for developers to hop on in a more predictable way. We changed the way you can compose components with features, added unit tests, TypeScript interfaces etc. -- **Better upgradability**: Each module is a separate NPM package therefore can be upgraded independently and since it has all the logic encapsulated, it shouldn't break any other part of the application when detached, modified or replaced. - -### Module config and capabilities - -Module config is the object that is required to instantiate VS module. The config object you provide is later used to extend and hook into different parts of the application (e.g. router, Vuex etc). -Please use this object as the only part that is responsible for extending Vue Storefront. Otherwise it may stop working after some breaking core updates. - -Vue Storefront module object with provided config should be exported to `index.ts` entry point. Ideally it should be an *export* named the same as modules key. - -This is how the signature of Vue Storefront Module looks like: - -```js -interface VueStorefrontModuleConfig { - key: string; - store?: { - modules?: { key: string, module: Module }[], - plugin?: Function, - }; - router?: { - routes?: RouteConfig[], - beforeEach?: NavigationGuard, - afterEach?: NavigationGuard, - }; - beforeRegistration?: (VSF) => void; - afterRegistration?: (VSF) => void; -} -``` - -See code [here](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/index.ts) - -#### `key` (required) - -A key is an ID of your module. It's used to identify your module and to set keys in all key-based extensions that module is associated (e.g. creating namespaced store). This key should be unique. - -#### `store` - -The entry point for Vuex. - -- `modules` - array of Vuex modules to register under given keys -- `plugin` - you can provide your own Vuex plugin here - -#### `router` - -The entry point for vue-router. You can provide additional routes and [navigation guards](https://router.vuejs.org/guide/advanced/navigation-guards.html) here. - -#### `beforeRegistration` - -A function that'll be called before registering the module both on server and client side. You have access to VSF object here. - -The `VSF` object is an instance of your Vue Storefront shop. It contains following properties -````js - Vue?: VueConstructor, - config?: Object, - store?: Store, - isServer?: boolean -```` -#### `afterRegistration` - -A function that'll be called after registering the module both on server and client side. You have access to VSF object here. - -The `VSF` object is an instance of your Vue Storefront shop. It contains following properties -````js - Vue?: VueConstructor, - config?: Object, - store?: Store, - isServer?: boolean -```` -### Module file structure - -Below you can see recommended file structure for VS module. All of the core ones are organised in this way. -Try to have a similar file structure inside the ones that you create. If all the modules are implemented with a similar architecture, it'll be much easier to maintain and understand them. Please avoid unnecessary changes in design unless otherwise required so. - -Not all of this folders and files should exist in every module. The only mandatory file is `index.ts` which is the entry point. The rest depends on your needs and module functionality. - -You can take a look at [module template](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/module-template) with an example implementation of all features listed in config. - -- `components` - Components logic related to this module (eg. Microcart for Cart module). Normally it contains `.ts` files but you can also create `.vue` files and provide some baseline markup if it is required for the component to work out of the box. -- `pages` - If you want to provide full pages with your module, place them here. It's also a good practice to extend router configuration for these pages -- `store` - Vuex Module associated to this module. You can also place Vuex modules extensions in here - - `index.ts` - Entry point and main export of your Vuex Module. Actions/getters/mutations can be split into different files if the logic is too complex to keep it in one file. Should be used in `store` config property. - - `mutation-types.ts` - Mutation strings represented by variables to use instead of plain strings - - `plugins.ts` - Good place to put vuex plugin. Should be used in `store.plugins` config object -- `types` - TypeScript types associated with the module -- `test` - Folder with unit tests which is _required_ for every new or rewritten module. -- `hooks` - before/after hooks that are called before and after registration of the module. - - `beforeRegistration.ts` - Should be used in `beforeRegistration` config property. - - `afterRegistration.ts` - Should be used in `afterRegistration` config property. -- `router` - routes and navigation guards associated to this module - - `routes.ts`- array of route objects that will be added to the current router configuration. Should be used in `router.routes` config property. - - `beforeEach.ts` - beforeEach navigation guard. Should be used in `router.beforeEach` config property. - - `afterEach.ts`- afterEach navigation guard. Should be used in `router.afterEach` config property. -- `queries` - GraphQL queries -- `helpers` - everything else that is meant to support modules behavior -- `index.js` - entry point for the module. Should export VueStorefrontModule. It's also a good place to instantiate cache storage. - -### Module registration - -All modules including the core ones are registered in `src/modules/index.ts` file. Thanks to this approach you can easily modify any of core modules object before registration (read more [here](#extending-and-overriding-vue-storefront-modules)). - -All VS modules from `registerModules` will be registered during the shop initialisation. - ---- - -### General rules and good practices - -First off, take a look at module template. It contains great examples, good practices and explanations for everything that can be put in a module. - -0. **THE MOST IMPORTANT RULE** Try to isolate all the logic required for a module to work properly and put them inside the module. You can import it from other parts of the app but the logic itself should exist in the module -1. **Try not to rely on any other module. Keep everything encapsulated and only rely on core helpers and libs**. Use other stores only if it's the only way to achieve the functionality and import `rootStore` for this purpose. Modules should work standalone and rely only on themselves. Try to think about each module as a standalone npm package. -1. Place all reusable features as Vuex actions (e.g. `addToCart(product)`, `subscribeNewsletter()` etc) instead of placing them in components. try to use getters for modified or filtered values from state. We are trying to place most of the logic in Vuex stores to allow easier core updates. Here is a good example of such externalisation. - -```js -export const Microcart = { - name: 'Microcart', - computed: { - productsInCart(): Product[] { - return this.$store.state.cart.cartItems; - }, - appliedCoupon(): AppliedCoupon | false { - return this.$store.getters['cart/coupon']; - }, - totals(): CartTotalSegments { - return this.$store.getters['cart/totals']; - }, - isMicrocartOpen(): boolean { - return this.$store.state.ui.microcart; - }, - }, - methods: { - applyCoupon(code: String): Promise { - return this.$store.dispatch('cart/applyCoupon', code); - }, - removeCoupon(): Promise { - return this.$store.dispatch('cart/removeCoupon'); - }, - toggleMicrocart(): void { - this.$store.dispatch('ui/toggleMicrocart'); - }, - }, -}; -``` - -3. If you want to inform of success/failure of core component's method you can either use a callback or scoped event. Omit Promises if you think that function can be called from the template and you'll need the resolved value. This is a good example of method that you can call either on `template` or `script` section: - -```js -addToCart(product, success, failure) { - this.$store.dispatch('cart/addToCart').then(res => - success(res) - ).catch(err => - failure(err) - ) -} -``` - -Try to choose a method based on use cases. [This](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/mailchimp/components/Subscribe.ts#L28) is a good example of using callbacks. - -5. Create pure functions that can be easily called with a different argument. Rely on `data` properties instead of arguments only if it's required (for example, they are validated as [here](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/mailchimp/components/Subscribe.ts#L28)) -6. Make a document for exported components like as follows : [document](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/mailchimp/components/Subscribe.ts) -7. If your module core functionality is an integration with external service, better name it the same as this service (for example `mailchimp`) -8. Use named exports and type check. - -### Adding new features as VS modules - -- If you want to crete a new module, copy content from `src/module-template` and use the parts that you need. -- If you are creating a new feature, then note it's not merely extending currently existing one. If you are sure the feature you want to provide is completely new then it should be introduced as a new VS module. -- Provide unique key that should represent the feature or 3rd party system name (if the module is an integration) -- Try not to rely on data and logic from other modules if your module is not claimed to directly extend it. In doing so, it's guaranteed to remain working and easier to reuse even after extensive VS core updates. - -### Extending and overriding Vue Storefront Modules - -You can extend and modify all parts of any of Vue Storefront module before its registration by providing a`VueStorefrontModuleConfig` object **with the same key** to `extendModule()` function. This config will be deep merged with the module of the same key, which means: -- All Vuex stores with the same keys will be merged (conflicting actions/mutations will be overwritten, others will be added) -- Leafs like before/after hooks, store plugins or router object properties will be overwritten by the new ones if provided. - - - -Let's see an example and assume we want to extend module `cart` by overriding its `beforeRegistration` hook and `load` Vuex action. -1. First we need to prepare a `VueStorefrontModuleConfig` that we will use to extend `cart` module. It must have the same `key` value as the module we want to extend. -2. Next we need to pass this object to `extendModule` function -3. That's all! Now when you register `cart` module it will be extended with provided config. - - -```js -import { Cart } from '@vue-storefront/core/modules/cart' - -// 1. Preparation of new VSMConfig -const extendCartVuex = { - actions: { - load () { - console.info('hey') - } - } -} - -const extendCartAfterRegistration = function (VSF) { - console.info('Hello, im extended now!') - } - -const cartExtend = { - key: 'cart', - afterRegistration: extendCartAfterRegistration, - store: { modules: [{ key: 'cart', module: extendCartVuex }] }, -} - -// 2. After passing the object to extendModule function it will be merged with Cart module during registration -extendModule(cartExtend) - -export const registerModules: VueStorefrontModule[] = [Cart] -``` - -If you want to make complex changes with your own app-specific VS module (which is not an npm package), it's a good practice to keep this module inside `src/modules/{module-name}`. To extend a module with another module just pass its config to `extendModule` function - -```js -import { Cart } from '@vue-storefront/core/modules/cart' -import { ExtendCartModule } from 'extend-cart'; - - -extendModule(ExtendCartModule.config) - -export const registerModules: VueStorefrontModule[] = [Cart] -``` - -### Creating third party modules - -If you want to create a third party module, just copy the `src/modules/module-template` raw code to your repo. Don't use any transpilation and build tools since it prevents proper tree shaking and optimization. A building process is handled by Vue Storefront build tools. A package name needs to start with `vsf-` prefix to be included into Vue Storefront build process. - -### Contributions - -Please introduce every new feature as a standalone, encapsulated module. We also need your help in rewriting Vue Storefront to modular approach - [here](https://github.com/vuestorefront/vue-storefront/issues?q=is%3Aissue+is%3Aopen+label%3A%22API+Module%22) you can find tasks related to this architecture change and [here](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/refactoring-to-modules.md) is the tutorial on how to approach applying these changes. - - -## Cart module - -This module contains all the logic, components and store related to cart operations. - -### Components - -#### AddToCart - -This component represents a single button that when pressed adds a product to cart. - -**Props** - -- `product` - product that'll be added to cart - -**Methods** - -- `addToCart(product)` - adds passed product to the cart. By default correlates with `product` prop - -#### Microcart - -User cart with a products list and price summary. - -**Computed** - -- `productsInCart` - array of products that are currently in the cart -- `appliedCoupon` - return applied cart coupon or `false` if no coupon was applied -- `totals` - cart totals -- `isMicrocartOpen` - returns `true` if microcart is open - -**Methods** - -- `applyCoupon(code)` - applies cart coupon -- `removeCoupon` - removes currently applied cart coupon -- `toggleMicrocart` - open/close microcart - -#### MicrocartButton - -Component responsible for opening/closing Microcart - -**Computed** - -- `quantity` - number of products in cart - -**Methods** - -- `toggleMicrocart` - open/close microcart - -#### Product - -Component representing product in microcart. Allows to modify it's quantity or remove from cart. - -**Computed** - -- `thumbnail` - returns src of products thumbnail - -**Methods** - -- `removeFromCart` - removes current product (data property `product`) from cart -- `updateQuantity` - updates cart quantity for current product (data property `product`) - -### Store - -Cart Store is designed to handle all actions related the shopping cart. - -#### State - -```js - state: { - itemsAfterPlatformTotals: {}, - platformTotals: null, - platformTotalSegments: null, - cartIsLoaded: false, - cartServerToken: '', // server side ID to synchronize with Backend (for example Magento) - shipping: [], - payment: [], - cartItemsHash: '', - bypassCount: 0, - cartItems: [] // TODO: check if it's properly namespaced - }, -``` - -Cart state is automatically loaded from `localForage` collection after page has been loaded whenever `core/components/blocks/Microcart.vue` is included. The cart state is loaded by dispatching `cart/load` action and [stored automatically by any change to the cart state](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/index.js#L118). - -The cart state data: - -- `itemsAfterPlatformTotals` - helper collection, dictionary where the key is Magento cart item `item_id` that stores the totals information per item - received from Magento; it's automatically populated when `config.cart.synchronize_totals` is enabled; -- `platformTotals` - similarly to above item, here we have the full totals from Magento for the current shopping cart. These collections are populated by [`cart/syncTotals`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/actions.js#L49) and the event handler for [`servercart-after-totals`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L30) -- `cartIsLoaded` (bool) - true after dispatching `cart/load` -- `shipping` - (object) currently selected shipping method - only when NOT using `cart.synchronize_totals` (if so, the shipping and payment's data comes from Magento2), -- `payment` - (object) currently selected shipping method - only when NOT using `cart.synchronize_totals` (if so, the shipping and payment's data comes from Magento2), -- `cartItems` - collection of the cart items; the item format is the same as described in [ElasticSearch Data formats](https://github.com/vuestorefront/vue-storefront/blob/master/doc/ElasticSearch%20data%20formats.md) - the `product` class; the only difference is that the (int) `qty` field is added - -#### Events - -The following events are published from `cart` store: - -- `EventBus.$emit('cart-after-itemchanged', { item: cartItem })` - executed after [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) - after server cart sync, that signalize the specific shopping cart item has been changed; `Microcart/Product.vue` component is subscribed to this event to refresh the shopping cart UI -- `EventBus.$emit('cart-before-add', { product: item })` - fired after product has been added to the cart, -- `EventBus.$emit('cart-before-save', { items: state.cartItems })` - fired after the product cart has been saved, -- `EventBus.$emit('cart-before-delete', { items: state.cartItems })` - the event fired before the cart item is going to be deleted with the current cart state (before item is deleted) -- `EventBus.$emit('cart-after-delete', { items: state.cartItems })` - the event fired before the cart item has been deleted with the current cart state (after item is deleted) -- `EventBus.$emit('cart-before-itemchanged', { item: record })` - item called before the specific item properties are going to be changed; for example called when [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) is going to change the `server_item_id` property -- `EventBus.$emit('cart-after-itemchanged', { item: record })` - item called after the specific item properites has been changed; for example called when [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) is going to change the `server_item_id` property -- `EventBus.$emit('application-after-loaded')` - event called after `cart/load` action has been dispatched to notify that cart is being available, -- `EventBus.$emit('cart-after-updatetotals', { platformTotals: totals, platformTotalSegments: platformTotalSegments })` - event called after the totals from Magento has been synchronized with current state; it's going to be emitted only when `cart.synchronize_totals` option is enabled. - -#### Actions - -The cart store provides following public actions: - -##### `disconnect (context)` - -Helper method used to clear the current server cart id (used for cart synchronization) - -##### `clear (context)` - -This method is called after order has been placed to empty the `cartItems` collection and create the new server cart when the `cart.synchronize_totals` is set to true - -##### `save (context)` - -Method used to save the cart to the `localForage` browser collection - -##### `sync (context, { forceClientState = false })` - -This method is used to synchronize the current state of the cart items back and forth between server and current client state. When the `forceClientState` is set to false the communication is one-way only (client -> server). This action is called automatically on any shopping cart change when the `cart.synchronize` is set to true. - -##### `syncTotals (context, { forceClientState = false })` - -Method is called whenever the cart totals should have been synchronized with the server (after `serverPull`). This method overrides local shopping cart grand totals and specific item values (for example prices after discount). - -##### `connect (context, { guestCart = false })` - -Action is dispatched to create the server cart and store the cart id (for further synchronization) - -##### `load (context)` - -This method loads the cart items from `localForage` browser state management. - -##### `getItem ({ commit, dispatch, state }, sku)` - -This action is used for search the particular item in the shopping cart (by SKU) - -##### `addItem ({ commit, dispatch, state }, { productToAdd, forceServerSilence = false })` - -This action is used to add the `productToAdd` to the cart, if `config.cart.synchronize` is set to true the next action subsequently called will be `serverPull` to synchronize the cart. The event `cart-before-add` is called whenever new product lands in the shopping cart. The option `forceServerSilence` is used to bypass the server synchronization and it's used for example then the item is added during the ... sync process to avoid circular synchronization cycles. - -##### `removeItem ({ commit, dispatch }, product)` - -As you may imagine :) This action simply removes the product from the shopping cart and synchronizes the server cart when set. You must at least specify the `product.sku`. - -##### `updateQuantity ({ commit, dispatch }, { product, qty, forceServerSilence = false })` - -This method is called whenever user changes the quantity of product in the cart (called from `Microcart.vue`). The parameter `qty` is the new quantity of product and by using `forceServerSilence` you may control if the server cart synchronization is being executed or not. - -##### `updateItem ({ commit }, { product })` - -Updates item properties. - -##### `syncPaymentMethods (context)` - -Gets a list of payment methods from the backend and saves them to `cart.payment` store state. - -##### `syncShippingMethods (context, address)` - -Gets a list of shipping methods from the backend and saves them to `cart.shipping` store state. Country ID is passed to this method in a mandatory `address` parameter. - -##### `syncTotals (context, methodsData)` - -This method sends request to the backend to collect cart totals. It calls different backend endpoints depending on if payment and shipping methods information is available or not. - -#### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -- `getCartToken` - get the current cart token, if empty it does mean we need to call an action `cart/connect` prior to sync with the server, -- `getLastSyncDate` - this is an integer, timestamp of the last shopping cart sync with the server -- `getLastTotalsSyncDate` - integer, timestamp of the last totals sync with the server, -- `getShippingMethod` - object, gets currently selected shipping method in the Checkout, -- `getPaymentMethod` - object, gets current payment method selected in the checkout, -- `getLastCartHash` - get the last saved hash/HMAC of the cart items + server token that let's you track the changes of the shipping cart. Hash is being saved by the server sync, -- `getCurrentCartHash` - get the current hash/HMAC of the cart items + server token. Coparing it to the `getLastCartHash` value let you know if we need a server sync or not, -- `isCartHashChanged` - comparing the `getLastCartHash` with the `getCurrentCartHash` in order to verify if we need a server sync or not, -- `isSyncRequired` - checking if the `isCartHashChanged` is true OR if this is the first sync attempt (after the SSR), -- `isTotalsSyncRequired` - same as `isSyncRequired` but for the totals (not the cart items), -- `isCartHashEmtpyOrChanged` - checks if `isCartHashChanged` or empty, -- `getCartItems` - array of products in the shopping cart, -- `isTotalsSyncEnabled` - check if the `config.cart.synchronize` is true + if we're online + if this is CSR request, -- `isCartConnected` - check if the `getCartToken` is not empty - which means the `cart/connect` action has been called and we're OK to sync with the server, -- `isCartSyncEnabled` - the same as `isTotalsSyncEnabled` but for totals (`config.cart.synchronize_totals` flag), -- `getTotals` - array with the total segments, -- `getItemsTotalQuantity` - get the sum of all the items in the shopping cart, -- `getCoupon` - get the currently applied discount code, - - -## User Module - -This module contains all the logic, components and store related to the user account - -### Components - -#### AccountButton - -A component to handle redirects to user account page and user logout. Usually used in header. - -**Computed** - -- `isLoggedIn` - represents if user is logged in; -- `user` - current user. - -**Methods** - -- `goToAccount` - is user is logged in, redirects user to account page. Otherwise shows sign-up modal -- `logout` - emits `user-before-logout` event and redirects user to home page - -#### Login - -**Methods** - -- `close` - closes sign-up modal -- `callLogin` - starts authentication process with emitting `notification-progress-start`, calls `user/login` action with user's email and password. -- `switchElem` - triggers `setAuthElem` mutation with `register` parameter -- `callForgotPassword` - triggers `setAuthElem` mutation with `forgot-pass` parameter - -#### Register - -**Methods** - -- `switchElem` - triggers `setAuthElem` mutation with `register` parameter -- `close` - closes sign-up modal -- `callRegister` - starts registration process with emitting `notification-progress-start`, calls `user/register` action with user's email, password, first name and last name. - -#### UserAccount - -**Methods** - -- `onLoggedIn` - sets `currentUser` and `userCompany`. This method is called on `user-after-loggedin` bus event -- `edit` - sets `isEdited` flag to `true` -- `objectsEqual (a, b, excludedFields = [])` - checks if two passed objects are equal to each other -- `updateProfile` - updates user profile with new data. Calls a method `exitSection(null, updatedProfile)` -- `exitSection` - emits `myAccount-before-updateUser` bus event with updated user profile. Resets component user data to default values. -- `getUserCompany` - finds user company -- `getCountryName` - finds user country name - -#### UserShippingDetails - -**Methods** - -- `onLoggedIn` - sets `currentUser` and `shippingDetails`. This method is called on `user-after-loggedin` bus event -- `edit` - sets `isEdited` flag to `true` -- `updateDetails` - updates shipping details with new data. Calls a method `updatedShippingDetails` -- `exitSection` - emits `myAccount-before-updateUser` bus event with updated shipping details. Resets component user data to default values -- `fillCompanyAddress` - finds shipping details -- `getCountryName` - finds country name -- `hasBillingAddres` - returns `true` if user has a billing address - -### Store - -User Store is designed to handle all actions related to the user account. -All user related data is stored in the original eCommerce CMS/Magento and the modifying actions are executed directly against the platform API. - -#### State - -```js - state: { - token: '', - current: null - }, -``` - -The user state data: - -- `token` - this is the current user token got from the [`user/login`](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L64). It's used to authorize all subsequent calls with the current user identity. If this token is not empty it does mean that the user is authorized. -- `current` - this is the current user object received from [`user/me`](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L105) - immediately called after the login action. - -The user data format: - -```json -{ - "code": 200, - "result": { - "id": 58, - "group_id": 1, - "default_billing": "62", - "default_shipping": "48", - "created_at": "2018-01-23 15:30:00", - "updated_at": "2018-03-04 06:39:28", - "created_in": "Default Store View", - "email": "pkarwatka28@example.pl", - "firstname": "Piotr", - "lastname": "Karwatka", - "store_id": 1, - "website_id": 1, - "addresses": [ - { - "id": 48, - "customer_id": 58, - "region": { - "region_code": null, - "region": null, - "region_id": 0 - }, - "region_id": 0, - "country_id": "PL", - "street": ["Street", "12"], - "telephone": "", - "postcode": "51-169", - "city": "City", - "firstname": "Piotr", - "lastname": "Karwatka", - "default_shipping": true - }, - { - "id": 62, - "customer_id": 58, - "region": { - "region_code": null, - "region": null, - "region_id": 0 - }, - "region_id": 0, - "country_id": "PL", - "street": ["Street", "12"], - "company": "example", - "telephone": "", - "postcode": "51-169", - "city": "City", - "firstname": "Piotr", - "lastname": "Karwatka", - "vat_id": "PL8951930748", - "default_billing": true - } - ], - "disable_auto_group_change": 0 - } -} -``` - -#### Events - -The following events are published from `user` store: - -- `EventBus.$emit('session-after-started')` - executed just [after the application has been loaded](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L22) and the User UI session has started -- `EventBus.$emit('user-after-loggedin', res)` - executed after the successful [`user/me` action call](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L123) - so the user has been authorized and the profile loaded - -#### Actions - -The user store provides the following public actions: - -##### `startSession (context)` - -Just to mark that the session is started and loading the current user token from the `localForage` - for the further usage. - -##### `resetPassword (context, { email })` - -Calls the `vue-storefront-api` endpoint to send the password reset link to specified `email` address - -##### `login (context, { username, password })` - -Called to login the user and receive the current token that can be used to authorize subsequent API calls. After user is successfully authorized the `user/me` action is dispatched to load the user profile data. - -##### `register (context, { email, firstname, lastname, password })` - -Registers the user account in the eCommerce platform / Magento. - -##### `me (context, { refresh = true, useCache = true })` - -Loads the user profile from eCommerce CMS; when `userCache` is set to true the result will be stored in the `localForage` and if it's stored before - returned from cache using the `fastest` strategy (network vs cache). If `refresh` is set to true - the user data will be pulled from the server despite the cached copy is available. - -##### `update (context, userData)` - -This action is used to update various user profile data. Please check the [user schema](https://github.com/vuestorefront/vue-storefront/blob/master/core/store/modules/user/userProfile.schema.json) for the data format details. - -##### `changePassword (context, passwordData)` - -Tries to change the user password to `passwordData.newPassword`. - -##### `logout (context)` - -This is used to log out the user, close the session and clear the user token. Please notice - the current shopping cart is closed after this call. - -#### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -const getters = { - isLoggedIn(state) { - return state.current !== null; - }, -}; -``` - - -## Checkout Module - -Checkout Module is designed to handle all logic related the checkout operations and UI. - -### Components - -#### CartSummary - -This component displays the cart summary information - -**Computed** - -- `totals` - mapped getter to show the cart totals - -#### OrderReview - -A summary of the current order - -**Props** - -- `isActive` - boolean, required prop - -**Methods** - -- `placeOrder` - checks if current user has an account. If not, will trigger a `register` method, otherwise will emit `checkout-before-placeOrder` bus event -- `register` - dispatches a `user/register` action to register a new user - -#### Payment - -A component to handle payment operations - -**Props** - -- `isActive` - boolean, required prop - -**Computed** - -- `currentUser` - the current user mapped from application state -- `paymentMethods` - available payment methods mapped from `payment/paymentMethods` getter - -**Methods** - -- `sendDataToCheckout` - emits `checkout-after-paymentDetails` bus event and sets `isFilled` to `true` -- `edit` - checks `isFilled` and if it's `true`, emits a `checkout-before-edit` bus event -- `hasBillingData` - checks if current user exists and if it has `default_billing_ property -- `initializeBillingAddress` - checks if current user exists and if it has `default_billing` property; if so, populates the `payment` data property with current user address data -- `useShippingAddress` - populates the `payment` data property with `$store.state.checkout.shippingDetails` -- `useBillingAddress` - populates the `payment` data property with `currentUser.addressess` -- `useGenerateInvoice` - negates the `generateInvoice` value and if it becomes `false`, will reset `this.payment.company` and `this.payment.taxId` -- `getCountryName` - gets the country name for the current payment by the country code -- `getPaymentMethod` - gets the payment method title for the current payment by the payment method code -- `notInMethods` - checks if passed method is present in `paymentMethods` -- `changePaymentMethod` - resets the additional payment method component container if exists and emits `checkout-payment-method-changed` bus event - -#### Personal Details - -User's personal details component - -**Props** - -- `isActive` - boolean, required prop -- `focusedField` - a string showing which field is focused - -**Computed** - -- `currentUser` - the current user mapped from application state - -**Methods** - -- `onLoggedIn` - populates `personalDetails` with data passed as a parameter -- `sendDataToCheckout` - performs a check if an account is already created and emits `checkout-after-personalDetails` bus event -- `edit` - emits `checkout-before-edit` bus event -- `gotoAccount` - shows a sign-up modal - -#### Product - -The component representing a product - -**Props** - -- `product` - current product - -**Computed** - -- `thumbnail` - returns a thumbnail for product image - -**Methods** - -- `onProductChanged` - checks `event.item.sku` and if it's equal to `product.sku`, the force update will be triggered - -#### Shipping - -Component handling all the shipping logic - -**Props** - -- `isActive` - boolean, required prop - -**Computed** - -- `currentUser` - the current user mapped from application state -- `shippingMethods` - available payment methods mapped from `payment/paymentMethods` getter -- `checkoutShippingDetails` - mapped from `state.checkout.shippingDetails` -- `paymentMethod` - mapped from `state.payment.methods` - -**Methods** - -- `onAfterShippingSet` - populates the `shipping` data property with a passed parameter -- `onAfterPersonalDetail` - checks `isFilled` data property and if it's false, dispatches `checkout/updatePropValue` with user's first and last names -- `sendDataToCheckout` - emits `checkout-after-shippingDetails` bus event; sets `isFilled` to `true` -- `edit` - is `isFilled` is true, emits `checkout-before-edit` bus event and sets `isFilled` to `false` -- `hasShippingDetails` - checks, if `currentUser` exists and has a property `default_shipping`; if so, populates `myAddressDetails` data property with `currentUser.addresses` -- `useMyAddress` - checks `shipToMyAddress`; if `true`, populates `shipping` data property with `myAddressDetails` -- `getShippingMethod` - gets the shipping method from `shippingMethods` data property -- `getCountryName` - gets country name with country code -- `changeCountry` - emits `checkout-before-shippingMethods` bus event -- `getCurrentShippingMethod` - calculates a current shipping method with shipping method code -- `changeShippingMethod` - if `getCurrentShippingMethod` exists, emits `checkout-after-shippingMethodChanged` bus event -- `notInMethods` - checks if passed method is present in `shippingMethods` - -### How to add a custom checkout step - -We now show an example of how to add a new step to the checkout page of Vue Storefront. - -The step is named `NewStep` and is placed just after the `PersonalDetails` step; changing the step's name and position requires small modifications to the procedure. - -#### First, create the NewStep component - -1. **Create the NewStep component** according to your needs. To do it quickly, make a copy of the `PersonalDetails` component, name it `NewStep` and customize it. - -2. **Customize the sendDataToCheckout method** of the `NewStep` component so that it emits the event `checkout-after-newStep`; for example: -```javascript - sendDataToCheckout () { - this.$bus.$emit('checkout-after-newStep', this.newStep, this.$v) - } -``` - -3. **Call the sendDataToCheckout method** when the button to the next section is clicked. This could be achieved in the template like this: -```vue - -``` - -#### Then, modify the checkout component - -1. **Insert the NewStep component in the checkout template** at the desired position. For example, you could place it between the Personal Details and Shipping steps: -```vue - - - - - -``` - -2. **Listen for the checkout-after-newStep event** by adding the following listener to the `beforeMount()` function: -```javascript - this.$bus.$on('checkout-after-newStep', this.onAfterNewStep) -``` - -3. **Specify how to jump from the previous step to NewStep**. Modify the `onAfterPersonalDetails()` method in order to activate the `newStep` section instead of the `shipping` step: -```javascript - onAfterPersonalDetails (receivedData, validationResult) { - this.personalDetails = receivedData - this.validationResults.personalDetails = validationResult - this.activateSection('newStep') // show the new step - this.savePersonalDetails() - this.focusedField = null - } -``` -This is assuming that the new checkout step follows the Personal Details step; if this is not the case, you will need to modify the `onAfter` metod of whatever step precedes `NewStep`. - -4. **Specify how to jump from NewStep to the next step** by creating the method `onAfterNewStep`; in this example, the next step is the shipping form: -```javascript - onAfterNewStep (receivedData, validationResult) { - this.newStep = receivedData - this.validationResults.newStep = validationResult - this.activateSection('shipping') // change 'shipping' to whatever you want the next step to be - this.saveNewStep() // include this line only if newStep has state - } -``` -Note that calling `activateSection('shipping')` is what ultimately shows the next checkout step to the user. - -5. **If needed, save NewStep state** by defining a non-empty method `saveNewStep()`; for example: -```javascript - saveNewStep () { - this.$store.dispatch('checkout/saveNewStep', this.newStep) - }, -``` -This is needed only if your new step has state, in which case you will also need to define the `checkout/saveNewStep` action in Vuex. - - -### Store - -The Checkout Store is designed to handle the passage from user's cart to actual order; it defines actions such as saving the information given by the user during checkout, and placing the order. - -#### State - -```js - state: { - order: {}, - personalDetails: { - firstName: '', - lastName: '', - emailAddress: '', - password: '', - createAccount: false - }, - shippingDetails: { - firstName: '', - lastName: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - shippingMethod: '' - }, - paymentDetails: { - firstName: '', - lastName: '', - company: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - taxId: '', - paymentMethod: '', - paymentMethodAdditional: {} - }, - isThankYouPage: false, - modifiedAt: 0 - } -``` - -The state of the Checkout module contains both the [Order object](https://github.com/vuestorefront/vue-storefront/blob/master/core/models/order.schema.json) and the information given by the user during the checkout process, to be stored for further use in the `localForage`. - -The state is modified by [`placeOrder`](https://github.com/vuestorefront/vue-storefront/blob/1793aaa7afc89b3f08e443f40dd5c6131dd477ba/core/store/modules/checkout/actions.js#L11) action and [`load`](https://github.com/vuestorefront/vue-storefront/blob/1793aaa7afc89b3f08e443f40dd5c6131dd477ba/core/store/modules/checkout/actions.js#L41) which loads the state from browser database. - -The category state data: - -- `order` - this is the last order to be placed, the [schema is defined](https://github.com/vuestorefront/vue-storefront/blob/master/core/models/order.schema.json) in Ajv compliant format -- `shippingDetails`, `paymentDetails` - the address information provided by the user during the [Checkout](https://github.com/vuestorefront/vue-storefront/blob/master/core/pages/Checkout.vue). - -#### Actions - -The cart store provides following public actions: - -##### `placeOrder (context, { order })` - -Action called by `Checkout.vue` to complete the order. Data object is validated against the [order schema](https://github.com/vuestorefront/vue-storefront/blob/master/core/models/order.schema.json), stored within the `localForage` collection by subseqent call of [`order/placeOrder`](https://github.com/vuestorefront/vue-storefront/blob/1793aaa7afc89b3f08e443f40dd5c6131dd477ba/core/store/modules/order/actions.js#L12) - -##### `savePersonalDetails ({ commit }, personalDetails)` - -Stores the personal Details (the format is exactly the same as this store `state.personalDetails`) for later use in the browser's storage - -##### `saveShippingDetails ({ commit }, shippingDetails)` - -Stores the shipping Details (the format is exactly the same as this store `state.shippingDetails`) for later use in the browser's storage - -##### `savePaymentDetails ({ commit }, paymentDetails)` - -Stores the payment Details (the format is exactly the same as this store `state.paymentDetails`) for later use in the browser's storage - -##### `load ({ commit })` - -Load the current state from the `localForage` - - -## Order module - -This module contains all the logic, components and store related to order operations. - -### Components - -#### UserOrder - -**Computed** - -- `ordersHistory` - maps the value from `state.user.orders_history.items` -- `isHistoryEmpty` - checks if `state.user.orders_history.items` array is empty - -**Methods** - -- `reorder (products)` - iterates through passed 'products' array, adding each item to cart -- `skipGrouped (items)` - filters passed 'items' array returning only items without `parent_id` - -#### UserSingleOrder - -**Computed** - -- `ordersHistory` - maps the value from `state.user.orders_history.items` -- `order` - finds the order in the `orderHistory` computed property with an id matching to route `orderId` parameter -- `paymentMethod` - returns `payment.additional_information[0]` from the `order` computed property -- `billingAddress` - returns `billing_address` from the `order` computed property -- `shippingAddress` - returns `extension_attributes.shipping_assignments[0].shipping.address` from the `order` computed property - -**Methods** - -- `remakeOrder (items)` - iterates through passed 'items' array, adding each item to cart as a single product -- `skipGrouped (items)` - filters passed 'items' array returning only items without `parent_id` - -### Store - -Order store is very simple, used just to pass the current order to the backend service. - -### Actions - -The order store provides following public actions: - -#### `placeOrder ({ commit }, order)` - -The order object is queued in the local, indexedDb `ordersCollection` to be sent to the server. -Please take a look at the [Working with data](../data/data.md) for the details about data formats and how does `localForage` is being used in this project. - - -## Catalog module - -Catalog module is a big one combining all the logic, components and store for attribute, category, product, stock and tax operations - -### Components - -### Store - -#### Attribute Store - -Attribute Store is designed to handle all actions related to the attributes management - -##### State - -```js - state: { - list_by_code: {}, - list_by_id: {}, - labels: {} - }, -``` - -As we're using the attributes dictionary for the product management in a very similar way Magento does ([EAV model](http://www.xpertdeveloper.com/2010/10/what-is-eav-model-in-magento/)) we're operating on the attributes, attribute types and dictionaries. - -Attributes are **explicitly** loaded by the user by calling the `attribute/list` method. For example, when you're going to work with customizable attributes of the product or to work on variants you need to prefetch the attributes metadata: - -```js -this.$store.dispatch('attribute/list', { - filterValues: [true], - filterField: 'is_user_defined', -}); -``` - -This is example from [product compare feature](https://github.com/vuestorefront/vue-storefront/blob/c954b96f6633a201e10bed1d2e4c0def1aeb3071/core/pages/Compare.vue). - -The attribute state data: - -- `list_by_code` - this is a dictionary where you can get the specific attribute by just accessing the `list_by_code['color']` etc. -- `list_by_id` - this is a dictionary where you can get the specific attribute by just accessing the `list_by_id[123]` etc. -- `labels` - the preloaded labels of attribute values (the V in EAV) - -##### Actions - -The attribute store provides following public actions: - -**`list (context, { filterValues = null, filterField = 'attribute_code', size = 150, start = 0 })`** - -This method is used to load the attributes metadata. `filterValues` is an array of multiple values like: `['color', 'size']` and the `filterField` is the attribute field to compare the `filterValues` against. Usually is a `attribute_code` or `attribute_id`. The `size` and `start` are just used to limit the list. - -##### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -export default { - attributeListByCode: state => state.list_by_code, - attributeListById: state => state.list_by_id, -}; -``` - -#### Category Store - -Category Store is designed to handle all actions related the categories data. - -This module works pretty tightly with Elastic Search and operates on the [Product data format](../data/elasticsearch.md) - -##### State - -```js -const state = { - list: [], - current: {}, - filters: { color: [], size: [], price: [] }, - breadcrumbs: { routes: [] }, - current_path: [], // list of categories from root to current -}; -``` - -Category state is generally populated by just two methods [list](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L38) and [single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L28) - -:::tip Note -The action `category/single` uses `localForage` cache only - no ElasticSearch data store directly; because of this optimization, please do download the categories list by dispatching `category/list` at first. -::: - -The category state data: - -- `breadcrumbs` - this is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - this is the current category object, -- `filters` is a current state of the category filters - dictionary of selected variant attributes; for example it contains dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, that we're using the Magento like EAV attributes structure - so the values here are an attribute value indexes not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference - -- `current_path` - this is the list of category objects: from current category to the top level root, - -##### Events - -The following events are published from `category` store: - -- `EventBus.$emit('category-after-single', { category: mainCategory })` - from [category/single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) after single category is loaded, -- `EventBus.$emit('category-after-current', { category: category })` - after current category has been changed - this is subsequent call of `category/single` action, -- `EventBus.$emit('category-after-reset', { })` - after category has been reset (for example in the process of moving from one category page to another) -- `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - this event emits the current category list as it's returned by `category/list`. - -##### Actions - -The cart store provides following public actions: - -**`list (context, { parent = null, onlyActive = true, onlyNotEmpty = false, size = 4000, start = 0, sort = 'position:asc' })`** - -This is the key method to load the category list. It returns the `Promise` that contains the product list object. This method should be used everywhere you need to get products data. - -**`single (context, { key, value, setCurrentCategory = true, setCurrentCategoryPath = true })`** - -This method gets the single category from `localForage`. - -:::warning Important -To make this method work you should call `category/list` before. This category works only on localFotage and cannot access ElasticSearch directly -::: - -:::warning Important -This method synchronizes products for offline usage by: storing the whole query results object into `localForage` and by caching each category individually (to be used on the Product page for example) -::: - -This method emits category list as `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - -- `parent` - `category` object to load the subcategories only - -- `start`, `size` - both parameters are used for paging; start is the starting index; size is a page size - -- `onlyActive` - (bool) load only the categories marked as active in CMS (for example in Magento) - -- `sort` - category attribute using to sort, this field must be mapped in ElasticSearch as a numeric field - -- `onlyNotEmpty` - (bool) load only the categories that contain any products - -##### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -const getters = { - current: state => state.current, - list: state => state.list, -}; -``` - -#### Product Store - -Product Store is designed to handle all actions related the product data. It's responsible for loading the list of products or a single product as well as configuring the configurable products and managing the products attachments. - -This module works pretty tightly with Elastic Search and operates on the [Product data format](../data/elasticsearch.md) - -##### State - -```js -const state = { - breadcrumbs: { routes: [] }, - current: null, // shown product - current_options: { color: [], size: [] }, - current_configuration: {}, - parent: null, - list: [], - original: null, // default, not configured product - related: {}, -}; -``` - -Product state is generally populated by just two methods [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) and [single](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L428) and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L215) - -The product state data: - -- `breadcrumbs` - this is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - this is the product object with selected `configurable_children` variant - so it's the base product with attributes overridden by the values from selected `configurable_children` variant; it's used on [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/pages/Product.vue#L203) this is the product which is added to the cart after "Add to cart" -- `current_options` - it's a list used to populate the variant selector on the [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/themes/default/pages/Product.vue#L56) it contains dictionary of attributes x possible attribute values and labels and it's populated by [setupVariants](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L344) based on the `configurable_children` property -- `current_configuration` is a current product configuration - dictionary of selected variant attributes; for example it contains dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, that we're using the Magento like EAV attributes structure - so the values here are an attribute value indexes not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference - -- `parent` - if the current product is a `type_id="single"` then in this variable the parent, `configurable` product is stored. This data is populated only on `Product.vue` by [checkConfigurableParent](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L323) -- `list` - this is an Array of products loaded by [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) -- `original` - used only for `configurable` products; this is the base product with no variant selected -- `related` - this is dictionary of related products; set outside this store (for [example here](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/blocks/Product/Related.vue)) by calling and [related action](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L528) - -##### Events - -The following events are published from `product` store: - -- `EventBus.$emit('product-after-priceupdate', product)` - from [syncProductPrice](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L33) after product price is synced with Magento; -- `EventBus.$emit('product-after-configure', { product: product, configuration: configuration, selectedVariant: selectedVariant })` from `configureProductAsync` (called by `product/configure` action after `product/single`). This event provides the information about selected product variant on the product page -- `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, result: resp })` - this event emits the current product list as it's returned by `product/list` providing the current filters etc. You can mark specific product list identifier by setting `meta` property; it's important because on single page this event can be executed multiple time for each individual block of products -- `EventBus.$emit('product-after-single', { key: key, options: options, product: cachedProduct })` - after single product has been loaded (invoked by `product/single` action) -- `EventBus.$emit('product-after-related', { key: key, items: items })` - invoked whenever the related products block is set for the current product; the key is the name of the related block and items are related products -- `EventBus.$emit('product-after-original', { original: product })` - invoked by `product/single` whenever product has been loaded -- `EventBus.$emit('product-after-parent', { parent: product })` - invoked externally by `product/checkConfigurableParent` provides the current single product configurable parent -- `EventBus.$emit('product-after-reset', { })` - after product has been reseted (for example in the process of moving from one product page to another) - -##### Actions - -The product store provides following public actions: - -**`setupBreadcrumbs (context, { product })`** - -This method is in charge of setting `state.breadcrumbs` to be used on `Product.vue` page. It's called from `Product.vue:fetchData`. The `product` parameter is a [ElasticSearch product object](../data/elasticsearch.md) - -**`syncPlatformPricesOver(context, { skus })`** - -When the config option `products.alwaysSyncPlatformPricesOver` is on, Vue Storefront will request the current product prices each time when `product/single` or `product/list` action is dispatched. It's called exclusively by these actions and shouldn't be called manually. This method calls `vue-storefront-api` proxy to get the current prices from Magento or any other backend CMS. - -`skus` - this is an Array with product SKU's to be synchronized - -**`setupAssociated (context, { product })`** - -This method is called as a subsequent call of `Product.vue:fetchData` or `product/list` action. It's used to get the child products of `grouped` or `bundle` types of products. - -**`checkConfigurableParent (context, {product})`** - -This method is called by `Product.vue:fetchData` to check if current, simple product has got an configurable parent. If so the redirect is being made to the parent product. It's a fix for [#508](https://github.com/vuestorefront/vue-storefront/issues/508) - -**`setupVariants (context, { product })`** - -This method is subsequently called by `Product.vue:fetchData` to load all configurable attributes defined in `product.configurable_options` and then to populate `state.current_configuration` and `state.current_options`. The main usage of this action is to prepare product to be configured by the user on the product page and to display the product configurator UI properly - -**`list (context, { query, start = 0, size = 50, entityType = 'product', sort = '', cacheByKey = 'sku', prefetchGroupProducts = true, updateState = true, meta = {} })`** - -This is the key method to load the product list. It returns the `Promise` that contains the product list object. This method should be used everywhere you need to get products data. When `config.tax.calculateServerSide=false` this method runs product taxes calculator and synchronizes prices with Magento if it's required. - -This method emits product list as `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, meta: meta, result: resp })` - -:::warning Important -This method synchronizes products for offline usage by: storing the whole query results object into `localForage` and by caching each product individually (to be used on the Product page for example) -::: - -- `query` - this is the `bodybuilder` ElasticSearch query (please check `bodybuilder` package or for example `Home.vue` for a reference how to use it) - -- `start`, `size` - both parameters are used for paging; start is the starting index; size is a page size - -- `entityType` - by default it's of course set to `product` and it's mapped to ElasticSearch entity class - -- `sort` - product attribute using to sort, this field must be mapped in ElasticSearch as a numeric field - -- `prefetchGroupProducts` - by default it's set to true and causes `setupAssociated` action to be dispatched to get all the associated products - -- `updateState` - if you set this to false, the `state.list` will not be updated - just the products will be returned - -- `meta` - this is an optional attribute which is returned with `product-after-list` event; it can be used for example to mark any specific ES call. - -**`single (context, { options, setCurrentProduct = true, selectDefaultVariant = true, key = 'sku' })`** - -This method subsequently dispatched `product/list` action to get the products and synchronize the taxes/prices. When the product has been recently downloaded via `product/list` this method will return the cached version from `localForage` - but update the cache anyway. - -**`configure (context, { product = null, configuration, selectDefaultVariant = true })`** - -This action is used to configure the `configurable` product with specified attributes. It gets the `configuration` object which should have the following format: `{ attribute_code: attribute_value_id }` and finds the `product.configurable_children` item which complies to this configuration. Then it merges this specific `configurable_child` with product itself - for example setting the product.price to the configurable price, color, size etc. The method is used on: `Product.vue` page for allowing user to select color, size etc. The second usage for it is on `Category.vue` page - after user selects some filters, the resulting products are configured to display the proper images (related to selected color and size) and prices. - -If `selectDefaultVariant` is set to true (default), the `state.current` will be altered with configured product. - -**`setCurrent (context, productVariant)`** - -Auxiliary method just to set `state.current` to productVariant - -**`setOriginal (context, originalProduct)`** - -Auxiliary method just to set `state.original` to originalProduct - -**`related (context, { key = 'related-products', items })`** - -Alters `state.related` dictionary to set specific list of related products to be displayed on `Product.vue` page (`RelatedProducts` component is used for this) - -##### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -const getters = { - productParent: state => state.parent, - productCurrent: state => state.current, - currentConfiguration: state => state.current_configuration, - productOriginal: state => state.original, - currentOptions: state => state.current_options, - breadcrumbs: state => state.breadcrumbs, -}; -``` - -#### Stock Store - -Stock Store is designed to handle stock quantity checks. - -##### Events - -The following events are published from `stock` store: - -- `stock-after-check` - emitted just after the stock item has been received from eCommerce backend / Magento - -##### Actions - -The cart store provides following public actions: - -**`check (context, { product, qty = 1 })`** - -Check if the `product` can be added to the shopping cart with a given quantity. - -The resulting promise is expanded to the following object: - -```js -{ - qty: 100, - status: 'ok', // another option is: 'out_of_stock' - onlineCheckTaskId: 14241 -} -``` - -#### Tax Store - -### Helpers - -#### optionLabel - -Used to get the Label for specific `optionId`. For example, when the user filters products and uses the 165 attribute_value we can call `optionLabel( { attributeKey: 'color', optionId: 165 })` to get back 'Red' label. - diff --git a/docs/guide/archives/modules/introduction.md b/docs/guide/archives/modules/introduction.md deleted file mode 100644 index 8931de0618..0000000000 --- a/docs/guide/archives/modules/introduction.md +++ /dev/null @@ -1,33 +0,0 @@ -# Introduction -## Table of contents - -**Introduction and motivation** -- [What are VS Modules](#what-are-vs-modules) -- [Motivation](#motivation) -- [What is the purpose of VS modules?](#what-is-the-purpose-of-vs-modules) - -**Technical part** -- [Module config and its capabilities](#module-config-and-capabilities) -- [Module file structure](#module-file-structure) -- [Module registration](#module-registration) - -**Patterns and good practices for common use cases** -- [General rules and good practices](#general-rules-and-good-practices) -- [Adding new features as VS modules](#adding-new-features-as-vs-modules) -- [Extending and overriding Vue Storefront modules](#extending-and-overriding-vue-storefront-modules) -- [Creating third party modules](#Creating-3rd-party-modules) - - -## What are VS modules? - -You can think about each module as a one, independent feature available in Vue Storefront with all its logic and dependencies inside. This *one feature* however is a common denominator that links all the features inside. For example, the common denominator for adding a product to the cart, receiving a list of items that is in the cart or applying a cart coupon is obviously a `cart` and `cart` is not a feature of anything bigger than itself (its common denominator is the shop) so it should be a module. Wishlist, Reviews or Newsletter are also good examples of the module as we intuitively think about them as standalone features. - -## Motivation - -I believe that an obvious metaphor can clearly describe the problem, at the same time, the solution. - -To better illustrate the whole concept I'll try to explain it with lego bricks. - -Let's say we have a box with 90 lego bricks that we can use to build some fancy things like Towers, Castles, or Helicopters. Unfortunately due to some stupid EU regulations we can only have 3 different colors of bricks in our box. As we all know, not every color is accurate for every structure that can be built so we need to swap one color with another in a shop from time to time in order to have bricks in colors that are best-suited for our next lego project. - -Cool, but there is one problem - since we have all our bricks in one box they look more or less as follows : diff --git a/docs/guide/archives/typescript.md b/docs/guide/archives/typescript.md deleted file mode 100644 index 341b73e735..0000000000 --- a/docs/guide/archives/typescript.md +++ /dev/null @@ -1,37 +0,0 @@ -# TypeScript Action Plan - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.12` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - - -We've started adding TypeScript support to Vue Storefront, mostly because of the following reasons: - -- Developer convenience (IntelliSense support in the IDEs) -- Types safety and code-testability -- Making Vue Storefront code base easier to understand for newcomers. - -## Desired state - -TypeScript is for internal implementation only. It does NOT affect ES users, but should improve TS integration for TS users. -The desired state is that Vue Storefront Core outputs JS libraries, it's written using some TypeScript features, but all the user code (themes, extensions) is still JavaScript. No TypeScript experience is required to build Vue Storefront stores. **This is just for core developers and transparent to the end users.** - -Therefore we're only refactoring: -- core/api -- core/store -- core/lib - -where it makes sense. The key TypeScript feature we feel is usable are data types. - -We're in the middle of [refactoring `core/components` to `core/api` modules](https://github.com/vuestorefront/vue-storefront/issues/1213). All the modules should be created using TypeScript - -### The Action Plan: - -1. Introduce types - move _.js modules to _.ts modules incrementally without breaking changes. -2. Use types when it's appropriate in your newly written modules and new features. -3. One Vuex module or just a few components refactored within one release (once a month) is fine. -4. All `core/api` modules should be created using TypeScript. -5. All new modules and Vuex stores should be created using TypeScript. -6. **For now, please don't refactor the existing UI layer (components, pages) to use TypeScript. We should focus on Vuex, core libraries, and APIs at first to not introduce chaos into theme development.** -7. We should put the types/interfaces inside `core/store/types` for all Entity/Data related models or in `core/types` for some shared ones—for example: `Product.ts` should be placed in `core/store/types/product/Product.ts` -8. We should use a minimal possible set of interfaces. Try to introduce one or two interfaces per entity (e.g. Product shouldn't be represented by more than two interfaces). diff --git a/docs/guide/archives/vuex.md b/docs/guide/archives/vuex.md deleted file mode 100644 index ae7f9eeb0b..0000000000 --- a/docs/guide/archives/vuex.md +++ /dev/null @@ -1,540 +0,0 @@ -# Vuex - -:::danger REMINDER -This document is _archived_ and _NOT_ relevant with the latest version which is `1.11` at the time of writing. Please keep in mind this document is supposed to help you maintain legacy product, not the fresh installation. -::: - -## Introduction - -All data processing and remote requests should be managed by Vuex data stores. The core modules generally contain `store` folder inside. -You can modify the existing store actions by responding to events. Events are specified in the docs below and can be found in the [core module](https://github.com/vuestorefront/vue-storefront/tree/master/core), where `EventBus.$emit` has been mostly used for Vuex Actions. - -**You should put all the REST calls, Elasticsearch data queries inside the Vuex Actions.** This is our default design pattern for managing the data. - -### Vuex conventions - -Before you start working with Vuex, it's recommended to get familiar with our [vuex conventions](./vuex-conventions.md) - -### Vuex modules - -- [Product](product-store.md) -- [Category](category-store.md) -- [Cart](Cart%20Store.md) -- [Checkout](Checkout%20Store.md) -- [Order](Order%20Store.md) -- [Stock](stock-store.md) -- [Sync](sync-store.md) -- [User](User%20Store.md) -- [Attribute](attribute-store.md) -- [UI Store]() - -### Override existing core modules - -Existing core modules can be overridden in the themes store. Just import any core store modules and override them using the `extendStore()` utility method like the example given below in `src/modules/ui-store/index.ts`. - -``` -import coreStore from '@vue-storefront/core/store/modules/ui-store' -import { extendStore } from '@vue-storefront/core/lib/themes' - -const state = { - // override state of core ui module... -} - -const mutations = { - // override mutations of core ui module... -} - -const actions = { - // override actions of core ui module... -} - -export default extendStore(coreStore, { - state, - mutations, - actions -}) -``` - -And then import it in `src/modules/index.ts` - -``` -import ui from './ui-store' - -export default { - ui -} -``` - -### Related - -[Working with data](data.md) - - - -## Vuex conventions - -### Module -The Vuex module should be created for a specific set of functionalities. It should also have only absolutely necessary dependencies to other modules. The name of module should be short, quite clear about it’s destination, and have words separated by a dash. - -Good examples: - -- products -- product -- user -- checkout -- compare-products -- notifications -- order - -Bad examples: - -- next-module -- compare (because it’s not saying what it compares) - -### State -State properties should be simple and their structure should not be nested. Their names are written in underscore-case notation and indicate what they contain. We should avoid having more than one instance of an object, even between modules. In the vast majority of cases, they can be referenced by their unique ID property. Example: - -``` -{ - "products_map": { - "WS08": { - "sku": "WS08", - "name": "Minerva LumaTech™ V-Tee" - // other options - }, - "WS12": { - "sku": "WS12", - "name": "Radiant Tee" - // other options - }, - "WS08-XS-Black": { - "sku": "WS08-XS-Black", - "name": "Minerva LumaTech™ V-Tee" - // other options - } - // maaaaaaaany more products - }, - "current_product_id": "WS08-XS-Black", - "wishlist": ["MP01-32-Black", "MSH05-32-Black"], - "cart_items": [ - { - "sku": "WH09-XS-Green", - "qty": 3 - }, - { - "sku": "WH09-S-Red", - "qty": 1 - } - ] -} -``` - -Good examples: - - - categories_map -- current_category_id -- order -- product_parent_id - -Bad examples -- list -- elements - -``` -filters: { - available: {}, - chosen: {} -}, -``` - -### Getters -The Vuex state, except of mutations, should always be accessed by getters, including actions. Getter should: - -* Start from `is` when returns Boolean, or `get` otherwise -* Answer to question `what am I returning?` -* Contain module name to ensure that getter is unique through whole Vuex, but it doesn’t have to start with that name. First, it should have a natural name, so for example we have module `category` and in the state `availableFilters`. So `what am I returning?` -> `available Filters` and these filters are `category filters`. It's not a Boolean, it’s an array or map so we’re starting with `get` -> `getAvailableCategoryFilters` - -Good examples: - -- For state user -> isUserLoggedIn, getUser -- For state availableFilters -> getAvailableCategoryFilters -- For state currentProductId -> getCurrentProduct (because it gets product object from map), getCurrentProductId - -Bad examples: - -- totals -- product -- current -- list - -### Actions - -Every state change from outside of a module should be invoked as an action. Actions are meant to: - -- Fetch something from the server(or cache) — in this case, they have to be asynchronous (return promise). -- Mutate state of current module. -- Dispatch actions from the same module (to avoid repeating logic). -- Dispatch actions from another module (only if it’s absolutely required). -- Their names should be as unique as possible and simply describe what specific action will happen. **Almost every action should return promise.** We allow you to replicate conventions for existing methods like list or single in new modules to have a consistent API. - -Good examples: - -- fetchProduct - Gets product by ID from server or cache, sets it in products map, and returns it by getter. -- findProducts - Fetches products by specific query, sets them in products map, and returns them as array. -- setCurrentProduct - Param could be ID, it could dispatch fetchProduct, mutate it to productsMap, and mutate its ID to currentProductId. Also if productId is null, then it removes currentProduct. -- addCartItem -- toggleMicrocart - -Bad examples: - -- products -- reset - -### Mutations - -Finally we have mutations. Only mutations can change the state of the module. They should be synchronous (never return promise), not contain any logic (be extremely fast), except one needed to keep the state as it should be (for example, sets default value for state). Mutations should be invoked only by actions from the same module. In most cases, it should only be a single action that invokes a specific mutation. Types of mutations: - -- SET_ - The most common type of mutation. It can set an object (or whole array), set default value of object (or maybe clean array), -- ADD_ - It can add a new element to the state property, which is an array or add new element to map. -- REMOVE_ - An opposite to ADD. It can remove the map element or array element by index (or by finding object, which is not recommended on big arrays, as mutation could be slow). - -Good examples: - -- ADD_PRODUCT -- SET_CURRENT_PRODUCT_ID -- ADD_CATEGORY_FILTER -- REMOVE_WISHLIST_PRODUCT_ID - -Bad examples: - -- CATEGORY_UPD_CURRENT_CATEGORY -- TAX_UPDATE_RULES - - - -## Product Vuex Store - -The Product Store is designed to handle all actions related product data. It's responsible for loading the list of products or a single product as well as configuring the configurable products and managing the product attachments. - -This module works pretty tightly with Elasticsearch and operates on the [Product data format](../data/elasticsearch.md) - -### State - -```js -const state = { - breadcrumbs: { routes: [] }, - current: null, // shown product - current_options: { color: [], size: [] }, - current_configuration: {}, - parent: null, - list: [], - original: null, // default, not configured product - related: {}, -}; -``` - -The product state is generally populated by just two methods - [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) and [single](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L428) - and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L215). - -The product state data: - -- `breadcrumbs` - This is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - This is the product object with selected `configurable_children` variant, so it's the base product with attributes overridden by the values from selected `configurable_children` variant; it's used on [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/pages/Product.vue#L203). This is the product which is added to the cart after "Add to cart" -- `current_options` - The list used to populate the variant selector on the [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/themes/default/pages/Product.vue#L56). It contains dictionary of attributes and possible attribute values and labels, and it's populated by [setupVariants](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L344) based on the `configurable_children` property. -- `current_configuration` The current product configuration. A dictionary of selected variant attributes; for example, it contains a dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, we're using the Magento-like EAV attributes structure so the values here are attribute value indexes, not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference. - -- `parent` - If the current product is a `type_id="single"`, then in this variable the parent, `configurable` product is stored. This data is populated only on `Product.vue` by [checkConfigurableParent](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L323) -- `list` - This is an array of products loaded by [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) -- `original` - Used only for `configurable` products, this is the base product with no selected variant. -- `related` - This is dictionary of related products; set outside this store (for [example here](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/blocks/Product/Related.vue)) by calling and [related action](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L528) - -### Events - -The following events are published from `product` store: - -- `EventBus.$emit('product-after-priceupdate', product)` - from [syncProductPrice](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L33) after product price is synced with Magento. -- `EventBus.$emit('product-after-configure', { product: product, configuration: configuration, selectedVariant: selectedVariant })` from `configureProductAsync` (called by `product/configure` action after `product/single`). This event provides the information about selected product variant on the product page. -- `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, result: resp })` - this event emits the current product list as it's returned by `product/list`providing the current filters, etc. You can mark the specific product list identifier by setting the `meta` property; it's important because on a single page, this event can be executed multiple time for each individual block of products. -- `EventBus.$emit('product-after-single', { key: key, options: options, product: cachedProduct })` - After single product has been loaded (invoked by `product/single` action). - -### Actions - -The product store provides following public actions: - -#### `setupBreadcrumbs (context, { product })` - -This method is in charge of setting `state.breadcrumbs` to be used on the `Product.vue` page. It's called from `Product.vue:fetchData`. The `product` parameter is an [Elasticsearch product object](../data/elasticsearch.md). - -#### `syncPlatformPricesOver(context, { skus })` - -When the config option `products.alwaysSyncPlatformPricesOver` is on, Vue Storefront will request the current product prices each time when `product/single` or `product/list` action is dispatched. It's called exclusively by these actions and shouldn't be called manually. This method calls `vue-storefront-api` proxy to get the current prices from Magento or any other backend CMS. - -`skus` - this is an array with product SKUs to be synchronized. - -#### `setupAssociated (context, { product })` - -This method is called as a subsequent call of `Product.vue:fetchData` or `product/list` action. It's used to get the child products of `grouped` or `bundle` types of products. - -#### `checkConfigurableParent (context, {product})` - -This method is called by `Product.vue:fetchData` to check if current, simple product has got an configurable parent. If so, the redirect is being made to the parent product. It's a fix for [#508](https://github.com/vuestorefront/vue-storefront/issues/508) - -#### `setupVariants (context, { product })` - -This method is subsequently called by `Product.vue:fetchData` to load all configurable attributes defined in `product.configurable_options` and then to populate `state.current_configuration` and `state.current_options`. The main usage of this action is to prepare a product to be configured by the user on the product page and to display the product configurator UI properly. - -#### `list (context, { query, start = 0, size = 50, entityType = 'product', sort = '', cacheByKey = 'sku', prefetchGroupProducts = true, updateState = true, meta = {} })` - -This is the key method to load the product list. It returns the Promise that contains the product list object. This method should be used everywhere you need to get product data. When `config.tax.calculateServerSide=false` this method runs product taxes calculator and synchronizes prices with Magento if it's required. - -**Events**: this method emits product list as `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, meta: meta, result: resp })` - -:::warning Important -This method synchronizes products for offline usage by storing the whole query results object into `localForage` and by caching each product individually (to be used on the product page, for example). -::: - -- `query` - This is the `bodybuilder` Elasticsearch query (please check `bodybuilder` package or for example `Home.vue` for a reference how to use it). - -- `start`, `size` - Both parameters are used for paging; start is the starting index; size is a page size. - -- `entityType` - By default it's of course set to `product` and it's mapped to Elasticsearch entity class. - -- `sort` - Product attribute using to sort. This field must be mapped in Elasticsearch as a numeric field, - -- `prefetchGroupProducts` - By default, it's set to true and causes the `setupAssociated` action to be dispatched to get all the associated products - -- `updateState` - If you set this to false, the `state.list` will not be updated, just the products will be returned. - -- `meta` - This is an optional attribute which is returned with the `product-after-list` event; it can be used for example to mark any specific ES call. - -#### `single (context, { options, setCurrentProduct = true, selectDefaultVariant = true, key = 'sku' })` - -This method subsequently dispatches the `product/list` action to get the products and synchronize the taxes/prices. When the product has been recently downloaded via `product/list` this method will return the cached version from `localForage`, but update the cache anyway. - -#### `configure (context, { product = null, configuration, selectDefaultVariant = true })` - -This action is used to configure the `configurable` product with specified attributes. It gets the `configuration` object, which should have the following format: `{ attribute_code: attribute_value_id }` and finds the `product.configurable_children` item which complies with this configuration. Then, it merges this specific `configurable_child` with the product itself - for example, setting the product.price to the configurable price, color, size etc. This method is used on: `Product.vue` page for allowing user to select color, size etc. The second usage for it is on `Category.vue` page after user selects some filters - the resulting products are configured to display the proper images (related to selected color and size) and prices. - -If `selectDefaultVariant` is set to true (default), the `state.current` will be altered with configured product. - -#### `setCurrent (context, productVariant)` - -Auxiliary method just to set `state.current` to productVariant. - -#### `setOriginal (context, originalProduct)` - -Auxiliary method just to set `state.original` to originalProduct. - -#### `related (context, { key = 'related-products', items })` - -Alters `state.related` dictionary to set specific list of related products to be displayed on `Product.vue` page (`RelatedProducts` component is used for this). - -### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats. - -```js -const getters = { - getParentProduct: state => state.parent, - getCurrentProduct: state => state.current, - getCurrentProductConfiguration: state => state.current_configuration, - getOriginalProduct: state => state.original, - getCurrentProductOptions: state => state.current_options, - breadcrumbs: state => state.breadcrumbs, -}; -``` - - - -## Category Vuex Store - -The Category Store is designed to handle all actions related to category data. - -This module works pretty tightly with Elasticsearch and operates on the [Product data format](../data/elasticsearch.md) - -### State - -```js -const state = { - list: [], - current: {}, - filters: { color: [], size: [], price: [] }, - breadcrumbs: { routes: [] }, - current_path: [], // list of categories from root to current -}; -``` - -The category state is generally populated by just two methods- [list](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L38) and [single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) - and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L28) - -:::tip Note -The action `category/single` uses `localForage` cache only—no Elasticsearch data store directly. Because of this optimization, download the categories list by dispatching the `category/list` at first. -::: - -The category state data: - -- `breadcrumbs` - This is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - This is the current category object. -- `filters` is a current state of the category filters, a dictionary of selected variant attributes; for example it contains dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, we're using the Magento-like EAV attributes structure so the values here are attribute value indexes, not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference - -- `current_path` - this is the list of category objects: from current category to the top level root, - -### Events - -The following events are published from `category` store: - -- `EventBus.$emit('category-after-single', { category: mainCategory })` - from [category/single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) after single category is loaded. -- `EventBus.$emit('category-after-current', { category: category })` - After current category has been changed - this is subsequent call of `category/single` action. -- `EventBus.$emit('category-after-reset', { })` - After the category has been reset (for example, in the process of moving from one category page to another). -- `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - This event emits the current category list as it's returned by `category/list`. - -### Actions - -The cart store provides following public actions: - -#### `list (context, { parent = null, onlyActive = true, onlyNotEmpty = false, size = 4000, start = 0, sort = 'position:asc' })` - -This is the key method to load the category list. It returns the Promise that contains the product list object. This method should be used everywhere you need to get products data. - -#### `single (context, { key, value, setCurrentCategory = true, setCurrentCategoryPath = true })` - -This method gets the single category from `localForage`. - -:::warning Important -To make this method work, you should call `category/list` before. This category works only on localForage and cannot access Elasticsearch directly -::: - -:::warning Important -This method synchronizes products for offline usage by storing the whole query results object into `localForage` and by caching each category individually (to be used on the product page, for example). -::: - -**Events**: this method emits category list as `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - -- `parent` - `category` - Object to load the subcategories only. - -- `start`, `size` - Both parameters are used for paging; start is the starting index; size is a page size. - -- `onlyActive` - (bool) load only the categories marked as active in CMS (for example, in Magento). - -- `sort` - Category attribute used to sort. This field must be mapped in Elasticsearch as a numeric field. - -- `onlyNotEmpty` - (bool) load only the categories that contain any products. - -### Getters - -All state members should be accessed only by getters. Please take a look at the state reference for data formats. - -```js -const getters = { - current: state => state.current, - list: state => state.list, -}; -``` - - - -## Stock Vuex Store - -Stock Store is designed to handle stock-quantity checks. - -### Events - -The following events are published from `stock` store: - -- `stock-after-check` - Emitted just after the stock item has been received from eCommerce backend / Magento. - - -### Actions - -The cart store provides the following public actions: - -#### `check (context, { product, qty = 1 })` - -Check if the `product` can be added to the shopping cart with a given quantity. - -The resulting promise is expanded to the following object: - -```js -{ - qty: 100, - status: 'ok', // another option is: 'out_of_stock' - onlineCheckTaskId: 14241 -} -``` - - -## Attribute Vuex Store - -Attribute Store is designed to handle all actions related to attributes management. - -### State - -```js - state: { - list_by_code: {}, - list_by_id: {}, - labels: {} - }, -``` - -As we're using the attributes dictionary for the product management in a very similar way as Magento ([EAV model](http://www.xpertdeveloper.com/2010/10/what-is-eav-model-in-magento/)), we're operating on the attributes, attribute types, and dictionaries. - -Attributes are **explicitly** loaded by the user by calling the `attribute/list` method. For example, when you're going to work with customizable attributes of the product, or to work on variants, you need to prefetch the attributes metadata: - -```js -this.$store.dispatch('attribute/list', { - filterValues: [true], - filterField: 'is_user_defined', -}); -``` - -This is an example from [product compare feature](https://github.com/vuestorefront/vue-storefront/blob/c954b96f6633a201e10bed1d2e4c0def1aeb3071/core/pages/Compare.vue). - -The attribute state data: - -- `list_by_code` - This is a dictionary where you can get the specific attribute just by accessing the `list_by_code['color']` etc. -- `list_by_id` - This is a dictionary where you can get the specific attribute just by accessing the `list_by_id[123]` etc. -- `labels` - The preloaded labels of attribute values (the V in EAV). - -### Actions - -The attribute store provides the following public actions: - -#### `list (context, { filterValues = null, filterField = 'attribute_code', size = 150, start = 0 })`` - -This method is used to load the attributes metadata. `filterValues` is an array of multiple values like: `['color', 'size']` and the `filterField` is the attribute field to compare the `filterValues` against. Usually, it is a `attribute_code` or `attribute_id`. The `size` and `start` are just used to limit the list. - -### Helpers - -Attribute module exports one very popular helper method: - -#### `export function optionLabel (state, { attributeKey, searchBy = 'code', optionId })` - -This is used to get the label for specific `optionId`. For example, when the user filters products and uses the 165 attribute_value we can call `optionLabel( { attributeKey: 'color', optionId: 165 })` to get back 'Red' label. - -### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -export default { - attributeListByCode: state => state.list_by_code, - attributeListById: state => state.list_by_id, -}; -``` diff --git a/docs/guide/basics/assets/release-cycle-1.png b/docs/guide/basics/assets/release-cycle-1.png deleted file mode 100644 index eefa1884d4..0000000000 Binary files a/docs/guide/basics/assets/release-cycle-1.png and /dev/null differ diff --git a/docs/guide/basics/assets/release-cycle-2.png b/docs/guide/basics/assets/release-cycle-2.png deleted file mode 100644 index 2b7690798e..0000000000 Binary files a/docs/guide/basics/assets/release-cycle-2.png and /dev/null differ diff --git a/docs/guide/basics/assets/release-cycle-3.png b/docs/guide/basics/assets/release-cycle-3.png deleted file mode 100644 index 0ea5f6cd45..0000000000 Binary files a/docs/guide/basics/assets/release-cycle-3.png and /dev/null differ diff --git a/docs/guide/basics/configuration.md b/docs/guide/basics/configuration.md deleted file mode 100644 index c864faf4d7..0000000000 --- a/docs/guide/basics/configuration.md +++ /dev/null @@ -1,854 +0,0 @@ -# Configuration file explained - -The Vue Storefront application uses the [node-config](https://github.com/lorenwest/node-config) npm module to manage configuration files. Configuration is stored in the `/config` directory within two JSON files: - -- `default.json` is a configuration file provided along with the core Vue Storefront code and updated with any new release of Vue Storefront. It contains the default values only and therefore it shouldn't be modified within your specific Vue Storefront instance. - -- `local.json` is the second configuration file which is .gitignore'd from the repository. This is the place where you should store all instance-specific configuration variables. - -:::tip NOTE -Please not that the `config` is bundled into JavaScript files that are returned to the user's browser. Please **NEVER PUT ANY SENSITIVE INFORMATION** into the config file of `vue-storefront`. If your application requires some authorization / tokens /etc - please store them and access via dedicated [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api) or [`storefront-api`](https://github.com/vuestorefront/storefront-api) extension that will prevent these sensitive information from being returned to the users. -::: - -The structure of these files is exactly the same! Vue Storefront does kind of `Object.assign(default, local)` (but with the deep-merge). This means that the `local.json` overrides the `default.json` properties. - -:::tip NOTE -Please take a look at the `node-config` docs as the library is open for some other ways to modify the configuration (using, for example the `ENV` variables). -::: - -:::tip NOTE -Currently, the configuration files are being processed by the webpack during the build process. This means that whenever you apply some configuration changes, you shall rebuild the app, even when using the `yarn dev` mode. This limitation can be solved with the VS 1.4 special config variable. Now the config can be reloaded on the fly with each server request if `config.server.dynamicConfigReload` and `config.server.dynamicConfigReloadWithEachRequest` are set to true. However, in that case, the config is added to `window.INITIAL_STATE` with the responses. - -When you are using the `config.server.dynamicConfigReload` please remember about `config.server.dynamicConfigExclude` and `config.server.dynamicConfigInclude`. -::: - -Please find the configuration properties reference below. - -## Server - -```json -"server": { - "host": "localhost", - "port": 3000, - "useHtmlMinifier": false, - "htmlMinifierOptions": { - "minifyJS": true, - "minifyCSS": true - }, - "useOutputCacheTagging": false, - "useOutputCache": false -}, -``` - -Vue Storefront starts an HTTP server to deliver the SSR (server-side rendered) pages and static assets. Its node.js server is located in the `core/scripts/server.js`. This is the hostname and TCP port which Vue Storefront is binding. - -When the `useHtmlMinifier` is set to true the generated SSR HTML is being minified [using the `htmlMinifierOptions`](https://www.npmjs.com/package/html-minifier#options-quick-reference). - -When the `useOutputCacheTagging` and `useOutputCache` options are enabled, Vue Storefront is storing the rendered pages in the Redis-based output cache. Some additional config options are available for the output cache. [Check the details](ssr-cache.md) - -## Seo - -```json -"seo": { - "useUrlDispatcher": true, - "disableUrlRoutesPersistentCache": true, - "defaultTitle": "Vuestore" -}, -``` - -When `config.seo.useUrlDispatcher` set to true the `product.url_path` and `category.url_path` fields are used as absolute URL addresses (no `/c` and `/p` prefixes anymore). Check the latest [`mage2vuestorefront`] snapshot and reimport Your products to properly set `url_path` fields. - -For example, when the `category.url_path` is set to `women/frauen-20` the product will be available under the following URL addresses: - -`http://localhost:3000/women/frauen-20` -`http://localhost:3000/de/women/frauen-20` - -For, `config.seo.disableUrlRoutesPersistentCache` - to not store the url mappings; they're stored in in-memory cache anyway so no additional requests will be made to the backend for url mapping; however it might cause some issues with url routing in the offline mode (when the offline mode PWA installed on homescreen got reloaded, the in-memory cache will be cleared so there won't potentially be the url mappings; however the same like with `product/list` the ServiceWorker cache SHOULD populate url mappings anyway) - -For, `config.seo.defaultTitle` is as name suggest it's default title for the store. - -## Redis - -```json -"redis": { - "host": "localhost", - "port": 6379, - "db": 0 -}, -``` - -This is the Redis configuration for the output cache. See additional information [here](../basics/ssr-cache.md) - -## GraphQL - -```json -"graphql":{ - "host": "localhost", - "port": 8080 -}, -``` - -This is an optional GraphQL endpoint. We're now supporting graphQL for the [catalog](/guide/basics/graphql.html) and this section is being used when `server.api` is set to `graphql` (default is "api") - -## ElasticSearch - -```json -"elasticsearch": { - "httpAuth": "", - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog", - "min_score": 0.02, - "csrTimeout": 5000, - "ssrTimeout": 1000, - "queryMethod": "POST" -}, -``` - -Vue Storefront uses the Elasticsearch Query Language to query for data. However, here you're putting the Vue Storefront API `/api/catalog` endpoint, which is a kind of Elasticsearch Proxy (dealing with the taxes, security etc.). - -If your `vue-storefront-api` instance is running on the `localhost`, port `8080` then the correct elasticsearch endpoint is as presented here. - -Starting from Vue Storefront v1.6, user may set `config.elasticsearch.queryMethod` either *POST* (default) or *GET*. When *GET* is set, the Elasticsearch Query object is passed to vue-storefront-api as a request parameter named *request*. By doing so, Service Worker will now be able to cache the results from Elasticsearch. Service Workers cannot cache any POST requests currently. - -:::tip Notice -Service Worker is not caching the /api requests on development envs. (localhost) as the vue-storefront-api by default runs on a different port (8080). - -::: - -## SSR - -```json -"ssr": { - "executeMixedinAsyncData": true -}, -``` - -By default, Vue Storefront themes are created by building a set of components that "mixins" the core-components. For example, you have `/src/themes/default/pages/Product.vue` which inherits the `/core/pages/Product.js` by having this core component included in the `"mixins": [Product]` section. - -The SSR data is being completed in the `asyncData` static method. If this configuration parameter is set to `true` (which is default) Vue Storefront will run the `asyncData` methods in the following sequence: -`core/pages/Product.js` -> `asyncData` -`src/themes/default/pages/Product.vue` -> `asyncData` - -If it's set to `false`, then **just the** `src/themes/default/pages/Product.vue` -> `asyncData` will be executed. -This option is referenced in the [core/client-entry.ts](https://github.com/vuestorefront/vue-storefront/blob/master/core/client-entry.ts) line: 85. - -```json - "lazyHydrateFor": ["category-next.products", "homepage"], -``` - -Filters out given properties from `window.__INITIAL_STATE__` and enables [lazy hydration](https://github.com/maoberlehner/vue-lazy-hydration) on client side -Available out of the box for `category-next.products` and `homepage`. -## Max attempt of tasks - -```json -"queues": { - "maxNetworkTaskAttempts": 1, - "maxCartBypassAttempts": 1 -}, -``` - -This both option is used when you don't want re-attempting task of just X number time attempt task. - -`maxNetworkTaskAttempts` config variable is referenced in the [core/lib/sync/task.ts](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/sync/task.ts) and It's reattempt if user token is invalid. - -`maxCartBypassAttempts` config variable is referenced in the [core/modules/cart/store/actions.ts](https://github.com/vuestorefront/vue-storefront/blob/master/core/modules/cart/store/actions.ts) - -## Default store code - -```json -"defaultStoreCode": "", -``` - -This option is used only in the [Multistore setup](../integrations/multistore.md). By default it's `''` but if you're running, for example, a multi-instance Vue Storefront setup and the current instance shall be connected to the `en` store on the backend, please just set it so. This config variable is referenced in the [core/lib/multistore.ts](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/multistore.ts) - -## Store views - -```json -"storeViews": { - "multistore": false, - "commonCache": false, - "mapStoreUrlsFor": ["de", "it"], -``` - -If the `storeViews.multistore` is set to `true` you'll see the `LanguageSwitcher.vue` included in the footer and all the [multistore operations](../integrations/multistore.md) will be included in the request flow. - -You should add all the multistore codes to the `mapStoreUrlsFor` as this property is used by [core/lib/multistore.ts](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/multistore.ts) -> `setupMultistoreRoutes` method to add the `//p/....` and other standard routes. By accessing them you're [instructing Vue Storefront to switch the current store](https://github.com/vuestorefront/vue-storefront/blob/master/core/client-entry.ts) settings (i18n, API requests with specific storeCode etc...) - -`commonCache` is refering to llocal browser cache. If it's set to false (default) the cache of cart, catalog, user data etc is shared between storeViews with default prefix (shop). Otherwise each of them is stored separately (storecode-shop prefix). - -`storeViews` section contains one or more additional store views configured to serve proper i18n translations, tax settings etc. Please find the docs for this section below. - -```json - "de": { - "storeCode": "de", -``` -This attribute is not inherited through the "extend" mechanism. - -```json - "storeId": 3, -``` - -This is the `storeId` as set in the backend panel. This parameter is being used by some API calls to get the specific store currency and/or tax settings. - -```json - "name": "German Store", -``` - -This is the store name as displayed in the `Language/Switcher.vue`. - -```json - "url": "/de", -``` - -This URL is used only in the `Switcher` component. Typically it equals just to `/`. Sometimes you may like to have different store views running as separate Vue Storefront instances, even under different URL addresses. This is the situation when this property comes into action. Just take a look at how [Language/Switcher.vue](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/blocks/Switcher/Language.vue) generates the list of the stores. -It accepts not only path, but also domains as well. -This attribute is not inherited through the "extend" mechanism. - -```json - "appendStoreCode": true, -``` - -In default configuration store codes are appended at the end of every url. If you want to use domain only as store url, you can set it to `false`. -This attribute is not inherited through the "extend" mechanism. - -```json - "elasticsearch": { - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog_de" - }, -``` - -ElasticSearch settings can be overridden in the specific `storeView` config. You can use different ElasticSearch instance powering specific `storeView`. - -```json - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true - }, -``` - -Taxes section is used by the [core/modules/catalog/helpers/tax](https://github.com/vuestorefront/vue-storefront/blob/master/core/modules/catalog/helpers/tax). When `sourcePricesIncludesTax` is set to `true` it means that the prices indexed in the ElasticSearch already consists of the taxes. If it's set to `false` the taxes will be calculated runtime. - -```json - "seo": { - "defaultTitle": 'Vuestore' - }, -``` - -SEO section's `defaultTitle` is used at the set title for the specific store. - -The `defaultCountry` and the `defaultRegion` settings are being used for finding the proper tax rate for the anonymous, unidentified user (which country is not yet set). - -```json - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - } -}, -``` - -The internationalization settings are used by the translation engine (`defautlLocale`) and the [Language/Switcher.vue](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/blocks/Switcher/Language.vue) (`fullCountryName`, `fullLanguageName`). `currencyCode` is used for some of the API calls (rendering prices, mostly) and `currencySign` is being used for displaying the prices in the frontend. - - -```json - "extend": "de" -``` - -You can inherit settings from other storeview of your choice. Result config will be deep merged with chosen storeview by storecode set in `extend` property prioritizing current storeview values. -Keep in mind that `url`, `storeCode` and `appendStoreCode` attributes cannot be inherited from other storeviews. - -## Entities - -```json -"entities": { - "optimize": true, -``` - -If this option is set to true, Vue Storefront will be limiting the data retrieved from the API endpoints to the `includeFields` and remove all the `excludeFields` as set for all the specific entities below. This option is set to `true` by default, as the JSON objects could be of significant size! - -This option property is referenced in the [core/modules/catalog/store/product](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/catalog/store/product), [core/modules/catalog/store/category](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/catalog/store/category), [core/modules/catalog/store/attribute](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/catalog/store/attribute) - -```json - "twoStageCaching": true, -``` - -Vue Storefront caches all the data entities retrieved from `vue-storefront-api` into indexedDB local cache. This is a key feature for providing users with offline mode. Unfortunately, when the `entities.optimize` option is set to `true`, we cannot cache the optimized entities, as they don't contain all the required information. - - which works like it executes two parallel server requests at once to get the required product, category, or attribute feeds. The first request is with the limited fields and the second is for full records. Only the second request is cached **but\*\*** the first (which typically ends up faster) is used for displaying the Category or Product page. - -Please take a look at the [core/modules/catalog/store/category](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/catalog/store/category) for the reference. - -```json - "optimizeShoppingCart": true, -``` - -Vue Storefront product objects can be quite large. They consist of `configurable_children`, `media_gallery` and other information. Quite significant for rendering the Product and Category pages, but not so useful in the Shopping Cart. To limit the cart size (as it's transferred to the server while making an order), this option is being used. - -Please take a look at the [core/modules/cart](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/cart). - -```json - "optimizeShoppingCartOmitFields": ["configurable_children", "configurable_options", "media_gallery", "description", "category", "category_ids", "product_links", "stock", "description"], -``` - -You can specify which fields get stripped out of the Cart object, by changing the `optimizeShoppingCartOmitFields` array. - -```json - "category": { - "includeFields": [ "children_data", "id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key", "product_count" ] - }, - "attribute": { - "includeFields": [ "attribute_code", "id", "entity_type_id", "options", "default_value", "is_user_defined", "frontend_label", "attribute_id", "default_frontend_label", "is_visible_on_front", "is_visible", "is_comparable" ] - }, - "productList": { - "sort": "", - "includeFields": [ "type_id", "sku", "product_links", "tax_class_id", "special_price", "special_to_date", "special_from_date", "name", "price", "price_incl_tax", "original_price_incl_tax", "original_price", "special_price_incl_tax", "id", "image", "sale", "new", "url_key", "status" ], - "excludeFields": [ "configurable_children", "description", "configurable_options", "sgn" ] - }, - "productListWithChildren": { - "includeFields": [ "type_id", "sku", "name", "tax_class_id", "special_price", "special_to_date", "special_from_date", "price", "priceInclTax", "original_price_incl_tax", "original_price", "special_price_incl_t_ax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.price_incl_tax", "configurable_children.special_price_incl_tax", "configurable_children.original_price", "configurable_children.original_price_incl_tax", "configurable_children.color", "configurable_children.size", "configurable_children.id", "product_links", "url_key", "status"], - "excludeFields": [ "description", "sgn"] - }, - "product": { - "excludeFields": [ "updated_at", "created_at", "attribute_set_id", "tier_prices", "options_container", "msrp_display_actual_price_type", "has_options", "stock.manage_stock", "stock.use_config_min_qty", "stock.use_config_notify_stock_qty", "stock.stock_id", "stock.use_config_backorders", "stock.use_config_enable_qty_inc", "stock.enable_qty_increments", "stock.use_config_manage_stock", "stock.use_config_min_sale_qty", "stock.notify_stock_qty", "stock.use_config_max_sale_qty", "stock.use_config_max_sale_qty", "stock.qty_increments", "small_image"], - "includeFields": null - } -}, -``` - -These settings are used just to configure the optimization strategy for different entity types. Please take a look that we have `productListWithChildren` and the `product` configuration separately. The former one is used in the Category page -> `core/pages/Category.js` and the latter is used in the Product page `core/pages/Product.js` - -### Dynamic Categories prefetching - -Starting with Vue Storefront 1.7, we added a configuration option `config.entities.category.categoriesDynamicPrefetch` (by default set to `true`). This option switches the way the category tree is fetched. Previously we were fetching the full categories tree. In some cases, it can generate a few MB of payload. Currently, with this option in place, we're prefetching the categories on demand while the user is browsing the category tree. - -## Cart - -```json -"cart": { - "serverMergeByDefault": true, -``` - -Server cart is being synchronized with the client's cart in the Vue Storefront by default. When it's not set, the Vue Storefront will execute the server cart merge algorithm anyway, but using the `dryRun` option, which means that only the following event will be emitted: - -```js -EventBus.$emit('servercart-after-diff', { - diffLog: diffLog, - serverItems: serverItems, - clientItems: clientItems, - dryRun: event.dry_run, - event: event, -}); // send the difflog -``` - -In the event handler, one can handle the merge process manually. For example, displaying the proper information to the user before the real merge takes place. -Please have a look at the [core/modules/cart](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/cart) for a reference. - -```json - "synchronize": true, -``` - -If it's set to `true` the `serverPull` Vuex method will be executed whenever the user adds, removes or edits any product in the shopping cart. This method syncs the client-side shopping cart with the server-side one. - -Please take a look at the [core/modules/cart](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/cart) for a reference. - -```json - "synchronize_totals": true, -``` - -Similarly to the `synchronize` option, you may want to disable or enable (the default behavior) the shopping-cart totals sync with the backend platform. If it's set to `true`, the shopping cart totals will be overridden by the Magento (or Pimcore, or any other platform you're using) totals whenever the user will add, remove, or change any item in the shopping cart. - -```json - "setCustomProductOptions": true, -``` - -If this option is set to `true`, in case of custom-options supporting products, Vue Storefront will add the main SKU to the shopping cart and set the `product_option`sub-object of the shopping-cart item to the currently configured set of custom options (for example, selected dates, checkboxes, captions, or other values). - -```json - "setConfigurableProductOptions": true, -``` - -If this option is set to `true`, in case of configurable products, Vue Storefront will add the main SKU to the shopping cart and set the `product_option` sub-object of the shopping-cart item to the currently configured set of configurable options (for example, color and size). Otherwise, the simple product (according to the selected configurable_options) will be added to the shopping cart instead. - -```json - "displayItemDiscounts": true -``` - -If this option is set to `true`, Vue Storefront will add price item with a discount to the shopping cart. Otherwise, the product price and special will be added to the shopping cart instead. - -```json - "minicartCountType": "quantities", -``` - -If this option is set to `items`, Vue Storefront will calculate the cart count based on items instead of item quantities. - -```json - "create_endpoint": "http://localhost:8080/api/cart/create?token={{token}}", - "updateitem_endpoint": "http://localhost:8080/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "http://localhost:8080/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "http://localhost:8080/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "http://localhost:8080/api/cart/totals?token={{token}}&cartId={{cartId}}", - "paymentmethods_endpoint": "http://localhost:8080/api/cart/payment-methods?token={{token}}&cartId={{cartId}}", - "shippingmethods_endpoint": "http://localhost:8080/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}", - "shippinginfo_endpoint": "http://localhost:8080/api/cart/shipping-information?token={{token}}&cartId={{cartId}}", - "collecttotals_endpoint": "http://localhost:8080/api/cart/collect-totals?token={{token}}&cartId={{cartId}}", - "deletecoupon_endpoint": "http://localhost:8080/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}", - "applycoupon_endpoint": "http://localhost:8080/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}" -``` - -These endpoints should point to the `vue-storefront-api` instance and typically, you're changing just the domain-name/base-url without touching the specific endpoint URLs, as it's related to the `vue-storefront-api` specifics. - -```json - "productsAreReconfigurable": true -``` - -If this option is set to `true`, you can edit current options such as color or size in the cart view. Works only for configurable products. - -## Products - -```json - "useMagentoUrlKeys": false, -``` - -When `useMagentoUrlKeys` is set to `true` the `product.url_key` value will be used for product and category slugs used in the URL building process. Otherwise, the slug will be generated based on the product or category name. Please take a look at the [core/lib/search.ts](https://github.com/vuestorefront/vue-storefront/tree/master/core/lib/search.ts) and [core/modules/catalog/store/category/mutations.ts](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/catalog/store/category/mutations.ts) for reference. - -**Please note:** The `url_key` field must be unique across the categories collection. Therefore, we're by default generating its value based on name and category ID. Please [switch this option off](https://github.com/vuestorefront/mage2vuestorefront/#initial-vue-storefront-import) if you'd like to keep the `url_key` as they come from Magento2. - -```json - "configurableChildrenStockPrefetchStatic": false, - "configurableChildrenStockPrefetchStaticPrefetchCount": 8, -``` -Vue Storefront tries to dynamically get the stock quantities for simple products related to the configurable ones (products included in the `configurabe_children` array). If the `configurableChildrenStockPrefetchStatic` is set to `true`, the stock items are prefetched from the Category page level. Please take a look at the [core/modules/catalog/store/category/actions.ts](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/catalog/store/category/actions.ts). The second option - `configurableChildrenStockPrefetchStaticPrefetchCount` sets how many products in the category should be prefetched using this mechanism. - -```json - "configurableChildrenStockPrefetchDynamic": false, -``` - -Opposite to the static prefetching, Vue Storefront could also prefetch the `configurable_children` stock items just for the products that are visible on the Category page. This option is used from the theme level—for example, [src/themes/default/pages/Category.vue](https://github.com/vuestorefront/vue-storefront/tree/master/src/themes/default/pages/Category.vue) - -```json - "filterUnavailableVariants": false, -``` - -By default, Vue Storefront displays all the variants assigned with the configurable product, no matter if they are visible or not. Then, by adding a specific variant to the shopping cart, the availability is checked. You can switch this setting to `true` to prefetch the variants availability (see the options described above) and hide unavailable options. - -```json - "listOutOfStockProducts": false, -``` - -By default, Vue Storefront is not displaying products with the stock availability = “Out of stock”. However, it can be changed using this variable. Vue Storefront uses the `product.stock` object to access the product-information availability. Please note that this information is updated just when the `mage2vuestorefront` updates the ElasticSearch index. - -```json - "preventConfigurableChildrenDirectAccess": true, -``` - -If this option is set to true (default), Vue Storefront will prevent accessing the simple products assigned with the configurable one. A user will be redirected to the main configurable product in such a case. - -```json - "alwaysSyncPlatformPricesOver": false, -``` - -This property is used in the [core/store/modules/product/actions.ts](https://github.com/vuestorefront/vue-storefront/tree/master/core/store/modules/product/actions.ts); if it's set to `true` Vue Storefront will query the `vue-storefront-api` endpoint (`/api/products/render-list`) to render the product prices for currently displayed product(s) **every time** the user is about to display the product or category page. - - -```json - "clearPricesBeforePlatformSync": false, -``` - -This is related to `alwaysSyncPlatformPricesOver` and when it's set to true, the prices provided from the Elasticsearch will be always overridden to zero before rendering the dynamic prices. - -```json - "waitForPlatformSync": false, -``` - -This is related to `alwaysSyncPlatformPricesOver`. When true, Vue Storefront will wait for dynamic prices before rendering the page. Otherwise, the product and category pages will be rendered using the default (Elasticsearch-based) prices and then asynchronously override them with current ones. - -```json - "alwaysSyncPricesClientSide": false, -``` - -This is related to `alwaysSyncPlatformPricesOver`. When true, Vue Storefront will force a refresh of the prices on the client side, including the token from the current logged in user, so customer specific pricing can be applied. - - -```json - "endpoint": "http://localhost:8080/api/product", -``` - -This is the `vue-storefront-api` endpoint for rendering product lists. - -```json - "defaultFilters": ["color", "size", "price", "erin_recommends"], -``` - -`defaultFilters` array should contain **all** the filters that could be used in the [Sidebar menu filters](https://github.com/vuestorefront/vue-storefront/tree/master/src/themes/default/components/core/blocks/Category/Sidebar.vue). - -```json - "sortByAttributes": { - "Latest": "updated_at", - "Price":"price" - }, -``` - -Here, we have the sort field settings as they're displayed on the Category page. - -```json - "systemFilterNames": ["sort"], -``` - -This is an array of query-fields which won't be treated as filter fields when in URL. - -```json - "gallery": { - "mergeConfigurableChildren": true -``` - -Vue Storefront is feeding the Product page gallery with the combination of: `product.media_gallery`, `product.image` and the `product.configurable_children.image`. If set to `false` the Product page gallery shows the `product.media_gallery`of the selected variant. - -```json - "gallery": { - "variantsGroupAttribute": "color" -``` - -If `mergeConfigurableChildren` is set to `true` in some cases simple products attached to the configurable one have the same photos as the main one assigned. If this option is set to the name of any particular attribute assigned with `configurable_children`, images that Vue Storefront gets will be grouped by the color (getting single color images from the `configurable_children` collection) - -```json - "gallery": { - "imageAttributes": ["image","thumbnail","small_image"] -``` - -Product attributes representing the images. We'll see it in the Product page gallery if `mergeConfigurableChildren` is set to `false` and the product is configured - -```json - "gallery": { - "width": 600, - "height": 744 -``` - -The dimensions of the images in the gallery. - -If you want to use max/min aggregations for prices you can enable them with this setting. It is being used by [Storefront Query Builder](https://github.com/vuestorefront/storefront-query-builder) so it requires it. Depending on other part of your config you might want to apply this setting in PWA or VSF-API's config. New returned aggregations might by used by some custom modules like [VSF Price Slider](https://github.com/Fifciu/vsf-price-slider). - -```json - "aggregate": { - "minPrice": false, - "maxPrice": false - } -``` - -## Orders - -```json -"orders": { - "endpoint": "http://localhost:8080/api/order", -``` - -This property sets the URL of the order endpoint. Orders will be placed to this specific URL as soon as the internet connection is available. - -```json - "payment_methods_mapping": { - }, -``` - -This is a simple map used in the [core/pages/Checkout.js](https://github.com/vuestorefront/vue-storefront/tree/master/core/pages/Checkout.js) to map the payment methods provided by the backend service with the ones available to Vue Storefront. Each payment method is a separate Vue Storefront extension and not all methods provided by the backend should necessarily be supported by the frontend. - -```json - "offline_orders": { - "notification" : { - "enabled": true, - "title" : "Order waiting!", - "message": "Click here to confirm the order that you made offline.", - "icon": "/assets/logo.png" - } - } -``` - -When a user places the order in offline mode and agrees to receive push notifications, these variables are used to determine the look and feel of the notification. - -Please check the [core/service-worker/order.js](https://github.com/vuestorefront/vue-storefront/tree/master/core/service-worker/order.js) for reference. - -Starting with Vue Storefront v1.6, we changed the default order-placing behavior. Currently, the `config.orders.directBackendSync` is set to `true` by default. With this option enabled, if the user is online, Vue Storefront tries to pass the order immediately and synchronously (waiting for result) to the eCommerce backend. This option gives immediate and direct feedback to the user. If there is an app-level error (for example, validation error on the Magento side), the user will be notified immediately. If there is a transmission issue (no connection, servers are down, etc.), the order is put into a queue (as it was prior to 1.6). If `config.orders.directBackendSync` is set to false, the legacy behavior with queuing all the orders is being used. With `directBackendSync` set to true, we do have access to the server confirmation (with backend orderId) in `store.state.order.last_order_confirmation` - -## Local Forage - -```json -"localForage": { - "defaultDrivers": { - "user": "LOCALSTORAGE", - "carts": "LOCALSTORAGE", - "orders": "LOCALSTORAGE", - "wishlist": "INDEXEDDB", - "categories": "INDEXEDDB", - "attributes": "INDEXEDDB", - "products": "INDEXEDDB", - "elasticCache": "INDEXEDDB", - "claims": "LOCALSTORAGE", - "compare": "INDEXEDDB", - "syncTasks": "INDEXEDDB", - "newsletterPreferences": "INDEXEDDB", - "ordersHistory": "INDEXEDDB", - "checkout": "LOCALSTORAGE" - } -}, -``` - -We're using [localForage](https://github.com/localForage/localForage) library to providing the persistence layer to Vue Storefront. `localForage` provides the compatibility fallbacks for the users not equipped with some specific storage methods (for example indexedDb). However, we may want to enforce some specific storage methods in the config. This is the place to set it up. - -## Users - -```json -"users": { - "autoRefreshTokens": true, - "endpoint": "http://localhost:8080/api/user", - "history_endpoint": "http://localhost:8080/api/user/order-history?token={{token}}", - "resetPassword_endpoint": "http://localhost:8080/api/user/reset-password", - "changePassword_endpoint": "http://localhost:8080/api/user/change-password?token={{token}}", - "login_endpoint": "http://localhost:8080/api/user/login", - "create_endpoint": "http://localhost:8080/api/user/create", - "me_endpoint": "http://localhost:8080/api/user/me?token={{token}}", - "refresh_endpoint": "http://localhost:8080/api/user/refresh" -}, -``` - -In the `users` section, we can set the API endpoints for specific use-related operations. Most of the time, you only need to change the basic URL. - -When the `autoRefreshTokens` property is set to `true` (default) Vue Storefront will try to refresh the user tokens automatically when the session ends. Please take a look at the [core/lib/sync/task.ts](https://github.com/vuestorefront/vue-storefront/tree/master/core/lib/sync/task.ts) for reference. - -## Stock - -```json -"stock": { - "synchronize": true, - "allowOutOfStockInCart": true, - "endpoint": "http://localhost:8080/api/stock" -}, -``` - -The `stock` section configures how the Vue Storefront behaves when the product is being added to the cart. By default, the request to `stock.endpoint` is being made asynchronously to the `add to cart` operation. When the `allowOutOfStockInCart` is set to `true`, and the product is no longer available, it will be removed from the cart (with a proper UI notification) shortly after the information becomes available to the Vue Storefront. - -## Images - -```json -"images": { - "baseUrl": "https://demo.vuestorefront.io/img/", - "productPlaceholder": "/assets/placeholder.jpg", - "useExactUrlsNoProxy": false, - "useSpecificImagePaths": false, - "paths": { - "product": "/catalog/product" - } -}, -``` - -This section is to set the default base URL of images. This should be a `vue-storefront-api` URL, pointing to its `/api/img` handler. The Vue Storefront API is in charge of downloading the local image cache from the Magento/Pimcore backend and does the resize/crop/scale operations to optimize the images for mobile devices and the UI. - -If you wan't to also show non-product image thumbnails you must set `useSpecificImagePaths` to `true` and remove `/catalog/product` from the end of your API `magento1.imgUrl` or `magento2.imgUrl` setting in your API's config file – e.g.: `http://magento-demo.local/media`. After that you can use the `pathType` parameter of the `getThumbnail()` mixin method to traverse other images than product ones. - -## Install - -```json -"install": { - "is_local_backend": true, - "backend_dir": "../vue-storefront-api" -}, -``` - -This is just to be used in the [core/scripts/installer.js](https://github.com/vuestorefront/vue-storefront/tree/master/core/scripts/installer.js) - -## Demo mode - -```json -"demomode": false, -``` - -When `demomode` is set to `true`, Vue Storefront will display the "Welcome to Vue Storefront demo" popup. - -## Taxes - -```json -"tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true, - "userGroupId": null -}, -``` - -The taxes section is used by the -[core/modules/catalog/helpers/tax](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/catalog/helpers/tax.ts). -When `sourcePricesIncludesTax` is set to `true` it means that the prices -indexed in the Elasticsearch already consist of the taxes. If it's set -to `false` the taxes will be calculated runtime. The `userGroupId` -config does only work when you have set `sourcePriceIncludesTax` set to -`false` and `calculateServerSide` is set to `false`. - -The `defaultCountry` and the `defaultRegion` settings are being used for finding the proper tax rate for the anonymous, unidentified user (which country is not yet set). - -## Shipping - -```json -"shipping": { - "methods": [ - { - "method_title": "DPD Courier", - "method_code": "flatrate", - "carrier_code": "flatrate", - "amount": 4, - "price_incl_tax": 5, - "default": true, - "offline": true - } - ] -}, -``` - -Available shipping methods when the backend platform is not providing the dynamic list / or for offline scenarios. - -## Internationalization - -```json - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } -} -}, -``` - -Internationalization settings are used by the translation engine (`defautlLocale`) and the [Language/Switcher.vue](https://github.com/vuestorefront/vue-storefront/tree/master/src/themes/default/components/core/blocks/Switcher/Language.vue) (`fullCountryName`, `fullLanguageName`). `currencyCode` is used for some of the API calls (rendering prices, mostly) and `currencySign` is being used for displaying the prices in the frontend. - -## Mailchimp - -```json -"mailchimp": { - "endpoint": "http://localhost:8080/api/ext/mailchimp-subscribe/subscribe" -}, -``` - -This property is used by the Mailchimp extension (See [src/extensions](https://github.com/vuestorefront/vue-storefront-api/tree/master/src/api/extensions) for a reference). - -## Theme - -```json -"theme": "@vue-storefront/theme-default", -``` - -This is the currently applied theme path. After changing it, Vue Storefront needs to be rebuilt. - - -## Analytics - -```json -"analytics": { - "id": false -}, -``` - -You can put your Google Analytics ID in here as to be used by the analytics extension. - -## Hotjar - -```json -"hotjar": { - "id": false -}, -``` - -You can put your Hotjar Site ID in here as to be used by the hotjar extension. - - -## CMS - -```json -"cms": { - "endpoint": "http://localhost:8080/api/ext/cms-data/cms{{type}}/{{cmsId}}" -} -``` - -This is the URL endpoint of the Snow.dog Magento 2 CMS extensions. It needs to be set when using the [src/api/extensions/cms-data](https://github.com/vuestorefront/vue-storefront-api/tree/master/src/api/extensions/cms-data) - -## Use price tiers - -```json -"usePriceTiers": false, -``` - -When set to `true` we're using Magento2 feature of tiered prices (individual prices set for client's groups). The prices are set in `product.tier_prices` property. - -## Manage products with price zero - -```json -"useZeroPriceProduct": true, -``` - -Set to `true` if you want the customer to add products with price zero to the cart, otherwise, an error is returned. Set true by default. - -## Boost - -```json -"boost": { - "name": 3, - "category.name": 1, - "short_description": 1, - "description": 1, - "sku": 1, - "configurable_children.sku": 1 -}, -``` - -This is a list of priorities for search features (higher boost = more important search field). - -## Query - -```json -"query": { - "inspirations": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Performance Fabrics" } - } - ] - }, - "newProducts": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Tees" } - } - ] - }, - "coolBags": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Women" } - } - ] - }, - "bestSellers": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Tees" } - } - ] - } - } -``` - -Search queries used by specific components (for example, related products). The format of the query has been described [here](/guide/data/elastic-queries.html) diff --git a/docs/guide/basics/contributing.md b/docs/guide/basics/contributing.md deleted file mode 100644 index b0b18bcdfb..0000000000 --- a/docs/guide/basics/contributing.md +++ /dev/null @@ -1,60 +0,0 @@ -# How to Contribute - -Already a JS/Vue.js developer? Pick an issue, push a PR, and instantly become a member of the vue-storefront contributors community. We've marked some issues as `Easy first pick` to make it easier for newcomers to begin! - -Thank you for your interest and engagement! - -## Branches - -You should fork the project or create a branch for new features. The main branches used by the core team are: - -- `master` - where we store the stable release of the app (that can be deployed to our demo instances). -- `develop` - the most recent version of the app, kind of a "nightly" build. - -Please use `develop` for development purposes as the `master` can be merged just as the new release is coming out (about once a month). - -## Issue reporting guidelines - -Always define the type of issue: - -- Bug report -- Feature request - -While writing issues, please be as specific as possible. All requests regarding support with implementation or application setup should be sent to [contributors@vuestorefront.io](mailto:contributors@vuestorefront.io) - -If the issue is about some changes with a particular theme, please prefix the issue with the theme name (ex. `[default] change product tile background color`). - -## TypeScript - -We're introducing TypeScript to Vue Storefront core, so you can use it where it's appropriate, but please be pragmatic. It would be nice to TS features only in new modules and Vuex. Here are some thoughts on how to use TS features in Vue Storefront: [TypeScript Action Plan](typescript.md). - -## Pull request checklist - -Here’s how to submit a pull request. **Pull requests that don't meet these requirements will not be merged.** - -::: warning -**ALWAYS** use [Pull Request template](https://github.com/vuestorefront/vue-storefront/blob/master/PULL_REQUEST_TEMPLATE.md), it's automatically added to each PR. -::: - -1. Fork the repository and clone it locally on the 'develop' branch. -2. Create a branch for your edits. Use the following branch naming conventions: - -- `bugfix/task-title` -- `feature/task-name` - -3. Describe what you've changed. Include screenshots of the new feature or the before and after if your changes include differences in HTML/CSS. Drag and drop the images into the body of your pull request. - -4. Reference any relevant issues or supporting documentation in your PR (ex. `Issue: 39. Issue title.`). - -5. If you are adding a new feature, please provide documentation along with the PR. Also, add it to the [upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/master/doc/Upgrade%20notes.md) - -6. If you are removing/renaming something or changing its behavior, please also include it in the [upgrade notes](https://github.com/vuestorefront/vue-storefront/blob/master/doc/Upgrade%20notes.md) - -7. Test your changes! Run your changes against any existing tests and create new ones when needed. Make sure your changes don’t break the existing project. Make sure that your branch is passing the Travis CI build. - -8. If you have found a potential security vulnerability, please do not report it on the public issue tracker. Instead, send it to us at [contributors@vuestorefront.io](mailto:contributors@vuestorefront.io). We will work with you to verify and fix it as soon as possible. - - -## New features - -If you are making any new feature, make sure it's adjusted to our new, modular approach. Read more [here](../modules/introduction.md) diff --git a/docs/guide/basics/e2e.md b/docs/guide/basics/e2e.md deleted file mode 100644 index 5c1f3aee42..0000000000 --- a/docs/guide/basics/e2e.md +++ /dev/null @@ -1,15 +0,0 @@ -# E2E tests - -For e2e test in Vue Storefront, we use [Cypress](https://www.cypress.io/). Head to their website for great docs, best practices, and tutorials on how to write e2e tests. - -We don't yet have our guidelines for e2e tests, but we try to follow best practices recommended by Cypress. Follow them to make tests more reliable and easier to maintain. - -## Running tests - -To open cypress, just run: - -`yarn test:e2e` - -To run all test in the background: - -`yarn test:e2e:ci` diff --git a/docs/guide/basics/feature-list.md b/docs/guide/basics/feature-list.md deleted file mode 100644 index 13965b450d..0000000000 --- a/docs/guide/basics/feature-list.md +++ /dev/null @@ -1,138 +0,0 @@ -# Feature list - -Vue Storefront is a platform-agnostic headless frontend for eCommerce. Here, we tried to put all the features available in 1.0 together: - -## Vue Storefront unique features - -- 100% offline support via in-browser IndexedDB database -- High speed with local caching (Service Workers + IndexedDB); avg. server response time < 0.3s; avg client-side rendering time < 2s -- Platform-agnostic: Magento 2, Magento 1, Pimcore supported out-of-the-box -- Offline orders support (via in-browser queue) -- Single page app user experience with server-side rendering support for SEO -- Native app features: install on home screen (iOS, Android, Chrome), push notifications (via customization) -- NoSQL/Elasticsearch database backend -- Modern Vue.js-based frontend: easy to design and customize, atomic-design, design-system backed -- It's a framework, customizable via extensions and themes. Easy to update with separated core. - - -## Vue Storefront Magento2 support - -### Checkout, Payment, & Shipping - -- One-page online checkout -- Integrated for real-time shipping rates -- SSL security support for all online orders and sensitive transactions -- Online tax and shipping calculation and prior-to-checkout estimates -- Option to create account as part of the online checkout process -- Configurable saved-cart expiration -- Multiple shipping address management -- Destination-country management -- Per-order and per-item flat-rate shipping option -- Free-shipping functionality -- Manage shipping by weight and destination - -### Search Engine Optimization (SEO) - -- SSR (server-side rendering) -- Light footprint design for fast load time and search-engine optimization -- Search-engine friendly URLs, including URL rewrite controls -- META information management at product and category levels -- Available via customization: auto-generated popular search terms page - -### Analytics and Reporting - -- Integration with Google Analytics -- Admin report dashboard with business overview -- Sales reports including total sales and returns -- Tax reports -- Abandoned shopping cart reports -- Best viewed products reports -- Top sold products report -- Low stock item report -- On-site search terms report -- Product reviews report with RSS support -- Tags report with RSS support -- Coupon usage report - -### Marketing Promotions and Tools - -- Newsletter management -- Catalog promotional pricing and controls -- Flexible coupons rule and pricing restrictions -- Free-shipping promotion management -- Bundled product options -- Customer group pricing -- New items promotional tool -- On-page upsells and cross-sells -- Wishlist management - -### Order Management - -Standard Magento 2 features are all supported: - - - View, edit, create, and fulfill orders from the admin panel. -- Create one or multiple invoices, shipments, and credit memos per order to allow for split fulfillment. -- Print invoices and packing slips. -- Call Center (phone) order creation—includes the ability to create a new customer, or select existing customer, and view shopping cart, wishlist, last ordered items, and compared products list, as well as select addresses, give discounts, and assign custom prices. -- Create re-orders for customers from the administration panel. -- Email notifications of orders. -- RSS feed of new orders. - - -### Customer Service - -- Contact Us form -- Feature-rich customer accounts -- Order history with status updates -- Password-reset email from frontend and admin panel -- Order and account update emails -- Standard Magento 2 feature: Customizable order emails -- Standard Magento 2 feature: Create and edit orders from the admin panel - - -### Customer Accounts - -- Order status and history -- Re-orders from account -- Address book with unlimited addresses -- Default billing and shipping addresses -- Wishlist -- Newsletter-subscription management - - -### Catalog Management Support - -- Inventory management with backordered items, minimum, and maximum quantities -- Simple, configurable (e.g. size, color, etc.), bundled and grouped products -- Virtual products -- Downloadable/digital products -- Available via customization: Customer personalized products—upload text for embroidery, monogramming, etc. -- Tax rates per location, customer group, and product type -- Attribute sets for quick product creation of different item types -- Create store-specific attributes on the fly -- Media manager with automatic image resizing and watermarking -- Advanced pricing rules and support for special prices (see marketing tools) -- Available via customization: Customer personalized products – upload image -- Available via customization: Customer personalized products – select date/time options for products - -### Product Browsing - -- Multiple images per product -- Product image zoom-in capability -- Related products -- Stock availability -- Product option selection -- Grouped products view -- Add to wishlist - -### Catalog Browsing - -- Ultrafast Elasticsearch full catalog support with Service Workers caching -- 100% offline support -- Layered / faceted navigation for filtering of products in categories -- Recently viewed products -- Product comparisons -- Cross-sells, upsells, and related Items -- Available via customization: Product listing in grid or list format -- Breadcrumbs - diff --git a/docs/guide/basics/graphql.md b/docs/guide/basics/graphql.md deleted file mode 100644 index 8bbd791e02..0000000000 --- a/docs/guide/basics/graphql.md +++ /dev/null @@ -1,65 +0,0 @@ -# GraphQL Action Plan - -Starting with Vue Storefront 1.4.0, we're supporting two ways of getting data from the backend: - -- existing `api` mode, which is using ElasticSearch DSL as a query language -- new `graphql` mode, which is using GraphQL queries. - -You can set the desired API format in the `config/local.json` and `vue-storefront-api` is supporting both of them, however [the default is still set to `api`](https://github.com/vuestorefront/vue-storefront/blob/4cbf866ca93f917b04461d3ae139a2d26ddf552a/config/default.json#L6). - -We've introduced an abstract [`SearchQuery`](https://github.com/vuestorefront/vue-storefront/tree/develop/core/store/lib/search) interface with switchable Query Adapters to provide the abstraction layer. This is an ultra-cool feature, especially when you're integrating Vue Storefront with a custom backend application—you're able [to create your own adapter](https://github.com/vuestorefront/vue-storefront/tree/develop/core/lib/search/adapter) to customize the way data is gathered from the backend. - -From now on the **bodybuilder** package is **deprecated** and you should start using the `SearchQuery` interface to build the search queries that will be translated to GraphQL / API queries. - -Here is an example of how to build the Query: - -```js -export function prepareRelatedQuery(key, sku) { - let relatedProductsQuery = new SearchQuery(); - - relatedProductsQuery = relatedProductsQuery.applyFilter({ - key: key, - value: { in: sku }, - }); - - relatedProductsQuery = relatedProductsQuery - .applyFilter({ key: 'visibility', value: { in: [2, 3, 4] } }) - .applyFilter({ key: 'status', value: { in: [0, 1, 2] } }); // @TODO Check if status 2 (disabled) was set not by occasion here - - if (config.products.listOutOfStockProducts === false) { - relatedProductsQuery = relatedProductsQuery.applyFilter({ - key: 'stock.is_in_stock', - value: { eq: true }, - }); - } - - return relatedProductsQuery; -} - -let relatedProductsQuery = prepareRelatedQuery(key, sku); - -this.$store - .dispatch('product/list', { - query: relatedProductsQuery, - size: 8, - prefetchGroupProducts: false, - updateState: false, - }) - .then(response => { - if (response) { - this.$store.dispatch('product/related', { - key: this.type, - items: response.items, - }); - this.$forceUpdate(); - } - }); -``` - -[More information on how to query the data](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/data/elastic-queries.md). - -**Bodybuilder** queries are still supported by our backward-compatibility mode, so if you've used bodybuilder in your theme, it's fine as long as you're using the `api` mode for the backend queries. - -The **legacy queries** using bodybuilder will still work - and [here is an example](https://github.com/pkarw/vue-storefront/blob/28feb8e5dc30ec216353ef87a859212379901c57/src/extensions/template/index.js#L36). - -You can also use direct **ApolloQuery** GraphQL queries thanks to `vue-apollo` support. Please find the example [in here](https://github.com/vuestorefront/vue-storefront/blob/4cbf866ca93f917b04461d3ae139a2d26ddf552a/src/themes/default/components/core/blocks/SearchPanel/SearchPanel.gql.vue#L21). diff --git a/docs/guide/basics/project-structure.md b/docs/guide/basics/project-structure.md deleted file mode 100644 index cf159ca926..0000000000 --- a/docs/guide/basics/project-structure.md +++ /dev/null @@ -1,37 +0,0 @@ -# Project structure - -## Using git for custom development - -One option is to do kind of a fork, or just get the whole repo to your git service. Then, if you want to do some VS updates, you will probably need to pull the changes from our origins. Another option will be available as soon as we manage to separate the core as an npm module. - -## Structure details - -Below, you can find the Vue Storefront project structure with explanations and corresponding docs. This is a good place to start with the project. - -- `config` - Config files for Vue Storefront. They're used to define backend addresses, current theme, etc. - - `default.json` - Default config template which should never be changed. If you want to make some changes in config, create a `local.json` file in the same folder, copy the content and make changes there. The default `config.json` will be overwritten by `local.json` for your setup. - - `local.json` (optional) - Our custom Vue Storefront config. You can find a detailed description of all config file properties in the [Vue Storefront configuration chapter](configuration.md). -- `core` - Vue Storefront core - :::warning Caution ! - - Don't modify the `core` directory on your project if you want to receive core updates. - ::: - - `build` - It contains `config.json` generated from files in `/config` folder and webpack build. It's made from vue-cli webpack template. You can extend the core webpack build in `{themeroot}/webpack.config.js` (related: [Working with Webpack](../core-themes/webpack.md)). - - `compatibility` - API port for old components after the 1.6 release. Don't use it in new projects. - - `filters` - Core Vue filters - - `helpers` - Global helpers - - `lib` - Core libraries allowing functionalities like theme support, modules, etc. - - `mixins` - Core Vue mixins - - `i18n` - Internationalization plugin - - `modules` - core VSModules. Read more about modules [here](https://divanteltd.github.io/vue-storefront/guide/modules/introduction.html) - - `pages` - Vue Storefront core pages - - `scripts` - scripts like installer - - `service-worker` - Core Service Worker. It's merged with `sw-precache` data from `build` and `{theme}/service-worker-ext.js` - - `store` - Core Vuex stores (related: [Working with Vuex](../vuex/introduction.md), [Working with data](../data/data.md)). **This part is deprecated and will be slowly migrated to modules and lib folder** - - `types` - Core TypeScript typings - -- `src` - Main project folder containing Vue Storefront core and themes. This is your app playground so you can modify this folder. - - `modules` - project-specific VSModules and extensions - - `themes` - Vue Storefront core theme along with amp-theme. You can change the active theme in `config/` folder. - - `server` - Additional Express routes - diff --git a/docs/guide/basics/recipes.md b/docs/guide/basics/recipes.md deleted file mode 100644 index be73b9ba35..0000000000 --- a/docs/guide/basics/recipes.md +++ /dev/null @@ -1,272 +0,0 @@ -# FAQ and Recipes - -Below, you can find solutions for the most common problems and advice for typical config changes required by Vue Storefront. If you solved any new issues by yourself, please let us know on [Slack](http://vuestorefront.slack.com) and we will add them to the list so others don't need to reinvent the wheel. - -## Problem starting Docker while installing vue-storefront - -In case you get the following error: - -``` -┌────────────────────────────────────────────────────────────────────────────┐ -│ ERROR │ -│ │ -│ Can't start Docker in background. │ -│ │ -│ Please check log file for details: /tmp/vue-storefront/var/log/install.log │ -└────────────────────────────────────────────────────────────────────────────┘ -``` - -Please check: - -- if there is `docker-compose` command available. If not, please install it; -- please check the output of running `docker-compose up -d` manually inside the `vue-storefront-api` instance. On some production environments, Docker is limited for superusers, in many cases it's just a matter of `/var/run/docker.sock` permissions to be changed (for example to 755) - -## Product not displayed (illegal_argument_exception) - -In a case of - -```json -{ - "root_cause": [ - { - "type": "illegal_argument_exception", - "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [created_at] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead." - } - ], - "type": "search_phase_execution_exception", - "reason": "all shards failed", - "phase": "query", - "grouped": true, - "failed_shards": [ - { - "shard": 0, - "index": "vue_storefront_catalog_1521776807", - "node": "xIOeZW2lTwaprGXh6YLyCA", - "reason": { - "type": "illegal_argument_exception", - "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [created_at] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead." - } - } - ] -} -``` - -See the discussion in [#137](https://github.com/vuestorefront/vue-storefront/issues/137). -Please also check the [Database tool](https://github.com/vuestorefront/vue-storefront/blob/master/docs/guide/data/database-tool.md) - -## HTTP 400 / CORS errors when trying to retrieve detailed product information from API - -If all the other requests work without any issues then it's highly possible that your products have too many attributes. All of them are included in ElasticSearch query sent via GET request which might become too long. Try to limit amount of attributes that are indexed and later used for this product view. - -This answer is valid until [this issue is resolved](https://github.com/vuestorefront/vue-storefront/issues/2167). - -## What's the recommended way to use git on custom development - -One of the options is to do kind of a fork, or just get the whole repo to your git service. Then, if you want to do some VS updates, you probably need to just pull the changes from our origins. Another option will be available as soon as we manage to separate the core as an npm module. - -## How to add custom configurable attributes to the Product page - -Where can we add filters and extra configurable options for the products? I've just added an iPhone X as an example, and I want to add the storage as an option. - -![How to add additional custom attribute?](../images/Apple_iPhone_X.png) - -To do so, you need to modify the theme, changing the following snippet: - -```html -
-
- -
-
- -
-``` - -You must add UI controls for additional configurable attributes. - -## Product name changed to SKU when adding to cart / on the product page - -By default, when the user selects any specific product variant on the `Product.vue` page for `configurable` products, the title, picture, price, and other attributes are changed to corresponding `simple` one (within `product.configurable_children`). If, in the Magento panel, the product names of the variants are set to SKU or anything else, the correct behavior is that the product name changes to it when variant is selected. - -To correct this behavior you can: - -- Modify the [core](https://github.com/vuestorefront/vue-storefront/blob/6a5a569a7e96703b865f841dabbe3c6a1020b3ab/core/store/modules/product/actions.js#L311) - to filter out the `name` attribute from `Object.assign`, which is responsible for copying the attributes from variant -> current product. - -- Modify `mage2vuestorefront` importer to correct the `configurable_children` [product names](https://github.com/vuestorefront/mage2vuestorefront/blob/ca0c4723530b148cfdfb99784168af529e39d599/src/adapters/magento/product.js#L167). - -- Use bound to the `EventBus.$emitFilter('product-after-single', { key: key, options: options, product: products[0] })` event and modify the `product.configurable_children` properties: - -```js - if (product.configurable_children) { - for (let configurableChild of product.configurable_children) { - configurableChild.name = product.name - } - } - } -``` - -## How to get dynamic prices to work (catalog rules) - -After following the Tutorial on [how to connect to Magento2](../installation/magento.md) the prices are updated just after manually running [mage2vuestorefront cli command](https://github.com/vuestorefront/mage2vuestorefront). - -However, there is an option to get the prices dynamically. To do so you must change the config inside `conf/local.json` from the default (`conf/default.json`): - -```json - "products": { - "preventConfigurableChildrenDirectAccess": true, - "alwaysSyncPlatformPricesOver": false, - "clearPricesBeforePlatformSync": false, - "waitForPlatformSync": false, - "endpoint": "http://localhost:8080/api/product" - }, -``` - -to: - -```json - "products": { - "preventConfigurableChildrenDirectAccess": true, - "alwaysSyncPlatformPricesOver": true, - "clearPricesBeforePlatformSync": true, - "waitForPlatformSync": false, - "endpoint": "http://localhost:8080/api/product" - }, -``` - -To make it work, you need have Magento 2 Oauth keys configured in your `vue-storefront-api` - `conf/local.json`. -his change means that each time the product list will be displayed, VS will get fresh prices directly from Magento without the need to re-index Elasticsearch. - -## No products found! after node --harmony cli.js fullreindex - -Take a look at the discussion at [#644](https://github.com/vuestorefront/vue-storefront/issues/644) -Long story short, you need to run the following command within the `mage2nosql` project: - -```bash -node cli.js products --partitions=1 -``` - -## How to sync the products cart with Magento to get the Cart Promo Rules up and running - -To display the proper prices and totals after Magento calculates all the discounts and taxes, you need to modify the `conf/local.json` config (for reference, take a look at `conf/default.json`) by putting there an additional section: - -```json - "cart": { - "synchronize": true, - "synchronize_totals": true, - "create_endpoint": "http://localhost:8080/api/cart/create?token={{token}}", - "updateitem_endpoint": "http://localhost:8080/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "http://localhost:8080/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "http://localhost:8080/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "http://localhost:8080/api/cart/totals?token={{token}}&cartId={{cartId}}" - }, -``` - -To make it work, you need have Magento 2 OAuth keys configured in your `vue-storefront-api` - `conf/local.json`. - -After this change, you need to restart the `yarn dev` command to take the config changes into consideration by the VS. All the cart actions (add to cart, remove from cart, modify the quantity) are now synchronized directly with Magento 2 for both guest and logged-in clients. - - -## How to integrate 3rd party platform? Do you think it could be used with a legacy bespoke PHP eCommerce? - -Yes, I believe it could. You should expose the API accordingly to our [spec](../extensions/extending-api.md) and the second step is to [create a data bridge](https://medium.com/@piotrkarwatka/how-to-connect-3rd-party-platform-to-vue-storefront-df9cb30779f6) to fill out the ElasticSearch with the current catalog data. - -## Is there any documentation on integrating payment gateways? - -We're working on kind of a boilerplate for payment modules. Right now, please just take a look at a [live example](https://github.com/develodesign/vue-storefront-stripe) and try to follow the design patterns from there. The task where boilerplate and docs will show up is [https://github.com/vuestorefront/vue-storefront/issues/923](https://github.com/vuestorefront/vue-storefront/issues/923). - -## Is there any internationalization support? - -Yes, we already have seven languages supported by default (EN, FR, ES, RU, JP, NL, DE) and the [documentation for translations](../core-themes/translations.md). - -The currency is set in the `local.json` configuration file and it's (along with the language) set per instance, so if you have a few languages and countries supported, you need to run (as for now) a few separate instances. - -## If 10k products are on the site, will it create a high bandwidth download when you navigate the site for the first time on a mobile device? - -Not necessarily. Vue Storefront is caching products from the categories browsed. This is the default solution, which can be changed by modifying `core/store/lib/search.js` - -## How to add/remove/change field types in the Elasticsearch schema - -It's done via Database Tool schema changes. Please follow the instructions from the [Database Tool Manual](../data/database-tool.md#chaning-the-index-structure--adding-new-fields--chaning-the-types). - -## How to integrate 3rd party Magento extensions - -Unfortunately, Magento extensions are not compliant with any PWA available solution yet. So if you would like to integrate some existing extensions, the simplest way is to: - -- Expose the data via some Magento 2 REST API endpoints. -- Consume the endpoints in the VS using Vuex stores; [read more](../vuex/introduction.md) about Vuex in Vue Storefront. -- Implement the UI in VS. - -If the extensions are not playing with the User Interface, they will likely work with VS out of the box, as we're using the standard Magento 2 API calls for the integration part. - -## How to support Multistore / Multi website setup - -Please check the [Multistore setup](../integrations/multistore.md) guide for details - -## How to deal with Category filters based on configurable_children - -If you would like to have a Category filter working with configurable products, you need to expand the `product.configurable_children.attrName` to `product.attrName_options` array. This is automatically done by [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) for all attributes set as `product.configurable_options` (by default: color, size). If you want to add additional fields like `manufacturer` to the filters, you need to expand `product.manufacturer_options` field. The easiest way to do so is to set `config.product.expandConfigurableFilters` to `['manufacturer']` and re-run the `mage2vuestorefront` indexer. - -## How to redirect original Magento2 URLs to Vue Storefront - -There is an SEO redirects generator for NGINX -> `https://serverfault.com/a/441517` available within the [vue-storefront-api](https://github.com/vuestorefront/vue-storefront-api/commit/2c7e10b4c4294f222f7a1aae96627d6a0e23f30e). Now you can generate an SEO map redirecting users from the original Magento URLs to Vue Storefront URLs by running: - -```bash -yarn seo redirects — —oldFormat=true | false -``` - -Please make sure that `vue-storefront/config/local.json` setting of `useMagentoUrlKeys` is set to `true` and you have ElasticSearch synchronized with the Magento2 instance using the current version of [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront). - -**Please note:** As `url_key` field must be unique across categories collection. Therefore, we're by default generating its value based on name and category ID. Please [switch this option off](https://github.com/vuestorefront/mage2vuestorefront/#initial-vue-storefront-import) if you'd like to keep the `url_key` as they come from Magento2. - -## You need to choose options for your item message when hit API for add to cart a configurable product - -This is because the demo data dump works on the `demo-magento2.vuestorefront.io` instance attribute ids. Please re-import all product data using [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) - -## Adding custom category filters - -You need to add the attributes you want to have displayed to the `config/local.json` field name is: `products.defaultFilters`: - -```json -"defaultFilters": ["color", "size", "price", "erin_recommends"], -``` - -And then you can use proper controls for each individual filter [here](https://github.com/vuestorefront/vue-storefront/blob/49dc8a2dc9326e9e83d663cc27f8bb0688525f13/src/themes/default/components/core/blocks/Category/Sidebar.vue). - -## Collecting all VSF i18n phrases into a CSV - -It might be very time-consuming to translate the whole project into a foreign language. A good start is to properly collect all i18n phrases into a CSV file. The following line of bash code would get the job done (a pipe-separated CSV file named i18n.csv would be created, adjust accordingly to your needs). - -Execute the following line on your project's root folder: - -```grep --include \*.js --include \*.vue -nrw ./ -e 'i18n.t(' -e '$t(' -h | grep -o -P "(?<=t\(\').*(?=\'\))" | awk -F"'" -v OFS='|' '{ print $1,$1 }' > i18n.csv``` - -The code basically looks into all project files for all ```i18n.t('some string')``` and ```$t('some string') ``` occurrences, parses and extracts the quoted text of each occurrence, and saves it into a pipe-separated CSV file, which might help you get your missing translations. - -## Running vue-storefront-api on a different machine than magento / images not working -When you separate vue-storefront-api and magento2 by putting them on different servers, it is necessary to link the vue-storefront-api machine with magento media folder via network folder. `sshsfs` is suggested for this. -Once the network connection is established, the correct folder needs to be pointed in the vue-storefront-api config -``` -"assetPath": "/../var/magento2-sample-data/pub/media", -``` -It is necessary for the correct product image creation by the vue-storefront-api. If this is not set corectly, images will just be placeholders regardless the fact the rest of the setup works and you can see products and categories correctly synced. - -## Syncing magento and vue-storefront-api servers / Fix for "An error occurred validating the nonce" -There might be an issue with JWT token validation if one of the servers is more than 600 seconds behind the other. The 600 seconds limit is defined on Magento side by its `\Magento\Integration\Model\Oauth\Nonce\Generator::TIME_DEVIATION`. To fix this issue you need to make sure both severs `date` command show the same datetime (or both at least below 10 minute difference). This can be done by utilizing `tzdata` package (sudo dpkg-reconfigure tzdata) or setting it directly with `date` package (e.g: `sudo date --set "23 Mar 2019 12:00:00"`, but providing the current datetime). diff --git a/docs/guide/basics/release-cycle.md b/docs/guide/basics/release-cycle.md deleted file mode 100644 index b70b2bfebd..0000000000 --- a/docs/guide/basics/release-cycle.md +++ /dev/null @@ -1,45 +0,0 @@ -# Release Cycle - -This document describes how Vue Storefront code versions are released. You'll learn what the release process looks like, the acceptance criteria, what our git flow looks like, and which branch to use as a stable one. - -## How Vue Storefront versions are released - -From version 1.9, we release each of the VSF versions in two phases: -- **Release Candidate phase (RC)**, also called "feature version." This version contains all the new features, improvements, and additions to the API, along with minor bug fixes. New features and additions are merged and released only during this phase. The API of features introduced during this phase may slightly change. -- **Stabilization phase** is the one that ends up with a production-ready version. During this phase, we only do stabilization and bug fixing for previously introduced features. No new features and API additions are merged. PRs from the RC version are tested and their API is simplified and/or adjusted according to feedback. - -Assuming the next version is 1.x, the two-month cycle will look like the following: -- v1.x-RC.y—unstable version with cutting-edge features ready to test and feedback. -- v1.x.y—stable version of the software **ready for production use**. - -## How new features are merged - -During the RC phase, features Pull Request with new features after feedback and acceptance are normally merged to the `develop` branch. -After entering the stabilization phase, we are tagging the current develop branch, creating a `release/x` (where `x` is the number of the current version) branch from it and working on stabilization there. During the stabilization phase, new features are merged to develop branch and will be merged in the next `RC` phase. - -## Release cycle flow - -### 1. Development phase - -In the first phase of the cycle, we're mostly focusing on features and improvements. Branches in this phase should be created from the actual `develop`, also PRs should be pointed to this branch. Changes merged to `develop` are available to test on https://test.storefrontcloud.io - -![Development phase](./assets/release-cycle-1.png) - -### 2. Release Candidate phase - -At some point, when milestone for next minor versions is completed, we're creating new branch from `develop` called `release/vx.y` (example: `release/v1.9`). -After that new branch is tagged as first RC for version (example `v1.10.0-rc.1`). Then it's ready for testing by community. -During tests, feedback and stabilization there could be multiple Release Candidate versions on this branch. When improvement is made on this phase, then branch should be created from actual `release/vx.y` and should not contain features at this point - only improvements for current release. -After merging a set of bugfixes and improvements into `release/*` branch, it needs to be tagged as the next RC version and merged into the `develop` branch, to update it. - -![Release Candidate phase](./assets/release-cycle-2.png) - -### 3. Release phase - -When RC version is stable, then release branch is merged into `master` and tagged as next stable version (example `v1.9.0`). After that, the currently merged release branch is deleted and starts a new development phase. If some critical bug is discovered on the current stable version, then a new hotfix (for example `hotfix/v1.9.1`, following [Semantic Versioning rules](https://semver.org/)) branch should be created from actual `master`. After merging and testing hotfixes, this branch (like release branch before) is merged into `master` and tagged following [Semantic Versioning rules](https://semver.org/). - -![Release phase](./assets/release-cycle-3.png) - -### Summary - -An important thing to note is that this releasing cycle ensures stable and reliable Storefront releases. Phases are also not blocking new features—you can develop new features on any phase, but it should be merged only to the `develop` branch and go through whole cycle. diff --git a/docs/guide/basics/security.md b/docs/guide/basics/security.md deleted file mode 100644 index 2cab9e4b98..0000000000 --- a/docs/guide/basics/security.md +++ /dev/null @@ -1,11 +0,0 @@ -# Security notes - -This document is very Work in Progress. Please feel free to contribute additional information on Vue Storefront security. - -Vue Storefront Security rules: - -- Vue Storefront is not processing / storing any sensitive information (no user account data, no payment's data). All this information just gets from the Magento/or another 3rd party backend thru API, -- We're using JWT tokens for authorization, the tokens are stored in the `localStorage` and passed via `GET` parameter to `vue-storefront-api` and then decoded + authorized on the API layer, -- We're not using classical session PLUS not using any cookies, -- All communication is over SSL and it's our very basic assumption (VS doesn't work over plain HTTP - with the distinction for localhost development mode), -- We're using Vue.js - so all information from a database are automatically escaped to prevent the XSS attacks, however as we're not using the cookies any CSRF / XSS is probably low-risk. \ No newline at end of file diff --git a/docs/guide/basics/ssr-cache.md b/docs/guide/basics/ssr-cache.md deleted file mode 100644 index 0c7e55e301..0000000000 --- a/docs/guide/basics/ssr-cache.md +++ /dev/null @@ -1,132 +0,0 @@ -# SSR Cache - -Vue Storefront generates the server-side rendered pages and from version to improve SEO results. In the latest version of Vue Storefront, we added the output cache option (disabled by default) to improve performance - for both Vue Storefront and Vue Storefront API. - -:::warning Caution ! -Vue Storefront API uses exactly the same output cache mechanisms like Vue Storefront with the same configuration and CLI interface. -::: - -The output cache is set by the following `config/local.json` variables: - -```json - "server": { - "host": "localhost", - "port": 3000, - "protocol": "http", - "api": "api", - "useOutputCacheTagging": true, - "useOutputCache": true, - "outputCacheDefaultTtl": 86400, - "invalidateCacheKey": "aeSu7aip", - "invalidateCacheForwarding": false, - "invalidateCacheForwardUrl": "http://localhost:8080/invalidate?key=aeSu7aip&tag=", - }, - "redis": { - "host": "localhost", - "port": 6379, - "db": 0 - }, -``` - -## Dynamic tags - -The dynamic tags config option: `useOutputCacheTagging` - if set to `true`, Vue Storefront is generating the special HTTP Header `X-VS-Cache-Tags` - -```js -res.setHeader('X-VS-Cache-Tags', cacheTags); -``` - -Cache tags are assigned regarding the products and categories that are used on the specific page. A typical `X-VS-Cache-Tags` tag looks like this: - -``` -X-VS-Cache-Tags: P1852 P198 C20 -``` - -The tags can be used to invalidate the Varnish cache, if you're using it. [Read more on that](https://www.drupal.org/docs/8/api/cache-api/cache-tags-varnish). - -## Redis - -If both `useOutputCache` and `useOutputCacheTagging` options are set to `true`, Vue Storefront is using output cache stored in Redis (configured in the redis section of the config file). Cache is tagged with dynamic tags and can be invalidated using a special webhook: - -An example call to clear all pages containing specific product and category: - -```bash -curl http://localhost:3000/invalidate?tag=P1852,C20 -``` - -An example call to clear all product, category, and homepages: - -```bash -curl http://localhost:3000/invalidate?tag=product,category,home -``` - -:::warning Caution ! -We strongly recommend you DO NOT USE output cache in development mode. By using it, you won't be able to refresh the UI changes after modifying the Vue components, etc. -::: - -## CLI cache clear - -You can manually clear the Redis cache for specific tags by running the following command: - -```bash -yarn run cache clear -yarn run cache clear -- --tag=product,category -yarn run cache clear -- --tag=P198 -yarn run cache clear -- --tag=* -``` - -**Note:** The commands presented above works exactly the same way in the `vue-storefront-api`. - -Available tag keys are set in the `config.server.availableCacheTags` (by default: `"product", "category", "home", "checkout", "page-not-found", "compare", "my-account", "P", "C"`) - - -**Dynamic cache invalidation:** Recent version of [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) supports output cache invalidation. Output cache is tagged with the product and categories ID (products and categories used on a specific page). Mage2vuestorefront can invalidate the cache of a product and category pages if you set the following ENV variables: - -```bash -export VS_INVALIDATE_CACHE_URL=http://localhost:3000/invalidate?key=SECRETKEY&tag= -export VS_INVALIDATE_CACHE=1 -``` - -:::warning Caution ! -All the official Vue Storefront data indexers including [magento1-vsbridge-indexer](https://github.com/vuestorefront/magento1-vsbridge-indexer) and [magento2-vsbridge-indexer](https://github.com/vuestorefront/magento2-vsbridge-indexer) support the cache invalidation. If the cache is enabled in both API and Vue Storefront frontend app, please make sure you are properly using the `config.server.invalidateCacheForwardUrl` config variable as the indexers can send the cache invalidate request only to one URL (frontend or backend) and it should be forwarded. Please check the default forwarding URLs in the `default.json` and adjust the `key` parameter to the value of `server.invalidateCacheKey`. -::: - -For `magento1-vsbridge-indexer` and `magento2-vsbridge-indexer` please do use the proper settings in the Magento admin panel. - -:::warning Security note -Please note that `key=SECRETKEY` should be equal to `vue-storefront/config/local.json` value of `server.invalidateCacheKey` -::: - -## Adding new types / cache tags - -If you're adding a new type of page (`core/pages`) and `config.server.useOutputCache=true`, you should also extend the `config.server.availableCacheTags` of new general purpose tag that will be connected with the URLs connected with this new page. - -After doing so, please add the `asyncData` method to your page code assigning the right tag (please take a look at `core/pages/Home.js` for instance): - -```js - asyncData ({ store, route, context }) { // this is for SSR purposes to prefetch data - return new Promise((resolve, reject) => { - if (context) context.output.cacheTags.add(`home`) - Logger.log('Entering asyncData for Home root ' + new Date())() - EventBus.$emitFilter('home-after-load', { store: store, route: route }).then((results) => { - return resolve() - }).catch((err) => { - Logger.error(err)() - reject(err) - }) - }) - }, -``` - -This line: - -```js -if (context) context.output.cacheTags.add(`home`); -``` - -is in charge of assigning the specific tag with current HTTP request output. - - -## Caching strategies on production - -When it comes to caching on production, we made a set of caches at each layer of _Vue Storefront_ infrastructure. There is a section about [_Production setup_](/guide/installation/production-setup.html) in our guide. Additionally read [this article](https://medium.com/the-vue-storefront-journal/caching-on-production-10b00a5614f8) for more details. diff --git a/docs/guide/basics/static-generator.md b/docs/guide/basics/static-generator.md deleted file mode 100644 index b53b23ecc0..0000000000 --- a/docs/guide/basics/static-generator.md +++ /dev/null @@ -1,44 +0,0 @@ -# Static Pages Generator - -Vue Storefront supports static (HTML) page generation rendering mode from version 1.11. By generating the static pages you avoids the need of running `core/scripts/server.js` and you can deploy your site statically to any hosting like Netlify, Github pages or Amazon S3. - -:::warning Important notice -Please note, that the `vue-storefront-api` endpoint should still be available for the application as the client's side rendering still uses the API to fetch the data. -::: - -The server rendered pages are being stored to disk files into `/static` folder. - - -## Available Commands - -To generate the static version of your site first you must build the application in production mode: - -``` -yarn build -``` - -Then you can run the static page generator: - -``` -yarn generate all -``` - -After this command being executed you can find all your category, product and CMS pages - also including the Home Page (`index.html`) rendered in the `/static` folder. - -Because of the absolute links being used by Vue Storefront and the CORS mode the generated files **will only work being served from HTTP server** and the `file://` protocol won't allow you to browse the site. - -There is a static files hosting included and you can run it by executing: - -``` -yarn static-server -``` - -Now you can open your browser and navigate to `http://localhost:3000` which URL is just hosting the `/static` folder. - -## Deployment -All your website and assets are placed under `/static` folder. Please deploy this folder to your hosting provider (root directory of the domain only!). - - -:::warning Important notice -Static Pages generator is an experimental feature and probably still requires some tweaks / improvements. Please do use it carefully. -::: diff --git a/docs/guide/basics/url.md b/docs/guide/basics/url.md deleted file mode 100644 index 6481ed1a5c..0000000000 --- a/docs/guide/basics/url.md +++ /dev/null @@ -1,31 +0,0 @@ -# Custom URL structure - -By default Vue Storefront uses pretty rigid URL structure - where each type of content is kind of "prefixed" like: `/p/MH08/product-slug` or `/c/women-20`. Starting with Vue Storefront **1.9** we added the `url` module including fully customizable url dispatcher. - -When `config.seo.useUrlDispatcher` set to true the `product.url_path` and `category.url_path` fields are used as absolute URL addresses (no `/c` and `/p` prefixes anymore). Check the latest [`mage2vuestorefront`] snapshot and reimport Your products to properly set `url_path` fields. - -For example, when the `category.url_path` is set to `women/frauen-20` the product will be available under the following URL addresses: - -`http://localhost:3000/women/frauen-20` -`http://localhost:3000/de/women/frauen-20` -... - -**Please note:** The `config.products.useShortCatalogUrls` should be set to `false` in order to have `urlDispatcher` working properly. It may interfere with the Url Dispatcher mechanism. From VS 1.10.rc1 the `useShortCatalogUrls` option has been removed. - -## How to customize the mapping mechanism - -The `url` module contains the Vuex Store actions that are responsible for proper mapping the content with URLs. - -By default, the [`url/mappingFallback`](https://github.com/pkarw/vue-storefront/blob/9847f0695df0b54774dceb3c381e64770fd5cfda/core/modules/url/store/actions.ts#L65) action queries first: `product/list` then `category/list` actions to check if provided `url_path` is related to product either category. - -Because it's a Vuex action - You might want to override it from Your custom module to customize the mapping logic (for example for: by using the [Magento2 URL dispatching mechanism](https://devdocs.magento.com/guides/v2.3/graphql/reference/url-resolver.html)). - -With all `product/list` Vuex action calls the `url/registerMapping` action is being called for registering the mappings for every particular product or category. Mappings are cached in `localStorage` so they work in the Offline mode as well + don't require any additional network calls once product/category list has been retrieved. - -## Custom URLs for CMS pages and other content types - -You can use the Url Dispatcher feature with all content types. The only thing You need to change is to customize the `url/mappingFallback` Vuex action to properly query other content sources. - -## Modules - -* [vsf-mapping-fallback](https://github.com/kodbruket/vsf-mapping-fallback) simplifies the process of adding URL mappings diff --git a/docs/guide/components/category-page.md b/docs/guide/components/category-page.md deleted file mode 100644 index ba08fa77d2..0000000000 --- a/docs/guide/components/category-page.md +++ /dev/null @@ -1,44 +0,0 @@ -# Core Category Page - -## Props - -No props - -## Data - -- `pagination` - an object that defines two settings: - - `perPage`of product items to load per page, currently set to 50. - - `offset` that probably defines which page was last loaded, currently set to 0 and isn't changed anywhere. -- `enabled` - Enables/disables paging. When it's disabled, it lazy-loads other products on a scroll. -- `filters.available`, `filters.chosen`- A set of filters that the user has defined on the Category page. There, we have available filters and chosen filter values. -- `products` - Computed property that returns a list of product items of the current category from the Vuex store. -- `isCategoryEmpty` - Computed property that returns true if the product list of the current category is empty. -- `category` - Computed property that returns the current category from the Vuex store. -- `categoryName` - Category name. -- `categoryId` - Category ID. -- `breadcrumbs` - Breadcrumbs for the current category from the Vuex store. -- `productsTotal` - How many products are in the category. -- `lazyLoadProductsOnscroll` - Allows lazy-loading more products on a scroll, by default it's true. - -## Methods - -- `fetchData ({ store, route })` - Prepares query for fetching a list of products of the current category and dispatches `product/list` action that extracts that list. - - - `{ store, route }` - An object consisting of the Vuex store and global router references. - -- `validateRoute ({ store, route })` - This method is called whenever the global `$route` object changes its value. It dispatches `'category/single'` action to load current category object and then calls `fetchData` method to load a list of products that relate to this category. - - `{ store, route }` - An object consisting of the Vuex store and global router references. - -## Events - -### asyncData - -Since the app is using SSR, this method prefetches and resolves the asynchronous data before rendering happens and saves it to the Vuex store. Asynchronous data for the Category page is a list of all categories, category attributes, and list of products for each category. - -### beforeMount - -`filter-changed-category` event listener is initialized. The event is fired when the user selects custom filter value. - -### beforeDestroy - -`filter-changed-category`event listener is removed. diff --git a/docs/guide/components/events-list.md b/docs/guide/components/events-list.md deleted file mode 100644 index 2062b1251f..0000000000 --- a/docs/guide/components/events-list.md +++ /dev/null @@ -1,92 +0,0 @@ -# Events List - -To keep track and make debugging of `$bus.$emit` events across components easier, here is a list of such events that are triggered by components of the default theme. - -## ForgotPass - -On component close: -- [`modal-hide`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L80) - -On send email action: -- [`notification-progress-start`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L95) -- [`notification-progress-stop`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L97) - -On error handler of email send action: -- [`notification-progress-stop`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Auth/ForgotPass.vue#L109) - -## OrderConfirmation - -On mounted lifecycle hook: -- [`modal-show`, `modal-order-confirmation`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue#L65) - -On order confirmation: -- [`modal-hide`, `modal-order-confirmation`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue#L71) - -On order cancelling: -- [`modal-show`, `modal-order-confirmation`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderConfirmation.vue#L75) - -## OrderReview - -On 'Term and conditions' link click: -- [`modal-toggle`, `modal-terms`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/OrderReview.vue#L51) - -## PersonalDetails - -On 'Term and conditions' link click: -- [`modal-toggle`, `modal-terms`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Checkout/PersonalDetails.vue#L151) - -## Newsletter - -On newsletter popup show: -- [`modal-show`, `modal-newsletter`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Footer/Newsletter.vue#L49) - -## Header - -On 'Login to your account' link click: -- [`modal-toggle`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Header/Header.vue#L122) - -## Reviews - -On 'Login to your account' link click: -- [`modal-show`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Reviews/Reviews.vue#L155) - -## SidebarMenu - -On 'Login to your account' link click: -- [`modal-show`, `modal-signup`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/SidebarMenu/SidebarMenu.vue#L201) - -## SubCategory - -On user logout: -- [`user-before-logout`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/SidebarMenu/SubCategory.vue#L131) - -## Language - -On mounted lifecycle hook: -- [`modal-show`, `modal-switcher`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Switcher/Language.vue#L55) - -On component close: -- [`modal-hide`, `modal-switcher`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/blocks/Switcher/Language.vue#L60) - -## LanguageSwitcher - -On showing language popup: -- [`modal-show`, `modal-switcher`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/LanguageSwitcher.vue#L30) - -## NewsletterPopup - -On showing newsletter popup: -- [`modal-show`, `modal-newsletter`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/NewsletterPopup.vue#L54) - -On hiding newsletter popup: -- [`modal-hide`, `modal-newsletter`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/core/NewsletterPopup.vue#L67) - -## Onboard - -On component close: -- [`modal-hide`, `modal-onboard`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/components/theme/blocks/Home/Onboard.vue#L45) - -## Home - -On beforeMount lifecycle hook: -- [`modal-toggle`, `modal-onboard`](https://github.com/vuestorefront/vue-storefront/blob/6c100f978aa79975e4db22be3cefa7f8d38b4c97/src/themes/default/pages/Home.vue#L74) diff --git a/docs/guide/components/home-page.md b/docs/guide/components/home-page.md deleted file mode 100644 index 745aaf0101..0000000000 --- a/docs/guide/components/home-page.md +++ /dev/null @@ -1,25 +0,0 @@ -# Core Home Page - -:::tip Note -Core page has almost zero functionality, everything is in theme component, which definitely needs to be replaced to the core. -::: - -## Props - -No props - -## Data - -`rootCategories` category list to be used for your own custom home page - -## Methods - -No methods - -## Events - -`home-after-load` event can be used to populate the vuex `store` with additional data required by SSR. - -### beforeMount - -Clears Vuex store entries that define the current category by dispatching `category/reset` action. diff --git a/docs/guide/components/modal.md b/docs/guide/components/modal.md deleted file mode 100644 index ce2a54b8d3..0000000000 --- a/docs/guide/components/modal.md +++ /dev/null @@ -1,53 +0,0 @@ -# Modal component - -Simple modal component. Visibility of modal container is based on internal state `isVisible`. We can set this state with the `$emit` event on the global `$bus` event. - -## Basic usage - -### Component markup - -```html - -
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
-
-``` - -### Available events: - -```html - - - -``` - -### Available props - -| Prop | Type | Required | Default | Description | -| -------------- | ------ | -------- | -------------- | ------------------------------------------ | -| name | String | true | | Unique name of modal | -| delay | Number | false | 300 | Timeout to show modal | -| width | Number | false | 0 | Optional fixed width of content, in pixels | -| transitionName | String | false | 'fade-in-down' | Content transition style | - -### Available Methods - -| Method | Argument | Description | -| ------ | -------------- | ---------------------------------------------------------- | -| toggle | state: Boolean | Manually toggles a modal | -| close | | Alias for manually hides a modal. Helpful for Close button | - -### Styles - -Core component doesn't have CSS styles. If you want to see an example of our implementation please look [here](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/Modal.vue) - -### Transitions - -The default theme defines one transition, `fade-in-down`, which can be seen [here](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/css/animations/_transitions.scss). This is the default value for the `transitionName` prop. Further transitions can be defined in a custom theme by following this example. Vue transitions are explained [here](https://vuejs.org/v2/guide/transitions.html). - -To have modal content display immediately, without any transition, just supply an empty string for the `transitionName` prop. For example: - -```html - - - -``` diff --git a/docs/guide/components/product.md b/docs/guide/components/product.md deleted file mode 100644 index 5f893281f2..0000000000 --- a/docs/guide/components/product.md +++ /dev/null @@ -1,106 +0,0 @@ -# Core Product Page - -## Props - -No props - -## Data - -- `loading` - If `true` this indicates the product is currently being loaded from the backend. -- `favorite` - An object that defines 1) if the current product is in the list of favorite products and 2) the name of an icon that will be shown to indicate its status in relation to being in the list of favorite products. -- `compare` - Defines if the current product is in compare list. -- `product` -A computed property that represents the current product that is shown on the page. Initially gets its value from `product/getCurrentProduct` Vuex store getter. Includes all the options like size and color that the user sets on the page. -- `originalProduct` - A computed property that represents the current product in its initial state. Gets its value from`product/getOriginalProduct` Vuex store getter. -- `parentProduct` - A computed property that represents the current product parent product, if any. Gets its value from `product/getParentProduct` Vuex store getter. -- `attributesByCode` - A computed property that returns the list of all product attributes by their code. Gets its value from `attribute/attributeListByCode` Vuex store getter. -- `attributesById` - A computed property that returns the list of all product attributes by their ID. Gets its value from `attribute/attributeListById` Vuex store getter. **This prop is not used anywhere**. -- `breadcrumbs` - A computed property that represents breadcrumbs for the current product. Gets its value from `category-next/getBreadcrumbs` Vuex store getter. -- `configuration` - A computed property that represents an object that shows which attributes (like size and color) are chosen on the product. Gets its value from `product/getCurrentProductConfiguration` Vuex store getter. -- `options` - A computed property that represents an object that shows what attributes (like size and color) with what values are available on the product. Gets its value from `product/getCurrentProductOptions` Vuex store getter. -- `category` - A computed property representing a category object of the current product. Gets its value from `category/getCurrentCategory` Vuex store getter. -- `productName` - A computed property that represents a product name. Gets its value from `category/getCurrentCategory` Vuex store getter. -- `productId` - A computed property representing a product ID. Gets its value from `category/getCurrentCategory` Vuex store getter. -- `isOnCompare` - A computed property that checks if a given product is in compare list. -- `image` - A computed property that defines an image (thumbnail) that will be shown on the page and its size. -- `customAttributes` - this is a subset of `attributesByCode` list of attributes that the current product has. - -## Methods - -### Unbound methods - -#### filterChanged (filterOption) - -Sets attributes on the product according to what the user has chosen on the page. Dispatches `product/configure` action. - -:::tip Note -This method is called when the 'filter-changed-product' event is triggered, but it's not triggered anywhere in the code. -::: - -_Parameters_ - -- `filterOption` - An object that represents an attribute that has changed on the product. - -#### fetchData (store, route) - -Fetches current product data from the backend by dispatching the product/single action. Also dispatches several other actions to get breadcrumbs, product attributes, variants for a configurable product, and to set sub-products if the product is grouped. - -_Parameters_ - -- `store` - Vuex store -- `route` - global router object - -#### loadData ({ store, route }) - -Dispatches `product/reset` action that sets the current product to the original product, nullifies all the configuration and options, then calls the `fetchData` method to load current product data. - -_Parameters_ - -- `{store, route}` - An object that consists of references to the Vuex store and global router object. - -#### stateCheck - -If the current product has a parent, redirects to a parent product page, then check if the current product is in the wishlist or in the compare list, and set `favorite` and `compare` props accordingly. - -_Parameters_ -No parameters - -### Bound methods - -#### validateRoute - -This method is called whenever the global `$route` object changes its value. Calls `loadData` and `stateCheck` methods. - -_Parameters_ -No parameters - -#### addToList - -Adds the current product to the compare by dispatching `compare/addItem` action accordingly. - -_Parameters_ - -- `list` - compare - -#### removeFromList - -Removes the current product from the compare by dispatching `compare/removeItem` action accordingly. - -_Parameters_ - -- `list` - compare - -## Hooks - -### asyncData - -Since the app is using SSR, this method prefetches and resolves the asynchronous data before rendering happens and saves it to the Vuex store. On the Product page, this is done by calling the `loadData` method. - -The `asyncData` fires the `product-after-load` event which can be used to populate the Vuex SSR store for additional data regarding the product. - -### beforeMount - -Calls `stateCheck` method. Defines `product-after-priceupdate` event listener which, if triggered, dispatches `product/setCurrent` action that sets current product object in Vuex store. Also defines `filter-changed-product` event listener, which, if triggered, calls `filterChanged` method. **Currently 'filter-changed-product' event is not triggered anywhere.** - -### beforeDestroy - -Removes event listeners that were defined in `beforeMount` hook. diff --git a/docs/guide/cookbook/checklist.md b/docs/guide/cookbook/checklist.md deleted file mode 100644 index cf0b2ca996..0000000000 --- a/docs/guide/cookbook/checklist.md +++ /dev/null @@ -1,906 +0,0 @@ -# Chef's secret note: Hardcore training for serious business - -:::tip GUIDE -This cookbook doesn't have recipes but provide you with a checklist before launching your _VSF_ on production. You can tick the list off one by one having your instance compared with, so that making your _restaurant_ ready for _Michelin Inspection_. -::: - -In this chapter, we will cover : - -[[toc]] - -:::tip SECURITY BEST PRACTICE -There is a separate site dedicated to PWA security and you can get a handful of advices to apply for your business from industry gurus. -[Headless Security Best Practices](https://headless-security.org/vsf-best-practices.html) -::: - -## 0. Introduction - -Vue Storefront is getting tremendous attention from the market and gain traction from developers with more than 20 sites running on production. In the mean time, Vue Storefront framework evolves with each new release and the docs can hardly catch up with it! - -These training materials are a set of chief-recipes, experiences learned from the trenches. I’m trying to answer how to run Vue Storefront on production, troubleshoot most of the common problems and explain all the hidden features of Vue Storefront that can help you scale the application and solve most common issues. - -Some topics here were taken from [frequently asked questions in our Forum](https://forum.vuestorefront.io/c/help). Some [came from Slack](http://slack.vuestorefront.io). Some came from core-consulting and our own works. One thing in common; each and every recipe is super-crucial for stable Vue Storefront run on production and they all cause some serious results when executed carelessly. - -## 1. Memory leaks -Vue Storefront consists of two Node.js applications: -- `vue-storefront` - which is the frontend app, with the entry point of [`core/scripts/server.js`](https://github.com/vuestorefront/vue-storefront/blob/4ed26d7f1978a9e798edcddf1cf2f970c3e64e4f/core/scripts/server.js#L269) -- `vue-storefornt-api` - which is backend/api app. - -If you're familiar with PHP web application and running PHP on production, be it fastCGI or FPM, _Node.js_ works totally different way. It's not executing `node` process per each request but rather running an internal http server which serves all the subsequent requests. It's single threaded, long running task. That's why it's fairly easy to get into memory leaks problems; especially with the `vue-storefront` app which is far more complex than the API. - -### Protip - -#### 1. First thing first, monitor memory leaks -How do you know you have memory leaks undercover? - -Start with `yarn pm2 status` or `yarn pm2 monit` for details from Vue Storefront root directory. - -The `pm2` [memory usage](http://pm2.keymetrics.io/docs/usage/monitoring/) is growing with each page refresh. - -_PM2_ restarts the process after [1GB of RAM (by default)](https://github.com/vuestorefront/vue-storefront/blob/master/ecosystem.json#L5) is in use; This can be adjusted and together with multiple node `instances` set in `ecosystem.json`, it's pretty good work-around for memory leaks. - -Additionally, there are many ways to trace memory leaks, however we're using the browser tools (Memory profile) most of the time. [Here you have it explained in details](https://marmelab.com/blog/2018/04/03/how-to-track-and-fix-memory-leak-with-nodejs.html). Another useful tools are [New Relic APM](http://newrelic.com) and [Google Trace](https://cloud.google.com/trace/docs/setup/nodejs) - - -#### 2. How to use Vue plugins - -One thing you must avoid is using `Vue.use` multiple times and you can make it sure by calling it always inside `once`. In the Vue Storefront code you can pretty often find a snippet like this: - -```js -import { once } from '@vue-storefront/core/helpers' -once('__VUE_EXTEND_RR__', () => { - Vue.use(VueRouter) -}) -``` -This is a helper __helping__ you safely use plugins. Feel free to use it around with all your plugins and mixins instantiation. - -:::tip FURTHER STUDY -Vue.js docs has pretty good section on [how to avoid Vue.js memory leaks](https://vuejs.org/v2/cookbook/avoiding-memory-leaks.html). -::: - -#### 3. Handling _Events_ in a proper way - -Another thing is to properly handle the events. Each `EventBus.$on` must have its corresponding `EventBus.$off`. You should be carefully turn them on and off in its life cycle. - -Secondly, please avoid the situation where **you bind the event in `created` hook** whenever possible. -The `created` is called in the SSR mode or server side in plain English; But if you bind in `beforeMount` it will be executed only in the CSR (which stands for Client Side Rendering or simply client's browser) which is 99% desired behavior and you won't risk the memory leaks on events. - -#### 4. Stateful Singleton - -Make sure you have `runInNewContext` set to `true` (default value!) in [`core/scripts/utils/ssr-renderer.js`](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/utils/ssr-renderer.js#L16). - -With setting it `false`, the [Stateful Singletons](https://github.com/vuestorefront/vue-storefront/issues/2664) like `RouteManager` or `i18n` we're using will cause the memory leaks at huuuuuge scale. - - -#### 5. Static pages generator - -We do have **Static Pages Generator** - currently experimental feature - that can generate the whole site into a set of static HTML files so they could be served even directly from cloud provider/CDN - no memory leaks possible; what you need to take care of in this mode is cache invalidation (not currently supported but easy to add). [Read more on static page generator](https://github.com/vuestorefront/vue-storefront/pull/3256). - -#### 6. Learn from core team -In case you want to dig deeper any concern related to memory leaks, [find out how core teams have dealt with memory leaks](https://github.com/vuestorefront/vue-storefront/pulls?utf8=%E2%9C%93&q=is%3Apr+memory+is%3Aclosed+leak) in Vue Storefront core - and check if any of those edge cases solved can be an inspiration for your project. - - -
-
- -## 2. SSR Output cache -Vue Storefront supports [Server Side Rendering](https://vuejs.org/v2/guide/ssr.html). In this mode the same code which is executed in browser (CSR; Client Side Rendering), runs on the server in order to generate the HTML markup. The markup, then, gets transfered to the browser, rendered (extremly fast as the browsers have been all optimized to ... render html text in the last 20+ years) and [hydrated](https://ssr.vuejs.org/guide/hydration.html) from the [initial state](https://ssr.vuejs.org/guide/data.html#final-state-injection). During this whole procedure the client side or say browser scripts can use exactly the same code base universally. Another cool feature is that static HTML markup is well indexed by Search Engine crawlers which is extremely important for SEO. - -Usually, Vue Storefront works pretty fast and all SSR requests are finished in between 100-300ms; However, if your database is huge or your server resources are low, or probably the traffic is extremely high you might want to enable the output cache. The other reason is that you might want to use SSR cache to prevent memory leaks or should I say, hide them ;). - -### Protip - -#### 1. SSR Cache docs - -The SSR cache is [pretty well documented in our docs](/guide/basics/ssr-cache.html). What's important; It works for both: `vue-storefront` and `vue-storefront-api`. - -[Read on all the caching mechanisms](https://medium.com/the-vue-storefront-journal/caching-on-production-10b00a5614f8) that Vue Storefront is using. - -#### 2. Using Redis tagging - -With the SSR Output cache mode enabled, the [`core/server.js`](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/scripts/server.js#L187) stores the rendered output pages along with http headers into Redis cache. If the page exists in Redis, then it gets served without even starting the Vue SSR Renderer. - -Vue Storefront uses Redis in order to use the [`redis-tagging`](https://www.npmjs.com/package/redis-tagging) library. Naming and caching are two most difficult areas of software development. Cache tagging helps us to deal with cache invalidation. - -Vue Storefront tags the output pages with [product](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/modules/catalog/helpers/search.ts#L69) and [category](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/modules/catalog/store/category/actions.ts#L121) tags. Then all the indexers including: `magento1-vsbridge-indexer`, `mage2vuestorefront` and `magento2-vsbridge-indexer` will invalidate the cache, by specific _product_ or _category_ _ID_. It means the [`invalidate`](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/scripts/server.js#L156) method will clear out the cache pages tagged with this specific _product id_. - -:::tip NOTE - This URL requires you to pass the invalidation token set in the [config](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/config/default.json#L12). -::: - -You can add any number of the specific cache tags - by just extending the [`availableCacheTags`](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/config/default.json#L11) and/or [pushing the tags to `ssrContext`](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/pages/Home.js#L19) so they can be used by `core/scripts/server`. - -This `context` argument passed to `asyncData()` is actually the same context object used by [`core/scripts/server.js`](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/scripts/server.js#L168), so we're using it as transfer object for passing the tags back and forth between server and Vue.js application. - -#### 3. Invalidating SSR cache - -:::tip NOTE -With the SSR cache enabled (in the `vue-storefront-api` or `vue-storefront` app) please make sure, you're not using the cache on different layer (for example Varnish or nginx). Otherwise the cache invalidation mechanism won't work. -::: - -The dynamic tags config option: `useOutputCacheTagging` - if set to `true`, Vue Storefront generates the special HTTP Header `X-VS-Cache-Tags` - -```js -res.setHeader('X-VS-Cache-Tags', cacheTags); -``` - -Cache tags are assigned regarding the products and categories that are used on the specific page. A typical `X-VS-Cache-Tags` tag looks like this: - -``` -X-VS-Cache-Tags: P1852 P198 C20 -``` - -The tags can be used to invalidate the Varnish cache if you use it. [Read more on that](https://www.drupal.org/docs/8/api/cache-api/cache-tags-varnish). - -:::tip NOTE - All the official Vue Storefront data indexers including [magento1-vsbridge-indexer](https://github.com/divanteLtd/magento1-vsbridge-indexer) and [magento2-vsbridge-indexer](https://github.com/vuestorefront/magento2-vsbridge-indexer) support the cache invalidation. - - If the cache is enabled in both API and Vue Storefront frontend app, please make sure you properly use the `config.server.invalidateCacheForwardUrl` config variable as the indexers can send the cache invalidate request only to one URL (either frontend or backend) and it **should be forwarded** to the other. Please check the default forwarding URLs in the `default.json` and adjust the `key` parameter to the value of `server.invalidateCacheKey`. -::: -
-
- - -## 3. Avoiding prices desynchronization - -Vue Storefront indexers (`magento2-vsbridge-indexer`, `magento1-vsbridge-indexer`, `mage2vuestorefront`) all store the product price (before/after catalog rules applied) into the Elasticsearch. But Elasticsearch can be easily out of sync or the synchronization can be lagged. To avoid the risk of displaying an incorrect price to the customers, Vue Storefront has at least 3 mechanisms - with the `alwaysSyncPlatformPricesOver` on the top. - -:::tip NOTE -If you're using the `mage2vuestorefront` for syncing the products please make sure you're syncing the prices **after catalog rules** applied. For this purpose we have special flags to be set on: - -```bash -export PRODUCTS_SPECIAL_PRICES=true -export PRODUCTS_RENDER_PRICES=true -node --harmony cli.js products --removeNonExistent=true --partitions=1 -``` -::: - -### Protip - -#### 1. _alwaysSyncPlatformPricesOver_ to be in sync - -When the `config.products.alwaysSyncPlatformPricesOver` option is on, Vue Storefront will update the visible price on all the listings and product detail pages **directly from source web store, say, Magento**. (Magento represents source web store hereunder) The code in charge for this operation is located in the [`doPlatformPricesSync`](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/catalog/helpers/index.ts#L212) helper which is called from the [`tax/calculateTaxes`](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/catalog/store/tax/actions.ts#L74) action. - -:::tip NOTE -This mode works whenever the price is calculated in either server or client's side (`config.tax.calculateServerSide` option). -::: - -Check if the way [Vue Storefront syncs the price](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/catalog/helpers/index.ts#L216) is exactly what you need, and not [override this action](https://docs.vuestorefront.io/guide/cookbook/module.html#_2-2-recipe-b-override-vuex-store-with-extendstore). - - -#### 2. _alwaysSyncPlatformPricesOver_ has 2 options - -The `alwaysSyncPlatformPricesOver` mode has two additional options: - -1. Clear the price before sync: `config.products.clearPricesBeforePlatformSync` - when set to `true`, users won't see the price cached in Elasticsearch before getting the new price from Magento -2. Synchronous mode - `config.products.waitForPlatformSync` - by default the price sync runs in parallel to displaying the product or category content, that is, _asynchronous_. We can make it synchronous (waiting for this process to finish) in order to have just the current price from Magento rendered in the HTML markup (SSR; otherwise the price in SSR will be from Elasticsearch). - -More than that - Vue Storefront always gets the **platform totals** (the final price visible in the shopping cart and the order summary) from Magento or any other backend. There is then no risk your customers see the product with incorrect price. - - -
-
- -## 4. Avoiding stock desynchronization - -Pretty much the same case as with the price can occur with the product stocks. By default, all the indexers set the [stock information right into the product object](https://github.com/vuestorefront/vue-storefront-integration-sdk/blob/tutorial/Format-product.md) as follows : - - - In the main structure of `product.stock` - - In `product.configurable_children.stock` for `configurable_children`. - - This information can be outdated. - -### Protip - -#### 1. How it stays in sync in real time - - Vue Storefront **by default** checks the current stock information when: - - - [**user adds product to the cart**](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/cart/store/actions/itemActions.ts#L53) - this is an asynchronous process (similar one is run when browsing the product variants - you can get info like `0 items available` when switching colors and sizes); `Checkout.js` is waiting for all the results from the [`stock/queueCheck`](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/pages/Checkout.js#L69) calls. - - when the **cart is synced** with the server - eCommerce backend [checks the product availability once again](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/cart/store/actions/mergeActions.ts#L45) and [notify user](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/cart/components/AddToCart.ts#L31) if the product can't be added to the cart or restores previous quantity (if changed). - - when the `filterOutUnavailableVariants` mode is on and the user a) enters the product page, b) browses the category pages. - - The `config.products.filterOutUnavailableVariants` mode is pretty important thing because only by having this mode switched on you can be sure we're **not displaying unavailable variants**. When it's set to `true` Vue Storefront takes the stock information out of Magento and updates the `product.stock` info for the whole product list + product page (current product). Then it removes all the `configurable_children` that are not avaialable. [See the detailed implementation](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/catalog/helpers/index.ts#L121). - - -#### 2. Additional options - -There are two additional settings for this mode on: - - `config.products.configurableChildrenStockPrefetchStatic` - when this is `true`, Vue Storefront prefetches the stock info for the statically set number of product, it can be configured by `config.products.configurableChildrenStockPrefetchStaticPrefetchCount`. - - `config.products.configurableChildrenStockPrefetchDynamic` - when this is set to true, Vue Storefront prefetches the stock info for any visible product; it's done in the [`ProductTile.vue`](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/src/themes/default/components/core/ProductTile.vue#L108) - Make sure your theme supports this. - -We've got the limited support for Magento MSI in the default implementation. [Make sure you've got it enabled when on Magento 2.3.x](https://github.com/vuestorefront/vue-storefront-api/pull/226). - -:::tip NOTE -This feature might then be used for the **Donut caching** strategies related to [Tip 2 - SSR cache](#_2-ssr-output-cache). -::: - -:::tip NOTE - If you want to bypass Magento stock calls there is a way by getting the data with the same format as `https://vue-storefront-api/api/stock/list` that returns it but from Elasticsearch. It should be a drop-in replacement - I mean [changing the `stock.endpoint`](https://github.com/vuestorefront/vue-storefront/blob/bb9044d6aaa36d4881733876f4646fabe7b6e102/config/default.json#L368) to this new one. Et voila: you skip asking Magento, still having this 'cache hole punching' with `config.products.filterOutUnavailableVariants` mode on -::: - -There is a ready-made endpoint for getting stock from Elasticsearch (not from Magento) is [here #PR330](https://github.com/vuestorefront/vue-storefront-api/pull/330). - -:::warning TROUBLESHOOTING -If the non-existing variants won't disappear that means some frontend work on your side needs to be done. -::: -I mean - with this `filterOutUnavailableVariants` setting, we're pulling the current stock info to `product.stock` and `product.configurable_children.stock` properties. As those properties updated we're then removing the out-of-stock `configurable_children`. -If the variants are still available then [take a look at this line](https://github.com/vuestorefront/vue-storefront/blob/48233bfa4575be218a51cccd2474ec358671fc01/core/modules/catalog/store/product/actions.ts#L629) and there should be a change made like - -from: - -```js -if (isServer) { - subloaders.push(context.dispatch('setupBreadcrumbs', { product: product })) - subloaders.push(context.dispatch('filterUnavailableVariants', { product: product })) -} else { - attributesPromise.then(() => context.dispatch('setupBreadcrumbs', { product: product })) // if this is client's side request postpone breadcrumbs setup till attributes are loaded to avoid too-early breadcrumb switch #2469 - context.dispatch('filterUnavailableVariants', { product: product }) // exec async -} -``` -to: - -```js - subloaders.push(context.dispatch('filterUnavailableVariants', { product: product })) -if (isServer) { - subloaders.push(context.dispatch('setupBreadcrumbs', { product: product })) -} else { - attributesPromise.then(() => context.dispatch('setupBreadcrumbs', { product: product })) // if this is client's side request postpone breadcrumbs setup till attributes are loaded to avoid too-early breadcrumb switch #2469 -} -``` - -Just to make sure that attribute filtering always takes place before rendering the PDP (Product Detail Page). - -
-
- -## 5. How Vue Storefront calculates prices and taxes - - -### Protip - -#### 1. Two modes for tax calculation - -Vue Storefront has two modes of calculating the product price : -- Client side (when `config.tax.calculateServerSide` is set to `false`) - that can be useful in case the tax should be recalculated based on the address change. -- Server side (when `config.tax.calculateServerSide` is set to `true`) - which is default. - -Depending on the mode, taxes are calulated by [`taxCalc.ts` client side](https://github.com/vuestorefront/vue-storefront/blob/5f2b5cd6a8496a60884c091e8509d3b58b7a0358/core/modules/catalog/helpers/taxCalc.ts#L74) or [`taxcalc.js` server side](https://github.com/vuestorefront/vue-storefront-api/blob/d3d0e7892cd063bbd69e545f3f2b6fdd9843d524/src/lib/taxcalc.js#L251-L253). - -You may see that both these files apply **exactly** the same logic. - -#### 2. Factors considered for tax rate - -In order to calculate the prices and taxes we need first to get the proper tax rate. It's based on [`taxrate`](https://github.com/vuestorefront/vue-storefront-integration-sdk#taxrate-entity) entity, stored in the Elasticsearch. Each product can have the property [`product.tax_class_id`](https://github.com/vuestorefront/vue-storefront/blob/5f2b5cd6a8496a60884c091e8509d3b58b7a0358/core/modules/catalog/helpers/taxCalc.ts#L213) set. Depending on its value, Vue Storefront applies the `taxrate` and the [country and region to the filter](https://github.com/vuestorefront/vue-storefront/blob/5f2b5cd6a8496a60884c091e8509d3b58b7a0358/core/modules/catalog/helpers/taxCalc.ts#L226). - -:::tip NOTE - We're currently not supporting searching the tax rules by neither `customer_tax_class_id` nor the `tax_postcode` fields of `taxrate` entity. Pull Requests are more than welcome ;) -::: - -#### 3. Calculation - -After getting the right tax rate we can calculate the prices. - -We've got the following price fields priority in the VSF: - -- `final_price` - if set, depending on the `config.tax.finalPriceIncludesTax` - it's taken as final price or Net final price, -- `special_price` - if it's set and lower than `price` it will replace the `price` and the `price` value will be set into `original_price` property, -- `price` - if set, depending on the `config.tax.sourcePriceIncludesTax` - it's taken as final price or Net final price. - -Depending on the `config.tax.finalPriceIncludesTax` and `config.tax.sourcePriceIncludesTax` settings, Vue Storefront calculates the price and stores them into following fields. - -Product Special price: -- `special_price` - optional, if set - it's always Net price, -- `special_price_incl_tax` - optional, if set - it's always price after taxes, -- `special_price_tax` - optional, if set it's the tax amount. - -Product Regular price: -- `price` - required, if set - it's always Net price, -- `price_incl_tax` - required, if set - it's always price after taxes, -- `price_tax` - required, if set it's the tax amount, - -Product Final price: -- `final_price` - optional, if set - it's always Net price, -- `final_price_incl_tax` - optional, if set - it's always price after taxes, -- `final_price_tax` - optional, if set it's the tax amount, - -Product Original price (set only if `final_price` or `special_price` are lower than `price`): -- `original_price` - optional, if set - it's always Net price, -- `original_price_incl_tax` - optional, if set - it's always price after taxes, -- `original_price_tax` - optional, if set it's the tax amount. - -:::tip NOTE - The prices are set for all `configurable_children` with the exact same format -::: - -:::tip NOTE -If any of the `configurable_children` has the price lower than the main product, the main product price will be updated accordingly. -::: - -
-
- -## 6. Limiting SSR HTML size a.k.a. INITIAL_STATE optimization - -One of the key side-effects of the [Server Side Rendering](https://vuejs.org/v2/guide/ssr.html) is the need to provide the initial Vuex state right to the browser just before the page is hydrated. - -Hydration means - Vue.js populates the statically generated HTML markups with virtually generated (CSR) Vue.js component tree. **Only after this process, site becomes interactive**. Even slightly different markup might cause SSR hydration errors. Therefore, Vue.js requires us to [output the `window.__INITIAL_STATE__`](https://github.com/vuestorefront/vue-storefront/blob/8f3ce717a823ef3a5c7469082b8a8bcb36abb5c1/core/client-entry.ts#L29) which is then used to **replace** the Vuex initial state. Then, the [app gets hydrated](https://github.com/vuestorefront/vue-storefront/blob/develop/core/client-entry.ts#L111) by `app.mount()` call. - -The only problem is, that the `__INITIAL_STATE__` can be really huuuuuuuge. On category pages, including a lot of product listings it can be in megabytes! -Vue Storefront provides you with a few mechanisms to control the initial state. - -### Protip - -#### 1. Filter `__INITIAL_STATE__` -1. Vue Storefront provides you a mechanism to control the `__INITIAL_STATE__` [based on the `config.ssr.initialStateFilter`](https://github.com/vuestorefront/vue-storefront/blob/8f3ce717a823ef3a5c7469082b8a8bcb36abb5c1/core/scripts/utils/ssr-renderer.js#L40) fields list. So you can remove the fields from `__INITIAL_STATE__` - even using the `.` notation. So you can put `attribute` on the list to remove the whole state for `attribute` Vuex module OR you can specify `attribute.list_by_code` to remove just that. By using this mechanism, you can process much more data in the SSR than are sent to the browser (see point no. 2 which is just about opposite approach to limit the set of processed information). - -2. You might also want to use the [`config.entities.*.includeFields`](https://github.com/vuestorefront/vue-storefront/blob/8f3ce717a823ef3a5c7469082b8a8bcb36abb5c1/config/default.json#L170) filter. These lists of fields are set to limit the number of fields [loaded from Elasticsearch](https://github.com/vuestorefront/vue-storefront/blob/8f3ce717a823ef3a5c7469082b8a8bcb36abb5c1/core/lib/search.ts#L31). If you add any new field to your entity though, please make sure you also include it in the `includeFields` list. - -:::warning CAUTION -With these mechanisms, you must be fully aware of the **hydration damage** they might cause. In order to prevent any hydration issues, you might use [`lazy-hydrate`](https://github.com/maoberlehner/vue-lazy-hydration) that will let you control the hydration flow for specific parts (components) on the page. Especially the [manual hydration](https://github.com/maoberlehner/vue-lazy-hydration#manually-trigger-hydration) can be useful. -::: - -The general rule of thumb is that **when you remove anything from the intial state** then you should: -- load this data ASAP in the client side (eg. in `beforeMount`). -- hydrate the component **only after** the data was loaded. - -### 2. Best practice for hydration from core team - -See how we did it for [`Category.vue`](https://github.com/vuestorefront/vue-storefront/blob/ab27bfbd8abef5f1d37666a38fa0387f50ba6eca/src/themes/default/pages/Category.vue#L70) - where the hydration is manually triggered by the [`loading`](https://github.com/vuestorefront/vue-storefront/blob/ab27bfbd8abef5f1d37666a38fa0387f50ba6eca/src/themes/default/pages/Category.vue#L70) flag. - -:::tip TIP - Please make sure if you're loading your category tree dynamically - as the category trees can be truly heavy with all these subcategories included. By default Vue Storefront offers this [dynamic category prefetching from v1.7](/guide/basics/configuration.html#dynamic-categories-prefetching). -::: - -You can save up to 30-40% of the page size which positively improve the Lighthouse/Pagespeed scores. However not always improves the User Experience - as the lazy hydration typically requires you to fetch the required data by another network call (which can be skipped by the initial state mechanism). - -Of course, in the end please make sure that you compress (gzip + minify) the SSR output - probably on [nginx level](https://www.digitalocean.com/community/tutorials/how-to-increase-pagespeed-score-by-changing-your-nginx-configuration-on-ubuntu-16-04) or using the [compression](https://www.npmjs.com/package/compression) and/or [minify](https://www.npmjs.com/package/express-minify) middleware added to the [`core/scripts/server.js`](https://github.com/vuestorefront/vue-storefront/blob/8f3ce717a823ef3a5c7469082b8a8bcb36abb5c1/core/scripts/server.js#L116) -
-
- -## 7. Url Dispatcher explained - -Starting with Vue Storefront 1.9 we support [custom url structure](https://docs.vuestorefront.io/guide/basics/url.html). `UrlDispatcher` is enabled by default in the [`config.seo.useUrlDispatcher`](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/config/default.json#L29). - -The business logic of the dispatcher has been implemented as a [Vue router guard](https://router.vuejs.org/guide/advanced/navigation-guards.html) - [`beforeEach`](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/core/modules/url/router/beforeEach.ts#L41). - -The dispatcher first runs the [`url/mapUrl`](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/core/modules/url/store/actions.ts#L42). This action first checks the `state.dispatcherMap` for the previously registered URL mapping. If no mapping is set then dispatcher checks the [`localStorage` cache](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/core/modules/url/store/actions.ts#L51) and only after that the `mappingFallback` action is called. - -It's the place where the true mapping takes place. By default, Vue Storefront first checks the URL against Elasticsearch, `product` entities - using the `url_path` as a filter. If it's not found (statistically products are 10x more frequently browsed by URL than categories because of their count), then the request to `category` collection is made. - -Once the route was mapped it's [registered](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/core/modules/url/store/actions.ts#L56) in the `dispatcherMap` in order to not execute the additional network request in the future. - -The optimization hack is that [`category-next/loadCategoryProducts`](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/core/modules/catalog-next/store/category/actions.ts#L100) already registers the mapping - so clicking the product from the category list doesn't require any network call to get the proper route data. - -As you've might seen the `url/mapUrl` returns the data in a very similar format to routes collection used by vue-router. **It's not the real route though**. It's converted to `Route` object by the `processDynamicRoute` helper before being processed by the router itself. To avoid any user redirections we're using the `RouterManager` to [add this route to the `vue-router` routing table](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/core/modules/url/router/beforeEach.ts#L43) and forward the user to this new, exact match route in order to render the proper page. - -This mechanism is pretty flexible as you may add the dynamic routes on the fly. There is even a [community module](https://github.com/kodbruket/vsf-mapping-fallback) letting you map the url routes programmatically. - -:::tip NOTE - The [`processDynamicRoute`](https://github.com/vuestorefront/vue-storefront/blob/3e4191e5e4b1bfc5b349f5d7cff919c695168125/core/modules/url/helpers/index.ts#L26) does convert the `routeData` from `url/mapUrl` to **real** vue `Route` object. It works like it's searching through all the routes registered by `theme` and `modules`. Example: -::: - -If your route data is (`routeData`): - -```js -{ - name: 'configurable-product', - params: { - slug: product.slug, - parentSku: product.sku, - childSku: params['childSku'] ? params['childSku'] : product.sku -} -``` - -and your `theme/router/index.js` consists of the following definition: (`userRoute`) - -```js - { name: 'configurable-product', path: '/p/:parentSku/:slug/:childSku', component: Product } -``` - -then `processDynamicRoute` helper will return the `Route` object created by merging the `userRoute` with `routeData` - -```js - Object.assign({}, userRoute, routeData, { path: '/' + fullRootPath, name: `urldispatcher-${fullRootPath}` }) -``` - -`fullRootPath` is the url processed by the dispatcher. This new, virtual route is added to the vue-router routing table and the user is forwarded to it. So you may see that `url` module can be switched on/off easily as it uses the on-top mechanism over the existing vue-router - mapping the virtual urls to existing theme or module routes. - -:::tip NOTE - In order to have it up and running please make sure your `products` and `categories` do have the `url_path` properly set and unique. -::: -
-
- -## 8. Multistore configuration explained - -You can read about the [basic Multistore configuration in the official docs](/guide/integrations/multistore.html#changing-the-ui-for-specific-store-views). Vue Storefront supports multistore based on the `StoreView` level. - -`StoreView` is a configuration context object, set by the Vue Storefront per each request - accessible via [`currentStoreView()`](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/core/lib/multistore.ts#L33) helper from [`multistore.ts`](https://github.com/vuestorefront/vue-storefront/blob/develop/core/lib/multistore.ts). - -One `StoreView` generally means a combination of Language + Currency. - -:::tip EXAMPLE -If you have a store per country, that supports two languages (e.g. Switzerland supporting EURO currency and both French + German languages) you'll probably end up with a pair of two `StoreViews`: (EUR; DE) + (EUR; FR). Each `StoreView` has its own unique name that is used to differentiate and switch the sites. -::: - -Vue Storefront `StoreViews` allows you to differentiate all the basic settings per specific site. [See the config](https://github.com/vuestorefront/vue-storefront/blob/af640f3aa0372308db534786fea587b24e8e87d3/config/default.json#L91): - -```json - "storeViews": { - "multistore": false, - "commonCache": true, - "mapStoreUrlsFor": ["de", "it"], - "de": { - "storeCode": "de", - "disabled": true, - "storeId": 3, - "name": "German Store", - "url": "/de", - "appendStoreCode": true, - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_de" - }, - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - }, - "it": { - "storeCode": "it", - "disabled": true, - "storeId": 4, - "name": "Italian Store", - "url": "/it", - "appendStoreCode": true, - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_it" - }, - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "IT", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "fullCountryName": "Italy", - "fullLanguageName": "Italian", - "defaultCountry": "IT", - "defaultLanguage": "IT", - "defaultLocale": "it-IT", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - } - } - ``` -### Protip - -#### 1. Create the individual indexes per each specific `StoreView` - -First of all - we have a separate Elasticsearch config per each storeView. This means you can have `product`, `categories` and `attributes` text attributes translated and stored - each in the separate Elasticsearch indices. - -Our [default indexer](https://github.com/vuestorefront/mage2vuestorefront#multistore-setup) and the [magento2-vsbridge-indexer](https://github.com/vuestorefront/magento2-vsbridge-indexer) both support the multistore indexing. - -#### 2. Setup the `storeViews` section in the `config/local.json` - -Each _storeView_ must have the unique `code` (`it` and `de` in the example above) set + elasticsearch section pointing to the corresponding index. - -:::tip NOTE -Remember to populate the same configuration [within the `vue-storefront-api` config file](https://github.com/vuestorefront/vue-storefront-api/blob/b4198929ef435e20162a192ea2a02cb25e552d45/config/default.json#L50). Please make sure that the `config.availableStores` collection contains all the `storeCodes` you'll be passing to the API as well. -::: - -:::tip NOTE -The multistore business logic is applied only when the `config.storeViews.multistore` is set to `true` (the default value is: `false`). -::: - -The `storeCode` parameter [will be appended as a query parameter (`?storeCode`)](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/core/lib/multistore.ts#L105) to all `vue-storefront-api` requests that will let API know which backend API endpoints to query. By default - with Magento 2 [we're adding the proper `storeCode` to the API request calls](https://github.com/vuestorefront/vue-storefront-api/blob/b4198929ef435e20162a192ea2a02cb25e552d45/src/platform/magento2/util.js#L7). However you can even [differentiate the base url or Magento 2 API credentials if you like](https://github.com/vuestorefront/vue-storefront-api/blob/b4198929ef435e20162a192ea2a02cb25e552d45/src/platform/magento2/util.js#L20). - -#### 3. Vue Storefront `storeCode` resolver - -Vue Storefront sets `currentStoreView` value as one of the first things processing the request. It's done in the [`app.ts:createStore`](https://github.com/vuestorefront/vue-storefront/blob/af640f3aa0372308db534786fea587b24e8e87d3/core/app.ts#L73) function. The `storeCode` is retrived from the [server context](https://github.com/vuestorefront/vue-storefront/blob/af640f3aa0372308db534786fea587b24e8e87d3/core/server-entry.ts#L63) or [from the current route](https://github.com/vuestorefront/vue-storefront/blob/af640f3aa0372308db534786fea587b24e8e87d3/core/server-entry.ts#L67). - -The [`storeCodeFromRoute`](https://github.com/vuestorefront/vue-storefront/blob/develop/core/lib/storeCodeFromRoute.ts) helper supports two ways of obtaining the current store code: - -1) from the url path: https://test.storefrontcloud.io/de vs. https://test.storefrontcloud.io/it -2) from the [url domain name and path](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/core/lib/storeCodeFromRoute.ts#L30); this way lets you run Vue Storefront multistore on multiple domains. - -:::tip NOTE - You can pass the `storeCode` via server context as well. Server context is set by the [`core/scripts/server.ts`](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/core/scripts/utils/ssr-renderer.js#L110) - and it's sourced from `ENV.STORE_CODE` or if you're using a HTTP Proxy (like nginx) - from the request header of `x-vs-store-code`. This way you can differentiate store view instances by many different ways and not only by the domain/url. -::: - -#### 4. Routing - -Vue Storefront adds all the routes to the routing table using [current `storeView` code prefix](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/src/themes/default/index.js#L31). If your [theme/router/index.js](https://github.com/vuestorefront/vue-storefront/blob/develop/src/themes/default/router/index.js) has the following routes defined, and the `currentStoreVioew().storeCode === 'de'` - -```js - let routes = [ - { name: 'checkout', path: '/checkout', component: Checkout }, - { name: 'legal', path: '/legal', component: Static, props: {page: 'lorem', title: 'Legal Notice'}, meta: {title: 'Legal Notice', description: 'Legal Notice - example of description usage'} }, - { name: 'privacy', path: '/privacy', component: Static, props: {page: 'lorem', title: 'Privacy'} }, -``` - -Then the [`setupMultistoreRoutes`](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/core/lib/multistore.ts#L172) helper will add these routes to `vue-router` as: - -```js - let routes = [ - { name: 'checkout', path: '/de/checkout', component: Checkout }, - { name: 'legal', path: '/de/legal', component: Static, props: {page: 'lorem', title: 'Legal Notice'}, meta: {title: 'Legal Notice', description: 'Legal Notice - example of description usage'} }, - { name: 'privacy', path: '/de/privacy', component: Static, props: {page: 'lorem', title: 'Privacy'} }, -``` - -The business logic of modifying the route configs is embeded in the [`localizedRouteConfig`](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/core/lib/multistore.ts#L189) helper. - -:::tip NOTE - When you're using the `storeCode` resolver, based on `domain + path` schema then you should set the `config.storeViews.*.appendStoreCode` to `false`. This option prevents `localizedRouteConfig` helper from adding the `storeCode` as a path so the store views can be differentiated based on the `currentStoreView().url` instead - which supports `domain + path`. -::: - -:::tip NOTE -Please make sure you're creating the links within your theme using the same `localizedRoute` helper. This helper supports string URLs: -```html -{{ - page.title -}} -``` -or route objects: -```html - -``` -::: - -:::tip NOTE -The `UrlDispatcher` feature - available from Vue Storefront 1.9 supports the multistore routes as well. The `url_path` field passed to [`url/mapUrl`](https://github.com/vuestorefront/vue-storefront/blob/9dca392a832ba45e9b1c3589eb84f51fbc2e8d6e/core/modules/url/store/actions.ts#L46) action takes the full url - including `storeCode` as an entry parameter. You might want to use [vsf-mapping-fallback](https://github.com/kodbruket/vsf-mapping-fallback) for some overrides. -::: - -#### 5. Customizing the theme per store view - -You can run all the `StoreViews` within one, single Vue Storefront instance. It's the default mode. The `StoreViews` are then selected based on the url/path/incoming request headers or `env` variables. As simple as it can get, this mode won't let you apply totally different themes for each individual `StoreView`. It's because the theme files are bundled within `app.js` bundle provided to the client. Having all themes bundled in will generate a really huge JS bundle and slow down the page in the end. - -You can still customize some UI elements per `storeView` using conditional `v-if` logic and loading specific components within single theme. - -:::tip NOTE -You can also override some root-level components (like `pages/Category.vue`) by modifying the `theme/router/index.js` routing schema by adding the specific store-view based urls directly in the routing table. -::: - -If you really need to use different themes per each individual `storeView`, then the best way would be to deploy and execute separate Vue Storefront node instances per each store view (e.g. `de` running on port 3000, `it` on 3001 etc); Then make sure your proxy service routes the request to the proper instance. The instances can have different configs, including different `config.theme` parameter. - -Your `nginx` config for this scheme will be something like this: - -``` -ProxyPass / http://localhost:3000/ -ProxyPassReverse / http://localhost:3000/ - -ProxyPass /de http://localhost:3001/de -ProxyPassReverse /de http://localhost:3001/de - -ProxyPass /it http://localhost:3002/it -ProxyPassReverse /it http://localhost:3002/it -``` - -
-
- -## 9. HTML minimization, compression, headers - -The HTML generated by Vue Storefront can be pretty ... well long :) We put a lot of CSS and JS in this single file. More than that there is the whole Vuex state included in the `window.__INITIAL_STATE__` dump in order to support the Client Side data hydration. - -To minimize the time the browser will need to download the initial SSR-rendered HTML, there are a few tricks to be implemented. - -### Protip - -#### 1. Compression - -You might want to enable the `gzip/deflate` or `brotli` compression in the first-line HTTP Server of your choice: `nginx`, `varnish` or `apache`. The good news is that Vue Storefront supports the `gzip` compression as well using the [`compression` Express.js middleware](https://github.com/expressjs/compression). It's enabled by default [in the production mode](https://github.com/vuestorefront/vue-storefront/blob/develop/src/modules/compress/server.ts). - -#### 2. HTML Minimization - -The second option is to minimize the HTML, CSS and JS by just removing the white characters. This option is by default on - by the [`config.server.useHtmlMinifier`](https://github.com/vuestorefront/vue-storefront/blob/5f1e36d611c983de252ce08dea78726b6e10044d/config/default.json#L8) switch. We use the [html-minifier](https://www.npmjs.com/package/html-minifier) npm package in order to get the work done here. You might want to adjust the [`html-minifier` configuration](https://www.npmjs.com/package/html-minifier#options-quick-reference) by tweaking the `config.server.htmlMinifierOptions` property. - - -#### 3. Headers - -You can set various set of different HTTP headers in order to tweak the edge-caching strategies with your CDN/proxy. You can set the output headers in the `asyncData` of any root-level component. - -```js - -``` - -
-
- -## 10. Production catalog indexing + cache invalidation - -Although many Vue Storefornt projects are being developed using [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront/tree/master/src) indexer - it's definitely not a production ready solution. Mostly because of the performance and it doesn't fully support on-demand indexing (indexing only the changes of products and categories, in real time). - -Because of this limitation we created a set of [native indexers](https://medium.com/the-vue-storefront-journal/native-magento-data-indexer-aec3c9ebfb). - -The indexers are available for: -- [Magento 1](https://github.com/divanteLtd/magento1-vsbridge-indexer) -- [Magento 2](https://github.com/vuestorefront/magento2-vsbridge-indexer) - -The native indexer updates the Elasticsearch index in the very same format as the `mage2vuestorefront`. Our intention was to speed up the indexation process and make it more reliable. With native indexer, we were able to use the Magento 2 ORM and events to optimize the indexation process. Please do use this module instead of `mage2vuestorefront` if you experience any issue regarding indexing performance. Both projects are currently officially supported by the Vue Storefront Core team. - -When the SSR caching is enabled (see [Tip 2](#_2-ssr-output-cache)), you need to make sure the indexers are properly configured to refresh exactly the pages that changed. - -We tag the output pages with [product](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/modules/catalog/helpers/search.ts#L69) and [category](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/modules/catalog/store/category/actions.ts#L121) tags. Then all the indexers including: `magento1-vsbridge-indexer`, `mage2vuestorefront`, `magento2-vsbridge-indexer` will invalidate the cache, by specific product or category ID. It means, the [`invalidate`](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/core/scripts/server.js#L156) method will clear out the cache pages tagged with this specific product ID. -:::tip NOTE -This URL requires you to pass the invalidation token set in the [config](https://github.com/vuestorefront/vue-storefront/blob/e96bc3c0d1ef8239bc2e64c399f1fe924cebed36/config/default.json#L12). -::: - -The tags can be used to invalidate the Varnish cache, if you're using it. [Read more on that](https://www.drupal.org/docs/8/api/cache-api/cache-tags-varnish). - -:::tip NOTE -All the official Vue Storefront data indexers including [magento1-vsbridge-indexer](https://github.com/divanteLtd/magento1-vsbridge-indexer), [magento2-vsbridge-indexer](https://github.com/vuestorefront/magento2-vsbridge-indexer) support the cache invalidation. If the cache is enabled in both API and Vue Storefront frontend app, please make sure you are properly using the `config.server.invalidateCacheForwardUrl` config variable as the indexers can send the cache invalidate request only to one URL (either frontend or backend) and it **should be forwarded** to the other. Please check the default forwarding URLs in the `default.json` and adjust the `key` parameter to the value of `server.invalidateCacheKey`. -::: - -
-
- -## 11. Using Magento Checkout - -Vue Storefront Checkout is fully ready to be deployed on production. The thing is, however, in order to complete, you need to **integrate Vue Storefront with payment providers**. Unfortunately some popular Vue Storefront payment modules ([Stripe](https://forum.vuestorefront.io/t/stripe-payment-integration/155), [Paypal](https://forum.vuestorefront.io/t/paypal-payment-integration/152)) are not supporting the **status notification** changes. This is mostly because the payment modules are **platform agnostic** as well. The status notification changes must be implemented on your own, depending on the platform. - -Having that said - one of the other viable options for the Checkout integration is [**Magento Checkout Fallback**](https://forum.vuestorefront.io/t/external-checkout/150) module, maintained by [Vendic](http://vendic.nl). - -When using this module, please make sure you've successfully dispatched the `cart/sync` (VSF 1.11), `cart/serverPull` (VSF 1.10) action and the sync process has finished. Otherwise there could be a situation when the sync hasn't been fully executed and user getting to the Magento checkout sees some discrepancies between Magento and Vue Storefront carts. For example - product added to the VSF cart hasn't been yet added to Magento cart. - -To avoid this situation you should modify the [beforeEach](https://github.com/Vendic/vsf-external-checkout/blob/baeefd179038b2bd9b4a1a00c95b82b131b61b65/router/beforeEach.ts#L14): - - -```js -export function beforeEach(to: Route, from: Route, next) { - const cartToken: string = rootStore.state.cart.cartServerToken; - const userToken: string = rootStore.state.user.token; - const externalCheckoutConfig = {...config.externalCheckout}; - const cmsUrl: string = externalCheckoutConfig.cmsUrl; - const stores = externalCheckoutConfig.stores; - const storeCode = currentStoreView().storeCode - const multistoreEnabled: boolean = config.storeViews.multistore - - if (multistoreEnabled) { - await rootStore.dispatch('cart/sync') - if (storeCode in stores && to.name === storeCode + '-checkout') { - window.location.replace(stores[storeCode].cmsUrl + '/vue/cart/sync/token/' + userToken + '/cart/' + cartToken) - } else if (storeCode in stores && to.name === 'checkout' && stores[storeCode].cmsUrl !== undefined) { - window.location.replace(stores[storeCode].cmsUrl + '/vue/cart/sync/token/' + userToken + '/cart/' + cartToken) - } else { - next() - } - } else { - if (to.name === 'checkout') { - window.location.replace(cmsUrl + '/vue/cart/sync/token/' + userToken + '/cart/' + cartToken) - } else { - next() - } - } -} - -``` - - -
-
- -## 12. Elasticsearch production setup - -Elasticsearch is a viable part of the [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api) middleware data source. The included Docker files are supposed to be used in the development mode and they're not ready for production. - -Elasticsearch should be run on cluster mode with minimum 3 nodes and having sufficient memory limits (usually it's around 8GB per node minimum). Otherwise Elasticsearch service won't provide the required High Availability level. - -Being Elasticsearch is a Java service, the critical setting is Java Heap size limits - that needs to be set to the limit as high as required to provide Elasticsearch with sufficient memory for the search operations and as low as required for the other parts of OS/services to keep running. To not overrun the container memory limits. - -By default, Elasticsearch tells the JVM to use a heap with a minimum and maximum size of 1 GB. When moving to production, it is important to configure heap size to ensure that Elasticsearch has enough heap available. - -Quote from the [ElasticSearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html) - -The value for these settings depends on the amount of RAM available on your server: - -- Set `Xmx` and `Xms` to no more than 50% of your physical RAM. Elasticsearch requires memory for purposes other than the JVM heap and it is important to leave space for this. For instance, Elasticsearch uses off-heap buffers for efficient network communication, relies on the operating system’s filesystem cache for efficient access to files, and the JVM itself requires some memory too. It is normal to observe the Elasticsearch process using more memory than the limit configured with the `Xmx` setting. -Set `Xmx` and `Xms` to no more than the threshold that the JVM uses for compressed object pointers (compressed oops); the exact threshold varies but is near 32 GB. You can verify that you are under the threshold by looking for a line in the logs like the following: - -- heap size `1.9gb`, compressed ordinary object pointers `true` -Ideally set `Xmx` and `Xms` to no more than the threshold for zero-based compressed oops; the exact threshold varies but 26 GB is safe on most systems, but can be as large as 30 GB on some systems. You can verify that you are under this threshold by starting Elasticsearch with the JVM options `-XX:+UnlockDiagnosticVMOptions -XX:`+PrintCompressedOopsMode and looking for a line like the following: - -```log -heap address: 0x000000011be00000, size: 27648 MB, zero based Compressed Oops -showing that zero-based compressed oops are enabled. If zero-based compressed oops are not enabled then you will see a line like the following instead: - -heap address: 0x0000000118400000, size: 28672 MB, Compressed Oops with base: 0x00000001183ff000 -``` - -Read more on [Elasticsearch deployment best practices](https://medium.com/@abhidrona/elasticsearch-deployment-best-practices-d6c1323b25d7) - - - -
-
- -## 13. .htaccess, server side redirects, HTTP codes and headers, middlewares - -We strongly recommend using kind of HTTP server as a proxy in front of Vue Storefront. Let it be `nginx` (suggested in our [production setup docs](/guide/installation/production-setup.html)) or `Varnish` or even `Apache`. Any of those HTTP servers allows you to add some authorization or redirects layer before Vue Storefront. - -This is a recommended way. - -### Protip - -#### 1. Advanced Output Processing - -However, by using [advanced output processing](/guide/core-themes/layouts.html#how-it-works) you can easily generate any text data output from your Vue Storefront site you want. Including JSON, XML and others. It's a way to generate sitemaps and other data based documents. - -#### 2. `Express.js` middleware - -The other option is to create a `Express.js` middleware. Our `core/scripts/server.ts` is a classical Node.js application so it should be easy. To do so you might want to create a [server module](https://github.com/vuestorefront/vue-storefront/blob/develop/src/modules/compress/server.ts). - -Server modules are located in `src/modules` and always have the `server.ts` entry point which responds to one of the few server entry points: - -- `afterProcessStarted` - executed just [after the server started](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L13). -- `afterApplicationInitialized` - executed just [after Express app got initialized](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L34). It's a good entry point to bind new request handlers (`app.get(...)`, `app.use(...)`). Read more on [Express.js request handlers and routing](https://expressjs.com/en/guide/routing.html). -- `beforeOutputRenderedResponse` - executed [after the SSR rendering has been done](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L189) but before sending it out to the browser; It lets you override the rendered SSR content with your own. -- `afterOutputRenderedResponse` - executed [after advanced output processing pipeline](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L212) executed. -- `beforeCacheInvalidated`, `afterCacheInvalidated` - executed [before and after cache has been invalidated](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L76) - -Here is an [example how to bind](https://github.com/vuestorefront/vue-storefront/blob/develop/src/modules/google-cloud-trace/server.ts) tracing module just after server process started: - -```js -import { serverHooks } from '@vue-storefront/core/server/hooks' - -serverHooks.afterProcessStarted((config) => { - let trace = require('@google-cloud/trace-agent') - if (config.has('trace') && config.get('trace.enabled')) { - trace.start(config.get('trace.config')) - } -}) -``` - -[Another example](https://github.com/vuestorefront/vue-storefront/blob/develop/src/modules/compress/server.ts) - pretty common case - binding new Express middleware to process all user requests BEFORE they're processed by SSR rendering pipeline (including custom URL addresses): - -```js -import { serverHooks } from '@vue-storefront/core/server/hooks' - -const compression = require('compression') -serverHooks.afterApplicationInitialized(({ app, isProd }) => { - if (isProd) { - console.log('Output Compression is enabled') - app.use(compression({ enabled: isProd })) - } -}) -``` - -If you'd like to bind custom URL address this example can be modified like this: - -```js -import { serverHooks } from '@vue-storefront/core/server/hooks' - -serverHooks.afterApplicationInitialized(({ app, isProd }) => { - app.get('/custom-url-address', (req, res) => { - res.end('Custom response') - }) -}) -``` - - -
-
- -## 14. Which fields of product, category and attribute are really used by VSF - - -The data formats used by Vue Storefront for products and categories are quite sophisticated with a whole range of optional fields. To get a better understanding which fields are truly required and how they work - please look at the [storefront-api](https://github.com/vuestorefront/storefront-api/) documentation: - -- [GraphQL schema](https://divanteltd.github.io/storefront-graphql-api-schema/) which describes the Product and Category entity with the subsequent types. -- [Product entity](https://sfa-docs.now.sh/guide/integration/format-product.html#product-entity) - minimal set of product fields and how they are used by VSF. -- [Category entity](https://sfa-docs.now.sh/guide/integration/format-category.html) - minimal set of category fields and how they are used by VSF. -- [Attribute entity](https://sfa-docs.now.sh/guide/integration/format-attribute.html) - minimal set of attribute fields and how they are used by VSF. - - -
-
- -## 15. Quite handy features in config file properties - -Vue Storefront contains some pretty useful config variables that are sometimes missed but can be pretty useful (worth mentioning two times!) : - -- `dynamicConfigReload` - by default the config files are processed by [`node-config`](https://github.com/lorenwest/node-config) only during the build process; whenever you modify the config file, it must be then re-compiled and bundled into `app.js`. However, with this mode on, the [`core/scripts/server.ts`](https://github.com/vuestorefront/vue-storefront/blob/77efcdc40a1a69191f8d96c381535517e801820d/core/scripts/server.ts#L271) reloads the config file with each request. This might be very useful for the scalability purposes and to pass some dynamic information during the build process. By modifying the `dynamicConfigExclude` and `dynamicConfigInclude` arrays you may change which particular sections of the config file are provided to the user browser and which are not. The config is passed via `window.__INITIAL_STATE__`. - -- `useExactUrlsNoProxy` - when set to `true`, the strings set in the product properties: `thumbnail`, `image` ... are used for the ` -
- -## 16. Cloudflare Autopurge -You might use CDN not only to serve dist & assets directory but also SSR Output. In this case, you would want to dynamiclly purge cache in Cloudflare when it is being purged in Varnish. -There is a 3rd party module just for that! Install the module at [here](https://github.com/new-fantastic/vsf-cloudflare) - - -
-
- -## 17. VSF Cache Varnish -By default VSF is able to cache SSR Output in the Redis Cache. This module will Cache Redis Output in the Varnish. So Node.js server is not being used even to load output from Redis. It makes our app's first load even faster! Follow the instruction [here](https://github.com/new-fantastic/vsf-cache-varnish) - - -
-
- - -## 18. VSF Cache NGINX -By default VSF is able to cache SSR Output in the Redis Cache. This module will Cache Redis Output in the NGINX. So Node.js server is not being used even to load output from Redis. It makes our app's first load even faster! Follow the instruction [here](https://github.com/new-fantastic/vsf-cache-nginx) - - -
-
- -## 19. Vue Storefront 1 config validator for Magento 1 and Magento 2 - -There is a simple PHP CLI tool to check whether your Vue Storefront PWA configuration matches the structure of your Magento site. You can rest assured whether your VSF works seamlessly with Magento 1 or Magento 2. Please follow the install [here](https://github.com/yireo/vsf-config-validator) - - -
-
- -## 20. A sample theme to start with - -This [sample theme](https://github.com/yireo-training/vsf-yireo-theme) can help you start with a Webpack configuration to allow for simple parent/child theming. - - -
-
- -## 21. Optimized Webpack configuration for Vue Storefront 1 development -The default Webpack configuration of Vue Storefront 1 allows for fully testing all features. However, because of various reasons, this leads to a slow transpilation time and therefore a bad developer experience. This [repository](https://github.com/yireo-training/vsf1-local-webpack) contains a separate Webpack - -## 22. Get rid of depracatedActions in VSF 1.12+ -We do not want to make **breaking changes** (only in special situation). That's why we left *depracatedActions* in product's vuex module. If you started your project in 1.12 you can be almost 100% sure that you can get rid of this file which will result in **31.75KB** less app.js bundle! In simple words, you can do that if your app does not use any of *depracatedActions*. - -To do that: -1. Remove `core/modules/catalog/store/product/deprecatedActions.ts` -2. In `core/modules/catalog/store/product/actions.ts`, remove 317-318 lines: -```ts -/** Below actions are not used from 1.12 and can be removed to reduce bundle */ - ...require('./deprecatedActions').default -``` \ No newline at end of file diff --git a/docs/guide/cookbook/common-pitfall.md b/docs/guide/cookbook/common-pitfall.md deleted file mode 100644 index d2838c67e6..0000000000 --- a/docs/guide/cookbook/common-pitfall.md +++ /dev/null @@ -1,4 +0,0 @@ -# Ch 7. Anti-Patterns & Common Pitfalls - -## Coming soon! - \ No newline at end of file diff --git a/docs/guide/cookbook/data-import.md b/docs/guide/cookbook/data-import.md deleted file mode 100644 index 7bbfb659a8..0000000000 --- a/docs/guide/cookbook/data-import.md +++ /dev/null @@ -1,1008 +0,0 @@ -# Ch 1. Data Imports - -In this chapter, we will cover : -[[toc]] - -## 0. Introduction -When you decide to migrate your web store to Vue Storefront, the first thing you need to do is filling the store (Elasticsearch) with data. This chapter deals with all the hassles related to data migration for Vue Storefront. -
-
- -## 1. Data Mapping Migration for Elasticsearch - -Vue Storefront uses Elasticsearch as a primary data store. Additionally Vue Storefront uses Redis for a cache layer and Kue for queue processing. -Although all these data stores are basically schema-free, some mappings and meta data should be imported for setting Elasticsearch indices and so forth. - -Vue Storefront uses a data-migration mechanism based on [node-migrate](https://github.com/tj/node-migrate). - -### 1. Preparation -- You need a [Vue Storefront API](https://github.com/vuestorefront/vue-storefront-api) instance [installed](setup.html#_1-install-with-docker) on your machine to run the migration.
-- You need an Elasticsearch instance [running](setup.html#_1-install-with-docker) into which the data will be migrated. - -### 2. Recipe -1. Run a node script from **Vue Storefront API root path** which is configured out of the box. -```bash -yarn migrate -``` -which runs the migrations in `migrations` folder. - -2. Successful result as follows : -```bash -> vue-storefront-api@1.9.0 migrate /home/dex/code/vue/vue-backend -> node node_modules/migrate/bin/migrate - -Elasticsearch INFO: 2019-05-29T09:41:04Z - Adding connection to http://localhost:9200/ - - up : 1513602693128-create_new_index.js -Elasticsearch DEBUG: 2019-05-29T09:41:04Z - starting request { - "method": "DELETE", - "path": "/*/_alias/vue_storefront_catalog", - "query": {} - } - - -Elasticsearch DEBUG: 2019-05-29T09:41:04Z - Request complete - -Public index alias does not exists [aliases_not_found_exception] aliases [vue_storefront_catalog] missing, with { resource.type="aliases" & resource.id="vue_storefront_catalog" } -Elasticsearch DEBUG: 2019-05-29T09:41:04Z - starting request { - "method": "DELETE", - "path": "/vue_storefront_catalog", - "query": {} - } - - ... # abridged - -Elasticsearch DEBUG: 2019-05-29T09:41:08Z - Request complete - -{ acknowledged: true } - up : 1530101328854-local_es_config_fix.js -Searching for deprecated parameters in file '/home/dex/code/vue/vue-backend/config/custom-environment-variables.json'... -File '/home/dex/code/vue/vue-backend/config/custom-environment-variables.json' updated. -Searching for deprecated parameters in file '/home/dex/code/vue/vue-backend/config/local.json'... -File '/home/dex/code/vue/vue-backend/config/local.json' updated. - - migration : complete - -``` -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/C9z7daIJAog0xPhNlzwoHhBl4) - -3. In order to verify whether mapping is successfully done or not, you may send a `curl` request against Elasticsearch API as follows : -```bash -curl -XGET 'http://localhost:9200/_mapping?pretty=true' -``` -:::tip Note -Please replace `http://localhost:9200` with your Elasticsearch endpoint if you configured it manually. -::: -4. Result will be as follows if it was successfully imported (abridged) : - - -```bash -{ - "vue_storefront_catalog" : { - "mappings" : { - "category" : { - "properties" : { - "created_at" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "is_active" : { - "type" : "boolean" - }, - "parent_id" : { - "type" : "integer" - }, - "product_count" : { - "type" : "integer" - }, - "slug" : { - "type" : "keyword" - }, - "updated_at" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "url_key" : { - "type" : "keyword" - }, - "url_path" : { - "type" : "keyword" - } - } - }, - "product" : { - "properties" : { - "Color_options" : { - "type" : "keyword" - }, - "Size_options" : { - "type" : "keyword" - }, - "category_gear" : { - "type" : "integer" - }, - "category_ids" : { - "type" : "long" - }, - "color" : { - "type" : "integer" - }, - "color_options" : { - "type" : "integer" - }, - "configurable_children" : { - "properties" : { - "has_options" : { - "type" : "boolean" - }, - "price" : { - "type" : "float" - }, - "sku" : { - "type" : "keyword" - }, - "special_price" : { - "type" : "float" - }, - "url_key" : { - "type" : "keyword" - } - } - }, - "configurable_options" : { - "properties" : { - "attribute_id" : { - "type" : "long" - }, - "default_label" : { - "type" : "text" - }, - "frontend_label" : { - "type" : "text" - }, - "label" : { - "type" : "text" - }, - "store_label" : { - "type" : "text" - }, - "values" : { - "properties" : { - "default_label" : { - "type" : "text" - }, - "frontend_label" : { - "type" : "text" - }, - "label" : { - "type" : "text" - }, - "store_label" : { - "type" : "text" - }, - "value_index" : { - "type" : "keyword" - } - } - } - } - }, - "created_at" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "description" : { - "type" : "text" - }, - "eco_collection" : { - "type" : "integer" - }, - "eco_collection_options" : { - "type" : "integer" - }, - "erin_recommends" : { - "type" : "integer" - }, - "gender" : { - "type" : "integer" - }, - "has_options" : { - "type" : "integer" - }, - "id" : { - "type" : "long" - }, - "material" : { - "type" : "integer" - }, - "name" : { - "type" : "text" - }, - "news_from_date" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "news_to_date" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "pattern" : { - "type" : "text" - }, - "position" : { - "type" : "integer" - }, - "price" : { - "type" : "float" - }, - "required_options" : { - "type" : "integer" - }, - "size" : { - "type" : "integer" - }, - "size_options" : { - "type" : "integer" - }, - "sku" : { - "type" : "keyword" - }, - "slug" : { - "type" : "keyword" - }, - "special_from_date" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "special_price" : { - "type" : "float" - }, - "special_to_date" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "status" : { - "type" : "integer" - }, - "tax_class_id" : { - "type" : "integer" - }, - "updated_at" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "url_key" : { - "type" : "keyword" - }, - "url_path" : { - "type" : "keyword" - }, - "visibility" : { - "type" : "integer" - }, - "weight" : { - "type" : "integer" - } - } - }, - "cms_block" : { - "properties" : { - "creation_time" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "id" : { - "type" : "long" - }, - "identifier" : { - "type" : "keyword" - }, - "update_time" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - } - } - }, - "attribute" : { - "properties" : { - "attribute_id" : { - "type" : "long" - }, - "id" : { - "type" : "long" - }, - "options" : { - "properties" : { - "value" : { - "type" : "text" - } - } - } - } - }, - "taxrule" : { - "properties" : { - "id" : { - "type" : "long" - }, - "rates" : { - "properties" : { - "rate" : { - "type" : "float" - } - } - } - } - }, - "cms_page" : { - "properties" : { - "creation_time" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "id" : { - "type" : "long" - }, - "identifier" : { - "type" : "keyword" - }, - "update_time" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - } - } - } - } - } -} - -``` - - -### 3. Peep into the kitchen (what happens internally) -![architecture-data-import-part](../images/GitHub-Architecture-VS-data-import.png) -We worked in the red rectangle part of the architecture as a preparation for data import. - -What we did in a simple term, we taught Elasticsearch types and sorts of data(mapping, also known as schema) we will use for Vue Storefront API later on. - -Upon running `yarn migrate`, it runs the pre-configured [migration scripts](https://github.com/vuestorefront/vue-storefront-api/tree/master/migrations) using [node-migrate](https://github.com/tj/node-migrate). If you take a closer look into the migration scripts, you will notice the ultimate js file which is located at [`./src/lib/elastic.js`](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/lib/elastic.js) that does the actual labor for migration. - If you take one more closer look in the `elastic.js` file, you will also find all the schema files are located under [`./config`](https://github.com/vuestorefront/vue-storefront-api/tree/master/config) folder. - What those scripts do can be divided into steps as per the file name. - It first creates index from index schema, then import schema from `elastic.schema.[types].json` files. It will then reindex them, and delete temporary index. Finally it will work a few workarounds to deal with deprecated process. - - Now, you are all set to proceed to pump your data into the store. - -### 4. Chef's secret (protip) -#### Secret 1. Deal with `index not found exception` -If you encountered with the exception as follows during the migration script : - -![index_not_found_exception](../images/sss.png) - -It means you don't have the temporary index `vue_storefront_catalog_temp` which is required. -Solution is : -```bash -yarn restore -``` -This will create the necessary temporary index, then the necessary temp index will be deleted by the steps mentioned [above](#_3-peep-into-the-kitchen-what-happens-internally) when the migration is finished - -#### Secret 2. Add a new migration script -You might need to write your own migration script. In that case, you can do so by adding a file under the `./migrations` directory though this is not a recommended way. `node-migrate` provides you with the cli command for the purpose as follows : -```bash -yarn migrate create name-of-migration -``` -This wil create a migration script template under `./migration` folder with the standard naming convention. - [more info](https://github.com/tj/node-migrate#creating-migrations) - -:::tip Example -The example migration shows how to manipulate product mappings as follows : -::: -```js -// Migration scripts use: https://github.com/tj/node-migrate -'use strict'; - -let config = require('config'); -let common = require('./.common'); - -module.exports.up = function(next) { - // example of adding a field to the schema - // other examples: https://stackoverflow.com/questions/22325708/elasticsearch-create-index-with-mappings-using-javascript, - common.db.indices - .putMapping({ - index: config.elasticsearch.indices[0], - type: 'product', - body: { - properties: { - slug: { type: 'string' }, // add slug field - suggest: { - type: 'completion', - analyzer: 'simple', - search_analyzer: 'simple', - }, - }, - }, - }) - .then(res => { - console.dir(res, { depth: null, colors: true }); - next(); - }); -}; - -module.exports.down = function(next) { - next(); -}; -``` - -#### Secret 3. Execute migration multiple times -If you run a migration multiple times using `yarn migrate`, it will only run the migration once and subsequent execution will be ignored and only repeat the result as follows : - -![migration complete](../images/npm-run-migrate-result.png) - -This happens because `node-migrate` knows it was already executed before by checking with `./migrate` file so that you won't need it repeating. Should you, however, need to run it more than once, you can do it by deleting `./migrate` file. - -
-
-
- - -## 2. Data Pump -A retail business can't start without stocking products in the shop in the first place. Likewise, starting an online shop business requires products in stock online (data store) too. - -Starting Vue Storefront is not an exception. We need to pump your data (products, categories, tax rules and so on) into the primary data store for Vue Storefront which is Elasticsearch. We also use Redis cache in between for enhancing performance. - -By using Elasticsearch as a data store in the architecture, we could make the platform backend-agnostic along with many other advantages such as performance and scalability. - -In this recipe we will walk you with **Magento 2** example. - -### 1. Preparation -- You need a [Vue Storefront API](https://github.com/vuestorefront/vue-storefront-api) instance [installed](setup.html#_1-install-with-docker) for backend. -
-- You need an Elasticsearch instance [running](setup.html#_1-install-with-docker) with mapping is done as in [*Recipe 1 Data Mapping Migration for Elasticsearch*](#_1-data-mapping-migration-for-elasticsearch) -
-- You need [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) downloaded for data bridge. This instance is backend-dependant (in this case, Magento 2), you may replace it with other data bridges such as [coreshop-vsbridge](https://github.com/divanteLtd/coreshop-vsbridge), [shopware2vuestorefront](https://github.com/divanteLtd/shopware2vuestorefront) to your advantage. -
-- Finally, you need a Magento 2 instance as a data source to pump your data from. (For [Recipe B](#_2-2-recipe-b-using-on-premise) only) - -We are going to import entities as follows : -- Products -- Categories -- Taxrules -- Attributes -- Product-to-categories -- Reviews (require custom module [Divante/ReviewApi](https://github.com/divanteLtd/magento2-review-api)) -- Cms Blocks & Pages (require custom module [SnowdogApps/magento2-cms-api](https://github.com/SnowdogApps/magento2-cms-api)) - -### 2-1. Recipe A (Using Demo) -Going with Demo data you can quickly experience the whole itinerary. If you want to work with your original data right away, look to [Recipe B](#_2-2-recipe-b-using-on-premise) -1. Start with npm install from **mage2vuestorefront root path** which installs dependencies for the project. -```bash -npm install -``` - -2. Set required options by setting config values or ENV variables. -```bash -export TIME_TO_EXIT=2000 -export MAGENTO_CONSUMER_KEY=byv3730rhoulpopcq64don8ukb8lf2gq -export MAGENTO_CONSUMER_SECRET=u9q4fcobv7vfx9td80oupa6uhexc27rb -export MAGENTO_ACCESS_TOKEN=040xx3qy7s0j28o3q0exrfop579cy20m -export MAGENTO_ACCESS_TOKEN_SECRET=7qunl3p505rubmr7u1ijt7odyialnih9 - -export MAGENTO_URL=http://demo-magento2.vuestorefront.io/rest -export INDEX_NAME=vue_storefront_catalog -``` -What this means is explained in more detail at the same step of [Recipe B](#_2-2-recipe-b-using-on-premise). - -3. Run the following command to import categories from demo store at `mage2vuestorefront/src`: -```bash -node --harmony cli.js categories --removeNonExistent=true -``` -:vhs: You may watch the result in [bash playback :movie_camera:](https://asciinema.org/a/75MTwaet3IO3vOCdDyCVOAgqL) - -Run the following commands to finish the pumping : - -```bash -node --harmony cli.js productcategories -node --harmony cli.js attributes --removeNonExistent=true -node --harmony cli.js taxrule --removeNonExistent=true -node --harmony cli.js products --removeNonExistent=true --partitions=1 -node --harmony cli.js reviews -node --harmony cli.js blocks -node --harmony cli.js pages -``` -### 2-2. Recipe B (Using On-premise) -1. Start with npm install from **mage2vuestorefront root path** which installs dependencies for the project. -```bash -npm install -``` - -2. Get a Magento Integration credentials by following these steps : - -- Login to your data pump source **Admin** and go to **Extensions** > **Integrations** as follows : - -![data_pump_1](../images/data_pump_1.png) - -- Click **Add New Integration** button in as follows : - -![data_pump_2](../images/data_pump_2.png) - -- Populate fields as you need it in the following : - -![data_pump_3](../images/data_pump_3.png) - -- Click **API** tab in the left sidebar. This screen lets you pick which API endpoint you will allow for this integration : - -![data_pump_6](../images/data_pump_6.png) - -- If you are not sure, select *All* in **Resource Access** as follows : - -![data_pump_7](../images/data_pump_7.png) - -- Click **Save** from previous screen will get you following screen : - -![data_pump_5](../images/data_pump_5.png) - -- In the previous screen, we successfully created an integration credentials, yet another step needs to be done in there, which is **Activate** button to click that will take you to the following screen : - -![data_pump_8](../images/data_pump_8.png) - -- This screen asks you to confirm endpoints that you want to grant for the integration. If you are OK with it, you are good to click **Allow**. - -![data_pump_9](../images/data_pump_9.png) - -- Then the application will prompt you with newly created tokens for your integration. Copy them, we will use them next step. - -3. Set required options by setting config values or ENV variables using the credentials acquired during the previous step. -```bash -export TIME_TO_EXIT=2000 -export MAGENTO_CONSUMER_KEY=lv1unkldzkcex68l3eojut4j66qqho8w -export MAGENTO_CONSUMER_SECRET=zhkuqvweo0bsg14noujqje49x3wht0qr -export MAGENTO_ACCESS_TOKEN=z6ftgc5005212bc6lnszxa7d7ocl8hgc -export MAGENTO_ACCESS_TOKEN_SECRET=h8tikjq9sz7tqm6hyhdfgs96krb6qzyk - -export MAGENTO_URL=http://local.magento/rest # Replace the url with your Magento 2 URL -export INDEX_NAME=vue_storefront_catalog # This will be the name of the index we use -``` -:::tip Note -- **Access Token** and **Access Token Secret** may change over time since they are created by a request made with **Consumer Key** and **Consumer Secret**. -- Replace *http://local.magento/* with the URL on which your Magento 2 is running. -::: - -4. Now is the time for data import. Run the following command at **`mage2vuestorefront/src`** : - -```bash -node --harmony cli.js categories --removeNonExistent=true -``` - -Successful result shows as follows : -```bash -2019-06-09T05:43:23.330Z - debug: Elasticsearch module initialized! -info: Winston logging library initialized. -2019-06-09T05:43:23.402Z - info: Connected correctly to server -2019-06-09T05:43:23.402Z - info: TRANSACTION KEY = 1560059003367 -debug: Calling API endpoint: GET http://local.magento/rest/V1/categories -debug: Response received. -Dest. cat path = default-category-2 -debug: Calling API endpoint: GET http://local.magento/rest/V1/categories/2 -debug: Response received. -Dest. cat path = default-category-2 -2019-06-09T05:43:24.042Z - debug: Storing extended category data to cache under: vue_storefront_catalog_cat_2 -debug: Calling API endpoint: GET http://local.magento/rest/V1/categories/44 -debug: Calling API endpoint: GET http://local.magento/rest/V1/categories/29 -debug: Calling API endpoint: GET http://local.magento/rest/V1/categories/30 -debug: Calling API endpoint: GET http://local.magento/rest/V1/categories/31 -debug: Calling API endpoint: GET http://local.magento/rest/V1/categories/32 - -... # abridged - -debug: Response received. -Dest. cat path = women/bottoms-women/shorts-women/shorts-34 -2019-06-09T05:44:32.360Z - debug: Storing extended category data to cache under: vue_storefront_catalog_cat_34 -2019-06-09T05:44:32.360Z - info: Importing 1 of 2 - [(34) Shorts] with tsk = 1560059042304 -2019-06-09T05:44:32.360Z - info: Tasks count = 0 -2019-06-09T05:44:32.361Z - info: No tasks to process. All records processed! -2019-06-09T05:44:32.361Z - info: Cleaning up with tsk = 1560059042304 -2019-06-09T05:44:32.363Z - info: Task done! Exiting in 30s... -2019-06-09T05:44:32.380Z - info: -{ took: 10, - timed_out: false, - total: 13, - deleted: 0, - batches: 1, - version_conflicts: 13, - noops: 0, - retries: { bulk: 0, search: 0 }, - throttled_millis: 0, - requests_per_second: -1, - throttled_until_millis: 0, - failures: [] } -``` - -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/BnDQONQSs3WSVvh0SUjHRJeNo) - -:::tip Note -- `--removeNonExistent` option means all records that were found in the index but currently don't exist in the API feed will be removed. Please use this option **ONLY** for the full reindex! -- `--harmony` flag means we are enabling of cutting-edge ECMAScript 6, staged features because we need it. [more info](https://nodejs.org/en/docs/es6/) - -::: - - -5. In order to verify it was imported as planned, please run the command as follows : - -```bash -curl -XGET "http://localhost:9200/vue_storefront_catalog/_search?pretty=true" -H 'Content-Type: application/json' -d' -{ - "query": { - "terms": { - "_type": [ "category" ] - } - } -}' -``` - -:::tip Note -The index name we set in ENV variables above at the step 3 is used in the command as : `http://localhost:9200/`**vue_storefront_catalog**`/_search?pretty=true` -::: - -A successful result will be something like this : -```bash -{ - "took" : 1, - "timed_out" : false, - "_shards" : { - "total" : 5, - "successful" : 5, - "skipped" : 0, - "failed" : 0 - }, - "hits" : { - "total" : 39, - "max_score" : 1.0, - "hits" : [ - { - "_index" : "vue_storefront_catalog_1559623128", - "_type" : "category", - "_id" : "44", - "_score" : 1.0, - "_source" : { - "id" : 44, - "parent_id" : 2, - "name" : "What's New", - "is_active" : true, - "position" : 1, - "level" : 2, - "product_count" : 0, - "children_data" : [ ], - "children" : "", - "created_at" : "2019-05-21 09:04:41", - "updated_at" : "2019-05-21 09:04:41", - "path" : "1/2/44", - "available_sort_by" : [ ], - "include_in_menu" : true, - "display_mode" : "PAGE", - "is_anchor" : "0", - "children_count" : "0", - "url_key" : "whats-new-44", - "url_path" : "what-is-new/whats-new-44", - "slug" : "whats-new-44", - "tsk" : 1560059042304 - } - }, - - ... # abridged -``` -Now import other remaining entities in the same fashion : - -```bash -node --harmony cli.js productcategories -node --harmony cli.js attributes --removeNonExistent=true -node --harmony cli.js taxrule --removeNonExistent=true -node --harmony cli.js products --removeNonExistent=true --partitions=1 -``` - -:::tip Note -`--partitions=1` flag denotes parallel mode. Value set here will become the process count. Thus, *1* means single process mode. -::: - -6. In order to import `reviews` and `cms`, we need to install additional Magento 2 modules, so that we can expose required API. - -Download and install [Review API module](https://github.com/divanteLtd/magento2-review-api) and run the following : -```bash -node --harmony cli.js reviews -``` -Download and install [CMS API module](https://github.com/SnowdogApps/magento2-cms-api) and run the following : -```bash -node --harmony cli.js blocks -node --harmony cli.js pages -``` - -7. Finally, reindex the Elasticsearch making sure up-to-date with data source in **Vue Storefront API** root path. -```bash -yarn db rebuild -``` - -### 3. Peep into the kitchen (what happens internally) -![data_pump_design](../images/datum_pump_design.png) -We worked in the red rectangle part of the architecture as pumping the data. - -During the recipe we imported the source data from Magento 2 into Elasticsearch as a data store which could make the platform backend-agnostic along with many other advantages such as performance, scalability, and more than anything, PWA. - -We started with demo data pump. [Divante Ltd.](https://vuestorefront.io/) prepared demo store for demonstration purpose so that we could quickly learn the process of data pump. - -Recipe B started creating an integration entry in Magento 2 Admin in order to grant a permission for data pump. Magento 2 asks you basic information with respect to the integration including ACL(access control list) that deals with permissions by each endpoint. Once you are done, Magento 2 will present you with credentials and tokens. - -Providing those credentials in config file, or in this case we set ENV variables, allows the [`src/cli.js`](https://github.com/vuestorefront/mage2vuestorefront/blob/master/src/cli.js) script file to run the pumping. In a deeper look into [`src/cli.js`](https://github.com/vuestorefront/mage2vuestorefront/blob/master/src/cli.js), you will notice functions that handle each entity. Inside the function, there is a [`factory`](https://github.com/vuestorefront/mage2vuestorefront/blob/master/src/adapters/factory.js) method that takes an `adapter` injected as a parameter - in this recipe, it was `magento` - which represents a backend type of data source, and the other parameter is `driver` which represents entity type you are importing, say, `products`. There is another `adapter` whose name is `nosql` which is Elasticsearch. The ultimate pump logic locates [`abstract`](https://github.com/vuestorefront/mage2vuestorefront/blob/master/src/adapters/abstract.js) that loads `nosql` as `db` at `constructor` and executes `run` method with individual logic inherited within. You may find individual `drivers` for `magento adapter` in [`magento`](https://github.com/vuestorefront/mage2vuestorefront/tree/master/src/adapters/magento) folder. - -Now, you are ready to serve your **Vue Storefront** instance with your original products! - -### 4. Chef's secret (protip) -#### Secret 1. Product image is not synced -When your products are successfully imported, there is another important thing to consider, that is product images. However, you should whitelist your source domain in order to fetch the images asynchronously. Otherwise, you will see a sad screen like this : - -![image_fails. data_pump](../images/img_catalog_prod_fail.png) - -Go to **Vue Storefront API** root and find `local.json` under `config` folder. -:::tip Info -`local.json` is the file created during the installation. It contains all the configuration for your Vue Storefront API instance. If you don't have it, you should copy the template from [`default.json`](https://github.com/vuestorefront/vue-storefront-api/blob/master/config/default.json) from the same directory and populate fields as you need it. -::: - -Find `imageable` node and add your source domain under `whitelist/allowedHosts` as follows : -```json - "imageable": { - "namespace": "", - "maxListeners": 512, - "imageSizeLimit": 1024, - "whitelist": { - "allowedHosts": [ - ".*divante.pl", - ".*vuestorefront.io", - "localhost", - // add a source domain here - "degi.magento" - ] - }, - -``` -Now, restart **Vue Storefront API** instance, reload the page and *Presto!* - -![image_fails. data_pump](../images/img_catalog_prod.png) - -#### Secret 2. Taking advantage of Delta Indexer -:::tip Quote -*There are only two hard things in Computer Science: cache invalidation and naming things.* - -*-- Phil Karlton* -::: - -In every corner of computer science, engineers should take care of resource economics. Likewise, **Vue Storefront** devised a method to deal with optimization as well. With that said, it'd be redundant and inefficient to run full reindex every time a product is added to data source (e.g. Magento 2). We have the solution for that problem : *Delta Indexer* - - A greek letter *Delta* normally means *`quantity changed`* in a plain math, which sounds plausible for the job it does. - -Now, run the following command at **mage2vuestorefront/src** : -```bash -node --harmony cli.js productsdelta -``` - -Successful result would be something like this : -```bash -# ... abridged - -2019-06-16T10:55:34.354Z - info: Product sub-stage 6: Getting product categories for dress_girl -2019-06-16T10:55:34.354Z - info: Using category_ids binding for dress_girl: 2,6,7 -Dest. product path = default-category-2/dress-girl-1.html -2019-06-16T10:55:34.355Z - info: Product sub-stages done for dress_girl -2019-06-16T10:55:34.356Z - info: Importing 0 of 3 - [(1 - dress_girl) Dress Girl ] with tsk = 1560682531040 -2019-06-16T10:55:34.356Z - info: Tasks count = 2 -debug: Response received. -2019-06-16T10:55:34.404Z - info: Product sub-stage 6: Getting product categories for trans_bng -2019-06-16T10:55:34.404Z - info: Using category_ids binding for trans_bng: 2,3,6,4,7 -Dest. product path = default-category-2/trans-boys-and-girls-2.html -2019-06-16T10:55:34.405Z - info: Product sub-stages done for trans_bng -2019-06-16T10:55:34.405Z - info: Importing 1 of 3 - [(2 - trans_bng) Trans Boys and Girls] with tsk = 1560682531040 -2019-06-16T10:55:34.405Z - info: Tasks count = 1 -debug: Response received. -2019-06-16T10:55:34.480Z - info: Product sub-stage 6: Getting product categories for romantic_t -2019-06-16T10:55:34.480Z - info: Using category_ids binding for romantic_t: 6,7,8 -Dest. product path = girls/girls-6/romantic-t-3.html -2019-06-16T10:55:34.481Z - info: Product sub-stages done for romantic_t -2019-06-16T10:55:34.481Z - info: Importing 2 of 3 - [(3 - romantic_t) Romantic T] with tsk = 1560682531040 -2019-06-16T10:55:34.481Z - info: Tasks count = 0 -2019-06-16T10:55:34.482Z - debug: --L:0 Level done! Current page: 1 of 1 -2019-06-16T10:55:34.482Z - info: All pages processed! -2019-06-16T10:55:34.482Z - info: Task done! Exiting in 30s... - -``` - -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/DWaasVJ5RXhSn7Aoc7PqDLG3F) - -Now, newly added products are also present in Elasticsearch, hence synced with Vue Storefront. - -:::warning Caution ! -You need to have cache entry for categories or it will be aborted as follows : -::: - -![delta_error](../images/delta_error.png) - -**Solution** is : run the categories import first, then delta import -```bash -node --harmony cli.js categories -node --harmony cli.js productsdelta -``` - - -#### Secret 3. When you have imported multiple data source -As Magento is famous for having a powerful multi stores feature, **Vue Storefront** is also ready to take on the feature smoothly. -You can have multiple indexes by specifying index name when you import data with [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront). - -Setting the ENV variable for `INDEX_NAME` differently for each store will create corresponding index in Elasticsearch. -You may as well need to provide different API base endpoint as per the store name. - -```bash -# ... abridged -export MAGENTO_ACCESS_TOKEN_SECRET=7qunl3p505rubmr7u1ijt7odyialnih9 - -export MAGENTO_URL=http://demo-magento2.vuestorefront.io/rest -# Change REST API base endpoint -export INDEX_NAME=vue_storefront_catalog -# Change INDEX_NAME variable to be distinguishable from each store -``` - -You also need to inform **Vue Storefront** and **Vue Storefront API** of the multi stores information. -[further instruction](guide/integrations/multistore.html#vue-storefront-and-vue-storefront-api-configuration) - -#### Secret 4. Dealing with `version_conflict_engine_exception` -`version_conflict_engine_exception` basically means there was a race condition while executing your Elasticsearch command. Elasticsearch is parallel and asynchronous so it is possible an older version might overwrite a newer version by accident. - -So it has means to protect a newer version of documents making it sure immutable from an older version of it, and `version_conflict_engine_exception` is one of them. - -Repeating the same request would simply resolve the conflict. But if it doesn't, sending a flag `conflicts=proceed` should ignore the conflict, however, you should take responsibility for consequences of those force updates. [more info](https://www.elastic.co/guide/en/elasticsearch/guide/current/optimistic-concurrency-control.html) - - - - -#### Secret 5. Options available for `cli.js` -`cli.js` takes care of all the commands for imports; it's the entrance to **mage2vuestorefront**. It not only accepts all the commands but also accepts options along with them. While majority of commands accepts similar sorts of options, but a few options only apply to a certain commands. -:::tip Note -Values in the example show default values hereunder -::: - -```bash -cli.js attributes \ - --adapter=magento \ - --removeNonExistent=false -``` -`adapter` option denotes which adapter you will use for data source. Basically you wouldn't need to change the default value which is `magento` - -`removeNonExistent` option removes entries that exist in index but don't exist in data source. - - -```bash -cli.js categories \ - --adapter=magento \ - --removeNonExistent=false \ - --extendedCategories=true \ - --generateUniqueUrlKeys=true -``` -`extendedCategories` option enables to import extended information about a category; such as `created_at`, `path`, `included_in_menu` and so on. - -`generateUniqueUrlKeys` option enables to generate url key during the import using `slugfied name` + `-` + `id`. - -```bash -cli.js cleanup \ - --adapter=magento \ - --cleanupType=product \ - --transactionKey=0 -``` -`cleanup` command is used to remove entries inserted before the current insert. Any entry with timestamp earlier than the current import will be removed by this command. This is what executes when `--removeNonExistent` option is `true`. -:::warning Caution ! -`cleanup` command is not intended to be used from commandline. It's **INTERNAL USE ONLY**. If you use it alone from command line, it will purge the index with designated index type whose transaction key is different from the current transaction key, which means all the entries for the type stored so far will be gone. please use it with care. -::: - -`cleanupType` option denotes index type that you want to purge. - -`transactionKey` option means timestamp of the execution which will distinguish your transaction from others. - -```bash -cli.js fullreindex \ - --adapter=magento \ - --partitions=1 \ - --partitionSize=50 \ - --initQueue=true \ - --skus= \ - --extendedCategories=true \ - --generateUniqueUrlKeys=true -``` -`fullreindex` is one command that will run all the other imports command in sequence. - -`partitions` option flag denotes parallel mode. Value set here will become the process count. Thus, 1 means single process mode. - -`partitionSize` option denotes so called `pageSize` that configures returned collection size by each request. - -`initQueue` option enables queue mode so that process runs in parallel. - -`skus` option fetch a query result by only a set of skus. - -```bash -cli.js productcategories \ - --adapter=magento -``` -`productcategories` is a command to fetch the data of `magento`'s `catalog_category_product` table. The table stores the information of index with respect to which category contains which products along with product position in that category. - -```bash -cli.js products \ - --adapter=magento \ - --partitions=1 \ - --partitionSize=50 \ - --initQueue=true \ - --skus= \ - --removeNonExistent=false \ - --updatedAfter= \ - --page= -``` -`updatedAfter` option confines a product query by only ones updated after this value. - -`page` option means so-called `setCurPage` in Magento. It helps you fetch data from a certain page, whose page size is defined by `partitionsSize`. - -```bash -cli.js productsdelta \ - --adapter=magento \ - --partitions=1 \ - --partitionSize=50 \ - --initQueue=true \ - --skus= \ - --removeNonExistent=false -``` - -```bash -cli.js productsworker \ - --adapter=magento \ - --partitions=1 -``` -`productsworker` is a command to run a process that was stacked in a queue scheduled to import products. - -```bash -cli.js reviews \ - --adapter=magento \ - --removeNonExistent=false -``` - -```bash -cli.js taxrule \ - --adapter=magento \ - --removeNonExistent=false -``` - -```bash -cli.js blocks \ - --adapter=magento \ - --removeNonExistent=false -``` - -```bash -cli.js pages \ - --adapter=magento \ - --removeNonExistent=false -``` -
-
-
- -## 3. Native Indexer in case of Magento 2 - - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
\ No newline at end of file diff --git a/docs/guide/cookbook/devops.md b/docs/guide/cookbook/devops.md deleted file mode 100644 index c6a5d50cf5..0000000000 --- a/docs/guide/cookbook/devops.md +++ /dev/null @@ -1,59 +0,0 @@ -# Ch 8. On DevOps - -In this chapter, we will cover : -[[toc]] - -## 0. Introduction -As the industry matures, developers' concerns are poised to move towards dealing with operations of server infrastructure rather than development itself. The reason is clear as fire, it's simply more painful to deal with server operation. I am not saying AI programmer will take away your job, but there are lots of resources out there to help you build a software with such as frameworks, libraries, templates, best practices of all sorts, and so on. Not to mention, IDEs on steroid are getting better all the time which provides you with tools for the degree to which proves foolproof. It just takes a single fool to build a great software these days. (Forgive me!) - -However, something people underestimated at the first sight turned out to be the most stressful experience; _Set the servers up and running_. (and maintain it!) Something you thought the last simple step for your development project turned out to be the separate portion of substantial workloads that needs to be taken care of from the beginning of the project. Now, that's where _DevOps_ comes in. - -## 1. Infrastructure design -_Vue Storefront_ approaches the online commerce problem with _MSA (Microservice Architecture)_ methodology. A handful army of technology stacks is positioned in place to bring in the maximum efficiency on top of the PWA concept. - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 2. Production ready? ready! -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 3. Cache Strategy -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 4. DevOps in VSF context -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 5. In case of StorefrontCloud.io -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 6. Rooms to improve -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
\ No newline at end of file diff --git a/docs/guide/cookbook/elastic.md b/docs/guide/cookbook/elastic.md deleted file mode 100644 index ece8c95795..0000000000 --- a/docs/guide/cookbook/elastic.md +++ /dev/null @@ -1,909 +0,0 @@ -# Ch 2. Elasticsearch in the VSF context - - -In this chapter, we are going to cover : -[[toc]] - -## 0. Introduction -_Elasticsearch_ is the choice of _Vue Storefront_ for its data store as naturally as there must be reasons behind this. -By its name, you can deduce _Elastic_ to mean scalable, extendable, distributed and type-agnostic which is great in this big data era while _Search_ implies indexing, filter, _Read_ among _CRUD_ which shows its focus. So far so good, then, what is all this fuss about Elasticsearch? - -_Elasticsearch_ is a full-text search and analysis engine based on _Apache Lucene_ by its definition. It employs inverted index which means _documents_ are indexed via all the unique _terms_ occurred and the ability to take advantage of assembling the per-field data structures can explain why _Elasticsearch_ is ultrafast. - -The other strong point is, notably, it's inborn distributed. Experience from a single node elasticsearch and multiple clusters of it is almost identical and doing so is painless as it works out of the box. There are virtually tons of points to make for why _Elasticsearch_ is your elected mid-stop between datahouse and storefront. Now let's move on to how it's implemented in _Vue Storefront_. - -_Vue Storefront_ defines itself backend-agnostic PWA e-commerce solution where _Vue Storefront_ is a storefront as the name dictates, and _Elasticsearch_ works as a datastore for _catalog_ and its sibling data such as _taxrule_, _products_ and so on. When a storefront requests information about a product, then it fetches _index_ of _documents_ about the _term_ queried from _Elasticsearch_ without traversing it to the source web store (be it Magento) so it skips all the heavy loading of the store whose database behind also is not concered which makes customers happy for pleasant experience. - -Without much further ado, let's see what's served as an appetizer :) -
-
- - -## 1. Now ES7 is also supported in VSF -_Elasticsearch_ has been under massive upgrade with interval so intense as only two weeks exist between release of `6.7` and `7.0`. Can you feel the heat of the community? While we can enjoy the improvement and enhancement of the _Elastic Stack_, there is a list to check before smooth upgrade. And it also works just the same way as you need to fix _Vue Storefront_ stack for compatibility with _Elasticsearch 7.x_. - -As _Vue Storefront_ stack is mostly associated with _Elasticsearch_ through _Vue Storefront API_, you should fix files for _Vue Storefront API_ along with a few callers for it from _Vue Storefront_. However, most changes take place in core parts of the platform on purpose so your labor will have been minimized for your inner peace. Still, _configs_ and/or _migration_ need fixes where it's necessary. This recipe walks you through how to do it one by one. - - -### 1. Preparation - - You need to have [setup _Vue Storefront_ stack](setup) including _Vue Storefront API_. - - ES7 is supported from _Vue Storefront_ version `1.11` and up. You should have it accordingly. - - ES7 is supported from _Vue Storefront API_ version `1.11` and up. You should have it accordingly too. - - ES7 is supported from _mage2vuestorefront_ with branch `feature/es7`. You should have it too. -:::tip NOTE -How to download the latest `1.11` via `git` is explained in [_Protip_](#secret-1-how-to-upgrade-to-1-11-technically-foolproof-approach) -::: - - -### 2. Recipe - 0. You should fix `docker-compose.nodejs.yml` file as linked _Elasticsearch_ container should be updated like below : - -
- -
- - - - 1. `docker-compose` for _Elasticsearch 7_ is included in `1.11`. Let's run the docker container for _Elasticsearch 7_ from **Vue Storefront API root path** as follows : - ```bash -docker-compose -f docker-compose.elastic7.yml -f docker-compose.nodejs.yml up - ``` -:::tip TIP -You can run it in the _detach_ mode with option flag `-d` as in - ```bash -docker-compose -f docker-compose.elastic7.yml -f docker-compose.nodejs.yml up -d - ``` -::: - - 2. You will see the screen as below : - ```bash -Starting es7 ... -Starting vuestorefrontapi_redis_1 ... -Starting vuestorefrontapi_redis_1 -Starting vuestorefrontapi_redis_1 ... done -Attaching to es7, vuestorefrontapi_redis_1 -es7 | OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release. -redis_1 | 1:C 23 Dec 18:00:28.554 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo -redis_1 | 1:C 23 Dec 18:00:28.554 # Redis version=4.0.14, bits=64, commit=00000000, modified=0, pid=1, just started -redis_1 | 1:C 23 Dec 18:00:28.554 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf -redis_1 | 1:M 23 Dec 18:00:28.555 * Running mode=standalone, port=6379. -redis_1 | 1:M 23 Dec 18:00:28.555 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. -redis_1 | 1:M 23 Dec 18:00:28.555 # Server initialized -redis_1 | 1:M 23 Dec 18:00:28.556 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. -redis_1 | 1:M 23 Dec 18:00:28.556 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. -redis_1 | 1:M 23 Dec 18:00:28.556 * DB loaded from disk: 0.000 seconds -redis_1 | 1:M 23 Dec 18:00:28.556 * Ready to accept connections -es7 | {"type": "server", "timestamp": "2019-12-23T18:00:30,129+0000", "level": "INFO", "component": "o.e.e.NodeEnvironment", "cluster.name": "docker-cluster", "node.name": "be374d24f82e", "message": "using [1] data paths, mounts [[/ (overlay)]], net usable_space [149.4gb], net total_space [250.9gb], types [overlay]" } -es7 | {"type": "server", "timestamp": "2019-12-23T18:00:30,133+0000", "level": "INFO", "component": "o.e.e.NodeEnvironment", "cluster.name": "docker-cluster", "node.name": "be374d24f82e", "message": "heap size [494.9mb], compressed ordinary object pointers [true]" } -es7 | {"type": "server", "timestamp": "2019-12-23T18:00:30,135+0000", "level": "INFO", "component": "o.e.n.Node", "cluster.name": "docker-cluster", "node.name": "be374d24f82e", "message": "node name [be374d24f82e], node ID [e8P_hrouSEKIWnylBaelVw], cluster name [docker-cluster]" } -# abridged ... - ``` - :vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/NcfdFuMkJ5LWzVbgb7m35coOV) - - You might notice the script spawns two containers, one of which is for `redis` while the other is for `elasticsearch 7`. (`kibana` container is optional from `1.11`) - - 3. Visit `localhost:9200` from your browser then it should print likewise as follows : -```text -{ - "name" : "be374d24f82e", - "cluster_name" : "docker-cluster", - "cluster_uuid" : "3Gk6anHkQU--5TmenJkdrw", - "version" : { - "number" : "7.3.2", - "build_flavor" : "default", - "build_type" : "docker", - "build_hash" : "1c1faf1", - "build_date" : "2019-09-06T14:40:30.409026Z", - "build_snapshot" : false, - "lucene_version" : "8.1.0", - "minimum_wire_compatibility_version" : "6.8.0", - "minimum_index_compatibility_version" : "6.0.0-beta1" - }, - "tagline" : "You Know, for Search" -} -``` - - 4. Fix `local.json` to update configuration for `indexTypes` and `apiVersion` under `elasticsearch` as follows : - -
- -
- - - - 5. Once _Elasticsearch 7_ instance is up and running, then run the new script which creates index with the proper data types of fields applied. -```bash -yarn db7 new -``` - -This is because you should newly put mapping for _Elasticsearch 7_ which only allows one _document_ per single _index_. [more info](https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking-changes-7.0.html) - - The screen spits log as follows : -```bash -yarn run v1.21.1 -$ node scripts/db7.js new -** Hello! I am going to create NEW ES index -Public index alias does not exists aliases_not_found_exception -Public index alias does not exists aliases_not_found_exception -Public index alias does not exists aliases_not_found_exception -Public index alias does not exists aliases_not_found_exception -Public index alias does not exists aliases_not_found_exception -Public index alias does not exists aliases_not_found_exception -Public index alias does not exists aliases_not_found_exception -Done in 2.27s. -``` - :vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/UErONnmqK1m2EFNkWRrG0E6p4) - -Don't worry about `aliases_not_found_exception`. It simply means it failed to cleanse the orphaned aliases since there was none to delete in the first place. - - 6. Check if the mapping has been created successfully from the terminal against Elasticsearch API : -```bash -curl -XGET 'http://localhost:9200/_mapping?pretty=true' -``` - - Result should be shown as : -```bash -{ - "vue_storefront_catalog_cms_block" : { - "mappings" : { - "properties" : { - "creation_time" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "id" : { - "type" : "long" - }, - "identifier" : { - "type" : "keyword" - }, - "update_time" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - } - } - } - }, - "vue_storefront_catalog_review" : { - "mappings" : { } - }, - "vue_storefront_catalog_taxrule" : { - "mappings" : { - "properties" : { - "id" : { - "type" : "long" - }, - "rates" : { - "properties" : { - "rate" : { - "type" : "float" - } - } - } - } - } - }, - -# ... abridged ... - - "vue_storefront_catalog_category" : { - "mappings" : { - "properties" : { - "created_at" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "is_active" : { - "type" : "boolean" - }, - "parent_id" : { - "type" : "integer" - }, - "position" : { - "type" : "integer" - }, - "product_count" : { - "type" : "integer" - }, - "slug" : { - "type" : "keyword" - }, - "updated_at" : { - "type" : "date", - "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" - }, - "url_key" : { - "type" : "keyword" - }, - "url_path" : { - "type" : "keyword" - } - } - } - } -} -``` -You can find each index has only its single mapping with convention `${indexName}_${entityType}`. - - 7. Next thing is, pumping data from source web store to the newly created ES7 index. Go to _mage2vuestorefront_ directory and fix `apiVersion` inside `elasticsearch` node in `config.js`. - -
- -
- - - With this change in `config.js`, `mage2vuestorefront` knows how to deal with your _Elasticsearch_ whose version is higher than `6`. - - 8. Now teach the machine your configuration using shell `ENV` as following example : - ```bash - export TIME_TO_EXIT=2000 - export MAGENTO_CONSUMER_KEY=lv1unkldzkcex68l3eojut4j66qqho8w - export MAGENTO_CONSUMER_SECRET=zhkuqvweo0bsg14noujqje49x3wht0qr - export MAGENTO_ACCESS_TOKEN=z6ftgc5005212bc6lnszxa7d7ocl8hgc - export MAGENTO_ACCESS_TOKEN_SECRET=h8tikjq9sz7tqm6hyhdfgs96krb6qzyk - - export MAGENTO_URL=http://local.magento/rest # Replace the url with your Magento 2 URL - export INDEX_NAME=vue_storefront_catalog # This will be the base name of the index we use - ``` - Type them in a shell then your `ENV` remembers the variables until the session expires. - - :::tip TIP - If you don't know how to get those credentials, please take a look at [data import](/guide/cookbook/data-import.html#_2-2-recipe-b-using-on-premise) - ::: - - 9. Run your worker to pump it up from source web store to Elasticsearch : -```bash -node --harmony cli.js categories --removeNonExistent=true -``` - -Screen will show you logs as follows : -```bash -2020-01-07T07:21:00.959Z - debug: Elasticsearch module initialized! -info: Winston logging library initialized. -2020-01-07T07:21:00.991Z - info: Connected correctly to server -2020-01-07T07:21:00.992Z - info: TRANSACTION KEY = 1578381660987 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories -debug: Response received. -Dest. cat path = default-category-2 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/2 -debug: Response received. -Dest. cat path = default-category-2 -2020-01-07T07:21:02.029Z - debug: Storing extended category data to cache under: vue_storefront_catalog_cat_2 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/4 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/5 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/3 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/7 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/8 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/9 -debug: Calling API endpoint: GET http://franko.local/rest/V1/categories/6 -debug: Response received. -Dest. cat path = boy/pants/pants-4 -2020-01-07T07:21:03.427Z - info: Subcategory data extended for 2, children object 4 -debug: Response received. -Dest. cat path = girl/pants/pants-9 -2020-01-07T07:21:03.431Z - info: Subcategory data extended for 2, children object 9 - -# abridged ... -``` -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/IViPWiFBkiE4of9L3ykncoPmU) - -Confirm you are on the correct path with following command : -```bash -curl -XGET "http://localhost:9200/vue_storefront_catalog_category/_search?pretty=true" -``` - -Response should be like below : -```bash -# ... abridged - { - "_index" : "vue_storefront_catalog_category", - "_type" : "_doc", - "_id" : "8", - "_score" : 1.0, - "_source" : { - "id" : 8, - "parent_id" : 6, - "name" : "Outer", - "is_active" : true, - "position" : 2, - "level" : 3, - "product_count" : 1, - "children_data" : [ ], - "children" : "", - "created_at" : "2019-12-24 17:05:27", - "updated_at" : "2019-12-24 17:05:27", - "path" : "1/2/6/8", - "available_sort_by" : [ ], - "include_in_menu" : true, - "display_mode" : "PRODUCTS", - "is_anchor" : "1", - "children_count" : "0", - "custom_use_parent_settings" : "0", - "custom_apply_to_products" : "0", - "url_key" : "outer-8", - "url_path" : "girl/outer/outer-8", - "slug" : "outer-8", - "tsk" : 1578381660987 - } - }, - { - "_index" : "vue_storefront_catalog_category", - "_type" : "_doc", - "_id" : "9", - "_score" : 1.0, - "_source" : { - "id" : 9, - "parent_id" : 6, - "name" : "Pants", - "is_active" : true, - "position" : 3, - "level" : 3, - "product_count" : 0, - "children_data" : [ ], - "children" : "", - "created_at" : "2019-12-24 17:05:52", - "updated_at" : "2019-12-24 17:05:52", - "path" : "1/2/6/9", - "available_sort_by" : [ ], - "include_in_menu" : true, - "display_mode" : "PRODUCTS", - "is_anchor" : "1", - "children_count" : "0", - "custom_use_parent_settings" : "0", - "custom_apply_to_products" : "0", - "url_key" : "pants-9", - "url_path" : "girl/pants/pants-9", - "slug" : "pants-9", - "tsk" : 1578381660987 - } - }, - { - "_index" : "vue_storefront_catalog_category", - "_type" : "_doc", - "_id" : "7", - "_score" : 1.0, - "_source" : { - "id" : 7, - "parent_id" : 6, - "name" : "Skirt", - "is_active" : true, - "position" : 1, - "level" : 3, - "product_count" : 1, - "children_data" : [ ], - "children" : "", - "created_at" : "2019-12-24 17:05:13", - "updated_at" : "2019-12-24 17:26:22", - "path" : "1/2/6/7", - "available_sort_by" : [ ], - "include_in_menu" : true, - "display_mode" : "PRODUCTS", - "is_anchor" : "1", - "children_count" : "0", - "custom_use_parent_settings" : "0", - "custom_apply_to_products" : "0", - "url_key" : "skirt-7", - "url_path" : "girl/skirt/skirt-7", - "slug" : "skirt-7", - "tsk" : 1578381660987 - } - } - ] - } -} - -``` - -10. Repeat this process until you are done importing entities with all the indices you mapped to _Elasticsearch_ before such as _products_, _attributes_, _taxrules_ and more as follows : -```bash -node --harmony cli.js productcategories -node --harmony cli.js attributes --removeNonExistent=true -node --harmony cli.js taxrule --removeNonExistent=true -node --harmony cli.js products --removeNonExistent=true --partitions=1 -node --harmony cli.js reviews -node --harmony cli.js blocks -node --harmony cli.js pages -``` - -:::tip NOTE -API endpoints for _reviews_, _blocks_ and _pages_ from Magento are not available out of the box. You should install additional modules for them on your own. [review](https://github.com/divanteLtd/magento2-review-api), [cms](https://github.com/SnowdogApps/magento2-cms-api) -::: - -Now you are ready to launch your Vue Storefront shop powered by _Elasticsearch 7_. - -11. Run the following to launch your shop at _Vue Storefront_ root path. -```sh -docker-compose up -``` -You are all good to go! - - -### 3. Peep into the kitchen (what happens internally) -_Elasticsearch_ has been through intensive updates ceaselessly. Mostly, it's gradual changes with backward compatibility so it won't have a big impact even if you upgrade one to another. This time, however, upgrade to `7.x` has a few breaking changes, one notably of which is, multiple `_type` is not allowed for an index. - -Fixing breaks for _Elasticsearch 7.x_ has been successfully included in _Vue Storefront 1.11_ upgrade. - -First thing you need to do is, download the upgrade with `git`. Upgraded version of containers are described in `docker-compose` files so what it contains inside is well encapsulated that you don't have to be concerned of how it works internally unless you want to tweak customization more than the default. - -Next, you should newly create each mapping for _Elasticsearch_ index as per the type. Then pumping your data into _Elasticsearch_ using _mage2vuestorefront_. - -If things go as planned so far so good, your above-7-Elasticsearch should serve as performant search engine for data store with enhanced indexing ability. - -### 4. Chef's secret (protip) - -#### Secret 1. How to upgrade to `1.11` technically? foolproof approach. -There are 3 repositories that should be upgraded for `1.11`. -:::warning CHECK -Please make sure your git space is clean and has nothing to conflict with upcoming merge. -::: - -First off, go to ___Vue Storefront API___ folder and type the following : -```sh -git fetch -git merge v1.11.0 -``` -:::tip NOTE -Please note `v1.11.0` is the _tag_ that denotes the final commit for the version at the moment. -::: - -Next, go to ___Vue Storefront___ folder and type the following : -```sh -git fetch -git merge v1.11.0 -``` - -Finally, go to ___mage2vuestorefront___ folder and run the following commands : -```sh -git fetch -git merge origin/feature/es7 -``` - -Now you are all set :) - - - -
-
- -## 2. Extend Elasticsearch entities for VSF -Online shops normally have certain types of models and scenarios in common. (Because shops are shops in the end! What do you expect from shops? ;)) They are well known to the community and most of e-commerce software already implemented them into their frameworks as expected which is good for your new business. Those are represented as entities, namely, _Catalog_, _Products_, _Attributes_, _Tax rule_ and more. Since _Vue Storefront_ functions as the gorgeous gateway to those e-commerce backend, it also needs to mirror those entities as smooth as possible. - - The large part of main entities are already implemented in VSF `core` as expected but you might still need to add or remove additional entities as you want it to fulfill your mission. This recipe will give you an idea of how to do it. - -### 1. Preparation - - You need to have [setup _Vue Storefront_ stack](setup) including _Vue Storefront API_. - - There are two ways to deal with _Search Adapter_ for _Entity Type_ ; One is with _API_ (Recipe A), The other is with [_GraphQL_](https://graphql.org/) (Recipe B) - - You should have custom entity module in Magento 2 to import custom entities. Download example module [here](https://github.com/kkdg/Offline_Stores) - - You should have imported data for the new entity for the sake of testing. [2-0. Appetizer](#_2-0-appetizer) guides you in how to do it. -:::tip TIP -The default _Search Adapter_ is _API_. - -In order to change which _Search Adapter_ should be in labor, please take a look at here [Chef's secret 1. how to switch search adapters](#secret-1-how-to-switch-search-adapters) -::: - -### 2-0 Appetizer - 0. Assume you need an entity type for _Offline Stores_ of your online shop for example. So you can store the information of your stores in data store, which is _Elasticsearch_ in this case, read the data whenever you need it like you want to display _offline stores_ while on checkout for customer to pick up if they live nearby. -:::tip NOTE -There are two ways to import your data into data store. One for using [`mage2vuestorefront`](https://github.com/vuestorefront/mage2vuestorefront) which runs _NodeJS_ scripts to do the job while the other for using [`magento2-vsbridge-indexer`](https://github.com/vuestorefront/magento2-vsbridge-indexer) that is a native Magento 2 module for the job. - -In this recipe, we choose the former. But don't worry we will also look into the latter in the [Chef's secret 2](#secret-2-how-to-make-a-custom-import-using-magento2-vsbridge-indexer). -::: - - 1. Go to ___mage2vuestorefront___ root folder and do the following : - ```bash -cd src/adapters/magento - ``` -This folder is where the connector adapter should be. - - 2. Create the following file named, say, `offline_stores.js` as follows : - ```js -'use strict'; - -let AbstractMagentoAdapter = require('./abstract'); -const CacheKeys = require('./cache_keys'); -const util = require('util'); - -class OfflineStoresAdapter extends AbstractMagentoAdapter { - - getEntityType() { - return 'offline_stores'; - } - - getName() { - return 'adapters/magento/OfflineStoresAdapter'; - } - - getSourceData(context) { - return this.api.offlineStores.list(); - } - - /** Regarding Magento 2 api docs and reality we do have an exception here that items aren't listed straight in the response but under "items" key */ - prepareItems(items) { - if(!items) - return items; - - if (items.total_count) - this.total_count = items.total_count; - - if(items.items) - items = items.items; // this is an exceptional behavior for Magento 2 api for attributes - - return items; - } - - isFederated() { - return false; - } - - preProcessItem(item) { - return new Promise((done, reject) => { - if (item) { - - } - - return done(item); - }); - } - - /** - * We're transforming the data structure of item to be compliant with Smile.fr Elastic Search Suite - * @param {object} item document to be updated in elastic search - */ - normalizeDocumentFormat(item) { - return item; - } -} - -module.exports = OfflineStoresAdapter; - - ``` - -This is the basic skeleton for an adapter. We will look at this later. - - 3. Now, move on to `magento2-rest-client` library : - ```bash -cd magento2-rest-client/lib - ``` - - 4. Here we need to create a library file `offline_stores.js` for the adapter with the following : - ```js -var util = require('util'); - -module.exports = function (restClient) { - var module = {}; - - module.list = function (searchCriteria) { - var endpointUrl = util.format('/offline-stores'); - return restClient.get(endpointUrl); - } - - return module; -} - - ```` -This library file only deals with _GET_ API to get a list of offline stores from Magento 2. - -:::tip NOTE -```js -var endpointUrl = util.format('/offline-stores'); -``` - This line is particularly important, since `'/offline-stores'` is where the API url endpoint is determined. It should match the API url endpoint of Magento 2 side. -::: - - 5. Now we need to include this library in `index.js` : - ```bash -cd .. -vi index.js - ``` - - Then fix it as follows : - - ```js{5,16} -// ... abridged - -var blocks = require('./lib/blocks'); -var pages = require('./lib/pages'); -var offlineStores = require('./lib/offline_stores'); - -const MAGENTO_API_VERSION = 'V1'; - -module.exports.Magento2Client = function (options) { - var instance = {}; - - options.version = MAGENTO_API_VERSION; - - var client = RestClient(options); - - instance.offlineStores = offlineStores(client); - instance.attributes = attributes(client); - instance.categories = categories(client); - -// abridged ... - ``` - - 6. Time for creating a command to import the data, go to the folder where `cli.js` locates : - ```bash -cd ../../.. # ./src - ``` - -Open `cli.js` and add a method and a command as follows : - ```js{3-22} - // ... abridged - -const reindexOfflineStores = (adapterName) => { - return new Promise((resolve, reject) => { - let adapter = factory.getAdapter(adapterName, 'offline_stores'); - adapter.run({ - done_callback: () => { - logger.info('Task done! Exiting in 30s...'); - setTimeout(process.exit, TIME_TO_EXIT); // let ES commit all changes made - resolve(); - } - }); - }); -} - -program - .command('offlinestores') - .option('--adapter ', 'name of the adapter', 'magento') - .option('--removeNonExistent ', 'remove non existent products', false) - .action(async (cmd) => { - await reindexOfflineStores(cmd.adapter, cmd.removeNonExistent); - }); - -program - .command('attributes') - .option('--adapter ', 'name of the adapter', 'magento') -// abridged... - ``` - - 7. All is good, now run the command to import offline stores information! - ```bash -node cli.js offlinestores - ``` - You will see a screen like below : - ```bash -2020-03-10T09:22:30.359Z - debug: Elasticsearch module initialized! -info: Winston logging library initialized. -2020-03-10T09:22:30.796Z - info: Connected correctly to server -2020-03-10T09:22:30.796Z - info: TRANSACTION KEY = 1583832150786 -debug: Calling API endpoint: GET http://localhost/rest//V1/offline-stores -debug: Response received. -2020-03-10T09:22:32.130Z - info: Importing 0 of 3 - 1 with tsk = 1583832150786 -2020-03-10T09:22:32.131Z - info: Tasks count = 2 -2020-03-10T09:22:32.139Z - info: Importing 1 of 3 - 2 with tsk = 1583832150786 -2020-03-10T09:22:32.139Z - info: Tasks count = 1 -2020-03-10T09:22:32.139Z - info: Importing 2 of 3 - 3 with tsk = 1583832150786 -2020-03-10T09:22:32.140Z - info: Tasks count = 0 -2020-03-10T09:22:32.140Z - info: No tasks to process. All records processed! -2020-03-10T09:22:32.140Z - info: Task done! Exiting in 30s... - ``` -:::warning NOTE -You should tell the machine the environment variable like this before running the command : -```bash -export MAGENTO_URL=http://localhost/rest -``` -Please replace the url if you use different one for the Magento 2 instance. -::: - - 8. Let's confirm the result and wrap this up! -```bash -curl localhost:8080/api/catalog/vue_storefront_catalog/offline_stores/_search -``` - -The response should be something as follows unless there is something wrong : -```bash -{ - "took": 2, - "timed_out": false, - "_shards": { - "total": 1, - "successful": 1, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": { - "value": 3, - "relation": "eq" - }, - "max_score": 1, - "hits": [ - { - "_index": "vue_storefront_catalog_offline_stores", - "_type": "_doc", - "_id": "2", - "_score": 1, - "_source": { - "id": "2", - "name": "Wrocaw", - "address": "Ulica Romana Dmowskiego 17, 50-203 Wrocaw, Poland", - "phone": "+48 577 032 500", - "is_active": "1", - "tsk": 1583832150786 - } - }, - { - "_index": "vue_storefront_catalog_offline_stores", - "_type": "_doc", - "_id": "3", - "_score": 1, - "_source": { - "id": "3", - "name": "Seoul", - "address": "Seoul GangNam Street 1st 12", - "phone": "+82 10 2364 3330", - "is_active": "1", - "tsk": 1583832150786 - } - }, - { - "_index": "vue_storefront_catalog_offline_stores", - "_type": "_doc", - "_id": "1", - "_score": 1, - "_source": { - "id": "1", - "name": "Warszawa", - "address": "Koci pw. w. Michaa Archanioa", - "phone": "+48 22 845 46 04", - "is_active": "1", - "tsk": 1583832150786 - } - } - ] - } -} -``` -You have successfully imported your custom entities! - - -### 2-1. Recipe A (with API) - - 1. First off, we need to create an `api` folder under `src/search/adapter/` as follows : - ```bash -cd src/search/adapter -mkdir api - ``` - - 2. Copy `searchAdapter` file from `core` folder : - ```bash -cp ../../../core/lib/search/adapter/api/searchAdapter.ts api/ - ``` - -:::tip NOTE -The reason you should copy the whole `searchAdapter.ts` file is, in doing so your adapter also includes default entities from `core` into custom file because your custom entities can't be added incrementally to the default. Here's [why](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/search/adapter/searchAdapterFactory.js#L12-L20) -::: - - 3. Write a function to handle adding a custom entity type in `searchAdapter.ts` you just copied and initialize it in the _constructor_ of the same class as follows : - ```js{9,14-24} -// ... abridged - -export class SearchAdapter { - public entities: any - - public constructor () { - this.entities = [] - this.initBaseTypes() - this.initCustomTypes() - } - - //... abridged ... - - public initCustomTypes() { - this.registerEntityType('offline_stores', { - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - return this.handleResult(resp, 'offline_stores', start, size) - } - }) - } -} - - ``` -Here you can see `registerEntityType` method add `offline_stores` entity type as an example. - -If you want to add more entities, you can clone the example as many times as you want and change entity name to your need. - -:::tip TIP -The method `initCustomTypes` above is arbitrarily named out of the blue, so you can actually have any other name for the method. -::: - -Now you are all set to use custom entity you just created. The next step lets you have a simple idea how to confirm it. (optional) - - 4. Go to `src/modules/instant-checkout/components` and open `InstantCheckout.vue`. Fix it as follows : - ```js{5,12-14} -// ... abridged -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import { registerModule } from '@vue-storefront/core/lib/modules' -import { OrderModule } from '@vue-storefront/core/modules/order' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search'; // Import the method to fetch data from ES - -const storeView = currentStoreView() - -// ... abridged - - methods: { - async showPayment () { // the method should be done with async/await - let offlineStores = await quickSearchByQuery({ entityType: 'offline_stores' }); - alert("Your item will be sent from the shop at " + offlineStores.items[0].address); - const payment = new PaymentRequest(this.paymentMethods, this.paymentDetails , this.paymentOptions) - - // abridged ... - ``` - -Now go to your online shop, put an item to cart and open it, click __Instant Checkout__ button, then you will see the screen like below : - -![instant_checkout_store_borderline](../images/stores.png) - - - - -### 3. Peep into the kitchen (what happens internally) -In this recipe, we iterated a whole journey of building custom entities on your online shop (it was Magento 2 for this time) for whatever reason to deal with various information for enhancing your customer experience. - -First, we downloaded a Magento 2 module for _Offline Stores_ entity. It contains basic information for each offline store. - -Second, as an appetizer, we had to import data from shop using _mage2vuestorefront_. We followed how core team did it with its sibling open source. - -Third, main dish, we extended core adapters in `src` folder so we are safe for future updates :). - It was actually very easy! you just need to `registerEntityType` for your custom entity! We also looked at how to implement it in real example though it was simplified version, you better follow `vuex` best practice. - -We also have a variety of main dish, by giving you an option to go with _GraphQL_. This approach took us a little more to tweak with, but believe me, _GraphQL_ has [pretty good advantage](https://www.altexsoft.com/blog/engineering/graphql-core-features-architecture-pros-and-cons/) over its competitors. - -Now we can extend our shop as good as it gets to handle more versatile information on your shop. Congratulation! - - -### 4. Chef's secret (protip) - -#### Secret 1. How to switch _Search Adapters_ -If you want to use _GraphQL_ adapter for your search, you need to change the value for `server.api` node to `graphql` in `./config/local.json` in _Vue Storefront_ as follows : -```json{6} -{ - "server": { - "host": "localhost", - "port": 3000, - "protocol": "http", - "api": "graphql", - "devServiceWorker": false, - "useHtmlMinifier": true, - "htmlMinifierOptions": { -``` - - -
-
- - diff --git a/docs/guide/cookbook/history.md b/docs/guide/cookbook/history.md deleted file mode 100644 index 905b6973a3..0000000000 --- a/docs/guide/cookbook/history.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ch 14. Old Tales about VSF - -## Coming soon! diff --git a/docs/guide/cookbook/integration.md b/docs/guide/cookbook/integration.md deleted file mode 100644 index 7d745958de..0000000000 --- a/docs/guide/cookbook/integration.md +++ /dev/null @@ -1,3 +0,0 @@ -# Ch 4. Integration with 3rd Party - -## Coming soon! diff --git a/docs/guide/cookbook/internals.md b/docs/guide/cookbook/internals.md deleted file mode 100644 index da8efdae3d..0000000000 --- a/docs/guide/cookbook/internals.md +++ /dev/null @@ -1,88 +0,0 @@ -# Ch 10. Internals internally - - -In this chapter, we are going to cover : -[[toc]] - -## 0. Introduction -Now you are experienced with ups and downs of **Vue Storefront**. You know how to tweak themes and modules to your liking. You are confident of the platform for unpredictable challenges with your hands already ready to kill them all. You are, however, still thirsty for something behind a curtain, in search of knowledge of who made what for whatever reason particularly for this particular spot. - -In this cookbook, we will decipher the glyphs and the cryptics of core code base from a launch to retire. -
-
- -## 1. Following the entrance -Ever wonder what happens when you enter into **Vue Storefront** shop? From gateway and pathway to all the magics inside a black box so that you get the hang of the whole concept of flow from browser request to server response. In this recipe, we will follow a processing of server/client from the beginning when you make a request to the shop. - - -### 1. Preparation -- You need the [whole infrastructure set up](/guide/cookbook/setup) to inspect the entire flow. - -### 2. Recipe - -1. Go to **Vue Storefront** root path and run the following : -```bash -yarn dev -``` - -2. Open your browser and go to your development **Vue Storefront** store, for example, [_http://localhost:3000_](http://localhost:3000) if you run it by default. - -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 2. How they are inter-dependent - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 3. After connection to server retired - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 4. Building your own with reflection over the design - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 5. Benchmarking for every corner - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 6. Improvement idea from thought experiment - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 7. Sources for further learning - - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
\ No newline at end of file diff --git a/docs/guide/cookbook/migration.md b/docs/guide/cookbook/migration.md deleted file mode 100644 index f942c2cb4f..0000000000 --- a/docs/guide/cookbook/migration.md +++ /dev/null @@ -1,24 +0,0 @@ -# Ch 13. Migration - -In this chapter, we are going to cover : -[[toc]] - -## 0. Introduction -Ever wonder if _the great legendary software_ has been invented once and for all and never needed an update since it was too great from its beginning? Even _Homer_ nodded that _Vue Storefront_ is such case, however, when _the great great legendary_ gets in the way, you should yield to it for the better. - -Handling migration or any sort of upgrade for a system takes developer's life. The more you had great times with previous version, the griever developer's concerns will likely be because complexity for upgrade will only gets worse due to the character of upgrades; _Removal of bad structure_. - -Remember when _Angular.js_ upgrade to _Angular_ was announced? A great number of developers forever left _Angular_ community since the upgrade showed no mercy at all with barely no backward compatibility. - -One might ask why fear for upgrade when it's a click away. If you say so, I bet you are not a developer who does the actual job or precisely speaking, _magic_ if I may say. - -Many attempts were made to mitigate such headache disaster for developers. This chapter will show you how we did it our way for that matter. And please don't forget, upgrades are here to help you in the end, not torture you. _Don't worry, be happy!_ - -## 1. Upgrade from _1.10_ to _1.11_ -_Vue Storefront_ has been under massive upgrades once again from 1.10 to 1.11. - -## 2. Upgrade from _1.09_ to _1.10_ - -## 3. Upgrade from _1.09_ to _1.11_ - Two steps in one shot - -## 4. Upgrade from _legacy_ to _latest_ - General Theory diff --git a/docs/guide/cookbook/module.md b/docs/guide/cookbook/module.md deleted file mode 100644 index ad83debb65..0000000000 --- a/docs/guide/cookbook/module.md +++ /dev/null @@ -1,859 +0,0 @@ -# Ch 5. Building a _Module_ from scratch - - -In this chapter, we are going to cover : -[[toc]] - -## 0. Introduction -Modular approach to a system is often considered an implementation of the traditional engineering concept [*High cohesion with low coupling*](https://stackoverflow.com/questions/14000762/what-does-low-in-coupling-and-high-in-cohesion-mean). By this we mean, one may ***extend*** a logic of others without breaking a flow of the original. **Vue Storefront** also employs the concept in order to secure 3rd party developers' happiness. This chapter opens up the belly of new **Vue Storefront** module structure for you as of *1.11* release. -
-
- -## 1. How to bootstrap a module -If the open source authors are serious about their offspring, one must admit it's impossible to take care of all the details to all the use cases out in the wild. So the creator should expose joint point of core parts so that 3rd party developers may inject their wild logics into the working machine when they need it. Now lo and behold, jungles conquered. - -When you want to tweak any open source for whatever reason needed to make it more fantastic, first thing you need to look for is *modules* within the code base. You may name *API*, *hooks* or *observers* for the same matter, but *module* basically represents all of them in one place in design. - -In this recipe, we are going to cover how we bootstrap a module in its bare minimum in order to inject our logic into the machine. We will explore two different methods, one for manual install, the other for [`CLI`](setup.html#_4-storefront-cli-at-your-service) module generation with the boilerplate. *Tarzans, follow!* - -:::tip TIP -If you want to know the detailed difference of _Manual_ method and _CLI_ method, please go to [Recipe 5. Packaging a module](#_5-packaging-a-module) -::: - -### 1. Preparation -- You need [**Vue Storefront**](https://github.com/vuestorefront/vue-storefront) instance [installed along with other infrastructure ](setup.html#_1-install-with-docker) on your machine to build a new module and test it working. -- You need a development editor of your choice for your own convenience. -- You need _Vue Storefront_ [`CLI`](https://www.npmjs.com/package/@vue-storefront/cli) [installed](setup.html#_4-storefront-cli-at-your-service) on your machine for [Recipe B](#_2-2-recipe-b-cli-bootstrapping) installing with `CLI`. - -### 2-1. Recipe A (Manual bootstrapping) - -1. Create a folder under `./src/modules/example-module` from your **Vue Storefront** root path. -```bash -cd src/modules -mkdir example-module && cd example-module -``` - -2. Create `index.ts` file in the directory -```bash -touch index.ts -``` - -3. Open that file and write down the minimum signature of a module -```bash -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - -} -``` -We export _ExampleModule_ here implementing _StorefrontModule_ interface. - -:::tip LOOK INSIDE -From `./core/lib/modules.ts`, you can check out _StorefrontModule_ signature as follows : -```bash -export interface StorefrontModule { ( - app: any, # app instance (Vue instance) - store: Store, # Vuex store instance - router: VueRouter, # router instance - moduleConfig: any, # module config during registration - appConfig: any): void # VSF config -} -``` -Judging by this signature, you can access `store`, `router`, `config`s from your module. -::: - -4. _(Optional)_ Add a comment in order to figure out when it's registered later : -```bash -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - console.log('Hello World and VSF!'); # Any punch line allowed! -} -``` - -5. Go to parent directory (`./src/modules`) and open `index.ts` file as follows : -```bash -cd .. -vi index.ts # here you can use another editor for sure instead of vi -``` -This file is where you can register any module you create. Now here insert registration for the module we just created as follows : -```ts{5,20} -/* ... abridged */ - -import { PaymentCashOnDeliveryModule } from './payment-cash-on-delivery'; -import { InstantCheckoutModule } from './instant-checkout' -import { ExampleModule } from './example-module' /* Import Example module we just created */ - -import { registerModule } from '@vue-storefront/core/lib/modules' - -export function registerNewModules () { - registerModule(UrlModule) - registerModule(CatalogModule) - registerModule(CheckoutModule) - -/* ... abridged ... */ - - registerModule(PaymentCashOnDeliveryModule) - registerModule(AmpRendererModule) - registerModule(InstantCheckoutModule) - - registerModule(ExampleModule) // Register Example module we just created -} -``` - -:::tip TIP -Modules can be _lazy-loaded_ naturally. The _lazy loading_ generally has a few advantages such as performance, low overhead during initialization and may allow code separation in more structured way. One advice is, if your module is required across the entire app, it's better to stay with the regular place that is `./src/modules/index.ts` as demonstrated above. On the contrary, if your module is confined and bound to a certain route or bundle, then it might be wiser to register it inside them and load the bundle lazily. - - _Lazy loading_, however, also has a downside that you may not be able to access some hooks such as `afterAppInit` because they are not lazy. (meaning only fired during app initialization) -::: - -6. Run the command at **Vue Storefront** root path to bootstrap **Vue Storefront** app -```bash -docker-compose up -``` -or without `docker` -```bash -yarn dev -``` - -Once the app is up and running, it will spit out tons of logs indicating the jobs done. Open your eyes wide and if you are lucky, you will pick lines similar to as follows from super fast scrolling : -```bash{4} -# ... abridged - -app_1 | [GTM] Google Tag Manager extensions is not working. Ensure Google Tag Manager container ID is defined in config null -app_1 | Hello World and VSF! # YAY! now we know it's successfully registered -app_1 | [module] VS Modules registration finished. { succesfulyRegistered: '0 / 0', registrationOrder: [] } -app_1 | Entity cache is enabled for productList null - -# abridged ... -``` - -### 2-2. Recipe B (CLI bootstrapping) - -1. Go to your project folder _or_ any prestine folder for your new `module` development. -```bash -mkdir example-folder -cd example-folder -``` - -2. Run the `vsf` CLI command as follows : -```bash -vsf init:module example-folder -``` -The command required for module initialization here is `vsf init:module` and your new module name is `example-folder` in this case. - -You will see the following result : -```bash -Module vsf-example-folder has been succesfully created! - cd vsf-example-folder -``` - -:::tip NOTE -You might have noticed `vsf` put a prefix _`vsf`_ by default to your newly created module name. This helps your module get compiled automatically during _[INSERT UPDATE REQUIRED]_ -::: - -3. As the result dictates, change your directory to : -```bash -cd vsf-example-folder -``` - -4. List the files inside as follows : -```bash -ls * -``` -You will see the following structure : -```bash -package.json README.md tsconfig.json - -src: -index.ts store.ts -``` -Congratulation, you are good to go now. - -Further scenarios of this can be found at [Recipe 5. Packaging a module](#_5-packaging-a-module) - -### 3. Peep into the kitchen (what happens internally) -We have created a module with only a few simple steps and registered it successfully. Even though it's doing nothing practically, it was enough to grab the concept in design, and helped you transform into a module developer which is great. - -We built our `module` house in the territory of `./src/modules`. We created the door named `index.ts` where all the module parts are assembled and exported, although we skipped building module parts for the sake of brevity, which we will look inside more in detail later. Instead what we created in `index.ts` was bare minimum skeleton required to build a module and declare it's a module. - -Here `index.ts` uncovers a module is basically a function with access to certain parts of Vue app instance and allows to interact with it. Additionally achieved is a better versatility thanks to `helpers` and `hooks` along with it. - -If you take a look a little deeper, you will arrive at `./core/lib/modules.ts` where module signature and `registerModule` function take up seats. `registerModule` is the hard worker who works in `src/modules/index.ts` to register modules pushing individuals into `registeredModules`. (Beware the small difference in those names as in `registerModule`, `registeredModules`, `registerModules` and so on, might be confusing if you skim it, but they are correct and appropriate in its own role with matching names) - -With this approach our module development experience is much straight-forward and *Vue native API* friendly so that code becomes simpler, cleaner and maintainable. - -Now you are officially a **Vue Storefront module developer**. Congratulation! - -### 4. Chef's secret (protip) -#### Secret 1. Lazy loading in practical examples - -#### Secret 2. How a module can be leveraged to build extensions or integrations with. - -#### Secret 3. On the border of modularity - -
-
- -## 2. Best practices for tweaking a module -Once you got the hang of building a skeleton for modules, now it's time for working a real deal with modules. There are tons of opportunities here with freedom of building new modules powered by variety of methods available being mainly in a sense with working, extending and hooking to Vue's main parts. - -In this recipe, we walk through steps to building a simple module for _Like button in product page_. This recipe browses a brief concept of each topic for the demonstration purpose. The full details for building the module continues at [Recipe 7. Building a module from A to Z](#_7-building-a-module-from-a-to-z-in-an-iteration) -### 1. Preparation -- You need a new module to play with. You would already have had one if you finished [_Recipe 1. How to bootstrap a module_](#_1-how-to-bootstrap-a-module) - -### 2-1. Recipe A (Extend Vuex store from inside a module) -_Vue Storefront_ takes advantage of _Vuex_'s `module` feature. You can encapsulate your module's data from global scope so that control over your data is secured easily within your grasp. - -1. Open the `index.ts` file of `example-module` at `./src/modules/example-module` - -```bash -cd src/modules/example-module -vi index.ts # of course you can open it with other editors! -``` - -2. Prepare a store for the module as follows : -```ts{3-8} -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -const exampleModuleStore = { - namespaced: true, - state: { - key: null - } -} - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { -// abridged ... -``` -`namespaced` with `true` value means this `store` is encapsulated inside a module and not registered to global store. - -`state` contains data object you want to track. - -3. Register this `store` to app's `store` with `registerModule` : -```ts{11} -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -const exampleModuleStore = { - namespaced: true, - state: { - key: null - } -} - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - store.registerModule('example-module', exampleModuleStore); -} - -``` -`registerModule` method is a Vue native API to dynamically register a store for each module. _Vue Storefront Module_ uses this method for data store so that module data is encapsulated from global scope. [more info](https://vuex.vuejs.org/guide/modules.html#dynamic-module-registration) - -Consider the `store` as a _Model_ in plain MVC model. - -4. You can add Vuex `plugins` to your store. -```ts{3-9,16} -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -const examplePlugin = store => { - store.subscribe((mutation, state) => { - if (mutation.type === 'PRESSED_LIKE') { - console.log('Customer pressed LIKE button on the product'); - } - }) -} - -const exampleModuleStore = { - namespaced: true, - state: { - key: null - }, - plugins: ['examplePlugin'] -} - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - store.registerModule('example-module', exampleModuleStore); -} -``` -`plugins` are handy when you want to _plug in_ an event to `mutation` of `state`. - -`mutation` is an object that contains `type` and `payload`. By checking `mutation` type, you can listen to the certain type of state changes. [more info](https://vuex.vuejs.org/guide/plugins.html) - -You can also listen not only to `mutations` but also to `actions` as follows : -```js -store.subscribeAction((action, state) => { - console.log(action.type) - console.log(action.payload) -}) -``` -It also provides `before` and `after` decorator to when the `plugin` should be fired of the event. (available since `3.1.0`) [more info](https://vuex.vuejs.org/api/#subscribeaction) - - -### 2-2. Recipe B (Override Vuex store with `extendStore`) -_Vue Storefront_ people came up with the idea to ___help___ module developers easily extend the module store already registered with the same name. - -1. Open the `index.ts` file of `example-module` again at `./src/modules/example-module` -```bash -cd src/modules/example-module -vi index.ts # of course you can open it with other editors! -``` - -2. Import `helpers` of core as follows : -```ts{1} -import { extendStore, isServer } from '@vue-storefront/core/helpers'; -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -const examplePlugin = store => { -// abridged ... -``` - -3. Add an additional `product` store : -```ts{5-9} -// ... abridged - plugins: ['examplePlugin'] -} - -const newProductModule = { - state: { - liked: false - } -} - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { -// abridged ... -``` - -4. Run `extendStore` helper method to override or add to existing store `product` as follows : -```ts{4} -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - store.registerModule('example-module', exampleModuleStore); - - extendStore('product', newProductModule); -} -``` - -5. In order to confirm it's successfully extended, we will use [Chrome vue-devtools extension](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=en) which really comes in handy when you develop _Vue.js_ application. - -Open _Chrome DevTools_ and go to _Vue_ tab, and click _Vuex_ tab or click `ctrl` + `2`. Finally click _Register module : product_, then you will see a screen like as follows confirming `product` store has been extended successfully : - -![product_liked_borderline](../images/product_like_state.png) - -:::tip TIP -You may use _vue-devtools_ for _Firefox_ if you use _Firefox_. -Install [Firefox vue-devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) - -::: - - -### 2-3. Recipe C (Extend router instance) -You can also dynamically add routes for your module using Vue native API. - -1. Create a dummy Vue component `Liked.vue` just to test `route` at `components` directory from `./src/modules/example-module` directory. -```bash -cd src/modules/example-module -mkdir components && cd components && vi Liked.vue -``` - -2. Fill the file with dummy template code as follows : -```vue - - - - - -``` - -3. Go back to parent directory, open `index.ts` file and work on the module router as follows : -```ts{3,14,21-22} -import { extendStore, isServer } from '@vue-storefront/core/helpers'; -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import Liked from './components/Liked.vue'; // Import the component - -const examplePlugin = store => { - -// abridged ... - - state: { - liked: false - } -} - -const exampleRoutes = [{ name: 'liked', path: '/liked', component: Liked, alias: '/liked.html' }]; // compose the router we will use - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - store.registerModule('example-module', exampleModuleStore); - - extendStore('product', newProductModule); - - router.addRoutes(exampleRoutes) // adding routes here - router.beforeEach((to, from, next) => { next() }) // navigation guards here -} - -``` -Routes to be added must be _Array_ type even if it only has one element as you can see it with `exampleRoutes` above. Each route object has `name`, `path`, `component` and `alias` property and it's pretty straightforward about what they are. - -`addRoutes` is Vue native API to add routes dynamically to the router. - -`beforeEach` is the navigation guard Vue API provides. [more info](https://router.vuejs.org/guide/advanced/navigation-guards.html) - -4. Open your browser and visit the route `/liked` we just created then you will see a screen like this : - -![route_liked_borderline](../images/route_liked.png) - -### 2-4. Recipe D (Use hooks) -One of the most intuitive way to build a module is using hooks. Open source creators more than often intentionally leave hooks as many as possible to everywhere they think extendable for 3rd party developers to inject logic into the flow of program. Here we will look into how _Vue Storefront_ did it its way. - -1. Open the `index.ts` file of `example-module` again at `./src/modules/example-module` -```bash -cd src/modules/example-module -vi index.ts # of course you can open it with other editors! -``` - -2. Import `coreHooks` from `core` : -```ts{4} -import { extendStore, isServer } from '@vue-storefront/core/helpers'; -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import Liked from './components/Liked.vue'; -import { coreHooks } from '@vue-storefront/core/hooks'; // Import hooks from core - -const examplePlugin = store => { -// abridged ... -``` - -3. Call any hook you want to use as follows : -```ts{11-13} -// ...abridged - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - store.registerModule('example-module', exampleModuleStore); - - extendStore('product', newProductModule); - - router.addRoutes(exampleRoutes) - router.beforeEach((to, from, next) => { next() }) - - coreHooks.afterAppInit(() => { // - console.log('App has just been initialized') - }) -} - -``` - -4. Confirm it's hooked, run the command at **Vue Storefront** root path to bootstrap **Vue Storefront** app -```bash -docker-compose up -``` -or without `docker` -```bash -yarn dev -``` - -Once again the app is up and running, it will spit out tons of logs indicating the jobs done including : -```bash{2} -app_1 | [module] VS Modules registration finished. { succesfulyRegistered: '0 / 0', registrationOrder: [] } -app_1 | App has just been initialized # Successfully Hooked ! -app_1 | Result from ES for 3e9eb2ab7b4d96276c016ae9d5aa18116483667603e7e84ad2346627 (category), ms=613 null -app_1 | whole request [/liked]: 1323ms -``` -You can read [more in depth](#_3-hooking-into-hooks) - -### 2-5. Recipe E (Manage module-level `config`) -Sometimes you may need to pass values to populate fields in your module configuration. We give you the ability to pass a `config` object to `registerModule` function, giving you options to choose when you register the `module`. - -Suppose you need to use a 3rd party service integrated to your storefront. Most of the time you need to provide an API credentials encapsulated in a request to the 3rd party so that they will know _you are you_ and process a service and return a result that belongs to you. This recipe tells you how to do it with using 3rd party account during module registration. - - -1. Open the `index.ts` file of `example-module` again at `./src/modules/example-module` -```bash -cd src/modules/example-module -vi index.ts # of course you can open it with other editors! -``` - -2. Prepare the module to accept dynamic `moduleConfig` option where you want to allow to override values as follows : -```ts{8-12,17} -// ...abridged - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - store.registerModule('example-module', exampleModuleStore); - // ... abridged ... - - // Prepare apiKey for a request to a 3rd party to integrate with it, example as follows : - if (moduleConfig.apiKey) { - const apiKey = moduleConfig.apiKey - } else { - // raise an error related to failure for sign-in to 3rd party service due to lack of apiKey - } - - // Continue to send a request to the 3rd party as the context demands - // ... abridged for the sake of brevity ... - - console.log(apiKey); // This line helps you confirm apiKey value is overridden as intended -} -``` -This indicates `moduleConfig` object has `apiKey` option for providing a choice for `module` user when registering it. - -3. Go to parent directory, open `index.ts` which is `./src/modules/index.ts` and fix the code as follows : -```ts{6-10} -// ... abridged - - registerModule(AmpRendererModule) - registerModule(InstantCheckoutModule) - - registerModule(ExampleModule, { - apiKey: "YOUR_VALUABLE_API_KEY_ON_THE_FLY" - }) // Here you pass config object as you want it -} - -// abridged ... -``` - -4. In order to confirm `moduleConfig` option passed as planned, run the command at **Vue Storefront** root path to bootstrap **Vue Storefront** app -```bash -docker-compose up -``` -or without `docker` -```bash -yarn dev -``` - -Once again the app is up and running, now look for the part we injected : -```bash{2} -app_1 | [module] VS Modules registration finished. { succesfulyRegistered: '0 / 0', registrationOrder: [] } -app_1 | YOUR_VALUABLE_API_KEY_ON_THE_FLY # moduleConfig injected successfully ! -app_1 | Calling asyncData in Home (theme) null -``` -You can read [more in depth](#_4-on-configuration) - -### 2-6. Recipe F (Access app-level `config`) -When you work on building a _module_ in _Vue Storefront_, you can also access app's `config`. `config` is compiled version of `./config` folder which is normally a copy of `local.json`. When you need to access `config`, you can do it inside a `module`. - -1. Open the `index.ts` file of `example-module` again at `./src/modules/example-module` -```bash -cd src/modules/example-module -vi index.ts # of course you can open it with other editors! -``` - -2. Call a node of `config` inside a `module` as follows : -```ts{4} -// ... abridged - -export const ExampleModule: StorefrontModule = function ({app, store, router, moduleConfig, appConfig}) { - console.log(appConfig.products.defaultFilters); // "products": {"defaultFilters": ["color", "size", "price", "erin_recommends"]} - -// abridged ... -``` - -3. You can see the log as follows if you bootstrap your _Vue Storefront_ app : -```bash{2} -app_1 | [GTM] Google Tag Manager extensions is not working. Ensure Google Tag Manager container ID is defined in config null -app_1 | [ 'color', 'size', 'price', 'erin_recommends' ] # here we go! successfully fetched global app config -app_1 | [module] VS Modules registration finished. { succesfulyRegistered: '0 / 0', registrationOrder: [] } -app_1 | This is one way to use moduleConfig -``` - - -### 2-7. Recipe G (Setting up server module) - -We strongly recommend using kind of HTTP server as a proxy in front of Vue Storefront. Let it be `nginx` (suggested in our [production setup docs](/guide/installation/production-setup.html)) or `Varnish` or even `Apache`. Any of those HTTP servers allows you to add some authorization or redirects layer before Vue Storefront. - -This is a recommended way. - -#### 1. Advanced Output Processing - -However, by using [advanced output processing](/guide/core-themes/layouts.html#how-it-works) you can easily generate any text data output from your Vue Storefront site you want. Including JSON, XML and others. It's a way to generate sitemaps and other data based documents. - -#### 2. `Express.js` middleware - -The other option is to create a `Express.js` middleware. Our `core/scripts/server.ts` is a classical Node.js application so it should be easy. To do so you might want to create a [server module](https://github.com/vuestorefront/vue-storefront/blob/develop/src/modules/compress/server.ts). - -Server modules are located in `src/modules` and always have the `server.ts` entry point which responds to one of the few server entry points: - -- `afterProcessStarted` - executed just [after the server started](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L13). -- `afterApplicationInitialized` - executed just [after Express app got initialized](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L34). It's a good entry point to bind new request handlers (`app.get(...)`, `app.use(...)`). Read more on [Express.js request handlers and routing](https://expressjs.com/en/guide/routing.html). -- `beforeOutputRenderedResponse` - executed [after the SSR rendering has been done](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L189) but before sending it out to the browser; It lets you override the rendered SSR content with your own. -- `afterOutputRenderedResponse` - executed [after advanced output processing pipeline](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L212) executed. -- `beforeCacheInvalidated`, `afterCacheInvalidated` - executed [before and after cache has been invalidated](https://github.com/vuestorefront/vue-storefront/blob/2c6e0e1c8e73952beabf550fe4530344a6bcce15/core/scripts/server.ts#L76) - -Here is an [example how to bind](https://github.com/vuestorefront/vue-storefront/blob/develop/src/modules/google-cloud-trace/server.ts) tracing module just after server process started: - -```js -import { serverHooks } from '@vue-storefront/core/server/hooks' - -serverHooks.afterProcessStarted((config) => { - let trace = require('@google-cloud/trace-agent') - if (config.has('trace') && config.get('trace.enabled')) { - trace.start(config.get('trace.config')) - } -}) -``` - -[Another example](https://github.com/vuestorefront/vue-storefront/blob/develop/src/modules/compress/server.ts) - pretty common case - binding new Express middleware to process all user requests BEFORE they're processed by SSR rendering pipeline (including custom URL addresses): - -```js -import { serverHooks } from '@vue-storefront/core/server/hooks' - -const compression = require('compression') -serverHooks.afterApplicationInitialized(({ app, isProd }) => { - if (isProd) { - console.log('Output Compression is enabled') - app.use(compression({ enabled: isProd })) - } -}) -``` - -If you'd like to bind custom URL address this example can be modified like this: - -```js -import { serverHooks } from '@vue-storefront/core/server/hooks' - -serverHooks.afterApplicationInitialized(({ app, isProd }) => { - app.get('/custom-url-address', (req, res) => { - res.end('Custom response') - }) -}) -``` - - -### 3. Peep into the kitchen (what happens internally) - -### 4. Chef's secret (protip) -
-
- -## 3. Hooking into hooks -Hooks are common development method written by core developers to allow 3rd party developers or module developers to inject their own logic at predefined spots of the program. With this approach, the software can be flexible in design, so that it helps handle issues which were unknown at the time it was designed initially. - -Core developers usually strive to optimize where to put hooks. In _Vue Storefront_, hooks generally fall under either of two groups. One of each is `listener`; it allows us to do something at certain moment of application lifecycle. The other of each is `mutator`; it allows us to modify internal objects before app performs some actions. - -In this recipe, we look into where they are and how this can be applied to your _module_ development. - -### 1. Preparation - - You need a new module to play with. You would already have had one if you finished [_Recipe 1. How to bootstrap a module_](#_1-how-to-bootstrap-a-module) - - You need [multistores set up](/guide/cookbook/multistores) (We assume you have set another store up whose `storeCode` is `de`) - -:::warning NOTICE - This recipe deals with hooks as of [_1.10_](/guide/upgrade-notes/#_1-9-1-10). If you work with other versions of _Vue Storefront_, please bear in mind they might be different in detail. -::: - -### 2. Recipe -:::tip OBJECTIVE -We build a module that applies a discount to certain `storeviews` only. -::: - -1. Make up a list of requirements for the module as follows : - - Need _configurations_ as to which `storeviews` may apply to the discount and how much it should be. - - Need to apply discount to the price of products in `category`, `product` pages. - - Need a list of points where the discount should be verified. - -2. Now start with the first item, create a _configuration_ for the module to consume. -```json - discountStore: { - "enableDiscountPerStoreViews": true, - "storeViewsToApplyTo": ["de"], - "globalDiscountInPercentage": 25, - "allowLocalOverride": true - } -``` - - `discountStore` contains nodes of configuration for our module. - - `enableDiscountPerStoreViews` : This value determines whether to set this module enabled or not. - - `storeViewsToApplyTo` : This array contains the `storeviews` code. - - `globalDiscountInPercentage` : This value is how much discount should be applied to target `storeviews`. - - `allowLocalOverride` : This value allows to override discount dynamically. - -3. Create a module whose name is _hookExample_ (change to your liking) - - - -### 3. Peep into the kitchen (what happens internally) - -### 4. Chef's secret (protip) - -#### Secret 1. The list of hooks - -1. `cart` - - `beforeSync` : - - `afterSync` : - - `beforeAddToCart` : - - `afterAddToCart` : - - `beforeRemoveFromCart` : - - `afterRemoveFromCart` : - -2. `order` - - `beforePlaceOrder` : - - `afterPlaceOrder` : - -3. `user` - - `afterUserAuthorize` : - - `afterUserUnauthorize` : - -4. `app` _global level_ - - `beforeStoreViewChange` : - - `afterStoreViewChange` : - - `afterAppInit` : - -:::warning NOTICE -The list is of course subject to change, it grows for each core module to handle all use cases. -::: -#### Secret 2. The core hooks design - -#### Secret 3. Rewriting the module again without the hooks - -
-
- -## 4. On Module configuration -_Configuration_ is a basic template for options designed by developers that users may change to their own liking. Those changes made and saved will change the course of software during its lifecycle based on values of configuration. - -_Configuration_ tends to have default values which entails default behaviors of the program so that users don't have to bother if they are OK with default behaviors. Best user experience with flexibility can be achieved with carefully designed _configuration_ with default values. - - - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 5. Packaging a module -It's hands down no-brainer to bootstrap a module _manually_ because the skeleton required for minimum signature is dead simple and straightforward. Compared to the _`CLI`_ method, however, the _manual_ method is usually prefered for _local_ development, in other words, a project-specific module is structured with [the manual method](#_2-1-recipe-a-manual-bootstrapping) for better legibility. In contrast, _`CLI`_ method can help you build your module easily as a `npm` package by providing you with the boilerplate. - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) - -## 6. Extend Elasticsearch request body using `storefront-query-builder` - -If you're using the new [`storefront-query-builder`](https://github.com/vuestorefront/storefront-query-builder) and the `api-search-query` search-adapter ([introduced with v1.1.12](/guide/upgrade-notes/#_1-11-1-12)) it is now possible to extend it by new filters, or even overwrite a existing filter, to customize your Elasticsearch request-bodies. - -So, this way you can add custom Elasticsearch queries to the query-chain and still use the notation of `SearchQuery` in the Vue Storefront. - -> **Note:** This will only work from `storefront-query-builder` version `1.0.0` and `vue-storefront` version `1.12.2`. - -### Usecases - -One usecases where this feature would come in handy is for example if you like to add complex queries on multiple points in your source code. Using the following technique you can just add a custom filter to your `SearchQuery` in a single line inside your VSF source-code using the `query.applyFilter(...)` method and then add the complex logic into your custom-filter inside the API. - -### Registering a new filter - -The `vue-storefront-api` will only try to load filters that are registered in the configs. The extension/module, that contains the filter, must be enabled and the new filter module-classes needs to be registered in its extension config inside the `catalogFilter` array. The filter files must be located inside `filter/catalog/` of your module folder. - -For example: If you have a module called `extend-catalog` with a filter called `StockFilter`, the file path to filter would be `src/api/extensions/extend-catalog/filter/catalog/StockFilter.ts` and the config would look like: -``` -{ - "registeredExtensions": [ "extend-catalog" ], - "extensions": { - "extend-catalog": { - "catalogFilter": [ "StockFilter" ] - } - } -} -``` - -### Filter module-class properties - -The filter can contain four different properties. Followed a short explaination, what they are doing. - -* `check` – This method checks the condition that be must matched to execute the filter. The first valid filter is executed – all afterwards are ignored. -* `priority` – This is the priority in which the filters are going to be called. The sort is lower to higher. -* `mutator` – The mutator method is in charge of prehandling the filter value, to e.g. set defaults or check and change the type. -* `filter` – This method contains the query logic we wan't to add and mutates the `bodybuilder` query-chain. - -### Example - -Lets assume we like to add a possibility to add a default set of product-attribute filters we can apply to each `SearchQuery` without repeating ourselfs in source-code. So, for example, it should filter for two `color`'s and a specific `cut` to supply a filter for spring-coloured short's we implement at several places in our VSF. - -#### Changes in `vue-storefront` repository - -The query in the VSF code would look like this (that's it on the VSF side): -```js -import { SearchQuery } from 'storefront-query-builder' -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' - -//... - -const query = new SearchQuery() -query.applyFilter({ key: 'spring-shorts', value: 'male', scope: 'default' }) -const products = await dispatch('product/list', { query, size: 5 }) -``` - -#### Changes in `vue-storefront-api` repository - -In the `vue-storefront-api` we are going to add the real filter/query magic. -There is already an example module called `example-custom-filter` which we are going to use for our filter. - -As you look inside its module folder `src/api/extensions/example-custom-filter/`, you will find a child folder `filter/catalog/` with all existing custom filters for this module. Inside this folder we are going to duplicate the existing `SampleFilter.ts` into another one called `SpringShorts.ts` – this is our new custom filter module-class. - -This file needs to be registered in the config JSON to let the API know that there is a new custom filter inside our extension. -Therefore you open your `default.json` or specific config JSON file and add our new filename `SpringShorts` to the config node `extensions.example-custom-filter.catalogFilter` array. - -Our `SpringShorts.ts` contains an object that contains [four properties](#filter-module-class-properties): `priority`, `check`, `filter`, `mutator`. We don't need a `mutator` nor `priority`, so we can remove these lines. `check` and `filter` needs to be changed to fulfill our needs. So, this is how our filter finally looks like: - -```js -import { FilterInterface } from 'storefront-query-builder' - -const filter: FilterInterface = { - check: ({ attribute }) => attribute === 'spring-shorts', - filter ({ value, attribute, operator, queryChain }) { - return queryChain - .filter('terms', 'pants', [ 'shorts' ]) - .filter('terms', 'cut', [ 1, 2 ]) - .filter('terms', 'color', [ 3, 4 ]) - .filter('terms', 'gender', [ value ]) - } -} - -export default filter -``` - -Inside `check` we tell the filter to just be applied if the attribute is named exactly `spring-shorts`. - -Inside `filter` we extend the Elasticsearch query-chain by our desired filters, using the `bodybuilder` library syntax. - -That's it, now we are able to filter by a complex query in only one line inside VSF. - -## 7. Working with translations in module - -Translations are provided in `core/i18n/resource/i18n/{your-locale}.csv` file and can be extended or overridden in `src/modules/{my-module}/resource/i18n/{your-locale}.csv` accordingly, but translations from `src/themes/{my-theme}/resource/i18n` have a bigger priority than translations from `src/module/{my-module}/resource/i18n`. - -### Example - -Here's an example of `en-US.csv` for `en-US` locale: - -```csv -"customMessage","Here is the core message that can be overwritten in the module" -``` - -When you create the `en-US.csv` file within your `src/modules/{my-module}/resource/i18n/` folder and override some messages like: - -```csv -"customMessage","You can define or override translation messages here." -``` - -... you may expect that `$t('customMessage')` will return `You can define or override translation messages here.` instead of `Here is the core message. that can be overwritten in the module`. - -## 8. Tests in module - -Our jest config allows you to write tests for each module which you have created. - -To create a test, you have to create a file inside `src/modules/{my-module}/test/unit/`. The file has to end with `.spec.ts` or `.spec.js`. Generally, it has to match this pattern: `src/modules/{my-module}/test/unit/{my-test}.spec.(js|ts)`. diff --git a/docs/guide/cookbook/multistores.md b/docs/guide/cookbook/multistores.md deleted file mode 100644 index aac6e47635..0000000000 --- a/docs/guide/cookbook/multistores.md +++ /dev/null @@ -1,24 +0,0 @@ -# Ch 12. Open _Multistores_ - -In this chapter, we are going to cover : -[[toc]] - -## 0. Introduction -You have contents, you have products to sell, and you are successful in selling your products online. Now what? _Expand_. In this global era, every aspect needed to go online global is at your finger tip waiting for your decision to go global. No matter how successful are you, however, you still need to know the subtle difference of each market in order to land in a new frontier gracefully. - -Each market more than often requires you to prepare your stores ready to serve different context in terms of languages, tax regulation, currency notation and price policy. And the list goes on. _Vue Storefront_ provides you with features for handling these issues out of the box. - -Other than going global, _Multistores_ also give you various options when you want to serve a variety of customer segmentation. The limit is your imagination. - -In this chapter, we are going to discuss the all aspects you need to consider before expanding your stores. - -## 1. _Multistores_ up and running -Many e-commerce open sources provide with _multistores_ features out of the box. One notable example is _Magento 2_ without a doubt. In this recipe, we will examine setting up _multistores_ on _Vue Storefront_ connected to _Magento 2_ as the backend. - -## 2. i18n in _Multistores_ - -## 3. Tax in _Multistores_ - -## 4. Price and currency - -## 5. diff --git a/docs/guide/cookbook/setup.md b/docs/guide/cookbook/setup.md deleted file mode 100644 index 8d10f059a1..0000000000 --- a/docs/guide/cookbook/setup.md +++ /dev/null @@ -1,1637 +0,0 @@ -# Ch 3. Starter pack for new comers (Install) - - -In this chapter, we will cover : - -[[toc]] - - -## 0. Introduction -Now you are definitely interested in **Vue Storefront**. That's why you are here. You've come across the line. You made a choice. You will have something in return, which is great. Be it developers, entrepreneurs or even marketing managers that they may want to try something new for better products in hopes of enhancing their clients or customers' experience. You chose the right path. We will explore anything you need to get you started at all with [**Vue Storefront** infrastructure](https://github.com/vuestorefront). - -## 1. Install with Docker -Docker has been arguably the most sought-after, brought to the market which took the community by storm ever since its introduction. Although it's yet controversial whether it's the best choice among its peers, I have never seen such an unanimous enthusiasm over one tech product throughout the whole developers community. - -Then, why so? In modern computer engineering, products are so complex with an endless list of dependencies intertwined with each other. Building such dependencies in place for every occasion where it's required is one hell of a job, not to mention glitches from all the version variation. That's where Docker steps in to make you achieve **infrastructure automation**. This concept was conceived to help you focus on your business logic rather than having you stuck with hassles of lower level tinkering. - -Luckily, we already have been through all this for you, got our hands dirty. All you need is run a set of docker commands to get you up and running from scratch. Without further ado, let's get started! - -:::tip UPDATE -From VSF `1.12` on, - 1. `CLI` is the main method to install _Vue Storefront_ infrastructure. (which is easier, hassle-free, and intuitive) - 2. Bleeding-edge `vsf-capybara` is default theme while original `default` theme remains to be chosen at your discretion. -::: - -### 1. Preparation -- You need [`docker`](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04) and [`docker-compose`](https://www.digitalocean.com/community/tutorials/how-to-install-docker-compose-on-ubuntu-18-04) installed. - -- You need [`git`](https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-18-04) installed. - -:::tip NOTE -We will walk you with docker on *Linux*. (Specifically *Ubuntu 18.04* if needed) - -There is only one bias for Docker before using it; *Run it on Linux*. Docker is native Linux, was created using a Linux technology; LXC (linux container) in the first place. Even though there were many attempts made to make it available to other platforms as it does on Linux, and it has definitely been on a progress, however, using Docker on Linux is the solidest way to deal with the technology. - -That being sad, there are tips for using other platforms for docker at [Chef's Secrets](#_4-chef-s-secret-protip) as well. -::: - -### 2. Recipe -1. First, start with backend, download [**Vue Storefront API**](https://github.com/vuestorefront/vue-storefront-api) from github. -```bash -git clone https://github.com/vuestorefront/vue-storefront-api.git vue-storefront-api -cd vue-storefront-api -``` - -2. Copy `./config/default.json` to `./config/local.json` -```bash -cp config/default.json config/local.json -``` -Then edit `local.json` to your need. -We will look into this in greater detail at [Chef's secret](#_4-chef-s-secret-protip) -:::tip TIP -This step can be skipped if you are OK with values of `default.json` since it follows the [files load order](https://github.com/lorenwest/node-config/wiki/Configuration-Files#file-load-order) of [node-config](https://github.com/lorenwest/node-config) - -::: - -3. Run the following Docker command : -```bash -docker-compose -f docker-compose.yml -f docker-compose.nodejs.yml up -d -``` - -Then the result would look something like this : -```bash -Building app -Step 1/8 : FROM node:10-alpine - ---> 9dfa73010b19 -Step 2/8 : ENV VS_ENV prod - ---> Using cache - ---> 4d0a83421665 -Step 3/8 : WORKDIR /var/www - ---> Using cache - ---> e3871c8db7f3 -Step 4/8 : RUN apk add --no-cache curl git - ---> Using cache - ---> 49e996f0f6cb -Step 5/8 : COPY package.json ./ - ---> 14ed18d76efc -Step 6/8 : RUN apk add --no-cache --virtual .build-deps ca-certificates wget && yarn install --no-cache && apk del .build-deps - ---> Running in 3d6f91acc2fe -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz -(1/2) Installing wget (1.20.3-r0) -(2/2) Installing .build-deps (0) -Executing busybox-1.29.3-r10.trigger -OK: 22 MiB in 26 packages -yarn install v1.16.0 -info No lockfile found. -[1/4] Resolving packages... -warning @babel/node > @babel/polyfill@7.4.4: 🚨 As of Babel 7.4.0, this -package has been deprecated in favor of directly -including core-js/stable (to polyfill ECMAScript -features) and regenerator-runtime/runtime -(needed to use transpiled generator functions): - - > import "core-js/stable"; - > import "regenerator-runtime/runtime"; -warning eslint > file-entry-cache > flat-cache > circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor. -[2/4] Fetching packages... - -# ... abridged - - -``` -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/7XT5CWP4ynrPItattiP3on6wS) - -:::tip TIP -`-f` flag allows you to use the following docker-compose file. Without this flag, it will use the default file that is `docker-compose.yml` - -`-d` flag allows you to run the command in `detach mode` which means *running background*. -::: -3. In order to verify, run `docker ps` to show which containers are up -```bash -docker ps -``` - -Then, -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -53a47d5a6440 vuestorefrontapi_kibana "/bin/bash /usr/loca…" 31 seconds ago Up 29 seconds 0.0.0.0:5601->5601/tcp vuestorefrontapi_kibana_1 -7d8f6328601b vuestorefrontapi_app "docker-entrypoint.s…" 31 seconds ago Up 27 seconds 0.0.0.0:8080->8080/tcp vuestorefrontapi_app_1 -165ae945dbe5 vuestorefrontapi_es1 "/bin/bash bin/es-do…" 8 days ago Up 30 seconds 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch -8dd144746cef redis:4-alpine "docker-entrypoint.s…" 11 days ago Up 31 seconds 0.0.0.0:6379->6379/tcp vuestorefrontapi_redis_1 -``` -The ports number will be used later in the frontend configuration. In fact, they are already set in as default values. - -You will see 4 containers are running, which is : -| Container | Port | -|------------------------|---------------------| -| Vue Storefront API app | :8080 | -| Elasticsearch | :9200 | -| Kibana | :5601 | -| Redis | :6379 | - - -4. Now that backend part is done, let's work on frontend part, install `@vue-storefront/cli` -```bash -yarn global add @vue-storefront/cli -``` - -5. Run the `init` command in a directory where you want to install _Vue Storefront_ as follows : -```bash -vsf init vue-storefront # [vue-storefront] is the directory where the app is going to be installed. -``` - -6. Choose your target version from below (Picked _Stable versions_ in this recipe) : - -```bash -dex@ubuntu:~/code$ vsf init vue-storefront - ✔ Check avalilable versions -? Which version of Vue Storefront you'd like to install? (Use arrow keys) -❯ Stable versions (recommended for production) - Release Candidates - In development branches (could be unstable!) -``` - -7. Then pick `1.12` version for the install from below : -```bash - ✔ Check avalilable versions -? Which version of Vue Storefront you'd like to install? Stable versions (recommended for production) -? Select specific version (Use arrow keys) -❯ v1.12.0 - v1.11.4 - v1.11.3 - v1.11.2 - v1.11.1 - v1.11.0 - v1.10.6 -``` -8. Select a theme you want to start with (This time `Capybara` for the recipe) : -```bash - ✔ Check available versions -? Which version of Vue Storefront you'd like to install? Stable version (recommended for production) -? Select specific version v1.12.0 -? Select theme for Vue Storefront -❯ Capybara - based on Storefront UI - Default -``` -:::tip INFO -Capybara is built on top of [Storefront UI](https://www.storefrontui.io/). Worth checking it out! -::: - -Then, `Stable version` as follows : -```bash - ✔ Check available versions -? Which version of Vue Storefront you'd like to install? Stable version (recommended for production) -? Select specific version v1.12.0 -? Select theme for Vue Storefront Capybara - based on Storefront UI -? Select theme version (Use arrow keys) -❯ Stable version (recommended for production) - In development branch (could be unstable!) - -``` - -8. Now wrap it up by choosing `Manual installation` from the next step : -```bash - ✔ Check avalilable versions -? Which version of Vue Storefront you'd like to install? Stable versions (recommended for production) -? Select specific version v1.12.0 -? Would you like to use friendly installer or install Vue Storefront manually? - Installer (MacOS/Linux only) -❯ Manual installation -``` - -Then the `cli` will copy all the necessary files to the designated folder. - -Let's `cd` to the folder. - -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/i4lHsWZHDDWvDjkdFEb8zQa0m) - -:::warning -Friendly `Installer` among options above is deprecated yet it is supported. -::: - -9. Prepare the config file at `./config/local.json`. There is `default.json` file in the same folder which is a default set of configuration. Copy it as follows : -```bash -cp config/default.json config/local.json -``` -Then fix the value as you need it in the `local.json` file. -In `local.json`, you may change values for information of backend family. But if you followed this recipe verbatim, you don't have to, because it's already there with the default value. Should you study the contents, please see to [Chef's secret](#secret-1-study-in-local-json) - - -10. Install theme - -From version 1.12 you need to add theme into your project. [Here is more information](/guide/installation/theme.html) - -11. Finally run the following Docker command : - -```bash -docker-compose up -d -``` -The result should be something like this : -```bash -Building app -Step 1/8 : FROM node:10-alpine - ---> 9dfa73010b19 -Step 2/8 : ENV VS_ENV prod - ---> Using cache - ---> 4d0a83421665 -Step 3/8 : WORKDIR /var/www - ---> Using cache - ---> e3871c8db7f3 -Step 4/8 : COPY package.json ./ - ---> 0eab68a8f13a -Step 5/8 : COPY yarn.lock ./ - ---> ac1f5e4a1831 -Step 6/8 : RUN apk add --no-cache --virtual .build-deps ca-certificates wget git && yarn install --no-cache && apk del .build-deps - ---> Running in 1ca7bc7782e3 -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz -fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz -(1/9) Installing ca-certificates (20190108-r0) -(2/9) Installing wget (1.20.3-r0) -(3/9) Installing nghttp2-libs (1.35.1-r0) -(4/9) Installing libssh2 (1.8.2-r0) -(5/9) Installing libcurl (7.64.0-r2) -(6/9) Installing expat (2.2.6-r0) -(7/9) Installing pcre2 (10.32-r1) -(8/9) Installing git (2.20.1-r0) -(9/9) Installing .build-deps (0) -Executing busybox-1.29.3-r10.trigger -Executing ca-certificates-20190108-r0.trigger -OK: 22 MiB in 25 packages -yarn install v1.16.0 -[1/5] Validating package.json... -[2/5] Resolving packages... -[3/5] Fetching packages... -info fsevents@1.2.4: The platform "linux" is incompatible with this module. - -# ... abridged - -``` - -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/JZYI9ZE6DHeC7N2keBNoFUWjQ) - - -12. In order to verify, run `docker ps`, there should be another container added to the list. - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -88d758bc24d0 vuestorefront_app "docker-entrypoint.s…" 2 minutes ago Up 2 minutes vuestorefront_app_1 -de560221fdaf vuestorefrontapi_kibana "/bin/bash /usr/loca…" 8 hours ago Up 23 minutes 0.0.0.0:5601->5601/tcp vuestorefrontapi_kibana_1 -5576cd9963a1 vuestorefrontapi_app "docker-entrypoint.s…" 8 hours ago Up 23 minutes 0.0.0.0:8080->8080/tcp vuestorefrontapi_app_1 -88f5db9486da vuestorefrontapi_es1 "/bin/bash bin/es-do…" 8 hours ago Up 24 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch -d46c1e0a22af redis:4-alpine "docker-entrypoint.s…" 8 hours ago Up 24 minutes 0.0.0.0:6379->6379/tcp vuestorefrontapi_redis_1 - -``` - -13. Open your browser and visit [http://localhost:3000/](http://localhost:3000/) - -After compiling, *Voila!* - -![vs_home_intro_borderline](../images/home_capybara.png) - -### 3. Peep into the kitchen (what happens internally) -We used `docker-compose` for setting up the entire environment of Vue Storefront. It was more than enough to launch the machines behind for running the shop. - -It was possible because `docker` encapsulated the whole bunch of infrastructure into a linear set of declarative definition for the desired state. - -We had 2 steps of `docker-compose` one of which is for backend **Vue Storefront API**, the other for frontend **Vue Storefront**. - -The first `docker-compose` had two `yml` files for input. The first input file `docker-compose.yml` describe its base requirement all but **Vue Storefront API** itself; that is, **Elasticsearch** as data store, **Redis** for cache and **Kibana** for helping you grab your data visually (a pair of Elasticsearch). -```yaml -# docker-compose.yml -version: '3.0' -services: - es1: - container_name: elasticsearch - build: docker/elasticsearch/ - volumes: - - ./docker/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro - - ./docker/elasticsearch/data:/usr/share/elasticsearch/data - ports: - - '9200:9200' - - '9300:9300' - environment: - ES_JAVA_OPTS: "-Xmx512m -Xms512m" - - kibana: - build: docker/kibana/ - volumes: - - ./docker/kibana/config/:/usr/share/kibana/config:ro - ports: - - '5601:5601' - depends_on: - - es1 - - redis: - image: 'redis:4-alpine' - ports: - - '6379:6379' - -volumes: - esdat1: -``` -:::tip NOTE -Once a term explained, it will be ignored thereafter for consecutive occurrence. -::: -`version` denotes which version of `docker-compose` this file uses. - -`services` describe containers. It codifies how they should run. In other words, it codifies option flags used with `docker run ...` - -`es1` contains information of data store *Elasticsearch* container. -- `build` denotes build path of container. -- `volumes` contains the mount path of volumes shared between host and container as *host:container* -- `ports` connect ports between host and container as in *host:container* -- `environment` allows you to add environment variables. `Xmx512m` means JVM will take up to maximum 512MB memory. `Xms512m` means minimum memory. Combining them, there will be no memory resize, it will just stick to 512MB from start to end throughout its life cycle. - -`kibana` contains information of *Kibana* application container. -- `depends_on` creates dependency for a container of other containers. So, this container is dependent on `es1` that's just described above. -- `volumes` mean volumes shared, `:ro` creates the volume in `read-only` mode for the container. - -`redis` contains information of *Redis* cache application container. - -- `image` node contains the name of image this container is based on. - -`volumes` in top level can be used as a reference to be used across multiple services(containers). -
-
- -The second input file `docker-compose.nodejs.yml` deals with **Vue Storefront API** node application. -```yaml -version: '3.0' -services: - app: - # image: divante/vue-storefront-api:latest - build: - context: . - dockerfile: docker/vue-storefront-api/Dockerfile - depends_on: - - es1 - - redis - env_file: docker/vue-storefront-api/default.env - environment: - VS_ENV: dev - volumes: - - './config:/var/www/config' - - './ecosystem.json:/var/www/ecosystem.json' - - './migrations:/var/www/migrations' - - './package.json:/var/www/package.json' - - './babel.config.js:/var/www/babel.config.js' - - './scripts:/var/www/scripts' - - './src:/var/www/src' - - './var:/var/www/var' - tmpfs: - - /var/www/dist - ports: - - '8080:8080' -``` -`app` contains information of *Vue Storefront API* application. -- `build` is path for build information. If the value is string, it's a plain path. When it's object, you may have a few options to add. `context` is relative path or git repo url where `Dockerfile` is located. `dockerfile` node may change the path/name of `Dockerfile`. [more info](https://docs.docker.com/compose/compose-file/#build) -- `depends_on` tells us this container is based on `es1` and `redis` containers we created above. -- `env_file` helps you add environment values from files. It's relative path from the `docker-compose` file that is in the process, in this case, it's `docker-compose.nodejs.yml` -- `environment` is to set `VS_ENV` as `dev` so that environment will be setup for developer mode. -- `tmpfs` denotes temporary volumes that are only available to host memory. Unlike `volumes`, this `tmpfs` will be gone once the container stops. This option is only available to *Linux*. - -
-
- -The second `docker-compose` step handles **Vue Storefront** frontend. -``` yaml -version: '2.0' -services: - app: - # image: divante/vue-storefront:latest - build: - context: . - dockerfile: docker/vue-storefront/Dockerfile - env_file: docker/vue-storefront/default.env - environment: - VS_ENV: dev - network_mode: host - volumes: - - './babel.config.js:/var/www/babel.config.js' - - './config:/var/www/config' - - './core:/var/www/core' - - './ecosystem.json:/var/www/ecosystem.json' - - './.eslintignore:/var/www/.eslintignore' - - './.eslintrc.js:/var/www/.eslintrc.js' - - './lerna.json:/var/www/lerna.json' - - './tsconfig.json:/var/www/tsconfig.json' - - './tsconfig-build.json:/var/www/tsconfig-build.json' - - './shims.d.ts:/var/www/shims.d.ts' - - './package.json:/var/www/package.json' - - './src:/var/www/src' - - './var:/var/www/var' - tmpfs: - - /var/www/dist - ports: - - '3000:3000' -``` -This looks like by and large the same with *Vue Storefront API* with a few changes. - -`app` service describes options for *Vue Storefront* frontend application. -- `network_mode` allows you to modify values for `--network` option of docker client. `host` option allows your designated container to open to host network. For example, if you bind your container in host's `80` port, then the container will be accessible at host's `:80` from the internet. In other words, the container is not isolated. [more info](https://docs.docker.com/network/host/) - -If you take a closer look inside `Dockerfile's`, you will notice they install all the dependencies of the project from `package.json` not to mention required OS features including `git`, `wget` and certificates. You don't have to worry what to do because we made it do for you. - -Next, you might want to import your goods data. Please jump to [Data imports](./data-import.md) if you don't want to stop. - -### 4. Chef's secret (protip) -#### Secret 1. Study in `local.json` for *Vue Storefront API* -Starting point of customization is `default.json` or its copy `local.json` where the platform seeks configuration values. -:::tip NOTE -If you want to modify `default.json`, don't edit it directly but copy the whole file into `local.json` and start editing it in that file. Why it should be done that way is explained later at [Secret 3. Why use node-config?](#secret-3-why-use-node-config) -::: -We have 2 `local.json` files, one of which is for backend here, and we will look at [Secret 2](#secret-2-study-in-local-json-for-vue-storefront), the other for frontend . - -At [`vue-storefront-api/config/default.json`](https://github.com/vuestorefront/vue-storefront-api/blob/master/config/default.json) for **backend** : -```json - "server": { - "host": "localhost", - "port": 8080, - "searchEngine": "elasticsearch" - }, -``` -- This is where your API backend is defined. The server will listen `server.host`:`server.port` unless it's defined otherwise in environment variables. - -- `server.searchEngine` is used in the integration with `graphql` so please don't change it. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/resolvers.js#L6) -```json - "orders": { - "useServerQueue": false - }, - "catalog": { - "excludeDisabledProducts": false - }, -``` -- `orders.useServerQueue` allows you to use queue process when `order` API is used to create an order. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/order.js#L65) - -- `catalog.excludeDisabledProducts` allows you to skip disabled products when importing products using `mage2vs`. -[jump to code](https://github.com/vuestorefront/mage2vuestorefront/blob/master/src/adapters/magento/product.js#L166) - -```json - "elasticsearch": { - "host": "localhost", - "port": 9200, - "protocol": "http", - "user": "elastic", - "password": "changeme", - "min_score": 0.01, - "indices": [ - "vue_storefront_catalog", - "vue_storefront_catalog_de", - "vue_storefront_catalog_it" - ], - "indexTypes": [ - "product", - "category", - "cms", - "attribute", - "taxrule", - "review" - ], - "apiVersion": "5.6" - }, -``` -- `elasticsearch` element is used widely across the whole platform. Considering `elasticsearch` works as a data store (database), it's natural. - - - `host`, `port`, `protocol` defines `elasticsearch` connect information. -- `user`, `password` is default credentials of `elasticsearch`. If you changed the credentials of `elasticsearch`, please change this accordingly. [more info](https://www.elastic.co/guide/en/x-pack/current/security-getting-started.html) - - `min_score` sets a `min_score` when building a query for `elasticsearch`. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/elasticsearch/queryBuilder.js#L172) - :::tip TIP - `min_score` helps you exclude documents with `_score` less than `min_score` value. - ::: - - `indices` may contain one or multiple indexes. Each index acts as a data store for a storefront. You may add entries to the array with arbitrary names or remove entries from it. - :::warning CAUTION ! - However, the index name should match the one you will use for [data pump](data-import.md#_2-2-recipe-b-using-on-premise). - ::: - The default values for `indices` assume you have 2 additional stores(`de`, `it`) plus the default store. - - `indexTypes` contains values for mapping. You can consider it as `table` if you take `indices` as database. - - `apiVersion` defines the `elasticsearch` version it uses. - -```json - "redis": { - "host": "localhost", - "port": 6379, - "db": 0 - }, - "kue": {}, -``` -- `redis` contains `redis` server connect information. -- `kue` contains `kue` application options. [jump to code for options](https://github.com/Automattic/kue/blob/master/lib/kue.js#L88) - -```json - "availableStores": [ - "de", - "it" - ], -``` -- `availableStores` contains additional stores code name. If this value is an empty array, it means you only have one default store. - -```json -"storeViews": { - "multistore": true, - "mapStoreUrlsFor": [ - "de", - "it" - ], - "de": { - "storeCode": "de", - "storeId": 3, - "name": "German Store", - "url": "/de", - "elasticsearch": { - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog_de" - }, - "tax": { - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true, - "sourcePriceIncludesTax": false - }, - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - }, - "it": { - "storeCode": "it", - "storeId": 4, - "name": "Italian Store", - "url": "/it", - "elasticsearch": { - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog_it" - }, - "tax": { - "defaultCountry": "IT", - "defaultRegion": "", - "calculateServerSide": true, - "sourcePriceIncludesTax": false - }, - "i18n": { - "fullCountryName": "Italy", - "fullLanguageName": "Italian", - "defaultCountry": "IT", - "defaultLanguage": "IT", - "defaultLocale": "it-IT", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - } - }, - -``` -- `storeViews` element contains the whole information of ***additional*** stores. The default store information doesn't exist here, it exists on top level. -- `multistore` is supposed to tell the platform if it has multiple stores to consider. For example, it is used to configure `tax` values of additional store. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L14) -- `mapStoreUrlsFor` is used for building url routes in frontend. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/multistore.ts#L85) -- `de` element contains detailed information of `de` store. You need to have this kind of element for all the additional stores you added to `availableStores` with `storeCode` as the key. `de` and `it` in the `default.json` exhibits an example you can copy & paste for other stores you need to add. - - `storeCode` denotes store code for the store. - - `storeId` denotes store ID of the store. - - `name` denotes the store name. - - `url` denotes URL for the store. - - `elasticsearch` contains information for the store. This information may override the default one defined above. - - `host` is where your *Elasticsearch* listens on. - - `index` is the name of the index for the store. - - `tax` contains tax information of the store. - - `defaultCountry` is the code name of the country on which tax is calculated for the store. - - `defaultRegion` is default region. - - `calculateServerSide` determines if price is fetched with(`true`)/without(`false`) tax calculated. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/product.js#L48) - - `sourcePriceIncludesTax` determines whether price is stored with tax applied (`true`) or tax calculated on runtime (`false`). [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L12) - - `i18n` connotes *internationalization*. [more info](https://en.wikipedia.org/wiki/Internationalization_and_localization) - - `fullCountryName` is the full name of the country this `i18n` is applied to. - - `fullLanguageName` is the full name of the language this `i18n` is applied to. - - `defaultCountry` is the abbreviated name of the country this `i18n` is applied to by default. - - `defaultLanguage` is the abbreviated name of the language this `i18n` is applied to by default. - - `defaultLocale` is the default locale this `i18n` uses. - - `currencyCode` is the currency code this store uses. - - `currencySign` is the currency sign this store uses. - - `dateFormat` is the date format this store uses. - - - ```json - "authHashSecret": "__SECRET_CHANGE_ME__", - "objHashSecret": "__SECRET_CHANGE_ME__", - ``` -- `authHashSecret` is used to encode & decode JWT for API use. -- `objHashSecret` is 1) fallback secret hash for `authHashSecret`, 2) used for hashing in tax calculation. - -```json - "cart": { - "setConfigurableProductOptions": false - }, - "tax": { - "defaultCountry": "PL", - "defaultRegion": "", - "calculateServerSide": true, - "alwaysSyncPlatformPricesOver": false, - "usePlatformTotals": true, - "setConfigurableProductOptions": true, - "sourcePriceIncludesTax": false - }, -``` -- `cart` - - `setConfigurableProductOptions` flag determines to show either the parent item or the child item (aka selected option item) in the cart context. `true` shows parent item instead of the option item selected. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/o2m.js#L94) -- `tax` - - `alwaysSyncPlatformPricesOver` [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/order.js#L49) - - `usePlatformTotals` - These two options are used to determine whether to fetch prices from data source on the fly or not. If you set `alwaysSyncPlatformPricesOver` true, then it skips checking the checksum for cart items based on price. - -```json - "bodyLimit": "100kb", - "corsHeaders": [ - "Link" - ], -``` -- `bodyLimit` limits how big a request can be for your application. -- `corsHeaders` allows you to add entries to `Access-Control-Expose-Headers` - -```json - "platform": "magento2", -``` -- `platform` defines which e-commerce platform is used as a source. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/order.js#L13) - -```json - "registeredExtensions": [ - "mailchimp-subscribe", - "example-magento-api", - "cms-data", - "mail-service" - ], - "extensions": { - "mailchimp": { - "listId": "e06875a7e1", - "apiKey": "a9a3318ea7d30f5c5596bd4a78ae0985-us3", - "apiUrl": "https://us3.api.mailchimp.com/3.0" - }, - "mailService": { - "transport": { - "host": "smtp.gmail.com", - "port": 465, - "secure": true, - "user": "vuestorefront", - "pass": "vuestorefront.io" - }, - "targetAddressWhitelist": ["contributors@vuestorefront.io"], - "secretString": "__THIS_IS_SO_SECRET__" - } - }, -``` -- `registeredExtensions` element contains the list of supported extensions, it bootstraps entry points for those extensions [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/index.js#L45) - -- `extensions` contains additional configuration for extensions. [jump to code](https://github.com/vuestorefront/vue-storefront-api/tree/master/src/api/extensions) - - `mailchimp` provides `POST`, `DELETE` APIs for *Mailchimp* `subscribe` method. - - `listId` is the ID of list you are publishing. - - `apiKey` is API key you are assigned. - - `apiUrl` is API base url for *Mailchimp* service. - - `mailService` is used to send emails from Vue Storefront via *Gmail*. - - `transport` contains basic information for *Gmail* service. - - `host` is where your mail is sent en route. - - `port` is the port number used for the service. - - `secure` determines to use SSL connection. - - `user` is `username` for the service. - - `pass` is `password` for the service. - - `targetAddressWhitelist` checks if an user confirmed his/her email address *and* source email is white-listed. - - `secretString` is used for hashing. - -```json - "magento2": { - "url": "http://demo-magento2.vuestorefront.io/", - "imgUrl": "http://demo-magento2.vuestorefront.io/media/catalog/product", - "assetPath": "/../var/magento2-sample-data/pub/media", - "magentoUserName": "", - "magentoUserPassword": "", - "httpUserName": "", - "httpUserPassword": "", - "api": { - "url": "http://demo-magento2.vuestorefront.io/rest", - "consumerKey": "byv3730rhoulpopcq64don8ukb8lf2gq", - "consumerSecret": "u9q4fcobv7vfx9td80oupa6uhexc27rb", - "accessToken": "040xx3qy7s0j28o3q0exrfop579cy20m", - "accessTokenSecret": "7qunl3p505rubmr7u1ijt7odyialnih9" - } - }, - "magento1": { - "url": "http://magento-demo.local", - "imgUrl": "http://magento-demo.local/media/catalog/product", - "magentoUserName": "", - "magentoUserPassword": "", - "httpUserName": "", - "httpUserPassword": "", - "api": { - "url": "http://magento-demo.local/vsbridge", - "consumerKey": "", - "consumerSecret": "", - "accessToken": "", - "accessTokenSecret": "" - } - }, -``` -- `magento2` is used to integrate with Magento 2 as a data source. - - - `imgUrl` is base image url. [jump to code](https://github.com/kkdg/vue-storefront-api/blob/master/src/api/img.js#L38) - - - `assetPath` is used for the `media` path. [jump to code](https://github.com/kkdg/vue-storefront-api/blob/master/src/index.js#L22) - - - `api` contains API credentials for integration. - - - `url` is base url for Magento 2 instance. - - `consumerKey` See **TIP** - - `consumerSecret` - - `accessToken` - - `accessTokenSecret` - - - - :::tip TIP - - These 4 nodes above is the required credentials for integration with Magento 2. [how to get them](data-import.html#_2-2-recipe-b-using-on-premise) - - ::: - -`magento1` has just the same structure with `magento2`. - - - -```json - "imageable": { - "namespace": "", - "maxListeners": 512, - "imageSizeLimit": 1024, - "whitelist": { - "allowedHosts": [ - ".*divante.pl", - ".*vuestorefront.io" - ] - }, - "cache": { - "memory": 50, - "files": 20, - "items": 100 - }, - "concurrency": 0, - "counters": { - "queue": 2, - "process": 4 - }, - "simd": true, - "keepDownloads": true - }, -``` -- `imageable` deals with everything you need to configure when it comes to your storefront images, especially product images. - - - `maxListeners` limits maximum listeners to request's socket. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/img.js#L21) - - `imageSizeLimit` limits maximum image size. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/img.js#L56) - - `whitelist` contains a white-list of image source domains - - - `allowedHosts` contains the array of white-list - - :::warning DON'T FORGET - - You should include your source domain in `allowedHosts` or your request for product images will fail. [more info](data-import.html#secret-1-product-image-is-not-synced) - - ::: - - :::tip NOTE - - From `cache` to `simd` they are used to configure [Sharp](https://github.com/lovell/sharp) library. *Sharp* is a popular library for image processing in *Node.js*. [jump to option docs](https://sharp.dimens.io/en/stable/api-utility/#cache) - - ::: - - - `cache` limits `libvips` operation cache from *Sharp*. Values hereunder are default values. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/lib/image.js#L5) - - - `memory` is the maximum memory in MB to use for the cache. - - `files` is the maximum number of files to hold open. - - `items` is the maximum number of operations to cache. - - - `concurrency` is the number of threads for processing each image. - - - `counters` provides access to internal task counters. - - - `queue` is the number of tasks in queue for *libuv* to provide a worker thread. - - `process` limits the number of resize tasks concurrently processed. - - - `simd` to use SIMD vector unit of the CPU in order to enhance the performance. - - - -```json - "entities": { - "category": { - "includeFields": [ "children_data", "id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key" ] - }, - "attribute": { - "includeFields": [ "attribute_code", "id", "entity_type_id", "options", "default_value", "is_user_defined", "frontend_label", "attribute_id", "default_frontend_label", "is_visible_on_front", "is_visible", "is_comparable" ] - }, - "productList": { - "sort": "", - "includeFields": [ "type_id", "sku", "product_links", "tax_class_id", "special_price", "special_to_date", "special_from_date", "name", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "url_key" ], - "excludeFields": [ "configurable_children", "description", "configurable_options", "sgn" ] - }, - "productListWithChildren": { - "includeFields": [ "type_id", "sku", "name", "tax_class_id", "special_price", "special_to_date", "special_from_date", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax", "configurable_children.color", "configurable_children.size", "product_links", "url_key"], - "excludeFields": [ "description", "sgn"] - }, - "product": { - "excludeFields": [ "updated_at", "created_at", "attribute_set_id", "status", "visibility", "tier_prices", "options_container", "msrp_display_actual_price_type", "has_options", "stock.manage_stock", "stock.use_config_min_qty", "stock.use_config_notify_stock_qty", "stock.stock_id", "stock.use_config_backorders", "stock.use_config_enable_qty_inc", "stock.enable_qty_increments", "stock.use_config_manage_stock", "stock.use_config_min_sale_qty", "stock.notify_stock_qty", "stock.use_config_max_sale_qty", "stock.use_config_max_sale_qty", "stock.qty_increments", "small_image"], - "includeFields": null, - "filterFieldMapping": { - "category.name": "category.name.keyword" - } - } - }, -``` -- `entities` is used to integrate with *GraphQL* in **Vue Storefront API**. - - `category` - - `includeFields` contains an array of fields to be added as `sourceInclude` [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/elasticsearch/category/resolver.js#L10) - - `product` - - `filterFieldMapping` adds a field mapping to apply a filter in a query [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/graphql/elasticsearch/mapping.js#L19) - - `category.name` - -```json - "usePriceTiers": false, - "boost": { - "name": 3, - "category.name": 1, - "short_description": 1, - "description": 1, - "sku": 1, - "configurable_children.sku": 1 - } -``` -- `usePriceTiers` determines whether to use price tiers for customers in groups -- `boost` is used to give weighted values to fields for a query to *Elasticsearch*, the bigger, the heavier. - - `name` field has the value *3* so that matching query with the `name` has the highest priority. - - `category.name` ,`short_description`, `description`, `sku`, `configurable_children.sku ` the rest of fields have the default value; 1. - - -
-
- -#### Secret 2. Study in `local.json` for *Vue Storefront* - -At [`vue-storefront/config/default.json`](https://github.com/vuestorefront/vue-storefront/blob/master/config/default.json) for **frontend** : - -```json -"server": { - "host": "localhost", - "port": 3000, - "protocol": "http", - "api": "api", - "devServiceWorker": false, - "useOutputCacheTagging": false, - "useOutputCache": false, - "outputCacheDefaultTtl": 86400, - "availableCacheTags": ["product", "category", "home", "checkout", "page-not-found", "compare", "my-account", "P", "C", "error"], - "invalidateCacheKey": "aeSu7aip", - "dynamicConfigReload": false, - "dynamicConfigContinueOnError": false, - "dynamicConfigExclude": ["ssr", "storeViews", "entities", "localForage", "shipping", "boost", "query"], - "dynamicConfigInclude": [], - "elasticCacheQuota": 4096 -}, -``` - -- `server` contains information of various features related to *frontend* server. - - - `host` is the host address in which your *Vue Storefront* instance starts at. - - - `port` is the port number in which your *Vue Storefront* instance listens to. - - - `protocol` is used for *GraphQL* integration. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/search/adapter/graphql/searchAdapter.ts#L48) - - - `api` determines API mode between `api` and `graphql`. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/resolvers/resolveGraphQL.js#L7) - - :::tip TIP - - You may take a look at [*GraphQL Action Plan*](/guide/basics/graphql.html) guide to help yourself make a decision which mode you should take. - ::: - - - `devServiceWorker` enables *service worker* in `develop` mode. The *service worker* is normally enabled by default for `production` mode, but not for `develop` mode. Setting this flag *true* forces to use *service worker* in `develop` mode too. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/service-worker/registration.js#L5) - :::tip TIP - - You may take a look at [Working with Service Workers](/guide/core-themes/service-workers.html) for better understanding. - ::: - - - `useOutputCacheTagging` determines to allow *Output Cache Tags*. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L168) - - - `useOutputCache` determines to allow *Output Cache*. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L64) - - - `outputCacheDefaultTtl` defines the default timeout for *Redis Tag Cache*. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/utils/cache-instance.js#L16) - - - `availableCacheTags` contains a list of available cache tags. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/cache.js#L7) - - - `invalidateCacheKey` is the key used for checking validity of invalidation. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L66) - :::tip TIP - - You may take a look at [SSR Cache](/guide/basics/ssr-cache.html) in order to grab the idea of *Output Cache* in *Vue Storefront* - ::: - - - `dynamicConfigReload` enables to reload `config.json` on the fly with each server request or only first request (configurable by setting below). [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L295) - - `dynamicConfigReloadWithEachRequest` enables to reload `config.json` on the fly with each server request. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L313) - - `dynamicConfigContinueOnError` allows to skip errors during configuration merge on the fly. [jump to code](https://github.com/vuestorefront/vue-storefront/blob/master/core/scripts/server.js#L240) - - `dynamicConfigExclude` - - `dynamicConfigInclude` - - `elasticCacheQuota` - - -```json -"seo": { - "useUrlDispatcher": true -}, -"console": { - "showErrorOnProduction" : true, - "verbosityLevel": "display-everything" -}, -"redis": { - "host": "localhost", - "port": 6379, - "db": 0 -}, -"graphql":{ - "host": "localhost", - "port": 8080 -}, -"api": { - "url": "http://localhost:8080" -}, -``` - -- `seo` -- `console` -- `redis` -- `graphql` -- `api` - -```json -"elasticsearch": { - "httpAuth": "", - "host": "/api/catalog", - "index": "vue_storefront_catalog", - "min_score": 0.02, - "csrTimeout": 5000, - "ssrTimeout": 1000, - "queryMethod": "GET", - "disableLocalStorageQueriesCache": true, - "searchScoring": { - "attributes": { - "attribute_code": { - "scoreValues": { "attribute_value": { "weight": 1 } } - } - }, - "fuzziness": 2, - "cutoff_frequency": 0.01, - "max_expansions": 3, - "minimum_should_match": "75%", - "prefix_length": 2, - "boost_mode": "multiply", - "score_mode": "multiply", - "max_boost": 100, - "function_min_score": 1 - }, - "searchableAttributes": { - "name": { - "boost": 4 - }, - "sku": { - "boost": 2 - }, - "category.name": { - "boost": 1 - } - } -}, -``` -`elasticsearch` ... - -```json -"ssr": { - "templates": { - "default": "dist/index.html", - "minimal": "dist/index.minimal.html", - "basic": "dist/index.basic.html", - "amp": "dist/index.amp.html" - }, - "executeMixedinAsyncData": true, - "initialStateFilter": ["__DEMO_MODE__", "version", "storeView"], - "useInitialStateFilter": true -}, -``` -- `ssr` - - `templates` - - `default` - -```json -"defaultStoreCode": "", -"storeViews": { - "multistore": false, - "commonCache": false, - "mapStoreUrlsFor": ["de", "it"], - "de": { - "storeCode": "de", - "storeId": 3, - "name": "German Store", - "url": "/de", - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_de" - }, - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "DE", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "fullCountryName": "Germany", - "fullLanguageName": "German", - "defaultLanguage": "DE", - "defaultCountry": "DE", - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - }, - "it": { - "storeCode": "it", - "storeId": 4, - "name": "Italian Store", - "url": "/it", - "elasticsearch": { - "host": "/api/catalog", - "index": "vue_storefront_catalog_it" - }, - "tax": { - "sourcePriceIncludesTax": false, - "defaultCountry": "IT", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "fullCountryName": "Italy", - "fullLanguageName": "Italian", - "defaultCountry": "IT", - "defaultLanguage": "IT", - "defaultLocale": "it-IT", - "currencyCode": "EUR", - "currencySign": "EUR", - "dateFormat": "HH:mm D-M-YYYY" - } - } -}, -``` -- `defaultStoreCode` - -```json -"entities": { - "optimize": true, - "twoStageCaching": true, - "optimizeShoppingCart": true, - "category": { - "includeFields": [ "id", "*.children_data.id", "*.id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key", "url_path", "product_count", "path"], - "excludeFields": [ "sgn" ], - "categoriesRootCategorylId": 2, - "categoriesDynamicPrefetchLevel": 2, - "categoriesDynamicPrefetch": true - }, - "attribute": { - "includeFields": [ "attribute_code", "id", "entity_type_id", "options", "default_value", "is_user_defined", "frontend_label", "attribute_id", "default_frontend_label", "is_visible_on_front", "is_visible", "is_comparable", "tier_prices", "frontend_input" ] - }, - "productList": { - "sort": "", - "includeFields": [ "type_id", "sku", "product_links", "tax_class_id", "special_price", "special_to_date", "special_from_date", "name", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "url_path", "url_key", "status", "tier_prices", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax" ], - "excludeFields": [ "description", "configurable_options", "sgn", "*.sgn", "msrp_display_actual_price_type", "*.msrp_display_actual_price_type", "required_options" ] - }, - "productListWithChildren": { - "includeFields": [ "type_id", "sku", "name", "tax_class_id", "special_price", "special_to_date", "special_from_date", "price", "priceInclTax", "originalPriceInclTax", "originalPrice", "specialPriceInclTax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.priceInclTax", "configurable_children.specialPriceInclTax", "configurable_children.originalPrice", "configurable_children.originalPriceInclTax", "configurable_children.color", "configurable_children.size", "configurable_children.id", "configurable_children.tier_prices", "product_links", "url_path", "url_key", "status", "tier_prices"], - "excludeFields": [ "description", "sgn", "*.sgn", "msrp_display_actual_price_type", "*.msrp_display_actual_price_type", "required_options"] - }, - "review": { - "excludeFields": ["review_entity", "review_status"] - }, - "product": { - "excludeFields": [ "*.msrp_display_actual_price_type", "required_options", "updated_at", "created_at", "attribute_set_id", "options_container", "msrp_display_actual_price_type", "has_options", "stock.manage_stock", "stock.use_config_min_qty", "stock.use_config_notify_stock_qty", "stock.stock_id", "stock.use_config_backorders", "stock.use_config_enable_qty_inc", "stock.enable_qty_increments", "stock.use_config_manage_stock", "stock.use_config_min_sale_qty", "stock.notify_stock_qty", "stock.use_config_max_sale_qty", "stock.use_config_max_sale_qty", "stock.qty_increments", "small_image", "sgn", "*.sgn"], - "includeFields": null, - "useDynamicAttributeLoader": true, - "standardSystemFields": [ - "description", - "configurable_options", - "tsk", - "custom_attributes", - "size_options", - "regular_price", - "final_price", - "price", - "color_options", - "id", - "links", - "gift_message_available", - "category_ids", - "sku", - "stock", - "image", - "thumbnail", - "visibility", - "type_id", - "tax_class_id", - "media_gallery", - "url_key", - "url_path", - "max_price", - "minimal_regular_price", - "special_price", - "minimal_price", - "name", - "configurable_children", - "max_regular_price", - "category", - "status", - "priceTax", - "priceInclTax", - "specialPriceTax", - "specialPriceInclTax", - "_score", - "slug", - "errors", - "info", - "erin_recommends", - "special_from_date", - "news_from_date", - "custom_design_from", - "originalPrice", - "originalPriceInclTax", - "parentSku", - "options", - "product_option", - "qty", - "is_configured" - ] - } -}, -"cart": { - "serverMergeByDefault": true, - "serverSyncCanRemoveLocalItems": false, - "serverSyncCanModifyLocalItems": false, - "synchronize": true, - "synchronize_totals": true, - "setCustomProductOptions": true, - "setConfigurableProductOptions": true, - "askBeforeRemoveProduct": true, - "displayItemDiscounts": true, - "minicartCountType": "quantities", - "create_endpoint": "http://localhost:8080/api/cart/create?token={{token}}", - "updateitem_endpoint": "http://localhost:8080/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "http://localhost:8080/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "http://localhost:8080/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "http://localhost:8080/api/cart/totals?token={{token}}&cartId={{cartId}}", - "paymentmethods_endpoint": "http://localhost:8080/api/cart/payment-methods?token={{token}}&cartId={{cartId}}", - "shippingmethods_endpoint": "http://localhost:8080/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}", - "shippinginfo_endpoint": "http://localhost:8080/api/cart/shipping-information?token={{token}}&cartId={{cartId}}", - "collecttotals_endpoint": "http://localhost:8080/api/cart/collect-totals?token={{token}}&cartId={{cartId}}", - "deletecoupon_endpoint": "http://localhost:8080/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}", - "applycoupon_endpoint": "http://localhost:8080/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}" -}, -"products": { - "useShortCatalogUrls": false, - "useMagentoUrlKeys": true, - "setFirstVarianAsDefaultInURL": false, - "configurableChildrenStockPrefetchStatic": false, - "configurableChildrenStockPrefetchDynamic": false, - "configurableChildrenStockPrefetchStaticPrefetchCount": 8, - "filterUnavailableVariants": false, - "listOutOfStockProducts": true, - "preventConfigurableChildrenDirectAccess": true, - "alwaysSyncPlatformPricesOver": false, - "clearPricesBeforePlatformSync": false, - "waitForPlatformSync": false, - "setupVariantByAttributeCode": true, - "endpoint": "http://localhost:8080/api/product", - "defaultFilters": ["color", "size", "price", "erin_recommends"], - "systemFilterNames": ["sort"], - "filterFieldMapping": { - "category.name": "category.name.keyword" - }, - "colorMappings": { - "Melange graphite": "#eeeeee" - }, - "sortByAttributes": { - "Latest": "updated_at", - "Price: Low to high":"final_price", - "Price: High to low":"final_price:desc" - }, - "gallery": { - "variantsGroupAttribute": "color", - "mergeConfigurableChildren": true, - "imageAttributes": ["image","thumbnail","small_image"], - "width": 600, - "height": 744 - }, - "filterAggregationSize": { - "default": 10, - "size": 10, - "color": 10 - } -}, -"orders": { - "directBackendSync": true, - "endpoint": "http://localhost:8080/api/order", - "payment_methods_mapping": { - }, - "offline_orders": { - "automatic_transmission_enabled": false, - "notification" : { - "enabled": true, - "title" : "Order waiting!", - "message": "Click here to confirm the order that you made offline.", - "icon": "/assets/logo.png" - } - } -}, -"localForage": { - "defaultDrivers": { - "user": "LOCALSTORAGE", - "cmspage": "LOCALSTORAGE", - "cmsblock": "LOCALSTORAGE", - "carts": "LOCALSTORAGE", - "orders": "LOCALSTORAGE", - "wishlist": "LOCALSTORAGE", - "categories": "LOCALSTORAGE", - "attributes": "LOCALSTORAGE", - "products": "INDEXEDDB", - "elasticCache": "LOCALSTORAGE", - "claims": "LOCALSTORAGE", - "syncTasks": "LOCALSTORAGE", - "ordersHistory": "LOCALSTORAGE", - "checkoutFieldValues": "LOCALSTORAGE" - } -}, -"reviews": { - "create_endpoint": "http://localhost:8080/api/review/create" -}, -"users": { - "autoRefreshTokens": true, - "endpoint": "http://localhost:8080/api/user", - "history_endpoint": "http://localhost:8080/api/user/order-history?token={{token}}", - "resetPassword_endpoint": "http://localhost:8080/api/user/reset-password", - "changePassword_endpoint": "http://localhost:8080/api/user/change-password?token={{token}}", - "login_endpoint": "http://localhost:8080/api/user/login", - "create_endpoint": "http://localhost:8080/api/user/create", - "me_endpoint": "http://localhost:8080/api/user/me?token={{token}}", - "refresh_endpoint": "http://localhost:8080/api/user/refresh" -}, -"stock": { - "synchronize": true, - "allowOutOfStockInCart": true, - "endpoint": "http://localhost:8080/api/stock" -}, -"images": { - "useExactUrlsNoProxy": false, - "baseUrl": "https://demo.vuestorefront.io/img/", - "productPlaceholder": "/assets/placeholder.jpg" -}, -"install": { - "is_local_backend": true, - "backend_dir": "../vue-storefront-api" -}, -"demomode": false, -"tax": { - "defaultCountry": "US", - "defaultRegion": "", - "sourcePriceIncludesTax": false, - "calculateServerSide": true, - "userGroupId": null, - "useOnlyDefaultUserGroupId": false, - "deprecatedPriceFieldsSupport": true, - "finalPriceIncludesTax": false -}, -``` - - `tax`: ... - - `defaultCountry` is the code name of the country on which tax is calculated for the store. - - `defaultRegion` is default region. - - `sourcePriceIncludesTax` determines whether price is stored with tax applied (`true`) or tax calculated on runtime (`false`). [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/platform/magento2/tax.js#L12) - - `calculateServerSide` determines if price is fetched with(`true`)/without(`false`) tax calculated. [jump to code](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/product.js#L48) - - `userGroupId`: null, - - `useOnlyDefaultUserGroupId`: false, - - `deprecatedPriceFieldsSupport`: true, - - `finalPriceIncludesTax`: false -```json -"shipping": { - "methods": [ - { - "method_title": "DPD Courier", - "method_code": "flatrate", - "carrier_code": "flatrate", - "amount": 4, - "price_incl_tax": 5, - "default": true, - "offline": true - } - ] -}, -"i18n": { - "defaultCountry": "US", - "defaultLanguage": "EN", - "availableLocale": ["en-US","de-DE","fr-FR","es-ES","nl-NL", "ja-JP", "ru-RU", "it-IT", "pt-BR", "pl-PL", "cs-CZ"], - "defaultLocale": "en-US", - "currencyCode": "USD", - "currencySign": "$", - "currencyDecimal": null, - "currencyGroup": null, - "fractionDigits": 2, - "priceFormat": "{sign}{amount}", - "dateFormat": "HH:mm D/M/YYYY", - "fullCountryName": "United States", - "fullLanguageName": "English", - "bundleAllStoreviewLanguages": true -}, -"mailchimp": { - "endpoint": "http://localhost:8080/api/ext/mailchimp-subscribe/subscribe" -}, -"mailer": { - "endpoint": { - "send": "http://localhost:8080/api/ext/mail-service/send-email", - "token": "http://localhost:8080/api/ext/mail-service/get-token" - }, - "contactAddress": "contributors@vuestorefront.io", - "sendConfirmation": true -}, -"theme": "@vue-storefront/theme-default", -"analytics": { - "id": false -}, -"hotjar": { - "id": false -}, -"cms": { - "endpoint": "http://localhost:8080/api/ext/cms-data/cms{{type}}/{{cmsId}}", - "endpointIdentifier": "http://localhost:8080/api/ext/cms-data/cms{{type}}Identifier/{{cmsIdentifier}}/storeId/{{storeId}}" -}, -"cms_block": { - "max_count": 500 -}, -"cms_page": { - "max_count": 500 -}, -"usePriceTiers": false, -"useZeroPriceProduct": true, -"query": { - "inspirations": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Performance Fabrics" } - } - ] - }, - "newProducts": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Tees" } - } - ] - }, - "bestSellers": { - "filter": [ - { - "key": "category.name", - "value" : { "eq": "Tees" } - } - ] - } -} - -``` - -#### Secret 3. How to launch multiple instances -#### Secret 4. Why use `node-config`? -#### Secret 5. Using Docker on Mac -#### Secret 6. Using Docker on Windows - -
-
-
- - -## 2. User-friendly installation using installer (Deprecated) -:::warning -This recipe is deprecated, and installer may not be supported at any version without prior notice. Please use it at your own peril. -::: - -We made it one step further where you just need to answer a series of questions to set up the whole bunch of architecture. - -### 1. Preparation -### 2. Recipe - -1. If you're MacOS or Linux user now you're able to install with pretty nice CLI installer :) - -```bash -git clone --single-branch --branch master https://github.com/vuestorefront/vue-storefront.git vue-storefront -cd vue-storefront -yarn -yarn installer -``` - -It will take some time for installation and during the last step you will be asked some questions. First one is - -``` -Would you like to use https://demo.vuestorefront.io as the backend? -``` - -2. If you answer `Yes`, you will have remote backend at `https://demo.vuestorefront.io`. Otherwise, you will need to install `vue-storefront-api`. - -2-1. Using Vue Storefront demo as a backend - -In this case you don't need to run Docker and you will be asked one additional question: - -``` -? Please provide path for images endpoint (https://demo.vuestorefront.io/img/) -``` - -You can simply proceed and as a result you will have a `vue-storefront` folder inside your project root and Storefront application running on `http://localhost:3000`. All images will be also hosted at `https://demo.vuestorefront.io/img/`. - -2-2. Installing the vue-storefront-api locally - -If you answer `No` on the previous question, please be sure the Docker is running, otherwise you might get an error. You will be asked some more questions immediately: - -``` -? Would you like to use https://demo.vuestorefront.io as the backend? No -? Please provide Git path (if it's not globally installed) git -? Please provide path for installing backend locally ../vue-storefront-api -? Choose path for images endpoint http://localhost:8080/img/ -``` - -As for images endpoint: you can choose between `https://demo.vuestorefront.io/img/` again or host your images on localhost. - -After you answered all the questions, the installation process will start (it might take some time to install all dependencies). When it's finished, you will get the following message: - -``` -┌────────────────────────────────────────────────────────────────┐ -│ Congratulations! │ -│ │ -│ You've just successfully installed vue-storefront. │ -│ All required servers are running in background │ -│ │ -│ Storefront: http://localhost:3000 │ -│ Backend: http://localhost:8080 │ -│ │ -│ Logs: /Users/natalia/Desktop/work/test/vue-storefront/var/log/ │ -│ │ -│ Good Luck! │ -└────────────────────────────────────────────────────────────────┘ -``` - -Your project should contain 2 folders at this moment: `vue-storefront` and `vue-storefront-api`. Vue Storefront should be running on `http://localhost:3000`: - -![Storefront screenshot](../images/storefront.png) - - -## 4. Storefront CLI at your service -Upon the release of 1.10, we also present a new way of setup and all its sorts from `CLI` which is the all-time most favorite tool of developers worldwide if I must say. There are lots of benefits when `CLI` methods are available such as automation in scripts in cooperation with other automation tools out there. - -We will continuously add new features to [`CLI`](https://www.npmjs.com/package/@vue-storefront/cli) as the version goes up. - -### 1. Preparation -- You need to have installed [`npm`](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) on your machine and [`yarn`](https://yarnpkg.com/lang/en/docs/install/#debian-stable). - -### 2. Recipe -1. Install _Vue Storefront CLI_ package on your machine with `-g` flag as follows : -```bash -npm install -g @vue-storefront/cli -``` -:vhs: You may also watch it in [bash playback :movie_camera:](https://asciinema.org/a/ZK0BVF7cQ8OaHHRcsaZgcOCfN) - -2. Now go to any random folder you want to install a _Vue Storefront_ app under, and run the following : -```bash -vsf init -``` - -3. You will encounter a series of questions to install the app, first of which is as follows : -```bash - ✔ Check avalilable versions -? Which version of Vue Storefront you'd like to install? -❯ Stable versions (recommended for production) - Release Candidates - In development branches (could be unstable!) -``` -Select an option based on which you are to install. - -4. Next question is about specific version to be installed as follows : -```bash -? Select specific version - v1.8.0 -❯ v1.10.0 - v1.9.2 - v1.9.1 - v1.9.0 - v1.8.5 - v1.8.4 -``` -Choose a version of your target. - -5. Next question is about theme installation. - -``` -? Select theme for Vue Storefront (Use arrow keys) -❯ Capybara - based on Storefront UI - Default -``` - -You will also get question about theme version. - -``` -? Select theme version (Use arrow keys) -❯ Stable version (recommended for production) - In development branch (could be unstable!) -``` - -6. Next question is about how you install it between `installer`/`manual` like below : -```bash -? Would you like to use friendly installer or install Vue Storefront manually? -❯ Installer (MacOS/Linux only) - Manual installation -``` -Let's pick the `Installer` option for now. - -Then you will see the machine start working on installation : -```bash -? Would you like to use friendly installer or install Vue Storefront manually? Installer (MacOS/Linux only) - ✔ Copying Vue Storefront files - ⠸ Installing dependencies - Running installer -``` - -7. Once the preparation is finished then another series of questions pops up as `installer` is associated with as follows : -```bash -yarn run v1.17.3 -$ node ./core/scripts/installer -┌─────────────────────────────────────────────────┐ -│ Hi, welcome to the vue-storefront installation. │ -│ Let's configure it together :) │ -└─────────────────────────────────────────────────┘ - -? Would you like to use https://demo.vuestorefront.io as the backend? (Y/n) -``` - -From this on, the questions would be the same as installation through `installer`. -You can follow it further at [Install using installer](#_2-using-installer) - -8. Once the questions have been answered then the remaining process is taken to action. You will see a screen as follows when they finished : -```bash -? Please provide path for images endpoint https://demo.vuestorefront.io/img/ - - Trying to create log files... - - Creating storefront config 'config/local.json'... - - Build storefront npm... - - Starting storefront server... - -┌────────────────────────────────────────────────────┐ -│ Congratulations! │ -│ │ -│ You've just successfully installed vue-storefront. │ -│ All required servers are running in background │ -│ │ -│ Storefront: http://localhost:3000 │ -│ Backend: https://demo.vuestorefront.io │ -│ │ -│ Logs: /home/dex/code/cli/vue-storefront/var/log/ │ -│ │ -│ Good Luck! │ - ✔ Copying Vue Storefront files - ✔ Installing dependencies - ✔ Running installer -``` - -9. Now visit the address on your browser as printed in the screen, then Voila! : - -![home_borderline](../images/home-vuestorefront.png) - -Congratulation! - -### 3. Peep into the kitchen (what happens internally) -_Vue Storefront_ people prepared the `CLI` way of installing the whole infrastructure for your _Vue Storefront_ app provided as an `npm` package. It's now as easy as to install an `npm` package on any machine. Installed then run a command with a few options would be more than enough for the app to be up and running. Believe me your next _Vue Storefront_ app will be with you instantly with a breeze as long as `CLI` is accessible. - -### 4. Chef's secret (protip) -#### Secret 1. Install with _manual_ path - -#### Secret 2. Install a _module_ skeleton - -#### Secret 3. Build your own command for `vsf-cli` - -
-
-
diff --git a/docs/guide/cookbook/tdd.md b/docs/guide/cookbook/tdd.md deleted file mode 100644 index 0e1e8a5117..0000000000 --- a/docs/guide/cookbook/tdd.md +++ /dev/null @@ -1,4 +0,0 @@ -# Ch 9. Test Driven Development for VSF - -## Coming soon! - diff --git a/docs/guide/cookbook/theme.md b/docs/guide/cookbook/theme.md deleted file mode 100644 index ef844c0988..0000000000 --- a/docs/guide/cookbook/theme.md +++ /dev/null @@ -1,2219 +0,0 @@ -# Ch 6. Theming in depth - -In this chapter, we are going to cover : -[[toc]] - - -## 0. Introduction -Theme is what customers get first impression from your shop. You will be majorly discouraged if your customers underestimate your shop by looks and feels of the first impression due to poorly designed theme when you had pearls and golds in value for your customers on your shop. Great products, meticulously calibrated technology backing your store, are abysmally depreciated which impact your sales in result. We are here to help you get away with such disasters by guiding you in wrapping your head around how to deal with `theme` in _Vue Storefront_ context. Are you ready? a _Picaso_? - - - -## 2. How to upgrade theme from 1.11 to 1.12 -Here comes again! We've got a lot of stuff packed in one package full of gift to you. This time, however, is different from the past in that we came up with the whole new approach to _Vue Storefront_ by building it up from scratch and gave it a new name [_Capybara_](https://github.com/vuestorefront/vsf-capybara). _Capybara_ is built on [Storefront UI Design System](https://www.storefrontui.io/) which employs modular approach to its smallest chunks. How versatile is that! Now that the former _default_ theme is no more default but the _Capybara_ replaces it taking the role of base theme. - -In this recipe, we will take a look at how to upgrade from `1.11` to `1.12` seamlessly with respect to themes. Changes made to your theme on `1.11` will not be taken into consideration since we will not use `default` theme but `capybara` for this recipe. - -### 1. Preparation - - You have a [_Vue Storefront_ `1.11` App installed](#_3-how-to-upgrade-theme-from-1-10-to-1-11) on your machine. - - You have pulled `1.12` commmits from the [_Vue Storefront_ git repository](https://github.com/vuestorefront/vue-storefront). - -### 2. Recipe -1. Pull `v1.12.0` tag to the `1.11` VSF app as follows : -```bash -git fetch -git checkout tags/v1.12.0 -b recipe # this checkouts to v1.12.0 tag and create a [recipe] branch for this tutorial. -``` - - -2. Change directory to `src/themes` as follows : -```bash -cd src -mkdir themes && cd themes -``` - - -3. Add `git` `submodule` into `themes` folder as follows : -```bash -git submodule add https://github.com/vuestorefront/vsf-capybara capybara -``` - -Now files are ready. - -:::tip -If you go to root `./config` folder and open `local.json`, then you will notice `theme` node is set to `capybara` by default as follows : -```json - "theme": "@vue-storefront/theme-capybara", -``` -::: - -4. Run the app then you will see the screen as below : -```bash -docker-compose up -d -``` - -![vs_home_intro_borderline](../images/home_capybara.png) - - -### 3. Peep into the kitchen (what happens internally) -In this recipe, we have checked out to `v1.12.0` and install `vsf-capybara` theme as a submodule to `themes` folder. And that's all it takes, it's that simple to adapt your application to new theme eco-system. - -_Capybara_ theme is based on [_Storefront UI_](https://www.storefrontui.io/) and it's completely different approach from other plain themes since it's kinda an eCommerce version of _Bootstrap_ in CSS framework. All the frequently used components have been divided into their chunks in the smallest meaningful size so that users can compose those components whatever way they want it to please their purposes. Easier, Organized, Planned, all the brighter sides of modular system have been implemented into it. You can follow the best practices made by core teams by looking at how _Capybara_ is structured. - -That said, you had to say good-bye to your old `1.11` theme. [_Start building your own theme_ recipe](#_1-start-building-your-own-theme) may help you achieve complete migration of theme's customized parts in that _say good-bye_. - -### 4. Chef's secret (protip) -
-
-
- - -## 3. How to upgrade theme from 1.10 to 1.11 -:::warning REMINDER -This recipe assumes you will use [`default` theme](https://github.com/vuestorefront/vsf-default) for your store which is natural because `1.11` has only one _default_ theme whose name is `default`. - -From `1.12` on, however, `default` theme becomes optional while `capybara` is _default_. -::: - -When you are already running your _Vue Storefront_ shop on production, chances are that you have made at least a few changes for your _theme_ even if you don't have developers resource. Hope you have made such changes to your child theme based on `default` theme so that normal upgrade won't make a huge impact in negative way for your shop. - -Sometimes, however, an upgrade so huge that you can't make a smooth conversion from one to another may take place. Helping you in such a case keep headaches at bay, we will show you the example where `1.10` to `1.11` upgrade affects how a theme works and fix broken pieces. - -This recipe helps you resolve errors you encounter after the upgrade as short a route as possible. There would be more warnings and small leftovers within your theme. To make a complete overhaul, look for [Migration from 1.10 to 11](/guide/cookbook/migration.html#_1-upgrade-from-1-10-to-1-11) - -### 1. Preparation - - You have a [Vue Storefront App running](/guide/cookbook/setup.html#_0-introduction) by `docker` or `yarn dev` and watch it in your browser. - - You have a child theme [running](/guide/cookbook/theme.html#_1-start-building-your-own-theme) on top of _Vue Storefront_ app. - - In this recipe, we start with _degi_ child theme based on `1.10` version (git hash : [___1b53bd2a___](https://github.com/vuestorefront/vue-storefront/commit/1b53bd2a829f7cab571dbd3c2a4021ea46857da7)) of `default` theme. This _degi_ theme is an example you might have created for your own. Which means, you can change it to whatever you like. - - In other words, suppose you have a _Vue Storefront_ shop running on a child theme `degi` that was branched off from _Vue Storefront_ `default` theme version `1.10` and want to upgrade to `1.11`. - - -### 1-1. Contents -There are many features added/removed/enhanced with `1.11`. This recipe deals with all of them. They are, however, too many to skim in a glance. That's why you might need this _contents_ for them. - - -- Pages - - [Home](#_3-now-we-start-hunting-down-the-culprits-one-by-one) - - [Category](#_11-the-next-big-chunk-to-take-care-of-is-category-from-pages) - - [Checkout](#_12-now-look-into-checkout-from-pages) - - [Compare](#_13-next-is-compare-in-pages) - - [MyAccount](#_14-here-comes-myaccount-in-pages) - - [PageNotFound](#_15-another-page-pagenotfound-in-pages) - - [Product](#_16-another-big-update-for-product-in-pages) - - [Static](#_17-now-static-in-pages) - -- Core components - - Blocks - - [Microcart](#_6-now-you-are-ok-with-home-page-but-there-are-still-subtle-changes-made-to-each-corner-of-the-app-let-s-find-them-out) - - [Auth](#_18-time-to-work-on-auth-part) - - ForgotPass - - Login - - Register - - Category - - [Sidebar](#_19-next-block-to-fix-is-category-sidebar) - - [Checkout](#_20-next-block-to-fix-is-checkout) - - OrderConfirmation - - OrderReview - - Payment - - Product - - Shipping - - ThankYouPage - - CMS - - [Block](#_21-move-on-to-cms-block) - - Compare - - [AddToCompare](#_22-copy-addtocompare) - - [Footer](#_23-time-for-footer-block) - - Footer - - MinimalFooter - - NewsLetter - - Form - - [BaseInputNumber](#_24-baseinputnumber-needs-update) - - [Header](#_25-header-needs-fix-too) - - CompareIcon - - Header - - MicroCartIcon - - MinimalHeader - - WishListIcon - - [MyAccount](#_26-myaccount-turn-is-now) - - MyNewsLetter - - MyOrder - - MyOrders - - MyProfile - - MyShippingDetails - - Product - - [Related](#_27-product-related-is-ready-to-transform) - - Reviews - - [Reviews](#_28-reviews-is-fixed-too) - - SearchPanel - - [SearchPanel](#_29-searchpanel-has-parts-to-update) - - [SidebarMenu](#_30-sidebarmenu-wants-to-update-too) - - SidebarMenu - - SubCategory - - Switcher - - [Language](#_31-it-s-switcher-language-turn) - - [Wishlist](#_32-let-s-look-into-wishlist-and-make-a-wish) - - AddToWishlist - - Product - - - [Breadcrumb](#_10-next-target-is-breadcrumb-now-breadcrumb-supports-the-multistore-feature) - - [AddToCart](#_33-start-with-addtocart) - - [ColorSelector](#_34-next-is-colorselector) - - [CookieNotification](#_35-update-cookienotification) - - [GenericSelector](#_36-time-for-fixing-genericselector) - - [Logo](#_37-now-it-s-logo-turn) - - [Modal](#_38-let-s-fix-modal) - - [NewsletterPopup](#_39-let-s-fix-newsletterpopup) - - [OfflineBadge](#_40-offlinebadge-to-be-tackled) - - [Overlay](#_41-overlay-needs-fix) - - [PriceSelector](#_42-priceselector-needs-fix) - - [ProductBundleOptions](#_43-productbundleoptions-ready-to-update) - - [ProductCustomOptions](#_44-productcustomoptions-ready-to-update) - - [ProductGalleryCarousel](#_45-productgallerycarousel-ready-to-update) - - [ProductGalleryZoomCarousel](#_46-productgalleryzoomcarousel-ready-to-update) - - [ProductImage](#_47-productimage-waits-in-line) - - [ProductLinks](#_48-productlinks-waits-in-line) - - [ProductQuantity](#_49-productquantity) - - [ProductTile](#_50-producttile) - - [SizeSelector](#_51-sizeselector) - - [SortBy](#_52-sortby) - - [Spinner](#_53-spinner) - -- Theme components - - [ButtonFull](#_54-buttonfull-in-theme) - - [ButtonOutline](#_55-buttonoutline-in-theme) - - [WebShare](#_56-webshare-in-theme) - - Blocks - - AsyncSidebar - - [AsyncSidebar](#_57-asyncsidebar-in-theme-blocks) - - Home - - [Onboard](#_58-onboard-in-theme-blocks) - - Inspirations - - [InspirationTile](#_59-inspirationtile-in-theme-blocks) - - Reviews - - [ReviewsList](#_60-reviewslist-in-theme-blocks) - -- [Store](#_4-add-new-files-introduced-from-1-11-as-following-path-from-the-default-theme) - -- Other Important Files - - [helpers/index.ts](#_61-helpers) - - [index.js](#_61-index-js) - - [mixins/filterMixin.ts](#_62-mixins-filtermixin-ts) - - [package.json] - - [router/index.js](#_63-router-index-js) - - Resource - - [i18n](#_64-resource-i18n) - - - -### 2. Recipe - - -#### 1. Go to your _Vue Storefront_ app root directory and `git checkout` to following hash `e4959550` : - -```bash -git fetch -git checkout e4959550 # origin/release/v1.11 -``` - -:::warning NOTICE -`origin/release/v1.11` is still evolving. It might be different with the latest hash at the time of your reading. This `docs` gets updated periodically, anyway you get the idea. -::: - -#### 2. Resulting screen in your browser would somewhat look like this as sad as can be : - -![error_1.11](../images/error_1.11.png) - -#### 3. Now we start hunting down the culprits one by one. - -:::tip TIP -By the way, you can also compare the changes made between `1.10` to `1.11` in [github link](https://github.com/vuestorefront/vue-storefront/commit/a42d480aea56d90f7ab65c5caf6ce3f49b00dfec) with a glance too. -::: - - - First target is located at `./src/themes/degi/components/core/blocks/MyAccount/MyOrders.vue`. Replace it as follows : -:::warning NOTICE -Line numbers denote the number in the file and they might not match since it assumes no modification. Think of it as an approximation reference. -::: - -
- -
- - - -As you can see `UserOrdersHistory` has been moved to `core/modules` package. - - - Next, go to `./src/themes/degi/pages/Home.vue` and fix it as follows : - -
- -
- - -
- -
- - - -There you can see `lazy-hydrate` is implemented for the better UX. - - -- Now, replace `36` we don't use any more with another because we now use lazy-hydrate feature : - -
- -
- - -Because we now use `lazy-hydrate` feature. - -Additionally `recently-viewed` module is added to `Home.vue` template from `1.11`. - -`loading` is required to determine if `lazy-hydrate` needs triggered - -From `1.11`, _collections_ comes from `vuex` `store` using `mapGetters` helper. - - -- Various `actions` in separate files under `./src/themes/degi/store` replaced in-page `actions`. - -We will add those files in the next step. - -
- -
- - -Again, new `actions` are used here instead of the old way. - -#### 4. Add new files introduced from `1.11` as following path from the `default` theme : -```bash -# Assuming you are at the root directory -cd src -cp -r themes/default/store themes/degi/ - -# Now replace index.js to import new features and abandon deprecated ones. -cp themes/default/index.js themes/degi/index.js -``` - -#### 5. Open your browser once again, then your errors now be gone as follows : - -![success_home_with_1.11_borderline](../images/success_1.11_home.png) - -#### 6. Now you are OK with _Home_ page, but there are still subtle changes made to each corner of the app. Let's find them out. -Click the _Microcart_ icon then you will see the page like below : - -![microcart_nan_borderline](../images/microcart_nan.png) - -Multiple spots need attention to be fixed. We upgraded _Microcart_ to enable _Edit Mode_ on the fly. Let's fix it. - -#### 7. Copy newly added files from the `default` theme to `degi` theme : -```bash -# you are at the root directory -cd src -cp themes/default/components/core/blocks/Microcart/EditButton.vue themes/degi/components/core/blocks/Microcart/ -cp themes/default/components/core/blocks/Microcart/EditMode.vue themes/degi/components/core/blocks/Microcart/ -``` - -#### 8. Then fix files that you might have modified before upgrade. - - - Go to `./src/themes/degi/components/core/blocks/Microcart/Microcart.vue` and fix it as follows at `3` : - -
- -
- - -This transition allows _EditMode_ overlay and also works as a toggle button. - - - Replace `48` with below : - -
- -
- - -Now, `product.checksum` can be used as a key - - - Fix it at `125` : - -
- -
- - - - -Importing new features while removing deprecated ones. - - - Change `mixins` at `148` : - -
- -
- - - - Add _functions_ and fix _methods_ at around `166` : - -
- -
- - -Here, you can see `InstantCheckoutModule` is registered at `beforeCreate`. - -`mapGetters` helper is used for getting `vuex` `actions`. [more info](https://stackoverflow.com/questions/49696542/differences-b-n-mapstate-mapgetters-mapactions-mapmutations-in-vuex) on `vuex` _helpers_ - - - Last but not least for the file, add some styles inside _\\_ tag at around `311` : - -
- -
- - - - Another big change has been made to `./src/themes/degi/components/core/blocks/Microcart/Product.vue`. - -Start with replacing _template_ at `2` as follows : - -
- -
- - -Here we replaced template. - - - Now fix script as follows at `135` : - -
- -
- - - -As you can see here, we added `EditMode` in _Microcart_. Many things were considered in doing so, e.g. `color`, `size`, `option`, _multistores_ and so on. - - - - Time to fix _styles_ : - - -
- -
- - - - - Move on to next file `./src/themes/degi/components/core/blocks/Microcart/RemoveButton.vue` and fix it at `2` as follows : - - -
- -
- - - -Here we added _Vue_ click event. - -#### 9. Let's confirm if we got it right so far on your browser. Open it then _Voila !_ : - -![editmode_in_mc_borderline](../images/editmodeInMC.png) - -Now you can edit options for your products in _Microcart_. - - -#### 10. Next target is _Breadcrumb_. Now _Breadcrumb_ supports the multistore feature. - - - Open `./src/themes/degi/components/core/Breadcrumbs.vue` and fix them as follows : - - - -
- -
- - - As you can see, `paths` are _computed_ for allocating storeviews. Now clicking on a breadcrumb link brings you to the link of designated storeview as it's supposed to. - -#### 11. The next big chunk to take care of is _Category_ from _pages_. - - - Go to `./src/themes/degi/pages/Category.vue` and overhaul it as follows : - - -
- -
- - - _Lazy Hydrate_ is implemented in _Category_ page too. - -Many _getters_ to fetch values from _store_ are _computed_ using `mapGetters` - -#### 12. Now look into _Checkout_ from _pages_ - - - Go to `./src/themes/degi/pages/Checkout.vue` and fix it as follows : - - -
- -
- - - - _Checkout_ page imports _Order_ module and register it at _beforeCreate_ hook. - - It also fix a small typo. - - -#### 13. Next is _Compare_ in _pages_ - - - Go to `./src/themes/degi/pages/Compare.vue` and fix it as follows : - - -
- -
- - - _RemoveButton_ was _removed!_ - - -#### 14. Here comes _MyAccount_ in _pages_. - - - - Go to `./src/themes/degi/pages/MyAccount.vue` and fix it as follows : - - -
- -
- - - _RecentlyViewed_ module is imported and registered at _beforeCreate_ hook. - -#### 15. Another page _PageNotFound_ in _pages_. - - - - Go to `./src/themes/degi/pages/PageNotFound.vue` and fix it as follows : - - -
- -
- - - `ourBestsellersCollection` is fetched by `vuex` _store_ via `mapGetters` - - - -#### 16. Another big update for _Product_ in _pages_. - - - - Go to `./src/themes/degi/pages/Product.vue` and fix it as follows : - - -
- -
- - - Here, _template_ part is overhauled. - - Data is filled up by `vuex` _getters_. - - _Reviews_ are also _lazy hydrated_. - - - - Continue with it for the _script_ part. - -
- -
- - -Here once again _vuex_ _getters_ are widely used to _compute_ data. - - -#### 17. Now, _Static_ in _pages_. - -- Go to `./src/themes/degi/pages/Static.vue` and fix it as follows : - -
- -
- - - _Routing_ has been updated to support the _multistore_ feature. - - -#### 18. Time to work on _Auth_ part - -- Go to `./src/themes/degi/components/core/blocks/Auth/ForgotPass.vue` and fix it as follows : - -
- -
- - - -- Go to `./src/themes/degi/components/core/blocks/Auth/Login.vue` and fix it as follows : - -
- -
- - - -- Go to `./src/themes/degi/components/core/blocks/Auth/Register.vue` and fix it as follows : - -
- -
- - - -#### 19. Next block to fix is _Category/Sidebar_ - -- Go to `./src/themes/degi/components/core/blocks/Category/Sidebar.vue` and fix it as follows : - -
- -
- - - _Events_ are added. - - `methods` and `computed` are also added for _filters_ - - - -#### 20. Next block to fix is _Checkout_ - -- Go to `./src/themes/degi/components/core/blocks/Checkout/OrderConfirmation.vue` and fix it as follows : - -
- -
- - - -- Go to `./src/themes/degi/components/core/blocks/Checkout/OrderReview.vue` and fix it as follows : - -
- -
- - - _Order_ module is imported for registration. - - -- Go to `./src/themes/degi/components/core/blocks/Checkout/Payment.vue` and fix it as follows : - -
- -
- - - -- Go to `./src/themes/degi/components/core/blocks/Checkout/Product.vue` and fix it as follows : - -
- -
- - - - -- Go to `./src/themes/degi/components/core/blocks/Checkout/Shipping.vue` and fix it as follows : - -
- -
- - - -- Go to `./src/themes/degi/components/core/blocks/Checkout/ThankYouPage.vue` and fix it as follows : - -
- -
- - - _Mailer_ module is registered and a message for offline order fulfillment is added. - - -#### 21. Move on to _CMS/Block_ - -- Go to `./src/themes/degi/components/core/blocks/Cms/Block.vue` and fix it as follows : - -
- -
- - - The names of _getters_ are changed to follow the convention throughout the whole update. - - -#### 22. Copy _AddToCompare_ - - - Add new files introduced at `1.11` - -```bash -cd src - -cp themes/default/components/core/blocks/Compare/AddToCompare.vue themes/degi/components/core/blocks/Compare/AddToCompare.vue -``` - - -#### 23. Time for _Footer_ _block_ - -- Go to `./src/themes/degi/components/core/blocks/Footer/Footer.vue` and fix it as follows : - - - - - Wrap _multistore_ feature for composing _static_ page path. - - -- Go to `./src/themes/degi/components/core/blocks/Footer/MinimalFooter.vue` and fix it as follows : - - - - - -- Go to `./src/themes/degi/components/core/blocks/Footer/NewsLetter.vue` and fix it as follows : - - - - - `v-if` has been changed to `v-show`. - - -#### 24. _BaseInputNumber_ needs update too - -- Go to `./src/themes/degi/components/core/blocks/Form/BaseInputNumber.vue` and fix it as follows : - -
- -
- - - `max` and `disabled` _properties_ are added. - - Some styles were updated. - - -#### 25. _Header_ needs fix too. - -- Go to `./src/themes/degi/components/core/blocks/Header/CompareIcon.vue` and fix it as follows : - -
- -
- - - -- Go to `./src/themes/degi/components/core/blocks/Header/Header.vue` and fix it as follows : - -
- -
- - - A few code styling has been put into place. - - -- Go to `./src/themes/degi/components/core/blocks/Header/MicrocartIcon.vue` and fix it as follows : - -
- -
- - - `mixins` is commented and `vuex` `mapGetters` are introduced as other updates. - - -- Go to `./src/themes/degi/components/core/blocks/Header/MinimalHeader.vue` and fix it as follows : - -
- -
- - - _Multistore_ feature and some styling are updated. - - -- Go to `./src/themes/degi/components/core/blocks/Header/WishListIcon.vue` and fix it as follows : - -
- -
- - - _Wishlist_ _count_ is added. - - - -#### 26. _MyAccount_ turn is now. - -- Go to `./src/themes/degi/components/core/blocks/MyAccount/MyNewsLetter.vue` and fix it as follows : - -
- -
- - - - - -- Go to `./src/themes/degi/components/core/blocks/MyAccount/MyOrder.vue` and fix it as follows : - -
- -
- - - A few styles have been put into place. - - Product thumbnail has been added. - - -- Go to `./src/themes/degi/components/core/blocks/MyAccount/MyOrders.vue` and fix it as follows : - -
- -
- - - `UserOrdersHistory` module has been moved to core module. - -- Go to `./src/themes/degi/components/core/blocks/MyAccount/MyProfile.vue` and fix it as follows : - -
- -
- - - Code styling update and _validation_ added. - - -- Go to `./src/themes/degi/components/core/blocks/MyAccount/MyShippingDetails.vue` and fix it as follows : - -
- -
- - - Code styling update and _validation_ added. - - -#### 27. _Product_ _Related_ is ready to transform. - -- Go to `./src/themes/degi/components/core/blocks/Product/Related.vue` and fix it as follows : - - - - - Now you are familiar with `vuex` `mapGetters` implemented in the update. - - `async`-`await` implementation also takes place. - - - -#### 28. _Reviews_ is fixed too - -- Go to `./src/themes/degi/components/core/blocks/Reviews/Reviews.vue` and fix it as follows : - -
- -
- - - `i18n` support is added. - - `async`-`await` implementation also takes place. - - Notification for _Review_ submission has been added. - - -#### 29. _SearchPanel_ has parts to update - -- Go to `./src/themes/degi/components/core/blocks/SearchPanel/SearchPanel.gql.vue` and add it as follows : - -
- -
- - -- Go to `./src/themes/degi/components/core/blocks/SearchPanel/SearchPanel.vue` and add it as follows : - - - - - _Disabling Scroll_ feature is added to _SearchPanel_. - - Javascript `filter`, `map` replaces `forEach` for _products_ - - -#### 30. _SidebarMenu_ wants to update too! - -- Go to `./src/themes/degi/components/core/blocks/SidebarMenu/SidebarMenu.vue` and fix it as follows : - -
- -
- - - _CategoryLink_ gets help from `helper` for formatting. - - _Disabling Scroll_ feature is added to _SidebarMenu_ too. - -- Go to `./src/themes/degi/components/core/blocks/SidebarMenu/SubCategory.vue` and add it as follows : - -
- -
- - - `async`-`await` implementation also takes place. - - _CategoryLink_ gets help from `helper` for formatting. - - -#### 31. It's _Switcher_ _Language_ turn - -- Go to `./src/themes/degi/components/core/blocks/Switcher/Language.vue` and fix it as follows : - -
- -
- - - _Validation_ for _storeCode_ is added. - - -#### 32. Let's look into _Wishlist_ and make a wish - -- Go to `./src/themes/degi/components/core/blocks/Wishlist/AddToWishlist.vue` and fix it as follows : - -
- -
- - - `i18n` support is added. - - `methods` for adding / removing _product_ to _Wishlist_ are added including notification for them. - - - -- Go to `./src/themes/degi/components/core/blocks/Wishlist/Product.vue` and fix it as follows : - -
- -
- - -Now, `core` `blocks` updates are all covered. Let's move to `core` components. - - -#### 33. Start with _AddToCart_ - -- Go to `./src/themes/degi/components/core/AddToCart.vue` and fix it as follows : - -
- -
- - - -#### 34. Next is _ColorSelector_ - -- Go to `./src/themes/degi/components/core/ColorSelector.vue` and fix it as follows : - -
- -
- - - -#### 35. Update _CookieNotification_ - -- Go to `./src/themes/degi/components/core/CookieNotification.vue` and fix it as follows : - - - - - -#### 36. Time for fixing _GenericSelector_ - -- Go to `./src/themes/degi/components/core/GenericSelector.vue` and fix it as follows : - -
- -
- - -#### 37. Now, it's _Logo_ turn - -- Go to `./src/themes/degi/components/core/Logo.vue` and fix it as follows : - - - - - -#### 38. Let's fix _Modal_ - -- Go to `./src/themes/degi/components/core/Modal.vue` and fix it as follows : - -
- -
- - - -#### 39. Let's fix _NewsletterPopup_ - -- Go to `./src/themes/degi/components/core/NewsletterPopup.vue` and fix it as follows : - -
- -
- - - -#### 40. _OfflineBadge_ to be tackled - -- Go to `./src/themes/degi/components/core/OfflineBadge.vue` and fix it as follows : - -
- -
- - - -#### 41. _Overlay_ needs fix - -- Go to `./src/themes/degi/components/core/Overlay.vue` and fix it as follows : - -
- -
- - - -#### 42. _PriceSelector_ needs fix - -- Go to `./src/themes/degi/components/core/PriceSelector.vue` and fix it as follows : - -
- -
- - - -#### 43. _ProductBundleOptions_ ready to update - -- Go to `./src/themes/degi/components/core/ProductBundleOptions.vue` and fix it as follows : - -
- -
- - - -#### 44. _ProductCustomOptions_ ready to update - -- Go to `./src/themes/degi/components/core/ProductCustomOptions.vue` and fix it as follows : - -
- -
- - - -#### 45. _ProductGalleryCarousel_ ready to update - -- Go to `./src/themes/degi/components/core/ProductGalleryCarousel.vue` and fix it as follows : - -
- -
- - - -#### 46. _ProductGalleryZoomCarousel_ ready to update - -- Go to `./src/themes/degi/components/core/ProductGalleryZoomCarousel.vue` and fix it as follows : - -
- -
- - - -#### 47. _ProductImage_ waits in line - -- Go to `./src/themes/degi/components/core/ProductImage.vue` and fix it as follows : - -
- -
- - - -#### 48. _ProductLinks_ - -- Go to `./src/themes/degi/components/core/ProductLinks.vue` and fix it as follows : - - - - - -#### 49. _ProductQuantity_ - -- Go to `./src/themes/degi/components/core/ProductQuantity.vue` and create it as follows : - -
- -
- - - -#### 50. _ProductTile_ - -- Go to `./src/themes/degi/components/core/ProductTile.vue` and fix it as follows : - -
- -
- - - -#### 51. _SizeSelector_ - -- Go to `./src/themes/degi/components/core/SizeSelector.vue` and fix it as follows : - -
- -
- - - -#### 52. _SortBy_ - -- Go to `./src/themes/degi/components/core/SortBy.vue` and fix it as follows : - -
- -
- - - -#### 53. _Spinner_ - -- Go to `./src/themes/degi/components/core/Spinner.vue` and fix it as follows : - -
- -
- - - - - -Now, you are done with `core` component part. Move on to `theme` part. - - -#### 54. _ButtonFull_ in `theme` - -- Go to `./src/themes/degi/components/theme/ButtonFull.vue` and fix it as follows : - -
- -
- - - -#### 55. _ButtonOutline_ in `theme` - -- Go to `./src/themes/degi/components/theme/ButtonOutline.vue` and fix it as follows : - -
- -
- - - -#### 56. _WebShare_ in `theme` - -- Go to `./src/themes/degi/components/theme/WebShare.vue` and create it as follows : - -
- -
- - - -#### 57. _AsyncSidebar_ in `theme` `blocks` - -- Go to `./src/themes/degi/components/theme/blocks/AsyncSidebar/AsyncSidebar.vue` and fix it as follows : - -
- -
- - - -#### 58. _Onboard_ in `theme` `blocks` - -- Go to `./src/themes/degi/components/theme/blocks/Home/Onboard.vue` and fix it as follows : - -
- -
- - - -#### 59. _InspirationTile_ in `theme` `blocks` - -- Go to `./src/themes/degi/components/theme/blocks/Inspirations/InspirationTile.vue` and fix it as follows : - -
- -
- - - -#### 60. _ReviewsList_ in `theme` `blocks` - -- Go to `./src/themes/degi/components/theme/blocks/Reviews/ReviewsList.vue` and fix it as follows : - -
- -
- - - - -Everything specifically related to `theme` has been covered so far, now time to work on other files. - - - -#### 61. _helpers_ - -- Go to `./src/themes/degi/helpers/index.ts` and create it as follows : - -
- -
- - - -#### 61. _index.js_ - -- Go to `./src/themes/degi/index.js` and fix it as follows : - -
- -
- - - -#### 62. _mixins/filterMixin.ts_ - -- Go to `./src/themes/degi/mixins/filterMixin.ts` and create it as follows : - -
- -
- - - -#### 63. _router/index.js_ - -- Go to `./src/themes/degi/router/index.js` and fix it as follows : - -
- -
- - -Finally we are done with it but `i18n`, that is, however, about copying `csv` files. - - -#### 64. _resource/i18n_ - -- Go to `./src/themes/degi/resource/i18n` and copy them as follows : -```bash -cp -r themes/default/resource/i18n/* themes/degi/resource/i18n/ -``` - -Keep it mind that if you added/modified/removed `i18n` files aside default ones and used them from a theme, you should also work on it. - -### 3. Peep into the kitchen (what happens internally) - -We have been through a thorough scrutiny on what's been changed to `1.11`. - -This upgrade recipe deals with _theme_ part only since _modules_ and _core_ part of the upgrade should be dealt diffrently. It won't be easy just to copy/paste updated parts and pray for smooth upgrade when it comes to custom _modules_ for it's next to impossible to guess the true intention of _module_ developer and bend it to work compatibly with the updates unless you are the one who created it. ([here](module.html) for how to upgrade custom modules) _Core_ parts, however, should be working fine with copy/paste upgrade because they were supposed to be untouched in the first place. Once the _core_ parts have been upgraded without a hiccup, it's time to upgrade _theme_ part and this recipe is all about how to just do it. - -In `1.11` upgrade, one notable change is about how it works with data throughout the app. It takes advantage of `vuex` _store_ for data container, and it can be seen in upgrading various blocks as using `mapGetters`. - -_Lazy Hydrate_ has been also applied to product listing and other features and improvement has been placed for better readability and efficiency. - -There are many patches in _theme_ for _core_ and _module_ upgrade to be compatible with new concepts introduced to `1.11`. - -In general, the theme upgrade might be a tedious process for you but it's worth it for your service! - - -### 4. Chef's secret (protip) -1. Debunk _github_ features to uncover the code changes - - -
-
-
diff --git a/docs/guide/cookbook/vue.md b/docs/guide/cookbook/vue.md deleted file mode 100644 index 7ea292428f..0000000000 --- a/docs/guide/cookbook/vue.md +++ /dev/null @@ -1,86 +0,0 @@ -# Ch 11. Vue Basics by VSF examples - - -In this chapter, we are going to cover : -[[toc]] - -## 0. Introduction -_**Vue.js**_ has been hotter than sun and sleeker than other competitors among frontend developers for many reasons in recent years. -
-
- -## 1. Debugging - - -### 1. Preparation - - -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 2. SSR - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 3. Event Bus - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 4. Mixins - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 5. Vuex - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 6. - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 7. - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- -## 8. - -### 1. Preparation -### 2. Recipe -### 3. Peep into the kitchen (what happens internally) -### 4. Chef's secret (protip) -
-
- diff --git a/docs/guide/core-themes/core-components-api.md b/docs/guide/core-themes/core-components-api.md deleted file mode 100644 index c6f4a99383..0000000000 --- a/docs/guide/core-themes/core-components-api.md +++ /dev/null @@ -1,626 +0,0 @@ -# Core components API - -:::warning Note -Temporary file for core components API. List of props, public methods and data that are available to use via mixin insertion. -In the case of injectable components (like modal) or the ones triggered by Vuex actions you should write them down also. Feel free to write some description/new api proposal for each documented component. -::: - -## AddToCart - -This component represents a single button that, when pressed, adds a product to the cart. - -### Props - -- `product` - An instance of a product - -### Data - -No data - -### Methods - -- `addToCart (product)` - Dispatches 'cart/addItem' action and passes a product instance as a parameter. - -#### Parameters - -_product_ - An instance of a product - -## BaseCheckbox - -This component represents a checkbox with label and validation. - -### Props - -- `id` - id for a checkbox input and a label -- `validation.condition` - vuelidate if statement -- `validation.text` - validation error text -- `disabled` - boolean prop to disable checkbox input - -### Data - -No data - -### Methods - -No methods - -## BaseInput - -This component represents an input with validation. - -### Props - -- `type` - input type -- `name` - input name -- `placeholder` - input placeholder -- `autocomplete` - input autocomplete -- `focus` - boolean prop that defines if this input is autofocused -- `validation.condition` - vuelidate if statement -- `validation.text` - validation error text -- `validations` - array of validation objects to apply multiple validations - -### Data - -- `passType` - a copy of password type for toggle password visibility -- `iconActive` - a boolean prop that defines if visibility button is enabled -- `icon` - default material icon name for visibility button - -### Methods - -- `togglePassType` - toggle password visibility and icon name - -## Breadcrumbs - -This component represents a hierarchy of the current page in relation to the application structure. It is used in Product, Category, My Account and Compare pages. - -### Props - -- `routes` - An array of route objects, each representing name and path to a parent page. -- `activeRoute` - A name of the current page - -### Data - -No data - -### Methods - -No methods - -## ColorSelector - -This component represents a button that is used for visualizing different options, specifically product filters. It is used on category's Sidebar and Product pages. - -### Props - -- `label` - a label that is shown on the component button. -- `id` - an identifier that unifies an option among others in an array. -- `code` - a name of an option that the component is being used to represent (currently 'color'). -- `context` - a name of an entity that the component belongs to (currently one of 'category' or 'product'). - -### Data - -- `active` - a boolean prop that defines if the button is pressed and is active. - -### Methods - -- `switchFilter (id, label)` - triggers `filter-changed-` event, where context is a value of _context_ prop. - -#### Parameters - -_id_ - same as _id_ prop. -_label_ - same as _label_ prop. - -### Hooks - -#### beforeMount - -If the current route's name is not 'product' defines 2 event listeners. First one is `filter-reset` that sets _active_ prop to false. Second is `filter-changed-`, where context is a value of _context_ prop. This event listener toggles the value of _active_ prop depending on which instance of ColorSelector component was passed to it as a parameter. - -#### beforeDestroy - -Removes event listeners defined in `beforeMount` hook. - -## Loader - -This component is used for visualizing loading process, when something is happening in the background. It is currently used when an account is being registered, the password is being reset and the user is logging in. - -### Props - -No props - -### Data - -- `message` - A message that is shown while the loading process is on. -- `isVisible` - Computed property which is equal to UI-store's _loader_ property. This prop defines whether to show or hide the spinner. - -### Methods - -- `show (message = null)` - Sets message property and calls UI-store mutation 'ui/setLoader', which causes a spinner to show up. - -#### Parameters - -_message_ - A text that is shown when the loading process is on. - -- `hide` - Calls UI-store mutation 'ui/setLoader', which hides a spinner. - -#### Parameters - -No parameters - -### Hooks - -#### Mounted - -Two listeners are defined: -`'notification-progress-start'` - calls _show_ method. -`'notification-progress-stop'` - calls _hide_ method. - -## Logo - -This component is intended to serve an image of an application logo and to navigate the user to a Home page when the logo is pressed. - -### Props - -No props - -### Data - -No data - -### Methods - -No methods - -## Newsletter popup (should be using modal) - -Shows popup modal window where the user can enter his/her newsletter subscription preferences, currently only email address. This component is used in a Footer page. - -### Props - -No props - -### Data - -No data - -### Methods - -- `closeNewsletter` - Closes newsletter popup modal window by calling UI-store mutation 'ui/setNewsletterPopup'. - -## Notification - -Shows notifications after some actions being performed in the shop. There are four types of notifications: success, error, warning and info. - -### Props - -No props - -### Data - -- `notifications` - An Array of notifications that should be currently displayed - -### Methods - -- `action(action, id)` - Performs an `action` defined as String on notification with passed `id` - usually the action is `close`. Actions are defined in Notification component. Current Notification object schema: - -```json -{ - // Choose one - "type": "info/success/error/warning", - "title": "Lorem ipsum", - "action1": { - "label": "OK", - "action": "close" - }, - // Optional param - "action2": { - "label": "NO", - "action": "close" - }, - // Optional param, if its empty TTL is 5s - "timeToLive": 10 -} -``` - -### Events - -- `('notification', notificationObject)` - takes notification object and adds it to Notification array, then the new notification is displayed - -## OfflineBadge - -When there's no active internet connection, shows notification about getting offline at the bottom of the screen. - -### Props - -No props - -### Data - -- `isOnline` - defines if there is an active internet connection - -### Methods - -No methods - -### Hooks - -#### Mounted - -Sets isOnline data property and defines two event listeners: - -- `'online'` - sets _isOnline_ data property to true. -- `'offline'` - sets _isOnline_ data property to false. - -## Overlay - -This component is used to shadow parts of the screen that are left after opening modal windows, like WishList or Cart. - -### Props - -- `isVisible` - computed property that is equal to _overlay_ property of UI-store. Defines whether to shadow parts of the screen or not. - -### Data - -No data - -### Methods - -- `close` - calls UI-store mutation 'ui/setOverlay' and sets its _overlay_ property to _false_. - -## PriceSelector - -Represents one of the options on the Category page. Shows price range and allows the user to choose one of the ranges. - -### Props - -- `content` - text that shows the price range -- `id` - the unique identifier of the option -- `code` - options' code, equals to 'price' -- `from` - minimum value of the price range -- `to` - maximum value of the price range -- `context` - a name of an entity that the component belongs to (currently 'category') - -### Data - -- `active` - boolean prop that defines if button is pressed and is active. - -### Methods - -- `switchFilter (id, label)` - triggers `'filter-changed-'` event, where context is a value of _context_ prop. - -#### Parameters - -_id_ - same as _id_ prop. -_label_ - same as _label_ prop. - -### Hooks - -#### beforeMount - -Defines 2 event listeners. First one is `filter-reset` that sets _active_ prop to false. Second is `filter-changed-`, where context is a value of _context_ prop. This event listener toggles the value of _active_ prop depending on which instance of PriceSelector component was passed to it as a parameter. - -#### beforeDestroy - -Removes event listeners defined in `beforeMoun`t hook. - -## ProductAttribute - -Shows attributes that a specific product has. Used on Product Page. - -### Props - -- `product` - reference to the product that the attribute belongs to -- `attribute` - attribute itself -- `emptyPlaceholder` - a string that is shown if an attribute has no value - -### Data - -- `label` - name of an attribute -- `value` - attribute's value(-s) - -### Methods - -No methods - -### Hooks - -#### beforeMount - -Extracts attribute's label and value(-s) from _product_ and _attribute_ properties. - -## ProductLinks - -If the product is grouped (which means it consists of several products) this component shows a list of compound products. Used on the Product page. - -### Props - -- `products` - the array of compound products of a given product - -### Data - -No data - -### Methods - -No methods - -## ProductListing - -Shows given array of products on a page in a given number of columns. Used on Category and Home pages, and also on Related block. - -### Props - -- `product` - an array of products to show -- `columns` - the number of columns to display on a page. Each product is displayed with ProductTile component. - -### Data - -No data - -### Methods - -No methods - -## ProductSlider - -Shows product tiles slider. Used in Collection component in _default_ theme. - -### Props - -- `title` - a title of a slider -- `products` - an array of products to show in a slider -- `config` - and object that defines the configuration of a slider, like the number of tiles to show on a page, pagination and looping. - -### Data - -No data - -### Methods - -No methods - -## ProductTile - -Shows a product in a compact way when several products are shown on one page. Used in many places, such as Home page, Search panel, 404 pages and so on. - -### Props - -- `product` - a specific product -- `thumbnail` - a computed property that represents a smaller image for the product to show in this component. _The size of an image is hard-coded in this property, it might be better to keep dimensions in a config file._ - -### Data - -No data - -### Methods - -No methods - -## SizeSelector - -Represents one of the options of a product, namely product's size. Used on Category and Product pages. - -### Props - -- `label` - a string that represents the size -- `id` - the unique identifier of the size -- `code` - a code name of an option, which is 'size' -- `context` - a name of an entity that the component belongs to (currently one of 'category' or 'product') - -### Data - -`active` - a boolean prop that defines if the button is pressed and is active. - -### Methods - -- `switchFilter (id, label)` - triggers `filter-changed-` event, where context is a value of _context_ prop. - -#### Parameters - -_id_ - same as _id_ prop. -_label_ - same as _label_ prop. - -### Hooks - -#### beforeMount - -Defines 2 event listeners. First one is `filter-reset` that sets _active_ prop to false. Second is `filter-changed-`, where context is a value of _context_ prop. This event listener toggles the value of _active_ prop depending on which instance of SizeSelector component was passed to it as a parameter. - -#### beforeDestroy - -Removes event listeners defined in `beforeMount` hook. - -## Tooltip - -Shows an informational icon and hint when focused on that icon. Used on My Account and Checkout pages. - -### Props - -No props - -### Data - -No data - -### Methods - -No methods - -## ValidationError - -This was supposed to show a validation error message, but is not used anywhere. _Has to be deleted_ - -### Props - -- `message` - a text that explains the error - -### Data - -No data - -### Methods - -No methods - -# Core pages - -## Category - -Category page has been refactored (1.0RC) to the new core proposal and the [docs has been moved here](../components/category-page.md). - -## Checkout - -### Props - -No props - -### Data - -- `stockCheckCompleted` - a boolean prop that shows if all products in the cart (if any) have been checked for availability (whether they are in stock or not). -- `stockCheckOK` - a boolean prop that shows if all products in the cart are in stock. -- `activeSection` - an object that consists of 4 boolean props: _personalDetails_, _shipping_, _payment_ and _orderReview_, - that define which section of Checkout page is currently active. At any point in time only one section can be active. -- `order` - an order object, that consists of all necessary order information that will be sent to the backend to place it. -- `personalDetails` - an object that contains personal details part of the Checkout page. -- `shipping` - an object that contains shipping details part of the Checkout page. -- `payment` - an object that contains payment details part of the Checkout page. -- `orderReview` - _this prop is not used_ -- `cartSummary` - this prop is supposed to be filled after `checkout.cartSummary` the event has been triggered. _But this event is not triggered anywhere, therefore this prop currently has no usage._ -- `validationResults` - an object that keeps validation result of 3 child components: Personal Details, Shipping Details and Payment Details. _Currently all the validation happens within those 3 child components and there's no need to store the result in a parent component. This prop is redundant._ -- `userId` - this new user ID is returned by child OrderReview component if a user registers a new account at checkout. It is then sent to the backend to bind an order to the user. -- `isValid` - this boolean computed property defines if an order can be placed. If there's any validation error within any child component or _stockCheckOK_ prop is not true, this returns false and order won't be placed. - -### Methods - -- `checkConnection (status)` - checks if there's an active internet connection. If not, fires a notification. - -#### Parameters - -_status_ - a boolean parameter that defines if there's an active internet connection. - -- `activateSection (sectionToActivate)` - sets _sectionToActivate_ named section in _activeSection_ prop object to true and all others to false. - -#### Parameters - -_sectionToActivate_ - a name of a section that needs to be activated. - -- `prepareOrder ()` - returns an order object that will be sent to the backend. - -- `placeOrder ()` - if _isValid_ prop is true dispatches `'checkout/placeOrder'` action which will place the order, otherwise fires a notification about the existence of validation errors. - -- `savePersonalDetails ()` - dispatches `'checkout/savePersonalDetails'` action which will save checkout personal details information (from _personalDetails_ prop) to the Vuex store. - -- `saveShippingDetails ()` - dispatches `'checkout/saveShippingDetails'` action which will save checkout shipping details information (from _shipping_ prop) to the Vuex store. - -- `savePaymentDetails ()` - dispatches `'checkout/savePaymentDetails'` action which will save checkout payment details information (from _payment_ prop) to the Vuex store. - -### Hooks - -#### created - -Defines several event listeners to communicate with child components. - -- **'network.status'** event listener receives internet connection status and calls _checkConnection_ method. -- **'checkout.personalDetails'** event listener receives personal details information from PersonalDetails child component and activates the next section of the Checkout page (which is shipping details). -- **'checkout.shipping'** event listener receives shipping details information from Shipping child component and activates next section of the Checkout page (which is payment details). -- **'checkout.payment'** event listener receives payment details information from Payment child component and activates next section of the Checkout page (which is order review). -- **'checkout.cartSummary'** - _this event listener is not called anywhere._ -- **'checkout.placeOrder'** the event listener is called by OrderReview child component. It has an optional _userId_ parameter that is passed to it in case user registers a new account at the checkout. With or without _userId_ this event listener calls _placeOrder_ method. -- **'checkout.edit'** event listener activates a section of the Checkout page, the name of which is passed to it in a parameter. -- **'order-after-placed'** event listener is called after placed order. - -#### beforeMount - -Checks if the cart is not empty. If it is, then a notification is fired. Otherwise, sets promises that will check the availability of the products from the cart and if they are all in stock. - -#### destroyed - -Removes all event listeners that were previously defined in `created` hook. - -## Compare - -### Props - -`title` - the title of the Compare page - -### Data - -- `attributesByCode` - a computed property that returns the list of all product attributes by their code. Gets its value from `'attribute/attributeListByCode'` Vuex store getter. -- `attributesById` - a computed property that returns the list of all product attributes by their Id. Gets its value from `'attribute/attributeListById'` Vuex store getter. _This prop is not used anywhere._ -- `items` - returns the list of products that were chosen for comparison from Vuex store. -- `all_comparable_attributes` - returns the subset of attributes from `attributesByCode` prop that have `is_comparable` property set to true. - -### Methods - -- `removeFromCompare (product)` - removes a given product from the compare list by dispatching `'compare/removeItem'` action. - -#### Parameters - -_product_ - a specific product to be removed. - -### Hooks - -#### created - -Dispatches `'compare/load'` action that loads the list of products to compare from localStorage into Vuex store. Also dispatches `'attribute/list'` action that loads all product attributes that have `is_user_defined` property set to true into Vuex store. - -## Home - -The home page has been refactored to the new core proposal (1.0RC) and the [docs has been moved](../components/home-page.md). - -## MyAccount - -### Props - -- `activeBlock` - currently active block displayed in component (i.e. newsletter preferences, shipping data or orders history) - -### Data - -- `navigation` - an object that contains names of sections of MyAccount page and anchor links to them. - -### Methods - -`notify (title)` - this is a temporary method that notifies the user if he presses on a link of a section that is not yet implemented. - -### Hooks - -#### created - -Defines several event listeners to communicate with child components. - -- **'myAccount.updateUser'** event listener receives filled out data from child components and dispatches `'user/update'` action to update user profile. It's called from PersonalDetails and ShippingDetails child components. -- **'myAccount.changePassword'** event listener receives updated authentication data from PersonalDetails child component and dispatches `'user/changePassword'` action. -- **'myAccount.updatePreferences'** event listener receives user's updated newsletter subscription preferences from MyNewsletter child component and updates them by dispatching `'user/updatePreferences'` action. - -#### mounted - -Checks if there's a user token in localStorage. If not, redirects user to the Home page. - -#### destroyed - -Removes all event listeners that were previously defined in `created` hook. - -## PageNotFound - -404 page - -### Props - -No props - -### Data - -No data - -### Methods - -No methods - -### Hooks - -#### asyncData - -Since the app is using SSR, this method prefetches and resolves the asynchronous data before rendering happens and saves it to Vuex store. Asynchronous data for PageNotFound page is a list of 8 random products that are called Bestsellers. - -## Product - -The Product page has been refactored to the new core proposal (1.0RC) and the [docs has been moved](../components/product.md). diff --git a/docs/guide/core-themes/core-components.md b/docs/guide/core-themes/core-components.md deleted file mode 100644 index 973a41d398..0000000000 --- a/docs/guide/core-themes/core-components.md +++ /dev/null @@ -1,72 +0,0 @@ -# Working with core components - -## Vue Storefront component types - -In Vue Storefront there are two types of components: - -- **Core components:** In core components, we implemented all the basic business logic for an eCommerce shop, so you don't need to write it from scratch by yourself. You can make use of them in your themes, where all you need to do is styling and creating the HTML markup. Every core component provides an interface to interact with. This interface can be extended or overwritten in your theme if you need to. Core components should be injected to themes as mixins.They contain only business logic—HTML markup and styling should be done in themes. You can usually find core components inside the `components` folder of every module. - -- **Theme components:** Theme components are what you really see in the app. They can inherit business logic from core components or be created as theme-specific components. All CSS, HTML, and ui-specific logic should be placed in theme. - -## Working with core components - -First, **override core components only when you're adding ui-agnostic features to the core.** The correct approach for using core components in your theme is thinking of them as an external API. You can inherit the functionalities and extend them in theme but never change it in core. - -**When you're modifying the core component, never change the component's API** (data and methods exposed by the component for themes). Such changes would break the theme using this core component. - -## Using core components in your theme - -### For components - -Inheritance by itself is done by [vue mixins](https://vuejs.org/v2/guide/mixins.html) with default merging strategy. - -To inherit from core component: - -1. **Create new component in your theme.** - -2. **Import the core component that you want to inherit from:** - -```js -import YourCoreComponent from '@vue-storefront/core/modules/{module_name}/YourCoreComponent'; -``` - -3. **Add the core components mixin to your newly created theme component:** - -```js -export default { - ... - mixins: [YourCoreComponent] -} -``` - -From now on, you can access and override all methods, data, and components from core component like it was declared in your own theme component. - -### For pages - -Inheritance in pages works exactly like in other components. The only difference is the importing alias. Instead of `core/components` we need to start with `core/pages` alias. - -```js -import YourCorePage from '@vue-storefront/core/pages/YourCorePage' - -export default { - ... - mixins: [YourCorePage] -} -``` - -Core pages are placed in `core/pages` folder. - -## Overriding and extending core components and pages - -Since core components are just plain JavaScript objects, you can easily modify them before mixing in your theme. - -```js -import YourCorePage from '@vue-storefront/core/pages/YourCorePage' - -YourCorePage.methods.foo = function () { Logger.log('Overrided method foo')() } - -export default { - ... - mixins: [YourCorePage] -} -``` diff --git a/docs/guide/core-themes/layouts.md b/docs/guide/core-themes/layouts.md deleted file mode 100644 index 492c122d23..0000000000 --- a/docs/guide/core-themes/layouts.md +++ /dev/null @@ -1,193 +0,0 @@ -# Layouts and advanced output operations - -Starting from version 1.4.0, Vue Storefront allows you to switch the HTML templates and layouts dynamically in the SSR mode. - -This feature can be very useful for non-standard rendering scenarios like: - -- Generating the XML output -- Generating the AMPHTML pages -- Generating widgets without `` section - -## How it works - -Before 1.4.0, Vue Storefront generated the output by a mix of: - -- Taking the base HTML template `src/index.template.html`, -- Rendering the `src/themes/default/App.vue` root component, -- Injecting the Vue SSR output into the template + adding CSS styles, script references etc. [Read more on Vue SSR Styles and Scripts injection](https://ssr.vuejs.org/guide/build-config.html#client-config) - -This mode is still in place and is enabled by default. What we've changed is that **you can now select which HTML template and layout your app is routing in per-route manner.** - -## Changelog - -The changes we’ve introduced include: - -- Distinct routes can now set `context.output.template` in `asyncData` method. By doing so, you can skip using `dist/index.html` (which contains typical HTML5 elements - like ``). This is important when we're going to generate either AMPHTML pages (that cannot contain any ` -``` - -The key part is: - -```js -contextserver.response.setHeader('Content-Type', 'text/xml'); -context.output.template = ''; -``` - -These two statements: - -- Set the HTTP header (by accessing ExpressJS response object— `contextserver.response`. There is also `contextserver.request` and `context.app` - the ExpressJS application)- set `output.template` to none, which will skip the HTML template rendering at all. - -### Switching off layout and injecting dynamic content - -Example URL: `http://localhost:3000/append-prepend.html` - -Route setup to switch the Vue layout: - -```js - { path: '/append-prepend.html', component: NoLayoutAppendPrependExample, meta: { layout: 'empty' } }, -``` - -Vue component to render the XML: - -```js - - - -``` - -The key part is: - -```js -context.output.template = ''; -context.output.append = context => { - return '
This content has been dynamically appended
'; -}; -context.output.prepend = context => { - return '
this content has been dynamically prepended
'; -}; -``` - -These two statements: - -- Set `output.template` to none, which will cause to skip the HTML template rendering at all. -- Add the `output.append` and `output.prepend` methods to the server context. - -The output will be generated with this logic: - -```js -const contentPrepend = - typeof context.output.prepend === 'function' - ? context.output.prepend(context) - : ''; -const contentAppend = - typeof context.output.append === 'function' - ? context.output.append(context) - : ''; -output = contentPrepend + output + contentAppend; -``` - -Please note that the `context` contains a lot of interesting features you can use to control the CSS, SCRIPT and META injection. [Read more on Vue SSR Styles and Scripts injection](https://ssr.vuejs.org/guide/build-config.html#client-config) - -**Note: [The context object = Vue.prototype.$ssrContext](https://ssr.vuejs.org/guide/head.html)** - - -## Output compression - -HTML Minifier has been added to Vue Storefront 1.11. To enable this feature please switch the `config.server.useHtmlMinifier`. You can set the specific configuration of the `htmlMinifier` using the `config.server.htmlMinifierOptions`. Read more on the [available configuration](https://www.npmjs.com/package/html-minifier). The minified output is then being cached by `SSR Output cache` mechanism. - -Output compression has been also enabled (if the `src/modules/server.ts` contains the `compression` module on the list). By default it works just for production builds. It uses the `gzip` compression by default. [Read more about the `compression` module](https://www.npmjs.com/package/compression) that we're using for this implementation. diff --git a/docs/guide/core-themes/plugins.md b/docs/guide/core-themes/plugins.md deleted file mode 100644 index d2eb51f207..0000000000 --- a/docs/guide/core-themes/plugins.md +++ /dev/null @@ -1,23 +0,0 @@ -# Working with plugins - -If you want to register a Vue plugin in Vue Storefront, the right place to do this is your theme entry `{theme}/index.js`. - -````js -// {theme}/index.js -import SomePlugin from 'some-plugin' - -Vue.use(SomePlugin) -```` - -## Dealing with server-side code - -Vue Storefront code is actually executed two times: once on the server side to generate an SSR page and then once on the client side. A majority of Vue plugins are ui-specific and rely on the `window` object that doesn't exist on the server side. - -To make sure your plugin is registered only on the client side, you can use `isServer` helper. - -````js -import { isServer } from '@vue-storefront/core/helpers' -import SomePlugin from 'some-plugin' - -if (!isServer ) Vue.use(SomePlugin) -```` diff --git a/docs/guide/core-themes/service-workers.md b/docs/guide/core-themes/service-workers.md deleted file mode 100644 index 7405914738..0000000000 --- a/docs/guide/core-themes/service-workers.md +++ /dev/null @@ -1,62 +0,0 @@ -# Working with Service Workers - -We're using Service Workers for two main purposes: - -1. To cache out static and dynamic data feeds, to make them [available offline](https://developers.google.com/web/fundamentals/primers/service-workers/) -2. To run offline data sync. - -To achieve the first point, we're using [sw-precache](https://github.com/GoogleChromeLabs/sw-precache) from Google, and for the second, Vanilla JS with a little help from [sw-toolbox](https://www.google.pl/search?q=sw-toolbox&oq=sw-toolbox&aqs=chrome..69i57j69i60l3j0l2.1529j0j4&sourceid=chrome&ie=UTF-8) - -## Making things happen - -The service-worker source code for `vue-storefront` is pre-compiled with Babel presets and all is stored in an additional theme-specific Service Worker in `src/{themename}/service-worker/index.js`. This file is attached to `service-worker.js` generated by `sw-toolbox`. - -After changing anything in `{themename}/service-worker/index.js`, despite you're in `yarn dev` auto reloading mode, you need to do two things: - -1. Recompile app (which regenerates service-worker): - `yarn build` - -2. Reload Service Worker in Dev Tools (in Chrome, just click **"Unregister"** and reload the page, and a new Service Worker will be installed). - - -![How to work with service-workers in Chrome](../images/chrome-dev-console.png) - -## Communication with the app - -The application can speak to the Service Worker using the event bus, and only doing so. Please take a look at `/core/lib/sw.js` where we have the following method: - -```js -export function postMessage(payload) { - if ('serviceWorker' in navigator && navigator.serviceWorker.controller) { - // check if it's properly installed - navigator.serviceWorker.controller.postMessage(payload); - return false; - } else { - // no service workers supported push the queue manualy - return true; - } -} -``` - -It allows you to send data to the Service Worker. For example, when the order is placed (`/core/store/modules/checkout`): - -```js - /** - * Add order to sync. queue - * @param {Object} product data format for products is described in /doc/ElasticSearch data formats.md - */ - [types.CHECKOUT_PLACE_ORDER] (state, order) { - const ordersCollection = StorageManager.get('orders') - const orderId = entities.uniqueEntityId(order) // timestamp as a order id is not the best we can do but it's enough - order.id = orderId.toString() - order.transmited = false - order.created_at = new Date() - order.updated_at = new Date() - - ordersCollection.setItem(orderId.toString(), order).catch((reason) => { - console.error(reason) // it doesn't work on SSR - sw.postMessage({ config: config, command: types.CHECKOUT_PROCESS_QUEUE }) // process checkout queue - console.info('Order placed, orderId = ' + orderId) - }) // populate cache - }, -``` diff --git a/docs/guide/core-themes/stylesheets.md b/docs/guide/core-themes/stylesheets.md deleted file mode 100644 index c6ea0f576b..0000000000 --- a/docs/guide/core-themes/stylesheets.md +++ /dev/null @@ -1,186 +0,0 @@ -# Working with stylesheets in the 'default' theme - -The default theme’s CSS is designed to be easily maintainable, which implies the smallest possible files footprint. We are using flexbox and reusable atomic classes to prevent CSS files’ rapid growth. We're trying to avoid nesting CSS classes (maximum nesting level is 1) to make them easier to understand and debug. - -All (S)CSS files should be placed in the `src/themes/{theme_name}/css` folder. - -All atomic classes should be created on demand (e.g. we're creating `pt-20` class for `padding-top: 20px` only when we need to use it). Following that rule will help you avoid unused CSS. - -In your own themes other than `default` you don't need to follow our technology stack and conventions. All we require is the correct path to the CSS folder (`{theme_name}/css`) - -## Technology Stack - -1. We are using [SASS](http://sass-lang.com/) as CSS preprocessors. -2. [Flexbox Grid](http://flexboxgrid.com/) for layout. -3. Atomic CSS convention for all repeatable CSS properties - margins, paddings, colors, text properties and borders. ([see some nice introduction to Atomic CSS](https://www.lucidchart.com/techblog/2014/01/31/atomic-css-tool-set/)). - -## How to style UI elements in Vue Storefront - -Let's say we have a small piece of UI and want to style it according to mocks provided by our graphic designer. - -```html -
Hello Vuers!
-``` - -According to mocks the above `
`should have 20px padding top and bottom (y-axis), 10px padding left and right (x-axis), a black background, white text color, and display inline-flex. - -Let's check the [CSS folder](https://github.com/vuestorefront/vue-storefront/tree/master/src/themes/default/css) and find the required classes. - -For padding top/bottom there is `py-x` class where `py` means 'padding y-axis' and `x` is a size in px. We will use `py-20` in this case. Same with x-axis - we will use `px-10`. - -For colors, we will use `c-white` for `color: white` property and `bg-black` for `background-color: black`. - -Now our `
` should look like this: - -```html -
Hello Vuers!
-``` - -The `display` properties aren't commonly used across the project, therefore, they are not handled by atomic classes. We can add properties like this in a "normal" way, via a class in the component's ` -``` - -## Extending (S)CSS files - -If you can't find a CSS rule that you need, please read the rules below before extending current stylesheets: - -1. Don't create new files in `default/css/` folder. If you think something is missing, please contact us on [slack](http://vuestorefront.slack.com) before creating a new file. -2. Follow the conventions! Usually, you can find arrays in the top of each SCSS file with sizes or colors and loops below them that iterates through these arrays to generate CSS classes. Please see the [margin.scss](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/css/margin.scss) file. - -On the top you can find arrays of sizes (in px and %): - -```SCSS - - // Pixels - - $margin-px: 0 5 10; - $margin-x-px: 10; - $margin-y-px: 0 5 10; - $margin-top-px: 0 6 10 15 25 50; - $margin-bottom-px: 0 5 10 15 20 25 30 35; - $margin-left-px: 10 15 30; - $margin-right-px: 0 5 10 15; - - // Percents - - $margin-p: 5; - $margin-x-p: 5; - $margin-y-p: 5; - $margin-top-p: 5; - $margin-bottom-p: 5; - $margin-left-p: 5; - $margin-right-p: 5; -``` - -Below you can see how they are used to generate Atomic CSS classes: - -```SCSS - // Generators - - @mixin margin { - @each $i in $margin-px { - .m#{$i} { - margin: #{$i}px; - } - } - @each $i in $margin-p { - .m#{$i}p { - margin: percentage($i/100); - } - } - - @each $i in $margin-x-px { - .mx#{$i} { - margin-left: #{$i}px; - margin-right: #{$i}px; - } - } - @each $i in $margin-x-p { - .mx#{$i}p { - margin-left: percentage($i/100); - margin-right: percentage($i/100); - } - } - - @each $i in $margin-y-px { - .my#{$i} { - margin-top: #{$i}px; - margin-bottom: #{$i}px; - } - } - @each $i in $margin-y-p { - .my#{$i}p { - margin-top: percentage($i/100); - margin-bottom: percentage($i/100); - } - } - - @each $i in $margin-top-px { - .mt#{$i} { - margin-top: #{$i}px; - } - } - @each $i in $margin-top-p { - .mt#{$i}p { - margin-top: percentage($i/100); - } - } - - @each $i in $margin-bottom-px { - .mb#{$i} { - margin-bottom: #{$i}px; - } - } - @each $i in $margin-bottom-p { - .mb#{$i}p { - margin-bottom: percentage($i/100); - } - } - - @each $i in $margin-left-px { - .ml#{$i} { - margin-left: #{$i}px; - } - } - @each $i in $margin-left-p { - .ml#{$i}p { - margin-left: percentage($i/100); - } - } - - @each $i in $margin-right-px { - .mr#{$i} { - margin-right: #{$i}px; - } - } - @each $i in $margin-right-p { - .mr#{$i}p { - margin-right: percentage($i/100); - } - } - } -``` - -Let's say you need a class for 55px top margin that isn't available now. In this case, you just need to add a `55` value to `$margin-top-px` array. - -```SCSS -$margin-top-px: 0 6 10 15 25 50; -``` - -After your changes it should look like this: - -```SCSS -$margin-top-px: 0 6 10 15 25 50 55; -``` diff --git a/docs/guide/core-themes/themes.md b/docs/guide/core-themes/themes.md deleted file mode 100644 index 8b14a37880..0000000000 --- a/docs/guide/core-themes/themes.md +++ /dev/null @@ -1,65 +0,0 @@ -# Themes in Vue Storefront - -:::tip REMINDER -This guide is based on `default` theme before `1.12` version release. General idea and structure addressed here were created based on the pre-`1.12` `default` theme. -::: - - -Vue Storefront allows you to quickly develop your own themes and use our core business logic. All e-commerce features are implemented in core, so you can easily develop fully working online shop only by writing HTML and CSS and inheriting the business logic from the core. Of course, you can easily modify and extend the core logic in your theme. - -You can read more about Vue Storefront core components and how to make use of them [here](core-components.md) - -All themes are located in `src/themes` folder and you can think about them as separate Vue.js applications that are using Vue Storefront core for out-of-the-box features. - -## Switching themes - -To use any of the themes located in `src/themes`, just change the `theme` property in your config file to `name` property from package.json file sitting in your theme's root dir. Config files are located in the `config` folder. You shouldn't make changes in `config/default.json`. Instead just copy the `default.json` file to the same folder, name it `local.json` and make changes there. - -## Creating your own themes - -To create your own Vue Storefront theme, you can copy and modify the default theme which is fully-styled and ready to work out of the box (it's the one that you can find on our demo). - -To do so: - -1. Copy the `default` folder located in `src/themes` and change its name to your new theme's name. -2. Change the `name` property in your theme's `package.json` file. -3. Insert this name in the `theme` property of your config file in `config/local.json`. -4. Run `yarn install` so _lerna_ can detect a new theme. -5. Start developing your own theme for Vue Storefront! - -Only official themes tested and accepted by the community should be in a `master` branch. Please develop your own themes on separate branches and keep them updated with `master` to be sure it works with the newest core. - -## Important theme files - -Each theme is a separate Vue.js application with its own dependencies, which can make use of the core or even modify it. -Below you can find the list of files that are essential for your theme to work: - -- `assets` - theme-specific assets -- `components` - theme-specific components -- `css` - theme-specific css files -- `helpers` - helper methods -- `layouts` - layout files -- `mixins` - theme-specific mixins (extends `core/mixins`) - - `index.js` - here you can register your theme-specific mixins -- `pages` - your shop pages -- `resource` - theme-specific resources (extends `core/resource`) -- `router` - theme router -- `store` - theme-specific stores (extends `core/store`) - - `ui-store.js` - here you can extend core `ui-store` - - `index.js` - here you can register theme-specific stores -- `App.vue` - theme's entry component -- `index.js` - theme initialization -- `package.json` - theme-specific dependencies -- `service-worker` - - `index.js` you can extend core service worker here (see [Working with Service Workers](service-workers.md) -- `webpack.config.js` - you can extend core webpack build in this file (extends `core/build/`, see [Working with webpack](webpack.md)) - -## Official Vue Storefront themes: - -- [Capybara](https://github.com/vuestorefront/vsf-capybara) - Capybara is a Storefront UI based theme for Vue Storefront. -- [Default](https://github.com/vuestorefront/vsf-default) - Default VS theme always with the newest features. The easiest way to adopt VS in your shop is taking this one and modifying it to your needs (check [gogetgold.com](https://www.gogetgold.com/) as an example) - -## Related - -- [Working with components](core-components.md) -- [Creating themes in Vue Storefront — backend-agnostic eCommerce PWA frontend (part 1  - understanding Vue Storefront core)](https://medium.com/@frakowski/developing-themes-in-vue-storefront-backend-agnostic-ecommerce-pwa-frontend-part-1-72ea3c939593) diff --git a/docs/guide/core-themes/translations.md b/docs/guide/core-themes/translations.md deleted file mode 100644 index 780a1ca1b5..0000000000 --- a/docs/guide/core-themes/translations.md +++ /dev/null @@ -1,54 +0,0 @@ -# Internationalization (i18n) of Vue Storefront - -Vue Storefront allows you to translate the whole UI using the powerful [vue-i18n](http://kazupon.github.io/vue-i18n/) library. - -Please be aware of i18n issues while writing your own themes/extensions and keep the i18n support in mind, especially when creating Pull Requests to the core. - -## Using i18n in code - -When you're working with a plain JS module, you can simply use the translation helper: - -```js -import i18n from '@vue-storefront/i18n'; -EventBus.$emit('notification', { - type: 'success', - message: i18n.t('Product has been added to the cart!'), - action1: { label: i18n.t('OK'), action: 'close' }, -}); -``` - -If you're working with `.vue` components the matter is even simpler with Vue directive `$t`: - -```html - - {{ $t('Size guide') }} - -``` - -For all helper methods and directives, along with available parameters, please do check the [vue-i18n documentation](http://kazupon.github.io/vue-i18n/introduction.html). - -## Working with translations - -Translations are provided in `core/i18n/resource/i18n/en-US.csv` file and can be extended / overridden in `src/themes/{themename}/resource/i18n/en-US.csv` accordingly. - -Here's an example of `en-US.csv` for `en-US` locale: - -```csv -"customMessage","You can define or override translation messages here." -"welcomeMessage", "Welcome to Vue Storefront theme starter!", -"In case of any problems please take a look at the docs. If you haven't found what you were looking for in docs feel free to ask your question on our Slack", "In case of any problems please take a look at the docs. If you haven't found what you were looking for in docs feel free to ask your question on our Slack", -"Here are some links that can help you with developing your own theme", "Here are some links that can help you with developing your own theme", -"Project structure", "Project structure", -"Working with themes", "Working with themes", -"Working with components", "Working with components", -"Working with data", "Working with data", -"Vue Storefront Logo", "Vue Storefront Logo" -``` - -When you create the `en-US.csv` file within your `src/themes/{themename}/resource/i18n/` folder and override some messages like: - -```csv -"customMessage","You can define or override translation messages here." -``` - -... you may expect that `$t('customMessage)` will return `You can define or override translation messages here.` instead of `Here is the core message. that can be overwritten in the theme`. As simple as that! :) diff --git a/docs/guide/core-themes/ui-store.md b/docs/guide/core-themes/ui-store.md deleted file mode 100644 index 57fd1ebed5..0000000000 --- a/docs/guide/core-themes/ui-store.md +++ /dev/null @@ -1,16 +0,0 @@ -# Working with UI Store (Interface state) - -We are using Vuex to store the application interface state. The [store file](https://github.com/vuestorefront/vue-storefront/blob/master/core/store/index.ts) contains the information about the state of different pieces of UI, such as overlay visibility, wishlist visibility, etc. Of course, you are not forced to make use of it in your theme, but keep in mind that many core components are using the UI store. - -## State object - -- `sidebar` - visible/hidden state of sidebar menu (find: `SidebarMenu.vue`) -- `microcart` - visible/hidden state of microcart (find: `Microcart.vue`) -- `wishlist` - visible/hidden state of wishlist (find: `Wishlist.vue`) -- `searchpanel` - visible/hidden state of search panel (find: `SearchPanel.vue`) -- `newsletterPopup` - visible/hidden state of newsletter popup (_will be removed from Vuex store_) -- `overlay` - visible/hidden state of overlay (find: `Overlay.vue`) -- `loader` - visible/hidden state of loader (find: `Loader.vue`) -- `authElem` - component to be displayed at Auth popup (will be changed and moved only to this component) -- `checkoutMode` - determines whether the user is in checkout or not—useful when you want to change some UI elements or behavior only on checkout (e.g. hide footer) -- `openMyAccount` - determines whether to redirect user to My Account page. Used when user clicked on My Account link in the sidebar, but had to login first. After successful logging in, user will be automatically redirected to My Account page. diff --git a/docs/guide/core-themes/webpack.md b/docs/guide/core-themes/webpack.md deleted file mode 100644 index 025e5a87bd..0000000000 --- a/docs/guide/core-themes/webpack.md +++ /dev/null @@ -1,64 +0,0 @@ -# Working with Webpack - -To make Vue Storefront fast and developer-friendly, we use webpack under the hood. We need it to transpile assets, handle `.vue` files, process all styles, and make our code a little more maintainable with linting provided by eslint. With that, you don't need to worry about configuring it by hand to start working on Vue Storefront or to build your own theme for it. However, when you want to tweak it to your special needs, there is also a possibility to do that with extendable webpack configuration for each theme. - -## Core webpack build - -All build scripts used by the core of Vue Storefront are available in `core/build` directory. If you want to improve our build or add support for new cases, you will probably only need to change files there and sometimes update `package.json`. - -Base config for client and server is set up in `webpack.base.config.js`. This configuration is then merged with specific client and server configs in `webpack.client.config.js` and `webpack.server.config.js`. - -For development mode (`yarn dev`) `dev-server.js` file is used to run previously mentioned config files (`webpack.client.config.js`, `webpack.server.config.js`) with custom config provided by the theme. We use `webpack-dev-middleware` and `webpack-hot-middleware` to make website development as fast and as easy as possible. - -In `vue-loader.config.js` the whole configuration for `vue-loader` is stored. If there is a need to change style processing for single-file components, you can set it up in this file (if you want to extend the Vue Storefront core). - -To build a production version of Vue Storefront `webpack.prod.client.config.js` and `webpack.prod.server.config.js` are used with a `build` script. In these files, our base configuration is merged with theme-specific extended config. - -## Extending core build in themes - -Vue Storefront follows technique popularized by [next.js](https://github.com/zeit/next.js/) and [Nuxt](https://nuxtjs.org/) for extending webpack config. For each theme, you can configure the `webpack.config.js` file that will allow you to have access to base configuration and customize it for your needs without changing core build files. - - -### Example - -Below is a simple example that adds `webpack-bundle-analyzer` to check generated webpack bundles. In addition to analyzer, `json5-loader` is used to handle JSON5 files `json5-loader` in the project. - -```js -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; - -module.exports = function(config, { isClient, isDev }) { - let configLoaders; - if (isClient) { - configLoaders = config.module.rules; - config.plugins.push( - new BundleAnalyzerPlugin({ - openAnalyzer: false, - statsFilename: 'test', - generateStatsFile: true, - analyzerMode: 'static', - }), - ); - } else { - configLoaders = config.module.rules; - } - configLoaders.push({ - test: /\.json5$/, - loader: 'json5-loader', - }); - return config; -}; -``` - -This file should export a function that returns a complete configuration. -This function is executed with two arguments. First is the complete core Vue Storefront webpack configuration. Second is an object that has properties: `isClient` and `isDev`. - -Option `isClient` indicates that the configuration is for client bundle. - -Option `isDev` is set to `true` if Vue Storefront runs in development mode. - -In the case of client build (`isClient == true`), config argument is an array with two elements. The first array element is the client configuration and the second one is used to generate a service worker file. - -For server build (`isClient == false`), config argument is a standard webpack configuration object. - -All loaders and plugins used in extended configuration will be fetched from the theme `node_modules` directory, so make sure you have it saved in the theme `package.json` file. diff --git a/docs/guide/data-resolvers/category-service.md b/docs/guide/data-resolvers/category-service.md deleted file mode 100644 index d85e474377..0000000000 --- a/docs/guide/data-resolvers/category-service.md +++ /dev/null @@ -1,7 +0,0 @@ -# CategoryService - -## Methods - -#### `getCategories (serachOptions: DataResolver.CategorySearchOptions) => Promise` - -It fetches categories by given parameters. If the `config.entities.optimize` is enabled, the `includeFields` and `excludeFields` are set accordingly to `config.entities.category.includeFields` and `config.entities.category.excludeFields`. diff --git a/docs/guide/data-resolvers/introduction.md b/docs/guide/data-resolvers/introduction.md deleted file mode 100644 index 12e1f9b5b1..0000000000 --- a/docs/guide/data-resolvers/introduction.md +++ /dev/null @@ -1,41 +0,0 @@ -# Introduction - -## What are the data resolvers? - -The `data resolvers` are the way of manage the network/api calls and split them from the rest of application. All of available `data resolvers` you can find in the `core/data-resolver` directory. -If you want to trigger a network call, you should create a new `data resolver`, and import it in the place where it's needed. - -## How to create a data resolver -First of all, please create a type for it under the namespace `DataResolver`, then just create a new data resolver like this example below: - - -```js -import { DataResolver } from './types/DataResolver'; -import { TaskQueue } from '@vue-storefront/core/lib/sync' -import Task from '@vue-storefront/core/lib/sync/types/Task' - -const headers = { - 'Accept': 'application/json, text/plain, */*', - 'Content-Type': 'application/json' -} - -const myNewNetworkCall = async (data: string): Promise => - TaskQueue.execute({ - url: processLocalizedURLAddress(/* some endpoint */), - payload: { - method: 'POST', - mode: 'cors', - headers, - body: JSON.stringify({ data }) - } - }) - -export const YourService: DataResolver.YourService = { - myNewNetworkCall, -} -``` - -## Available data resolvers - -- [CategoryService](category-service.md) -- [UserService](user-service.md) diff --git a/docs/guide/data-resolvers/user-service.md b/docs/guide/data-resolvers/user-service.md deleted file mode 100644 index ba178aca23..0000000000 --- a/docs/guide/data-resolvers/user-service.md +++ /dev/null @@ -1,35 +0,0 @@ -# UserService - -## Methods - -#### `resetPassword: (email: string) => Promise` - -It resets the user password by given `email`. - -#### `login: (username: string, password: string) => Promise` - -It try to login user by given `username` and `password` - -#### `register: (customer: DataResolver.Customer, pssword: string) => Promise` - -Registering the new user by given user data (`customer`) and `password`. - -#### `updateProfile: (userProfile: UserProfile) => Promise` - -It updates the current logged user profile (`userProfile`). - -#### `getProfile: () => Promise` - -It fetches the current logged user profile. - -#### `getOrdersHistory: () => Promise` - -It fetches order history by current logged user. - -#### `changePassword: (passwordData: PasswordData) => Promise` - -It changes the password for current logged user. - -#### `refreshToken: (refreshToken: string) => Promise` - -It refreshes the token for current user session by given `refreshToken`. diff --git a/docs/guide/data/data-loader.md b/docs/guide/data/data-loader.md deleted file mode 100644 index 4d82bbc978..0000000000 --- a/docs/guide/data/data-loader.md +++ /dev/null @@ -1,76 +0,0 @@ -# Async Data Loader - -Starting with Vue Storefront 1.8, there is a new, experimental API for extending server-side data fetching. The library is called `AsyncDataLoader` and consists of two methods: `push` , which is used to enqueue new data-fetching promises and `flush`, which is used to execute all the enqueued promises. - -The server-side rendering feature of Vue Storefront has been designed according to general [Vue.js SSR principles](https://vuejs.org/v2/guide/ssr.html). Each root-level page (which is assigned to a route) consists of a special `asyncData` method which is executed before any component is created, just to fill the Vuex state with all the necessary data. - -The pre-fetched Vuex state is then provided within the `window.__INITIAL_STATE__` object to the client to hydrate the client side data. - -Because `asyncData` methods are centralized (one per route), it was not possible to inject any data pre-fetching method from within any module / custom code either theme added to Vue Storefront. If you've created your own version of `Product.vue` or `Category.vue` page in the theme, you could have changed `asyncData` but that's all. - -So there we have the AsyncLoader 😃 - -## Examples - -Take a look at the `src/module-template/hooks/beforeRegistration` for an example: - -```js -import { AsyncDataLoader } from '@vue-storefront/core/lib/async-data-loader' - AsyncDataLoader.push({ // this is an example showing how to call data loader from another module - execute: ({ route, store, context }) => { - return new Promise ((resolve, reject) => { - store.dispatch('example/dataloader').then((results) => { - resolve(results) - }) - }) - } - }) -``` - -That's all! The action enqueued in here will be executed with every SSR request and the `store.state.exampleDataFetchedByLoader` will be attached to the `window.__INITIAL_STATE__.exampleDataFetchedByLoader`so your data will be accessible in the SSR mode. - -You can selectively execute the fetching logic by checking the `route` or `context` objects provided: - -```js -import { AsyncDataLoader } from '@vue-storefront/core/lib/async-data-loader' - AsyncDataLoader.push({ // this is an example showing how to call data loader from another module - execute: ({ route, store, context }) => { - return new Promise ((resolve, reject) => { - if (route.name === 'bundle-product') { - store.dispatch('example/dataloader').then((results) => { - resolve(results) - }) - } else { - resolve(null) - } - }) - } - }) -``` - -The `context` object is a `Vue.prototype.$ssrContext` and it equals to `context` object passed to `asyncData` methods: - -```js - const context = { - url: req.url, - output: { - prepend: (context) => { return '' }, // these functions can be replaced in the Vue components to append or prepend some content AFTER all other things are rendered. So in this function You may call: output.prepend() { return context.renderStyles() } to attach styles - append: (context) => { return '' }, - appendHead: (context) => { return '' }, - template: 'default', - cacheTags: null - }, - server: { - app: app, - response: res, - request: req - }, - meta: null, - vs: { - config: config, - storeCode: req.header('x-vs-store-code') ? req.header('x-vs-store-code') : process.env.STORE_CODE - } - } - ``` - - As You might see by the context object Your modules have the access to `Express.js` app (thru `context.server`)! diff --git a/docs/guide/data/data-migrations.md b/docs/guide/data/data-migrations.md deleted file mode 100644 index 14019fefd2..0000000000 --- a/docs/guide/data/data-migrations.md +++ /dev/null @@ -1,69 +0,0 @@ -# Data Migrations for Elacticsearch - -Vue Storefront uses Elasticsearch as a primary data store. We're using Redis as a cache layer and Kue for queue processing. - -Although all of these data stores are basically schema-free, some mappings and meta data should be used for setting ES indices and so forth. - -Vue Storefront uses a data-migration mechanism based on [node-migrate](https://github.com/tj/node-migrate). - -## Migration tool - -We use node-migrate, which is pre-configured with npm, so we're using the following alias: - -```bash -yarn migrate -``` - -which runs the migrations against `migrations` folder. - -## How to add new migration? - -You can add a new migration by simply adding a file to the `migrations` directory (not recommended) or using the command line tool: - -```bash -yarn migrate create name-of-my-migration -``` - -The tool automatically generates the file under the `migrations` folder. - -## Examples - -The example migrations show how to manipulate products and mappings. Let's take a look at the mapping modification: - -```js -// Migration scripts use: https://github.com/tj/node-migrate -'use strict'; - -let config = require('config'); -let common = require('./.common'); - -module.exports.up = function(next) { - // example of adding a field to the schema - // other examples: https://stackoverflow.com/questions/22325708/elasticsearch-create-index-with-mappings-using-javascript, - common.db.indices - .putMapping({ - index: config.elasticsearch.indices[0], - type: 'product', - body: { - properties: { - slug: { type: 'string' }, // add slug field - suggest: { - type: 'completion', - analyzer: 'simple', - search_analyzer: 'simple', - }, - }, - }, - }) - .then(res => { - console.dir(res, { depth: null, colors: true }); - next(); - }); -}; - -module.exports.down = function(next) { - next(); -}; -``` - -... and that's it :) diff --git a/docs/guide/data/data.md b/docs/guide/data/data.md deleted file mode 100644 index c20604459f..0000000000 --- a/docs/guide/data/data.md +++ /dev/null @@ -1,247 +0,0 @@ -# Working with data - -Vue storefront uses two primary data sources: - -1. IndexedDb/WebSQL data store in the browser using [localForage](https://github.com/localForage/localForage) -2. Server data source via [vue-storefront-api](https://github.com/vuestorefront/vue-storefront-api), which is compliant with Elasticsearch (regarding product catalog). - -## Local data store - -You can access localForage repositories through the `StorageManager` (`@vue-storefront/core/lib/storage-manager`) object anywhere in the code, BUT all data-related operations ___should___ be placed in Vuex stores. - -Details on localForage API can be found [here](http://localforage.github.io/localForage/) - -## Example Vuex store - -Here you have an example of how the _Vuex_ store in a _Vue Storefront Module_ should be constructed. The _VSF Core Module_ itself is nothing more than [_Vuex module_](https://vuex.vuejs.org/guide/modules.html). - -Let's take a look at `store` of `core/modules/checkout` module. [`index.ts`](https://github.com/vuestorefront/vue-storefront/blob/master/core/modules/checkout/store/checkout/index.ts) file shows as follows : - -```js -import { Module } from 'vuex' -import actions from './actions' -import getters from './getters' -import mutations from './mutations' -import RootState from '@vue-storefront/core/types/RootState' -import CheckoutState from '../../types/CheckoutState' -import config from 'config' - -export const checkoutModule: Module = { - namespaced: true, - state: { - order: {}, - paymentMethods: [], - shippingMethods: config.shipping.methods, - personalDetails: { - firstName: '', - lastName: '', - emailAddress: '', - password: '', - createAccount: false - }, - shippingDetails: { - firstName: '', - lastName: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - shippingMethod: '' - }, - paymentDetails: { - firstName: '', - lastName: '', - company: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - taxId: '', - paymentMethod: '', - paymentMethodAdditional: {} - }, - isThankYouPage: false, - modifiedAt: 0 - }, - getters, - actions, - mutations -} - -``` - -The actual parts of the _store_ have been separated into several files as _getters_, _mutations_, and _actions_. Parts are assembled here and exported as a _Module_. - - -## Data formats & validation - -Data formats for vue-storefront and vue-storefront-api are the same JSON files. - -The convention is that schemas are stored under `/core/modules//store/.schema.json` - for example [Order schema](https://github.com/vuestorefront/vue-storefront/blob/master/core/modules/order/store/order.schema.json). - - -### Orders - -`Orders` repository stores all orders transmitted and to be transmitted (aka order queue) used by the Service Worker. - -![Orders data format as seen on Developers Tools](../images/orders-localstorage.png) - -Here you have a [validation schema for order](https://github.com/vuestorefront/vue-storefront/blob/master/core/store/modules/order/order.schema.json): - -```json -{ - "order_id": "123456789", - "created_at": "2017-09-28 12:00:00", - "updated_at": "2017-09-28 12:00:00", - "transmited_at": "2017-09-28 12:00:00", - "transmited": false, - "products": [ - { - "sku": "product_dynamic_1", - "qty": 1, - "name": "Product one", - "price": 19, - "product_type": "simple" - }, - { - "sku": "product_dynamic_2", - "qty": 1, - "name": "Product two", - "price": 54, - "product_type": "simple" - } - ], - "addressInformation": { - "shippingAddress": { - "region": "MH", - "region_id": 0, - "country_id": "PL", - "street": ["Street name line no 1", "Street name line no 2"], - "company": "Company name", - "telephone": "123123123", - "postcode": "00123", - "city": "Cityname", - "firstname": "John ", - "lastname": "Doe", - "email": "john@doe.com", - "region_code": "MH", - "sameAsBilling": 1 - }, - "billingAddress": { - "region": "MH", - "region_id": 0, - "country_id": "PL", - "street": ["Street name line no 1", "Street name line no 2"], - "company": "abc", - "telephone": "1111111", - "postcode": "00123", - "city": "Mumbai", - "firstname": "Sameer", - "lastname": "Sawant", - "email": "john@doe.com", - "prefix": "address_", - "region_code": "MH" - }, - "shipping_method_code": "flatrate", - "shipping_carrier_code": "flatrate", - "payment_method_code": "cashondelivery", - "payment_method_additional": {} // Payment Method Payload (eg, stripe token) - } -} -``` - -### Categories - -`Categories` is a hash organized by category 'slug' (for example, for the category with name = 'Example category', the slug is 'example-category'). - -![Categories data format as seen on Developers Tools](../images/categories-localstorage.png) - -If the category has any child categories, you have access to them via the `children_data` property. - -```json -{ - "id": 13, - "parent_id": 11, - "name": "Bottoms", - "is_active": true, - "position": 2, - "level": 3, - "product_count": 0, - "children_data": [ - { - "id": 18, - "parent_id": 13, - "name": "Pants", - "is_active": true, - "position": 1, - "level": 4, - "product_count": 156, - "children_data": [] - }, - { - "id": 19, - "parent_id": 13, - "name": "Shorts", - "is_active": true, - "position": 2, - "level": 4, - "product_count": 148, - "children_data": [] - } - ], - "tsk": 1505573191094 -} -``` - -### Carts - -`Carts` is a store for a shopping cart with a default key `current-cart` representing a current shopping cart. Cart object is an array consisting of products with an additional field `qty` in the case when two or more items are ordered. - -![Carts data format as seen on Developers Tools](../images/cart-localstorage.png) - -```json -[ - { - "id": 26, - "qty": 5, - "sku": "24-WG081-blue", - "name": "Sprite Stasis Ball 55 cm", - "attribute_set_id": 12, - "price": 23, - "status": 1, - "visibility": 1, - "type_id": "simple", - "created_at": "2017-09-16 13:46:48", - "updated_at": "2017-09-16 13:46:48", - "extension_attributes": [], - "product_links": [], - "tier_prices": [], - "custom_attributes": null, - "category": [], - "tsk": 1505573582376, - "description": "

The Sprite Stasis Ball gives you the toned abs, sides, and back you want by amping up your core workout. With bright colors and a burst-resistant design, it's a must-have for every hard-core exercise addict. Use for abdominal conditioning, balance training, yoga, or even physical therapy.

  • Durable, burst-resistant design.
  • Hand pump included.
", - "image": "/l/u/luma-stability-ball.jpg", - "small_image": "/l/u/luma-stability-ball.jpg", - "thumbnail": "/l/u/luma-stability-ball.jpg", - "color": "50", - "options_container": "container2", - "required_options": "0", - "has_options": "0", - "url_key": "sprite-stasis-ball-55-cm-blue", - "tax_class_id": "2", - "activity": "8,11", - "material": "44", - "gender": "80,81,82,83,84", - "category_gear": "87", - "size": "91" - } -] -``` diff --git a/docs/guide/data/database-tool.md b/docs/guide/data/database-tool.md deleted file mode 100644 index cd8c9ad65e..0000000000 --- a/docs/guide/data/database-tool.md +++ /dev/null @@ -1,76 +0,0 @@ -# Database tool - -Vue Storefront gets all of its data from [vue-storefront-api](https://github.com/vuestorefront/vue-storefront-api) endpoints, operating on top of the Elasticsearch data store. - -If you installed the project using `yarn installer` command, the database has been set up, data imported from demo-dump, and everything should be just fine. - -After more extensive data operations, like custom imports using [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) or [magento1-vsbridge](https://github.com/divanteLtd/magento1-vsbridge), there is a need to reindex the Elasticsearch and set up the proper metadata for fields. - - -The main reason you’ll know you must reindex the database is the following error you get from the `vue-storefront` console: - -```json -Error: {"root_cause":[{"type":"illegal_argument_exception","reason":"Fielddata is disabled on text fields by default. Set fielddata=true on [created_at] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead."}],"type":"search_phase_execution_exception","reason":"all shards failed","phase":"query","grouped":true,"failed_shards":[{"shard":0,"index":"vue_storefront_catalog_1521776807","node":"xIOeZW2lTwaprGXh6YLyCA","reason":{"type":"illegal_argument_exception","reason":"Fielddata is disabled on text fields by default. Set fielddata=true on [created_at] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead."}}]} -``` - -In this case, there is a db tool inside your local `vue-storefront-api` installation to the rescue. - -## Re-indexing an existing database - -Please go to `vue-storefront-api` directory and run: - -```bash -yarn db rebuild -``` - -This command will: - -- Reindex your currently set (in the `config/local.json` config file) Elasticsearch index to temp-one. -- Put the right Elasticsearch mappings on top of the temp index. -- Drop the original index. -- Create the alias with original name to the temp one so you can use the original name without any reference changes. - - -You can specify different (than this set in `config/local.json`) index name by running: - -```bash -yarn db rebuild -- --indexName=custom_index_name -``` - -## Creating the new index - -If you want to create a new, empty index please run: - -```bash -yarn db new -``` - -This tool will drop your current index and create a new, empty one with all the metafields set. - -You can specify different (than this set in `config/local.json`) index name by running: - -```bash -yarn db rebuild -- --indexName=custom_index_name -``` - -## Changing the index structure / adding new fields / changing the types - -If you want to extend the Elasticsearch data structures or map some particular field types, for example, after getting kind of this error: - -``` -[{"type":"illegal_argument_exception","reason":"Fielddata is disabled on text fields by default. Set fielddata=true on [created_at] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead."}] -``` - -Please do change the ES schema by modifying: - -- [config/elastic.schema.product.extension.json](https://github.com/vuestorefront/vue-storefront-api/blob/master/config/elastic.schema.product.extension.json) -- [config/elastic.schema.attribute.extension.json](https://github.com/vuestorefront/vue-storefront-api/blob/master/config/elastic.schema.attribute.extension.json) -- [config/elastic.schema.taxrate.extension.json](https://github.com/vuestorefront/vue-storefront-api/blob/master/config/elastic.schema.taxrate.extension.json) - -The format is compliant with [ES DSL for schema modifications](https://www.elastic.co/blog/found-elasticsearch-mapping-introduction) - -After the changes, run the following indexing command: - -```bash -yarn db rebuild -``` diff --git a/docs/guide/data/elastic-queries.md b/docs/guide/data/elastic-queries.md deleted file mode 100644 index 5bce0fa9a0..0000000000 --- a/docs/guide/data/elastic-queries.md +++ /dev/null @@ -1,52 +0,0 @@ -# ElasticSearch Queries - -## Getting data from ElasticSearch - -Vue Storefront stores most of the catalog data within the Elasticsearch data store. Please have a look at our architecture diagram: - -![Architecture diagram](../images/Vue-storefront-architecture.png). - -To properly access Elasticsearch data, you should implement a specific Vuex action. Here is an example of [vuex action for getting the data](https://github.com/vuestorefront/vue-storefront/blob/master/core/modules/catalog/store/category/actions.ts#L40) : - -```js -import { quickSearchByQuery } from '@vue-storefront/core/lib/search' -import createCategoryListQuery from '@vue-storefront/core/modules/catalog/helpers/createCategoryListQuery' - - /** - * Load categories within specified parent - * @param {Object} commit promise - * @param {Object} parent parent category - */ - async list ({ commit, state, dispatch }, { parent = null, key = null, value = null, level = null, onlyActive = true, onlyNotEmpty = false, size = 4000, start = 0, sort = 'position:asc', includeFields = config.entities.optimize ? config.entities.category.includeFields : null, excludeFields = config.entities.optimize ? config.entities.category.excludeFields : null, skipCache = false, updateState = true }) { - const { searchQuery, isCustomizedQuery } = createCategoryListQuery({ parent, level, key, value, onlyActive, onlyNotEmpty }) - const shouldLoadCategories = skipCache || ((!state.list || state.list.length === 0) || isCustomizedQuery) - - if (shouldLoadCategories) { - const resp = await quickSearchByQuery({ entityType: 'category', query: searchQuery, sort, size, start, includeFields, excludeFields }) - - if (updateState) { - await dispatch('registerCategoryMapping', { categories: resp.items }) - - commit(types.CATEGORY_UPD_CATEGORIES, { ...resp, includeFields, excludeFields }) - EventBus.$emit('category-after-list', { query: searchQuery, sort, size, start, list: resp }) - } - - return resp - } - - const list = { items: state.list, total: state.list.length } - - if (updateState) { - EventBus.$emit('category-after-list', { query: searchQuery, sort, size, start, list }) - } - - return list - }, -``` - -As You may see, we're using [quickSearchByQuery](https://github.com/vuestorefront/vue-storefront/blob/master/core/lib/search.ts#L31) for executing search. This method is pretty interesting because: - -- It uses the `searchQuery` query object, which has an ability to apply filters in common way. -- It does cache the received data into `localForage` collection, named `elasticCache`; the next call with the same queryObject will return the data directly from browser storage, not hitting the server. - -We do not build another Elasticsearch query on this step. We use a search layer object containing all necessary filters and search text. ES query builds using the powerful bodybuilder package right before sending Elasticsearch request. Please take a look at the [reference docs for more options](https://github.com/danpaz/bodybuilder). diff --git a/docs/guide/data/elasticsearch.md b/docs/guide/data/elasticsearch.md deleted file mode 100644 index 172eaad14c..0000000000 --- a/docs/guide/data/elasticsearch.md +++ /dev/null @@ -1,3 +0,0 @@ -# Elasticsearch Data Formats - -External links : [Product entity](https://docs.storefrontapi.com/guide/integration/format-product.html) \ No newline at end of file diff --git a/docs/guide/data/entity-types.md b/docs/guide/data/entity-types.md deleted file mode 100644 index 71db8f8522..0000000000 --- a/docs/guide/data/entity-types.md +++ /dev/null @@ -1,109 +0,0 @@ -# Data Entity Types - -Vue Storefront uses multiple data-entity types to cover the whole scope of the storefront. Default entity types are: - -- Product -- Category -- Attribute -- Taxrule - -These entity types were hardcoded and there was no ability to easily use another custom entity type required for customization. - -Now, Vue Storefront has a new logic to work with entities in the data-fetching perspective: Entity Types. - -Each search adapter should register an entity type to cover a search feature. Default API and new GraphQL search adapters are updated to register all required existing entity types, but developers can also inject custom entity types to work with some other custom entity type data (for example, to get a list of offline stores or something else). - -To use it, an internal GraphQL server should be updated with adding a corresponding resolver for the new entity type. Also, you can use some other external GraphQL server that already has implemented a resolver for this entity type. - -To register such an entity type, you should use the `searchAdapter.registerEntityTypeByQuery` method like shown in the example below: - -```js -const factory = new SearchAdapterFactory(); -let searchAdapter = factory.getSearchAdapter('graphql'); -searchAdapter.registerEntityTypeByQuery('testentity', { - url: 'http://localhost:8080/graphql/', - query: require('./queries/testentity.gql'), - queryProcessor: query => { - // function that can modify the query each time before it's being executed - return query; - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted'); - } - if (resp.hasOwnProperty('data')) { - return processESResponseType(resp.data.testentity, start, size); - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)); - } else { - throw new Error( - "Unknown error with graphQl result in resultProcessor for entity type 'category'", - ); - } - } - }, -}); -``` - -The sample extension `sample-custom-entity-graphql` was added to illustrate how it can be used. It injects a custom entity type `testentity` and sets a custom GraphQL server URL (it is the same as a default API host in the example, because a resolver for this `testentity` was added there for testing. But please notice it was removed there). - -To test a sample extension with resolver, you can add a GraphQL schema file and resolver file in the separate `src/graphql/elastcisearch/testentity` folder in the Vue Storefront API. - -`schema.graphqls` file: - -```graphql -type Query { - testentity(filter: TestInput): ESResponse -} -input TestInput - @doc( - description: "TaxRuleInput specifies the tax rules information to search" - ) { - id: FilterTypeInput - @doc(description: "An ID that uniquely identifies the tax rule") - code: FilterTypeInput - @doc( - description: "The unique identifier for an tax rule. This value should be in lowercase letters without spaces." - ) - priority: FilterTypeInput @doc(description: "Priority of the tax rule") - position: FilterTypeInput @doc(description: "Position of the tax rule") - customer_tax_class_ids: FilterTypeInput - @doc(description: "Cunstomer tax class ids of the tax rule") - product_tax_class_ids: FilterTypeInput - @doc(description: "Products tax class ids of the tax rule") - tax_rate_ids: FilterTypeInput - @doc(description: "Tax rates ids of the tax rule") - calculate_subtotal: FilterTypeInput - @doc(description: "Calculating subtotals of the tax rule") - rates: FilterTypeInput @doc(description: "Rates of the tax rule") -} -``` - -Resolver file `resolver.js`: - -```js -import config from 'config'; -import client from '../client'; -import { buildQuery } from '../queryBuilder'; - -async function testentity(filter) { - let query = buildQuery({ filter, pageSize: 150, type: 'taxrule' }); - - const response = await client.search({ - index: config.elasticsearch.indices[0], - type: config.elasticsearch.indexTypes[4], - body: query, - }); - - return response; -} - -const resolver = { - Query: { - testentity: (_, { filter }) => testentity(filter), - }, -}; - -export default resolver; -``` diff --git a/docs/guide/data/static-data.md b/docs/guide/data/static-data.md deleted file mode 100644 index 92f12ba580..0000000000 --- a/docs/guide/data/static-data.md +++ /dev/null @@ -1,60 +0,0 @@ -# Static Data support in Vue Storefront - -In Vue Storefront, we can use CMS Static Blocks and CMS Static Pages from Magento 2. - -From version 1.6, thanks to @yuriboyko we have a better solution for static data—it's added to the Elasticsearch database and is using graphQL query, displayed on the storefront. - -### How it works? - -The data are synchronized with ElasticSearch from Magento 2 on the adapter level in `mage2vuestorefront` - -After synchronized basic Magento 2 data, run `node --harmony cli.js blocks` and `node --harmony cli.js pages`. Make sure you have `SnowdogApps/magento2-cms-api`, it’s required to compile Magento WYSIWYG data and variable into HTML code. Unfortunately, the widgets are not fully supported, so try to avoid them. - -Static data are pumped to ElasticSearch db with entityTypes: - -- `cms_block` for blocks -- `cms_page` for pages - -Using a new CMS Core Module and two components (for CMS Block and for CMS Page) we easily display on storefront. Check out the example CMS Page Component. - -The route for the CMS Page is set in the default theme: - -```js -{ name: 'cms-page', path: '/i/:slug', component: CmsPage }]) -``` - -So your cms page with `about_us` identifier (in Magento 2 admin URL key) will be found at: `/i/about_us` - -## Provide your own static data for Vue Storefront - -You don’t have to use Magento 2 to provide the static data. You can pump the ElasticSearch database with your own data. - -1. Add you data with appropriate types: -- `cms_block` for blocks -- `cms_page` for pages - -2. Keep data schema: -- Blocks: -``` -type CmsBlock @doc(description: "CMS block defines all CMS block information") { - identifier: String @doc(description: "CMS block identifier") - title: String @doc(description: "CMS block title") - content: String @doc(description: "CMS block content") - creation_time: String @doc(description: "Timestamp indicating when the CMS block was created") - store_id: Int @doc(description: "Store Id of CMS block") -} -``` -- Pages: -``` -type CmsPages @doc(description: "CMS page defines all CMS page information") { - page_id: Int @doc(description: "Id of CMS page") - title: String @doc(description: "CMS page title") - identifier: String @doc(description: "URL key of CMS page") - content: String @doc(description: "CMS page content") - content_heading: String @doc(description: "CMS page content heading") - meta_description: String @doc(description: "CMS page meta description") - meta_keywords: String @doc(description: "CMS page meta keywords") - store_id: Int @doc(description: "Store Id of CMS page") -} -``` - diff --git a/docs/guide/extensions/extending-api.md b/docs/guide/extensions/extending-api.md deleted file mode 100644 index a0e54919c5..0000000000 --- a/docs/guide/extensions/extending-api.md +++ /dev/null @@ -1,103 +0,0 @@ -# Extending the API - -Some extensions need to have additional API methods to get some data directly from Magento/other CMS or just from custom Elasticsearch data collections. - -You may add new ES collections [using the Migration mechanism](../data/data-migrations.md) - -Then you may extend the [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api) to add your custom API methods. Please take a look at: [mailchimp-subscribe](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/api/extensions/mailchimp-subscribe/index.js) for reference. - -To add the API extension to `vue-storefront-api`: - -1. Create the folder within `src/api/extensions` for example 'custom_extension`. -2. Then add the `index.js` file and put the API methods code inside. We're using Express.js. Here is a boilerplate/example for the extension code: - -```js -import { apiStatus } from '../../../lib/util'; -import { Router } from 'express'; - -module.exports = ({ config, db }) => { - let mcApi = Router(); - - /** - * POST create an user - */ - mcApi.post('/subscribe', (req, res) => { - let userData = req.body; - if (!userData.email) { - apiStatus(res, 'Invalid e-mail provided!', 500); - return; - } - - let request = require('request'); - request( - { - url: - config.extensions.mailchimp.apiUrl + - '/lists/' + - encodeURIComponent(config.extensions.mailchimp.listId) + - '/members', - method: 'POST', - headers: { - Authorization: 'apikey ' + config.extensions.mailchimp.apiKey, - }, - json: true, - body: { email_address: userData.email, status: 'subscribed' }, - }, - function(error, response, body) { - if (error) { - apiStatus(res, error, 500); - } else { - apiStatus(res, body, 200); - } - }, - ); - }); - - /** - * DELETE delete an user - */ - mcApi.delete('/subscribe', (req, res) => { - let userData = req.body; - if (!userData.email) { - apiStatus(res, 'Invalid e-mail provided!', 500); - return; - } - - let request = require('request'); - request( - { - url: - config.extensions.mailchimp.apiUrl + - '/lists/' + - encodeURIComponent(config.extensions.mailchimp.listId), - method: 'POST', - headers: { - Authorization: 'apikey ' + config.extensions.mailchimp.apiKey, - }, - json: true, - body: { - members: [{ email_address: userData.email, status: 'unsubscribed' }], - update_existing: true, - }, - }, - function(error, response, body) { - if (error) { - apiStatus(res, error, 500); - } else { - apiStatus(res, body, 200); - } - }, - ); - }); - return mcApi; -}; -``` - -3. Add the extension to `config/local.json`: - -```json - "registeredExtensions": ["mailchimp-subscribe"], -``` - -4. Restart the `vue-storefront-api` -5. Your new API method is available on `localhost:8080/api/ext//` for example: `localhost:8080/api/ext/mailchimp-subscribe/subscribe` diff --git a/docs/guide/extensions/extending-server-side-routes.md b/docs/guide/extensions/extending-server-side-routes.md deleted file mode 100644 index f9abab9a3f..0000000000 --- a/docs/guide/extensions/extending-server-side-routes.md +++ /dev/null @@ -1,31 +0,0 @@ -# Extending Express.js server-side routes - -From Vue Storefront 1.4.0, you can add your own custom server-side routes without Vue.js SSR context. These routes may be used, for example, for generating large, unbuffered files like XML maps, binary files, etc. - -You can add numerous, custom Express js middlewares and routes by simply modifying the `src/server/index.js`: - -```js -// You can extend Vue Storefront server routes by binding to the Express.js (expressApp) in here -module.exports.registerUserServerRoutes = expressApp => { - require('./example/generator')(expressApp); -}; -``` - -The example route handler looks like this: - -```js -module.exports = expressApp => { - /** - * This is an example on how You can bind Your own Express.js server routes to SSR server running Vue Storefront. - * It may be usefull to avoid all the Vue.js processing and context - and useful for example for large XML/binary file generation - */ - expressApp.get('/vue-storefront.xml', (req, res) => { - res.end('Vue Storefront custom XML generator example'); - }); -}; -``` - -## Data operations inside Express routes - -Unfortunately, as you may have seen above in the `core/scripts/server.js`, all modules used by the script (including the dynamic routes) can not use ES modules (`import ... from ...` type of statements). By this limitation, you can't currently use `@vue-storefront`modules inside the custom Express.js routes as they're not compiled to the CommonJS. This is likely to be fixed. To get the data, you may execute `fetch()` requests to the `vue-storefront-api` endpoints. You can still use `const config = require('config')` to read the endpoints, URLs, etc. - diff --git a/docs/guide/extensions/extensions-to-modify-results.md b/docs/guide/extensions/extensions-to-modify-results.md deleted file mode 100644 index 570c50b9bd..0000000000 --- a/docs/guide/extensions/extensions-to-modify-results.md +++ /dev/null @@ -1,71 +0,0 @@ -# Using extensions to Modify Elasticsearch results - -Vue Storefront API has a built-in processor for calculating taxes and adding that data to product search results. API extensions can also add their own processors for modifying Elasticsearch results. - -Some possible use cases for this could be: -- Replacing Magento product descriptions with data from a CMS. -- Cleaning Magento "WYSIWYG" data. -- Adding product ratings or other data from third-party systems. - -Here is an example of creating a custom result processor to replace Product descriptions with Prismic CMS data: - -1. Create the extension folder in `src/api/extensions`. -2. The extension folder must contain another folder called `processors`. -3. Add the processor file, for example `src/api/extensions/example-extension/processors/prismic-product.js`. -```js -import Prismic from 'prismic-javascript' -import PrismicDOM from 'prismic-dom' - -class ProductPrismic { - constructor (config, request) { - this._request = request - this._config = config - } - - process (productList) { - const skus = productList.map( prod => { - return prod._source.sku.toLowerCase() - }) - return Prismic.getApi(this._config.extensions['example-extension'].baseUrl).then((api) => { - return api.query(Prismic.Predicates.in('my.product.uid', skus)) - }).then((result) => { - for (const item of result.results) { - const product = productList.find( prod => { - return prod._source.sku.toLowerCase() === item.uid - }) - if (product) { - try { - product._source.description = PrismicDOM.RichText.asHtml(item.data.description) - } - catch(error) { - console.log(error) - } - } - } - return productList - }).catch(err => { - console.log(err) - }) - } -} - -module.exports = ProductPrismic -``` -4. Add the extension to `config/local.json` and declare the custom processor in the extension settings. It needs to be in this structure: -```json - "registeredExtensions": ["example-extension"], - "extensions": { - "example-extension": { - "baseUrl": "https://my_account.cdn.prismic.io/api/v2", - "resultProcessors": { - "product": "prismic-product" - } - } - } -``` - -That's it. In Prismic, create documents with a uid matching each product SKU, and a description field. Those description will then appear in Vue Storefront product listings. The data update instantly, whenever a document is published in Prismic. - -Note: This example uses Prismic and PrismicDOM, so they'll need to be added to your dependencies in package.json - -Note 2: See `src/platform/magento2/tax.js` for another example of a results processor. diff --git a/docs/guide/extensions/introduction.md b/docs/guide/extensions/introduction.md deleted file mode 100644 index 9e9cd2da6e..0000000000 --- a/docs/guide/extensions/introduction.md +++ /dev/null @@ -1,24 +0,0 @@ -# Introduction - -## What do Vue Storefront extensions look like? - -Depending on your needs, Vue Storefront extensions can have two parts: -- **Client-side part,** which is just a [Vue Storefront module](https://github.com/vuestorefront/vue-storefront/blob/master/docs/guide/modules/introduction.md). It covers most of the use cases. - -- **Server-side part** which is a [Vue Storefront API extension](https://github.com/vuestorefront/vue-storefront/blob/master/docs/guide/extensions/extending-api.md) and should be used if you want to add some endpoints to `vue-storefront-api` or interact with Elasticsearch. - -## Where extensions are located -- On the client side, extension modules should be placed in `src/modules` folder of `vue-storefront` or installed via NPM cli and registered in `src/modules/index.ts` -- On the server side, extensions should be placed in `src/api/extensions` folder of `vue-storefront-api` and registered in config file - -## Writing extensions -If you are writing a VS extension as an NPM module, start the package name with a `vsf-` prefix so it can be transpiled with other VS code and ship it as a raw es6/typescript module. If you don't use the prefix, you need to handle transpilation by yourself. We are currently building an extension boilerplate to make it easier to develop one. - -Here, you can find two articles explaining how to create custom Vue Storefront extensions: -- [How to create an Instagram Feed module for Vue Storefront](https://itnext.io/how-to-create-an-instagram-feed-module-for-vue-storefront-eaa03019b288) by Javier Villanueva -- [Developing a Vue Storefront payment module](https://www.develodesign.co.uk/news/development-of-the-paypal-module-for-vue-storefront/#.XCoa2h2Mmmo.twitter) by Dmitry Schegolikhin from [Develo Design](https://www.develodesign.co.uk/) - -**IMPORTANT** If you are an extension developer, please join `#extension-dev` channel on our Slack to receive information about important API updates and new features. - -## Extensions list -You can find a curated list of VS extensions in [Awesome Vue Storefront](https://github.com/frqnck/awesome-vue-storefront) list. diff --git a/docs/guide/general/introduction.md b/docs/guide/general/introduction.md deleted file mode 100644 index a50ef558e0..0000000000 --- a/docs/guide/general/introduction.md +++ /dev/null @@ -1,123 +0,0 @@ -# Introduction to Vue Storefront - -Vue Storefront is a rather complex solution with a lot of possibilities. Learning all of them can take some time. In this introduction, we will learn all of its crucial concepts in a few minutes, which is enough to start playing with [Vue Storefront](https://www.vuestorefront.io/). - - -## What is Vue Storefront -![Vue Storefront](https://cdn-images-1.medium.com/max/1600/0*X7cXhVkWidbWFrbM) - -Vue Storefront is a headless and backend-agnostic eCommerce [Progressive Web App (PWA)](https://developers.google.com/web/progressive-web-apps/) written in Vue.js. The fact that it's using headless architecture allows Vue Storefront to connect with any eCommerce platform so it can be a frontend PWA for Magento, Shopify, BigCommerce, WooCommerce and etc. - - It's a very popular [Open Source project](https://github.com/vuestorefront/vue-storefront) with a strong and growing community. - -**Key features of Vue Storefront:** -- Platform-agnostic -- Focus on performance -- Mobile-first approach -- Cutting-edge tech -- No limitations in theming and customization -- Open Source with MIT license -- Exciting developer experience -- Out-of-the-box Server Side Rendering (for SEO) -- Offline mode - - -## How does it connect with backend platforms? -Vue Storefront manages to be platform-agnostic thanks to the [vue-storefront-api](https://github.com/vuestorefront/vue-storefront-api) and [dedicated API connectors](https://github.com/vuestorefront/vue-storefront#integrations) for eCommerce backend platforms. The data format in vue-storefront-api is always the same for any platform, which means no matter what eCommerce backend you use, your frontend remains the same without any change. - -It's a great strategy for migrations since you can easily migrate from one platform to another (or one version to another, e.g. Magento 1 to 2) without touching your frontend. - - -![Architecture diagram](https://raw.githubusercontent.com/vuestorefront/vue-storefront/master/docs/.vuepress/public/GitHub-Architecture-VS.png) - -- **VSBridge Indexer** ([magento2-vsbridge-indexer](https://github.com/vuestorefront/magento2-vsbridge-indexer) on the image) is multi-process data synchronizer between Magento and Elasticsearch which is sending data about products, categories, taxrules, attributes, cms blocks and cms pages. - -- **VSF-API (or SF-API)** is just a proxy to the eCommerce backend which gives us variety of benefits like caching layer, mapping to agnostic types, possibility to create own "processors" (mapping functions for certain entities). - -VueStorefront works seamlessly with your backend platform while two integration phases are managed as above. - -Some of the most popular backend platforms already have their integrations ([Magento 2](https://github.com/vuestorefront/magento2-vsbridge-indexer), [Magento 1](https://github.com/divanteLtd/magento1-vsbridge), [CoreShop](https://github.com/divanteLtd/coreshop-vsbridge), [BigCommerce](https://github.com/divanteLtd/bigcommerce2vuestorefront), [WooCommerce](https://github.com/divanteLtd/woocommerce2vuestorefront)), but you can easily make your own with the [integration boilerplate](https://github.com/divanteLtd/vue-storefront-integration-boilerplate). - -In terms of data synchronizers, as a core team we are maintaining only a `magento2-vsbridge-indexer`. The person responsible for that is Maciej Kucmus. - -The blue parts on the diagram are responsible for offline cache and will be explained later in the article. - -## How does it work? - -There are 3 concepts you need to be familiar with while working with Vue Storefront. - -- **Vue Storefront Core** ( `core` folder) is the glue for all the features that allow Vue Storefront to work. It contains all the entry points, SSR behavior, build process, in-app libs and helpers. You shouldn't touch this folder directly when building your own implementations in order to stay up-to-date with its features and security. - -- **Vue Storefront Modules** ( `core/modules` and `src/modules` ) are the eCommerce features. Each module is one encapsulated feature (like cart, wishlist, catalog, and some third-party integrations). You can add/remove/edit these modules as you wish and compose your Vue Storefront shop with only the features that you need. They are also used for 3rd-party extensions. - -- **Vue Storefront Themes** ( `src/themes` ) are the actual shop implementation. In themes, you can use and extend all the logic from registered modules / core and add your HTML markup and styles. Vue Storefront provides a fully customizable [default theme](https://github.com/vuestorefront/vsf-default) and [capybara theme](https://github.com/vuestorefront/vsf-capybara). We recommend using default theme because it is more stable than capybara theme. - -To summarize: Your shop is basically a Vue Storefront theme that uses features provided by modules. Vue Storefront Core glues it all together. - -Knowing these 3 concepts allows you to confidently work with Vue Storefront and make your own shops. - -## Installing Vue Storefront -When you want to play with Vue Storefront, there are three options: - -![install](https://cdn-images-1.medium.com/max/1200/0*dz-mwiEQ_Qkzpd5H) -*This is everything you need to have VS working with our demo backend.* - -- You can set up the frontend connected to our demo backend platform (best for trying out Vue Storefront). -- You can set up frontend with your own `vue-storefront-api` and database dumped from the demo. -- You can set up frontend with `vue-storefront-api` connected to your eCommerce backend. - -To do any of this, simply type `yarn installer` in the root of the project and answer the questions in the console. Once the installation is done, type `yarn dev` to run your project (by default, on port `3000`). No matter what option you choose, you can change the settings in the config file later. - -## Vue Storefront config file - -Most of the Vue Storefront configuration (like the active theme, backend API addresses, multistore setup, etc.) is done through its [config](/guide/basics/configuration.html) file that can be found under the `config` folder. The `default.json` file contains all the default setup. - -For your own implementation you should create a `local.json` file in the same directory and include fields from `default.json` that you want to override. These two files will be merged in favor of `local.json` during the build process. If you use the installer to set up your Vue Storefront instance, it'll generate proper config files. - -## Building themes in Vue Storefront -![themes structure](https://cdn-images-1.medium.com/max/1200/1*jMel95nhs5UTIi2DQdeq4Q.png) - -While making themes in Vue Storefront, in most cases, all you need to do is create your own HTML and CSS markup. All the required business logic is exposed by the core with its core modules and can be easily injected into any of the theme components. -![biz-logic](https://cdn-images-1.medium.com/max/1200/1*tMwC0smduKIwKh82jTiJmw.png) -*The business logic from the core component can be easily injected into any theme component as a Vue.js mixin.* - -The mechanism of injecting core business logic into themes is ridiculously simple. We are using [Vue.js mixins](https://vuejs.org/v2/guide/mixins.html) to maintain business logic upgradable in the core. - -So assume we have a core Microcart component with business logic as above (left side), we can easily inject it into any of our theme components (right side) just by importing it and adding as a mixin `mixins: [Microcart]`. This is all you need to make use of core business logic inside your theme. With this approach, we can easily ship updates to all core components without breaking your shop. - -[Check how to create theme based on our official themes](/guide/installation/theme.html). - -## Offline mode and cache -Vue Storefront still works even while the user is offline. - -We managed to do this by making extensive use of the browser cache.  -- **For the static assets** (only prod) we use the [sw-precache](https://github.com/GoogleChromeLabs/sw-precache) plugin (config can be found in `core/build/webpack.prod.sw.config.js` ). They are cached in Service Worker and can be inspected under the `Application/Cache Storage` tab of your Developer Tools. - -![cache](https://cdn-images-1.medium.com/max/1200/1*BHVzt7oCIxcM3bNPZriKmw.png) -*Here you can find cached static assets.* - -:::warning -Please note that Service Worker works only in production mode. -::: - -- **For the catalog and store-data cache** we use [LocalForage](https://localforage.github.io/localForage/). We also prefetch products from visited categories so once you enter one, all of its products are available offline. The mechanism of offline storage is located under `core/lib/store/`. - -We use some of the cached data even while the user is online to display the content instantly. This explains why Vue Storefront is lightning fast. - - -## What else -You may not believe me but this is all you need to know to start working with Vue Storefront! Once you are done wrapping your head around the basics, just look around docs and visit community [slack](https://vuestorefront.slack.com) to dig deeper into the project. - -## Useful Links - -- [Documentation](/) -- [Community slack invitation link](https://join.slack.com/t/vuestorefront/shared_invite/enQtOTUwNjQyNjY5MDI0LWFmYzE4NTYxNDBhZDRlMjM5MDUzY2RiMjU0YTRjYWQ3YzdkY2YzZjZhZDZmMDUwMWQyOWRmZjQ3NDgwZGQ3NTk) -- [Project structure explained](https://docs.vuestorefront.io/guide/basics/project-structure.html) -- [Configuration file explained](https://docs.vuestorefront.io/guide/basics/configuration.html) -- [Extending Vue Storefront](https://docs.vuestorefront.io/guide/extensions/introduction.html) -- [How to contribute](https://docs.vuestorefront.io/guide/basics/contributing.html#how-to-contribute) - -## Video with training -You can also watch a video recording from 4th Vue Storefront hackathon with free introduction training - -[![0.jpg](http://img.youtube.com/vi/IL2HMtvf_hw/0.jpg)](https://youtu.be/IL2HMtvf_hw) diff --git a/docs/guide/images/Apple_iPhone_X.png b/docs/guide/images/Apple_iPhone_X.png deleted file mode 100644 index 65a84df374..0000000000 Binary files a/docs/guide/images/Apple_iPhone_X.png and /dev/null differ diff --git a/docs/guide/images/GitHub-Architecture-VS-data-import.png b/docs/guide/images/GitHub-Architecture-VS-data-import.png deleted file mode 100644 index 3de3e1d348..0000000000 Binary files a/docs/guide/images/GitHub-Architecture-VS-data-import.png and /dev/null differ diff --git a/docs/guide/images/Vue-storefront-architecture-proxy-requests.png b/docs/guide/images/Vue-storefront-architecture-proxy-requests.png deleted file mode 100644 index d1561e3849..0000000000 Binary files a/docs/guide/images/Vue-storefront-architecture-proxy-requests.png and /dev/null differ diff --git a/docs/guide/images/Vue-storefront-architecture.png b/docs/guide/images/Vue-storefront-architecture.png deleted file mode 100644 index d9d27aeee2..0000000000 Binary files a/docs/guide/images/Vue-storefront-architecture.png and /dev/null differ diff --git a/docs/guide/images/cart-localstorage.png b/docs/guide/images/cart-localstorage.png deleted file mode 100644 index eba2da24e4..0000000000 Binary files a/docs/guide/images/cart-localstorage.png and /dev/null differ diff --git a/docs/guide/images/cart-sync.png b/docs/guide/images/cart-sync.png deleted file mode 100644 index f3afb0a740..0000000000 Binary files a/docs/guide/images/cart-sync.png and /dev/null differ diff --git a/docs/guide/images/categories-localstorage.png b/docs/guide/images/categories-localstorage.png deleted file mode 100644 index 3be79872e8..0000000000 Binary files a/docs/guide/images/categories-localstorage.png and /dev/null differ diff --git a/docs/guide/images/chrome-dev-console.png b/docs/guide/images/chrome-dev-console.png deleted file mode 100644 index 707b5c5f41..0000000000 Binary files a/docs/guide/images/chrome-dev-console.png and /dev/null differ diff --git a/docs/guide/images/data_pump_1.png b/docs/guide/images/data_pump_1.png deleted file mode 100644 index defe71207c..0000000000 Binary files a/docs/guide/images/data_pump_1.png and /dev/null differ diff --git a/docs/guide/images/data_pump_10.png b/docs/guide/images/data_pump_10.png deleted file mode 100644 index ab14461993..0000000000 Binary files a/docs/guide/images/data_pump_10.png and /dev/null differ diff --git a/docs/guide/images/data_pump_2.png b/docs/guide/images/data_pump_2.png deleted file mode 100644 index 0cd29683f5..0000000000 Binary files a/docs/guide/images/data_pump_2.png and /dev/null differ diff --git a/docs/guide/images/data_pump_3.png b/docs/guide/images/data_pump_3.png deleted file mode 100644 index e1ef639d89..0000000000 Binary files a/docs/guide/images/data_pump_3.png and /dev/null differ diff --git a/docs/guide/images/data_pump_5.png b/docs/guide/images/data_pump_5.png deleted file mode 100644 index 3fef5c3b62..0000000000 Binary files a/docs/guide/images/data_pump_5.png and /dev/null differ diff --git a/docs/guide/images/data_pump_6.png b/docs/guide/images/data_pump_6.png deleted file mode 100644 index fc07b85385..0000000000 Binary files a/docs/guide/images/data_pump_6.png and /dev/null differ diff --git a/docs/guide/images/data_pump_7.png b/docs/guide/images/data_pump_7.png deleted file mode 100644 index 2cc2c80203..0000000000 Binary files a/docs/guide/images/data_pump_7.png and /dev/null differ diff --git a/docs/guide/images/data_pump_8.png b/docs/guide/images/data_pump_8.png deleted file mode 100644 index 3378624d8b..0000000000 Binary files a/docs/guide/images/data_pump_8.png and /dev/null differ diff --git a/docs/guide/images/data_pump_9.png b/docs/guide/images/data_pump_9.png deleted file mode 100644 index cd4377a9c7..0000000000 Binary files a/docs/guide/images/data_pump_9.png and /dev/null differ diff --git a/docs/guide/images/data_pump_design.png b/docs/guide/images/data_pump_design.png deleted file mode 100644 index 8510c08180..0000000000 Binary files a/docs/guide/images/data_pump_design.png and /dev/null differ diff --git a/docs/guide/images/data_pump_err_1.png b/docs/guide/images/data_pump_err_1.png deleted file mode 100644 index fdc1a1bf22..0000000000 Binary files a/docs/guide/images/data_pump_err_1.png and /dev/null differ diff --git a/docs/guide/images/datum_pump_design.png b/docs/guide/images/datum_pump_design.png deleted file mode 100644 index 64f0794bfc..0000000000 Binary files a/docs/guide/images/datum_pump_design.png and /dev/null differ diff --git a/docs/guide/images/delta_error.png b/docs/guide/images/delta_error.png deleted file mode 100644 index a9995b868f..0000000000 Binary files a/docs/guide/images/delta_error.png and /dev/null differ diff --git a/docs/guide/images/editmodeInMC.png b/docs/guide/images/editmodeInMC.png deleted file mode 100644 index f44e235401..0000000000 Binary files a/docs/guide/images/editmodeInMC.png and /dev/null differ diff --git a/docs/guide/images/error_1.11.png b/docs/guide/images/error_1.11.png deleted file mode 100644 index 8e1a211b93..0000000000 Binary files a/docs/guide/images/error_1.11.png and /dev/null differ diff --git a/docs/guide/images/es-map-result.png b/docs/guide/images/es-map-result.png deleted file mode 100644 index 98ce5da5de..0000000000 Binary files a/docs/guide/images/es-map-result.png and /dev/null differ diff --git a/docs/guide/images/home-vuestorefront.png b/docs/guide/images/home-vuestorefront.png deleted file mode 100644 index 499208de5e..0000000000 Binary files a/docs/guide/images/home-vuestorefront.png and /dev/null differ diff --git a/docs/guide/images/home_capybara.png b/docs/guide/images/home_capybara.png deleted file mode 100644 index 4d9dc6065e..0000000000 Binary files a/docs/guide/images/home_capybara.png and /dev/null differ diff --git a/docs/guide/images/img_catalog_prod.png b/docs/guide/images/img_catalog_prod.png deleted file mode 100644 index 40dfa3d63a..0000000000 Binary files a/docs/guide/images/img_catalog_prod.png and /dev/null differ diff --git a/docs/guide/images/img_catalog_prod_fail.png b/docs/guide/images/img_catalog_prod_fail.png deleted file mode 100644 index 75548af8ba..0000000000 Binary files a/docs/guide/images/img_catalog_prod_fail.png and /dev/null differ diff --git a/docs/guide/images/index-not-found-ex.png b/docs/guide/images/index-not-found-ex.png deleted file mode 100644 index 378963111e..0000000000 Binary files a/docs/guide/images/index-not-found-ex.png and /dev/null differ diff --git a/docs/guide/images/lego_palm.jpeg b/docs/guide/images/lego_palm.jpeg deleted file mode 100644 index ab9f1da25e..0000000000 Binary files a/docs/guide/images/lego_palm.jpeg and /dev/null differ diff --git a/docs/guide/images/magento_1.png b/docs/guide/images/magento_1.png deleted file mode 100644 index e322bbf1f5..0000000000 Binary files a/docs/guide/images/magento_1.png and /dev/null differ diff --git a/docs/guide/images/magento_2.png b/docs/guide/images/magento_2.png deleted file mode 100644 index 200a11fdc5..0000000000 Binary files a/docs/guide/images/magento_2.png and /dev/null differ diff --git a/docs/guide/images/magento_3.png b/docs/guide/images/magento_3.png deleted file mode 100644 index 2b3051e762..0000000000 Binary files a/docs/guide/images/magento_3.png and /dev/null differ diff --git a/docs/guide/images/microcart_nan.png b/docs/guide/images/microcart_nan.png deleted file mode 100644 index e962aeb56c..0000000000 Binary files a/docs/guide/images/microcart_nan.png and /dev/null differ diff --git a/docs/guide/images/npm-run-migrate-result.png b/docs/guide/images/npm-run-migrate-result.png deleted file mode 100644 index 74ef3dc7e6..0000000000 Binary files a/docs/guide/images/npm-run-migrate-result.png and /dev/null differ diff --git a/docs/guide/images/o2m-output.png b/docs/guide/images/o2m-output.png deleted file mode 100644 index b52b063280..0000000000 Binary files a/docs/guide/images/o2m-output.png and /dev/null differ diff --git a/docs/guide/images/orders-collection.png b/docs/guide/images/orders-collection.png deleted file mode 100644 index 9c05578b57..0000000000 Binary files a/docs/guide/images/orders-collection.png and /dev/null differ diff --git a/docs/guide/images/orders-localstorage.png b/docs/guide/images/orders-localstorage.png deleted file mode 100644 index 34c1ffa548..0000000000 Binary files a/docs/guide/images/orders-localstorage.png and /dev/null differ diff --git a/docs/guide/images/organized_lego_bricks.jpeg b/docs/guide/images/organized_lego_bricks.jpeg deleted file mode 100644 index 4085a2f378..0000000000 Binary files a/docs/guide/images/organized_lego_bricks.jpeg and /dev/null differ diff --git a/docs/guide/images/paypal.svg b/docs/guide/images/paypal.svg deleted file mode 100644 index 787ed4a114..0000000000 --- a/docs/guide/images/paypal.svg +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - /execute - - - - - - - - - - - - YOUR SERVER - - - - - - - - - - - - - - - - - - API - - - - - - - Pay Now - - - - - - - - - - - - - - - - 1 - - - 2 - - - 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - 4 - - - diff --git a/docs/guide/images/pile_of_legos.png b/docs/guide/images/pile_of_legos.png deleted file mode 100644 index 6f5ef484f0..0000000000 Binary files a/docs/guide/images/pile_of_legos.png and /dev/null differ diff --git a/docs/guide/images/product_like_state.png b/docs/guide/images/product_like_state.png deleted file mode 100644 index bb64c9c7ff..0000000000 Binary files a/docs/guide/images/product_like_state.png and /dev/null differ diff --git a/docs/guide/images/route_liked.png b/docs/guide/images/route_liked.png deleted file mode 100644 index 982cc79226..0000000000 Binary files a/docs/guide/images/route_liked.png and /dev/null differ diff --git a/docs/guide/images/sss.png b/docs/guide/images/sss.png deleted file mode 100644 index dd7a63f2e9..0000000000 Binary files a/docs/guide/images/sss.png and /dev/null differ diff --git a/docs/guide/images/storefront.png b/docs/guide/images/storefront.png deleted file mode 100644 index 5c94d43070..0000000000 Binary files a/docs/guide/images/storefront.png and /dev/null differ diff --git a/docs/guide/images/stores.png b/docs/guide/images/stores.png deleted file mode 100644 index 4c474121ff..0000000000 Binary files a/docs/guide/images/stores.png and /dev/null differ diff --git a/docs/guide/images/success_1.11_home.png b/docs/guide/images/success_1.11_home.png deleted file mode 100644 index 348eb063bf..0000000000 Binary files a/docs/guide/images/success_1.11_home.png and /dev/null differ diff --git a/docs/guide/images/syncTasks-example.png b/docs/guide/images/syncTasks-example.png deleted file mode 100644 index e33d6a2619..0000000000 Binary files a/docs/guide/images/syncTasks-example.png and /dev/null differ diff --git a/docs/guide/images/vuelogo.jpg b/docs/guide/images/vuelogo.jpg deleted file mode 100644 index b81c182c39..0000000000 Binary files a/docs/guide/images/vuelogo.jpg and /dev/null differ diff --git a/docs/guide/installation/etc/nginx/sites-enabled/prod.vuestorefront.io b/docs/guide/installation/etc/nginx/sites-enabled/prod.vuestorefront.io deleted file mode 100644 index 2c0e423c9e..0000000000 --- a/docs/guide/installation/etc/nginx/sites-enabled/prod.vuestorefront.io +++ /dev/null @@ -1,35 +0,0 @@ -server { - listen 80; - server_name prod.vuestorefront.io; - add_header Strict-Transport-Security "max-age=31536000" always; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - - gzip on; - gzip_proxied any; - gzip_types - text/css - text/javascript - text/xml - application/javascript - application/json - text/json; - - location / { - proxy_pass http://localhost:3000/; - } - - location /assets/ { - proxy_pass http://localhost:3000/assets/; - } - - location /api/ { - proxy_pass http://localhost:8080/api/; - } - - location /img/ { - proxy_pass http://localhost:8080/img/; - } -} diff --git a/docs/guide/installation/linux-mac.md b/docs/guide/installation/linux-mac.md deleted file mode 100644 index 29f83d3fe3..0000000000 --- a/docs/guide/installation/linux-mac.md +++ /dev/null @@ -1,223 +0,0 @@ -# Installing on Linux/MacOS - -The steps below are tested on MacOS and Linux environments. If you're on Windows please check [Windows Installation Tutorial](windows.md). - -Let's go! - -## Requirements - -- Docker (with [docker-compose](https://docs.docker.com/compose/install/) installed). - - Already included in `vue-storefront` and `vue-storefront-api` Docker images (required locally, if you do not use containerization): - -- Node.js [Active LTS](https://nodejs.org/en/) (>=10.x) -- [Yarn](https://yarnpkg.com/en/docs/install) (>=1.0.0) -- [ImageMagick](https://www.imagemagick.org/script/index.php) (to fit, resize and crop images) - -## User-friendly installation - -If you're MacOS or Linux user now you're able to install with pretty nice CLI installer :) - -```bash -git clone --single-branch --branch master https://github.com/vuestorefront/vue-storefront.git vue-storefront -cd vue-storefront -yarn -yarn installer -``` - -It will take some time for installation and during the last step you will be asked some questions. First one is - -``` -Would you like to use https://demo.vuestorefront.io as the backend? -``` - -If you answer `Yes`, you will have remote backend at `https://demo.vuestorefront.io`. Otherwise, you will need to install `vue-storefront-api`. - -### Using Vue Storefront demo as a backend - -In this case you don't need to run Docker and you will be asked one additional question: - -``` -? Please provide path for images endpoint (https://demo.vuestorefront.io/img/) -``` - -You can simply proceed and as a result you will have a `vue-storefront` folder inside your project root and Storefront application running on `http://localhost:3000`. All images will be also hosted at `https://demo.vuestorefront.io/img/`. - -### Theme - -You will get question about official theme installation and its version. -``` -? Select theme for Vue Storefront (Use arrow keys) -❯ Capybara - based on Storefront UI - Default -``` -``` -? Select theme version (Use arrow keys) -❯ Stable version (recommended for production) - In development branch (could be unstable!) -``` - -### Installing the vue-storefront-api locally - -If you answer `No` on the previous question, please be sure the Docker is running, otherwise you might get an error. You will be asked some more questions immediately: - -``` -? Would you like to use https://demo.vuestorefront.io as the backend? No -? Please provide Git path (if it's not globally installed) git -? Please provide path for installing backend locally ../vue-storefront-api -? Choose path for images endpoint http://localhost:8080/img/ -``` - -As for images endpoint: you can choose between `https://demo.vuestorefront.io/img/` again or host your images on localhost. - -After you answered all the questions, the installation process will start (it might take some time to install all dependencies). When it's finished, you will get the following message: - -``` -┌────────────────────────────────────────────────────────────────┐ -│ Congratulations! │ -│ │ -│ You've just successfully installed vue-storefront. │ -│ All required servers are running in background │ -│ │ -│ Storefront: http://localhost:3000 │ -│ Backend: http://localhost:8080 │ -│ │ -│ Logs: /Users/natalia/Desktop/work/test/vue-storefront/var/log/ │ -│ │ -│ Good Luck! │ -└────────────────────────────────────────────────────────────────┘ -``` - -Your project should contain 2 folders at this moment: `vue-storefront` and `vue-storefront-api`. Vue Storefront should be running on `http://localhost:3000`: - -![Storefront screenshot](../images/storefront.png) - -## Manual installation - -### Install the vue-storefront-api - -You need to use [https://github.com/vuestorefront/vue-storefront-api](https://github.com/vuestorefront/vue-storefront-api). It's the ultimate API backend for this application. - -```bash -git clone https://github.com/vuestorefront/vue-storefront-api.git vue-storefront-api -cd vue-storefront-api -``` - -You can choose between two modes of running the application: - -1. The **legacy** mode - starting just Elastic and Redis containers: - - ```bash - docker-compose up -d - ``` - -2. The **standard** mode - starting Elastic, Redis and Vue Storefront API containers: - - ```bash - docker-compose -f docker-compose.yml -f docker-compose.nodejs.yml up -d - ``` - -If you choose to use **legacy** mode, you must manually install the Yarn dependencies for the project: - -```bash -yarn install -``` - -As a result, all necessary services will be launched: - -- Vue Storefront API runtime environment (Node.js with dependencies from `package.json`) -- [ElasticSearch](https://www.elastic.co/products/elasticsearch) -- [Redis](https://redis.io/) -- Kibana (optional) - -To test out the application you'll need some test data. In `vue-storefront-api/var/catalog.json` you have data dump for ElasticSearch with default Magento2 products database. We're using for development purposes. - -First step is to configure the application: - -```bash -cp config/default.json config/local.json -nano config/local.json -``` - -The config file is quite simple, but here you have some comments: [Config file for vue-storefront](https://github.com/vuestorefront/vue-storefront/wiki/Config-file-format-for-vue-storefront). - -:::tip NOTE -We're using the powerful node.js library for config files, check the docs to learn more on it: [https://github.com/lorenwest/node-config](https://github.com/lorenwest/node-config). -::: - -To import these products we'll use `elasticdump` - which is provided by default with `package.json` dependencies and yarn command. Then, we need to update the structures in the database to the latest version (data migrations). - -Depending on the selected mode, execute the following commands: - -- **legacy** mode: - ```bash - yarn restore - yarn migrate - ``` -- **standard** mode: - ```bash - docker exec -it vue-storefront-api_app_1 yarn restore - docker exec -it vue-storefront-api_app_1 yarn migrate - ``` - -Clone the image files for default product database (we're using [Magento2 example products dataset](https://github.com/magento/magento2-sample-data). Please execute the following command in **the root folder of vue-storefront-api project**: - -```bash -git clone https://github.com/magento/magento2-sample-data.git var/magento2-sample-data -``` - -If you choose to use **standard** mode, the application is already running in the background. However, if you decided to stay with the **legacy** mode, you must start the application manually using the following command (development mode with dynamic file reloads when changed): - -```bash -yarn dev -``` - -After all these steps you should be able to use the API application! - -You can check if everything works just fine by executing the following command: - -```bash -curl -i http://localhost:8080/api/catalog/vue_storefront_catalog/product/_search?q=bag&size=50&from=0 -``` - -Now, it's the time to install the frontend itself. - -### Install the vue-storefront - -First step is to clone [vue-storefront](https://github.com/vuestorefront/vue-storefront) - -```bash -git clone --single-branch --branch master https://github.com/vuestorefront/vue-storefront.git vue-storefront -cd vue-storefront -``` - -Next, you have to prepare the config - -```bash -cp config/default.json config/local.json -nano config/local.json -``` - -The default config file should work perfectly fine for default purposes. - -Next [install theme](theme.md) - -Finally, you have to choose between two modes of running the application (similarly as in the case of vue-storefront-api). - -:::warning -If you choose the **legacy** mode, be sure to run `yarn install` first! -::: - -1. The **legacy** mode: - - ```bash - yarn build - yarn dev - ``` - -2. The **standard** mode (whole runtime environment inside the container): - ```bash - docker-compose up - ``` - -That's all - your frontend application is now up and running! You can check it on `localhost:3000` diff --git a/docs/guide/installation/magento.md b/docs/guide/installation/magento.md deleted file mode 100644 index 5fcbcce377..0000000000 --- a/docs/guide/installation/magento.md +++ /dev/null @@ -1,185 +0,0 @@ -# Integration with Magento 2 -## Using native Magento 2 module -There is a native Magento 2 [module](https://github.com/vuestorefront/magento2-vsbridge-indexer) that synchronizes Magento 2 source data and **Vue Storefront** data store; *Elasticsearch*. -[Magento 2 VSBridge Indexer](https://github.com/vuestorefront/magento2-vsbridge-indexer) has a few advantages. More than anything, it's faster and reliable. - -## Using Magento 2 API via OAuth authorization -The tool is using Magento 2 API via OAuth authorization, so you need to prepare Magento Integration access at first. Go to your Magento 2 admin panel and click: System → Integrations. - -![Magento Admin Panel](../images/magento_1.png) - -Click _Add New Integration_ and fill in: -- Name (whatever) -- Your password to confirm the changes -- On the API Permissions tab, check the following resources: - - Catalog - - Sales - - My Account - - Carts - - Stores > Settings > Configuration > Inventory Section - - Stores > Taxes - - Stores > Attributes > Product -- Save - -![Magento API](../images/magento_2.png) - -In the result, you’ll click _Activate_ and get some OAuth access tokens: - -![Magento tokens](../images/magento_3.png) - -## Integrating Magento 2 with your local instance - -### Fast integration - -The Magento2 data import is now integrated into `vue-storefront-api` for simplicity. It's still managed by the [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront), added as a dependency to `vue-storefront-api`. - -After setting the `config.magento2.api` section using yours Magento 2 OAuth credentials: - -```json - "magento2": { - "url": "http://magento2.demo-1.xyz.com", - "imgUrl": "http://localhost:8080/media/catalog/product", - "assetPath": "/../var/magento2-sample-data/pub/media", - "magentoUserName": "", - "magentoUserPassword": "", - "httpUserName": "", - "httpUserPassword": "", - "api": { - "url": "http://demo-magento2.vuestorefront.io/rest", - "consumerKey": "byv3730rhoulpopcq64don8ukb8lf2gq", - "consumerSecret": "u9q4fcobv7vfx9td80oupa6uhexc27rb", - "accessToken": "040xx3qy7s0j28o3q0exrfop579cy20m", - "accessTokenSecret": "7qunl3p505rubmr7u1ijt7odyialnih9" - } - }, -``` - -You can run the following command to execute the full import of all the products, categories, and other important stuff to your Elasticsearch instance: - -```bash -yarn mage2vs import -``` - -... or in multistore setup, you can run the same command with specified `store-code` parameter - -```bash - yarn mage2vs import --store-code=de -``` - -### Manual integration - -First, you need to install [mage2vuestorefront ](https://github.com/vuestorefront/mage2vuestorefront): - -```bash -git clone https://github.com/vuestorefront/mage2vuestorefront.git mage2vs -cd mage2vs/src -yarn install -``` - -Now edit the `src/config.js` file in your `mage2vuestorefront` directory to set the following section: - -```js -magento: { - url: process.env.MAGENTO_URL || "http://your-magento-url.com/rest/", <- change to your Magento 2 URL, - consumerKey: process.env.MAGENTO_CONSUMER_KEY || 'alva6h6hku9qxrpfe02c2jalopx7od1q', - consumerSecret: process.env.MAGENTO_CONSUMER_SECRET || '9tgfpgoojlx9tfy21b8kw7ssfu2aynpm', - accessToken: process.env.MAGENTO_ACCESS_TOKEN || 'rw5w0si9imbu45h3m9hkyrfr4gjina8q', - accessTokenSecret: process.env.MAGENTO_ACCESS_TOKEN_SECRET || '00y9dl4vpxgcef3gn5mntbxtylowjcc9', -}, -``` - -As you can see, you can override the defaults by ENV variables as well. - - -The rest of the config.js entries point out to your `vue-storefront-api` based Docker and Redis instances, which are required by `mage2nosql` to work. - -To make the full import, you should run the following commands (the sequence of commands is important ,  as, for example, `node cli.js categories` populates the Redis cache for further use of `node cli.js` products and so on). - -```bash -node cli.js taxrule -node cli.js attributes -node cli.js categories -node cli.js productcategories -node cli.js products -``` - -It’s safe to run these commands over and over, as they’re doing `upsert` operation, so it inserts or updates the existing records. - -`cli.js` has a lot of other modes to be run in—dynamic changes, queue support, etc. You may experiment with them, but remember ,  the basic sequence for syncing the whole Magento 2 database is like just shown. - -## Synchronizing orders and Magento images - -As you should have the products and categories already synchronized, you may want to send some orders back to Magento or synchronize the shopping carts in real time. - -`vue-storefront-api` is responsible for this write access to Magento. You may want to edit your `conf/local.json` within the `vue-storefront-api` directory to set the OAuth Magento API access (`magento2` section): - -```json -"magento2": { - "url": "http://your-magento-url.com", - "imgUrl": "http://your-magento-url.com/media/catalog/product", - "assetPath": "/../var/magento2-sample-data/pub/media", - "api": { - "url": "http://your-magento-url.com/rest/", - "consumerKey": "alva6h6hku9qxrpfe02c2jalopx7od1q", - "consumerSecret": "9tgfpgoojlx9tfy21b8kw7ssfu2aynpm", - "accessToken": "rw5w0si9imbu45h3m9hkyrfr4gjina8q", - "accessTokenSecret": "00y9dl4vpxgcef3gn5mntbxtylowjcc9" - } -}, -``` - -To allow `vue-storefront-api` to resize your Magento images, please edit the `imgUrl` property under `magento2` section and add your Magento’s domain to `imageable` -> `whitelist`. - -```json -"imageable": { - "namespace": "", - "maxListeners": 512, - "imageSizeLimit": 1024, - "timeouts": { - "convert": 15000, - "identify": 300, - "download": 5000 - }, - "whitelist": { - "allowedHosts": [ - ".*your-magento-url.com", - ".*divante.pl", - ".*vuestorefront.io", - "localhost" - ], - "trustedHosts": [ - ".*your-magento-url.com", - ".*divante.pl", - ".*vuestorefront.io", - "localhost" - ] - }, -``` - -:::tip NOTE -After changing the config files you need to restart `yarn dev` -::: - -After setting up Magento access, you just need to run the Order2Magento worker, which works on a Redis-based queue to process all the orders made by users. - -``` -yarn o2m -``` - -The code of this script is [located here](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/worker/order_to_magento2.js) so you can easily check how it’s working. - -Starting from Vue Storefront v1.6, now we have a special switch in `vue-storefront-api`: `config.orders.useServerQueue` which is set to `false` by default. With this option disabled, the `order_2_magento` process is no longer needed, as the incoming orders are directly send to Magento 2. If it's set to `true`, the old behavior of the server-based Redis queues used to stack the orders first is being used. - -## Synchronizing shopping carts - -By default, shopping carts are not synchronized in real-time , only after the order is placed, Magento 2 cart is created, etc. - -This was limiting behavior because you need to keep the user cart current all the time to get Magento 2 shopping-cart promotion rules into action . - -We have an option for that! If you have the Magento 2 API configured within the `vue-storefront-api`, you just need to go to `vue-storefront/conf/local.json` and add - -```js -synchronize: true; -``` - -to `cart` section. Please check the [default config for reference](https://github.com/vuestorefront/vue-storefront/blob/193cf44a6e936136fc19e22b45fe8dbc4b33f844/config/default.json#L8). diff --git a/docs/guide/installation/prod.vuestorefront.io b/docs/guide/installation/prod.vuestorefront.io deleted file mode 100644 index 9eb311788f..0000000000 --- a/docs/guide/installation/prod.vuestorefront.io +++ /dev/null @@ -1,65 +0,0 @@ -server { - listen 80; - server_name prod.vuestorefront.io; - return 301 https://prod.vuestorefront.io$request_uri; -} - -server { - listen 443 ssl; - server_name prod.vuestorefront.io http2; - - ssl on; - - ssl_certificate /etc/nginx/ssl/prod.vuestorefront.io.chained.crt; - ssl_certificate_key /etc/nginx/ssl/prod.vuestorefront.io.key; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA; - ssl_ecdh_curve secp384r1; - ssl_session_timeout 10m; - ssl_session_cache shared:SSL:10m; - ssl_session_tickets off; - ssl_stapling on; - ssl_stapling_verify on; - resolver 8.8.8.8 8.8.4.4 valid=300s; - resolver_timeout 5s; - - ssl_dhparam /etc/nginx/ssl/dhparam.pem; - - add_header Strict-Transport-Security "max-age=31536000" always; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - - gzip on; - gzip_proxied any; - gzip_types - text/css - text/javascript - application/javascript - application/json - text/xml - text/json; - - location / { - proxy_pass http://localhost:3000/; - } - - location /assets/ { - proxy_pass http://localhost:3000/assets/; - } - - location /api/ { - proxy_pass http://localhost:8080/api/; - } - - location /graphql { - proxy_pass http://localhost:8080/graphql; - } - - location /img/ { - proxy_pass http://localhost:8080/img/; - } -} diff --git a/docs/guide/installation/production-setup.md b/docs/guide/installation/production-setup.md deleted file mode 100644 index bd85d6f5ba..0000000000 --- a/docs/guide/installation/production-setup.md +++ /dev/null @@ -1,457 +0,0 @@ -# Production setup - -If you’d like to start developing sites using Vue Storefront, you should start with the [Installation guide](linux-mac.md). For development purposes, you'll likely use the `yarn install` sequence, which will set up Vue Storefront locally using the automated installer and prepared Docker images for having Elasticsearch and Redis support. - -Development mode means you're using a node.js-based server as HTTP service and running the app on the `3000` TCP port. As it's great for local testing, it's not recommended to use the installer and direct-user access to node.js in production configurations. - -## Production setup: Bare VPS - -To run Vue Storefront in production mode without Docker/Kubernetes, you'll need the Virtual Private Server with `root` access (for setup purposes). We assume that you're using `Debian GNU Linux` in the following steps. - -Assumptions for the rest of this tutorial: - -- You have root access to a Debian Linux machine. -- We'll be using the default local ports `3000` for [`vue-storefront`](https://github.com/vuestorefront/vue-storefront) and `8080` for [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api); the ports **should not be exposed**, as they will be hidden behind **NGINX proxy**. -- We're using **prod.vuestorefront.io** as a domain name. Please replace it with your host URL address. -- We assume that you have an SSL certificate for **prod.vuestorefront.io** (or your domain, of course). SSL encryption is required for PWA and Service Workers. - -General Solution Architecture: -_USER -> NGINX proxy -> vue-storefront / vue-storefront-api_ - -We'll be hiding the `vue-storefront` and `vue-storefront-api` services behind the NGINX proxy. You can use NGINX for caching proxy, but in our case, it will just forward the requests without cache (as VS is pretty fast and caching is not required). The key features we're using are: SSL encryption, gzip-encoding, and URL routing (to merge `vue-storefront` and `vue-storefront-api` services under one domain). - -### Prerequisites - -:::tip NOTE -This guide is tested on _Ubuntu 18.04_ and other major distros. The list will be updated continuously. -::: - -Vue Storefront requires **Elasticsearch** and the **Redis server** to be installed. By default, in the development mode, both dependencies are provided with the `docker-compose.yml` Docker images. However, for production purposes, we recommend installing the servers natively. - -For the purpose of this tutorial, we will use default packages distributed along with Debian operating systems, without any security hardening, config hardening operations. - -**Please make sure** that your security/devops team has taken a look at the configs you're using and do harden the server configuration before launching your app publicly! - -First, let's create the user (as root user): - -```bash -mkdir /home/www -useradd -m -d /home/www/vuestorefront vuestorefront -``` - -Then install the Elasticsearch and Redis (as root user): - -```bash -apt-get update -apt-get install curl -apt-get install git - -curl -sL https://deb.nodesource.com/setup_10.x | bash - -apt-get install -y nodejs -npm install -g yarn - -apt-get install redis-server - -apt-get install openjdk-8-jre -curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.16.deb -dpkg -i elasticsearch-5.6.16.deb -/etc/init.d/elasticsearch start - -apt-get install imagemagick -apt-get install nginx -``` - -### Nginx - -We decided to use **NGINX** as an HTTP proxy, exposed in front of the users, handling the network traffic, and dealing with the `vue-storefront` and the `vue-storefront-api` apps as a backend. - -This is a general rule of setting up a production node.js app, which gives you a lot of flexibility regarding the SSL, gzip compression, URL routing, and other techniques to be configured without additional hassle. You can use any other proxy server for this purpose, such as Varnish or Apache2 + mod_proxy. - -Some additional materials: - -- [How to setup production Node.js app in the Digital Ocean environment (Ubuntu 16)](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04) -- [How to setup NGINX reverse proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) - -#### NGINX configuration - -:::warning OPTIONAL -In case you have already set up SSL on your own domain, please skip to [the next step](#now-you-can-run-the-nginx-with-ssl-applied). -::: - -Create NGINX config file from the template (please run as a root user): - -```bash -curl https://raw.githubusercontent.com/DivanteLtd/vue-storefront/develop/docs/guide/installation/prod.vuestorefront.io > /etc/nginx/sites-available/prod.vuestorefront.io -ln -s /etc/nginx/sites-available/prod.vuestorefront.io /etc/nginx/sites-enabled/prod.vuestorefront.io -``` - -You need to replace two lines of the configuration you just downloaded with the actual path to your certificate files with its key. - -**Install the SSL certificate** - -SSL secured connection is a ___must-have requisite___ for PWA and service-workers by its spec. - -In this guide, we will use free ___Let's Enrypt___ service to get the SSL certificate for the sake of simplicity. -In order to use ___Let's Encrypt___, you need to install `certbot`, the guide is [here](https://certbot.eff.org/lets-encrypt/ubuntubionic-nginx). - -:::tip NOTE -As sure as it gets, you can use any other SSL service provider of your choice which best suits your need. It's not free most of time though. -::: - -Once `certbot` installation is done, run the following command to get the certificate information. -```bash -certbot certificates -``` - -The result would be like as follows : -```bash -Saving debug log to /var/log/letsencrypt/letsencrypt.log - -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Found the following certs: - Certificate Name: prod.vuestorefront.io - Domains: prod.vuestorefront.io - Expiry Date: 2020-04-19 22:47:19+00:00 (VALID: 89 days) - Certificate Path: /etc/letsencrypt/live/prod.vuestorefront.io/fullchain.pem - Private Key Path: /etc/letsencrypt/live/prod.vuestorefront.io/privkey.pem -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -Replace the paths for certificate and its key in the `/etc/nginx/sites-available/prod.vuestorefront.io` with the info above as follows : -```bash{5,6} -# ... abridged - - ssl on; - - ssl_certificate /etc/letsencrypt/live/prod.vuestorefront.io/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/prod.vuestorefront.io/privkey.pem; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - -# abridged ... -``` - -:::tip NOTE -```bash -server { - listen 80; - server_name prod.vuestorefront.io; - return 301 https://prod.vuestorefront.io$request_uri; -} -``` - -This section runs the standard `http://prod.vuestorefront.io` and creates a wildcard redirect from `http://prod.vuestorefront.io/*` -> `https://prod.vuestorefront.io/*`. -::: - -#### Now you can run the NGINX with SSL applied : - -```bash -/etc/init.d/nginx restart -``` - -:::tip TIP -After you're done with the installation, once again open `/etc/nginx/sites-available/prod.vuestorefront.io` and add `http2` after the `listen 443 ssl` (but before the semicolon!). It should look like this: -``` -server { - listen 443 ssl http2; - server_name prod.vuestorefront.io; - - ssl on; - (...the rest of the config...) -} -``` - -`http2` is not required, but can optimize the experience for browsers that support it. More details on http/2 can be found at [here](https://developers.google.com/web/fundamentals/performance/http2/) -::: - -#### Some notes on the provided nginx config - -Here we go with the SSL settings based on our best experiences from the past. Please read details in the - [NGINX documentation](http://nginx.org/en/docs/http/configuring_https_servers.html) if you like ;) - -``` - gzip on; - gzip_proxied any; - gzip_types - text/css - text/javascript - text/xml - application/javascript - application/json - text/json; -``` - -Vue Storefront SSR responses contain the full markup and JSON objects included for speeding up the first page view. Unfortunately, with significant JS bundle sizes, it can generate a significant network load. We're optimizing it by using gzip compression server-side. - -``` -location / { - proxy_pass http://localhost:3000/; -} -``` - -We're using [`proxy_pass`](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) from the `ngx_http_proxy_module` to pass content from the Vue Storefront node.js server. The content should be available under ___https://prod.vuestorefront.io/___ according to the configuration. - -``` -location /assets/ { - proxy_pass http://localhost:3000/assets/; -} -``` -It just works the same way with sub directories too. - -``` -location /api/ { - proxy_pass http://localhost:8080/api/; -} -``` - -The next proxy section is used for serving the API. It's a proxy to [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api) app running on `8080` port (default config). API will be available under ___https://prod.vuestorefront.io/api___ - -``` -location /img/ { - proxy_pass http://localhost:8080/img/; -} -``` - -The last proxy is used for serving product images. It's a proxy to the [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api) app running on `8080` port (default config). Images will be available under ___https://prod.vuestorefront.io/img___ - -#### Apache2 configuration - -In case you are already using the apache2 web server in your environment as well and can't (or don't want) to use NGINX, you can also set up apache2 as a reverse proxy instead of NGINX. This is done by adding this block to your apache2 virtual host. - -``` -ProxyRequests off - -ProxyPass /api/ http://localhost:8080/api/ -ProxyPassReverse /api http://localhost:8080/api/ - -ProxyPass /img/ http://localhost:8080/img/ -ProxyPassReverse /img http://localhost:8080/img/ - -ProxyPass /assets/ http://localhost:3000/assets/ -ProxyPassReverse /assets http://localhost:3000/assets/ - -ProxyPass / http://localhost:3000/ -ProxyPassReverse / http://localhost:3000/ -``` - -You also need to enable [mod_proxy](https://httpd.apache.org/docs/current/mod/mod_proxy.html) for this. - -### Vue Storefront and Vue Storefront API - -After you have the NGINX set up, you should get a `502 error` when accessing the https://prod.vuestorefront.io. This is totally fine! We just missed the most important step, which is running backend services that will power up our installation. Now NGINX is trying to connect to `localhost:3000` for `vue-storefront` and `localhost:8080` for `vue-storefront-api` without any success. - -We created a Linux user called `vuestorefront` and go to `/home/www/vuestorefront` which is our home directory. - -You need to clone the `vue-storefront` and the `vue-storefront-api` repos accordingly with the following commands: - -```bash -su vuestorefront -cd /home/www/vuestorefront -git clone --single-branch --branch master https://github.com/vuestorefront/vue-storefront.git -git clone https://github.com/vuestorefront/vue-storefront-api.git -``` - -Then, you will need to install the required node packages: - -```bash -cd /home/www/vuestorefront/vue-storefront-api -yarn install -``` - -... and ... - -```bash -cd /home/www/vuestorefront/vue-storefront -yarn install -``` - -It may take a few minutes. The phantomjs dependency requires bzip2 to be installed. Once the modules are installed, we can set configuration files for both services. - -#### Vue Storefront configuration - -The full configuration files are available here to download: [vue-storefront](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/installation/vue-storefront/config) and [vue-storefront-api](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/installation/vue-storefront-api/config). - -Please create the `vue-storefront-api/config/local.json` and `vue-storefront/config/local.json` files accordingly by copying default.json into local.json by using `cp` command: -```bash -cp /home/www/vuestorefront/vue-storefront-api/config/default.json /home/www/vuestorefront/vue-storefront-api/config/local.json -``` - -...and ... -```bash -cp /home/www/vuestorefront/vue-storefront/config/default.json /home/www/vuestorefront/vue-storefront/config/local.json -``` - -Please find the key sections of the `vue-storefront/config/local.json` file described in below: - -```json -"elasticsearch": { - "httpAuth": "", - "host": "https://prod.vuestorefront.io/api/catalog", - "index": "vue_storefront_catalog" -}, -"storeViews": { - "mapStoreUrlsFor": [ - "de", - "it" - ], - "multistore": true, - "de": { - "elasticsearch": { - "httpAuth": "", - "host": "https://prod.vuestorefront.io/api/catalog", - "index": "vue_storefront_catalog_de" - } - }, - "it": { - "elasticsearch": { - "httpAuth": "", - "host": "https://prod.vuestorefront.io/api/catalog", - "index": "vue_storefront_catalog_it" - } - } -}, -``` - -We're setting up the product's endpoint to https://prod.vuestorefront.io/api/catalog (please use your domain accordingly of course). As you may notice, the `/api` url is proxied by the NGINX to `localhost:8080` - our `vue-storefront-api` instance. - -```json -"cart": { - "synchronize": true, - "synchronize_totals": true, - "create_endpoint": "https://prod.vuestorefront.io/api/cart/create?token={{token}}", - "updateitem_endpoint": "https://prod.vuestorefront.io/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "https://prod.vuestorefront.io/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "https://prod.vuestorefront.io/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "https://prod.vuestorefront.io/api/cart/totals?token={{token}}&cartId={{cartId}}", - "paymentmethods_endpoint": "https://prod.vuestorefront.io/api/cart/payment-methods?token={{token}}&cartId={{cartId}}", - "shippingmethods_endpoint": "https://prod.vuestorefront.io/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}", - "shippinginfo_endpoint": "https://prod.vuestorefront.io/api/cart/shipping-information?token={{token}}&cartId={{cartId}}", - "collecttotals_endpoint": "https://prod.vuestorefront.io/api/cart/collect-totals?token={{token}}&cartId={{cartId}}", - "deletecoupon_endpoint": "https://prod.vuestorefront.io/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}", - "applycoupon_endpoint": "https://prod.vuestorefront.io/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}" - }, -``` - -There are 27 more instances of `prod.vuestorefront.io` to be replaced with your production URL address in this file. Please just do so :) - -If you want to see how the local.json should look like after your modifications, the configs we prepared for `prod.vuestorefront.io` are available under: - -[vue-storefront local.json](https://raw.githubusercontent.com/DivanteLtd/vue-storefront/develop/docs/guide/installation/vue-storefront-api/config/local.json) - -[vue-storefront-api local.json](https://raw.githubusercontent.com/DivanteLtd/vue-storefront/develop/docs/guide/installation/vue-storefront/config/local.json) - -#### Vue Storefront API configuration - -The [provided vue-storefront-api configuration](https://github.com/vuestorefront/vue-storefront/blob/develop/docs/guide/installation/vue-storefront-api/config) requires almost no changes. - -The only lines you need to alter are: - -```json -"imageable": { - "namespace": "", - "maxListeners": 512, - "imageSizeLimit": 1024, - "timeouts": { - "convert": 5000, - "identify": 100, - "download": 1000 - }, - "whitelist": { - "allowedHosts": [ - ".*divante.pl", - ".*vuestorefront.io" - ] - }, - "keepDownloads": true, - "maxDownloadCacheSize": 1000, - "tmpPathRoot": "/tmp" -}, -"elasticsearch": { - "host": "localhost", - "port": "9200", - "indices": [ - "vue_storefront_catalog", - "vue_storefront_catalog_it", - "vue_storefront_catalog_de" - ] -} -``` - -You should put here the `allowedHosts` for the _imageable_ node to download the product images. The domain name points to the Magento 2 instance where images are sourced. In this example, Magento 2 is running under **http://demo-magento2.vuestorefront.io**. - - #### Using your own Magento 2 instance - - In this case, you'll have to update `magento2` config node with the correct hostname in the vue-storefront-api config file. To get all necessary Magento 2 API data for the `api` node, navigate to SYSTEM -> Extensions -> Integrations in the Magento 2 Admin. -node, navigate to SYSTEM -> Extensions -> Integrations in the Magento 2 Admin. - -- Click Add New Integration -- Check the necessary permissions (check Catalog, Sales, My Account, and Carts on the API Permissions tab ) -- Click Activate -- Copy necessary keys, secrets, and tokens into the api section of vue-storefront-api config - -#### Build VS - -Before we can run Vue Storefront and Vue Storefront API, we should build it in production mode. To do so, please execute the following commands: - -```bash -cd /home/www/vuestorefront/vue-storefront/ -yarn build -``` - -```bash -cd /home/www/vuestorefront/vue-storefront-api/ -yarn build -``` - -#### Data import - -Vue Storefront needs to have some data in the Elasticsearch to properly display products and categories. Of course, you can install [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) and configure the data pump to synchronize and update the Elasticsearch index whenever data is being changed in Magento. For the purpose of this tutorial, we'll just restore the data from the JSON file. - -You can easily dump your current VS index using the following command (your local installation): - -```bash -cd vue-storefront-api -rm var/catalog.json -yarn dump -``` - -Now in the `var/catalog.json` you have your current database dump. Please transfer this file to the server—for example, using the following ssh command: - -```bash -ssh vuestorefront@prod.vuestorefront.io rm ~/vue-storefront-api/var/catalog.json -scp vue-storefront-api/var/catalog.json vuestorefront@prod.vuestorefront.io:~/vue-storefront-api/var/catalog.json -``` - -Then, after logging in to your `prod.vuestorefront.io` server as a `vuestorefront`, you can run the following command to import the data: - -```bash -cd vue-storefront-api -yarn db new -yarn restore2main -yarn db rebuild -``` - -#### Running the Vue Storefront and Vue Storefront API - -After everything set, you can just start the `vue-storefront` and `vue-storefront-api`: - -```bash -cd vue-storefront-api -yarn start -cd vue-storefront -yarn start -``` - -Both applications use [`PM2` process manager](https://pm2.keymetrics.io/docs/usage/process-management/) in production mode (`start` commands) to manage and respawn the node.js processes when needed. - -## Cache Strategies - -### Varnish cache for VSF -_Vue Storefront_ has multiple layers of cache, and the forefront cache is _Varnish_ which serves a request just as fast as a static HTML page once it's hit. You can install it from [here](https://github.com/new-fantastic/vsf-cache-varnish). - -### Vue Storefront Proxy -_Vue Storefront_ can be set up with [OpenResty](http://openresty.org/en/) based reverse proxy serving cached pages from Redis without Vue StoreFront (VSF or VSF API) calls, using LUA. [Here](https://github.com/ClickAndMortar/docker/tree/master/vue-storefront/proxy) is the github repo. - -## Production setup - using Docker / Kubernetes - -To be prepared. diff --git a/docs/guide/installation/theme.md b/docs/guide/installation/theme.md deleted file mode 100644 index bd766939be..0000000000 --- a/docs/guide/installation/theme.md +++ /dev/null @@ -1,55 +0,0 @@ -# Installing theme - -The easiest way to create your own theme is to create a copy from one of our official themes. First of all you need to install official theme. - -## Install official theme - -### Use vsf-cli - -If you have already setup VSF and you just want to install the theme, then you can use [vsf-cli](https://www.npmjs.com/package/%40vue-storefront/cli). First install vsf-cli: - -```sh -// You have to us 0.2.1 version of VSF CLI for VSF1 -$ npm i -g @vue-storefront/cli@0.2.1 -// or via yarn -$ yarn global add @vue-storefront/cli@0.2.1 -``` - -Then run command in your project directory: -```sh -$ vsf init:theme -``` - -Select theme: -```sh -$ ? Select theme for Vue Storefront (Use arrow keys) -$ ❯ Capybara - based on Storefront UI - Default -``` - -Select theme version: -```sh -$ ? Select theme version (Use arrow keys) -$ ❯ Stable version (recommended for production) - In development branch (could be unstable!) -``` - -After that you should have theme in `src/themes/{themeName}`. Now what you need to do is [Create your theme](#create-your-theme). - -### Manual installation - -Each theme has its own Readme file on github repository. What you need to do is to follow installation steps described in Readme and then move to [Create your theme](#create-your-theme). - -## Create your theme - -After official theme installation, you need to copy it and place it in `src/themes/{themeName}`. Then you need to change its name in its `package.json` file, change the active theme in `config/local.json`: - -```json - "theme": "@vue-storefront/theme-myThemeName", -``` - -and run `yarn` to make [Lerna](https://github.com/lerna/lerna) linking (which we use for monorepos). - -## Our official themes: -- [Capybara](https://github.com/vuestorefront/vsf-capybara) -- [Default](https://github.com/vuestorefront/vsf-default) diff --git a/docs/guide/installation/vue-storefront-api/config/local.json b/docs/guide/installation/vue-storefront-api/config/local.json deleted file mode 100644 index 0269f1b0ae..0000000000 --- a/docs/guide/installation/vue-storefront-api/config/local.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "port": 8080, - "objHashSecret": "__SECRET_CHANGE_ME__", - "bodyLimit": "100kb", - "corsHeaders": [ - "Link" - ], - "kue": {}, - "tax": { - "defaultCountry": "PL", - "defaultRegion": "", - "calculateServerSide": true - }, - "platform": "magento2", - "registeredExtensions": [ - "mailchimp-subscribe" - ], - "extensions": { - "mailchimp": { - "listId": "e06875a7e1", - "apiKey": "a9a3318ea7d30f5c5596bd4a78ae0985-us3", - "apiUrl": "https://us3.api.mailchimp.com/3.0" - } - }, - "magento2": { - "url": "http://demo-magento2.vuestorefront.io/", - "imgUrl": "http://demo-magento2.vuestorefront.io/media/catalog/product", - "magentoUserName": "", - "magentoUserPassword": "", - "httpUserName": "", - "httpUserPassword": "", - "api": { - "url": "http://demo-magento2.vuestorefront.io/rest", - "consumerKey": "byv3730rhoulpopcq64don8ukb8lf2gq", - "consumerSecret": "u9q4fcobv7vfx9td80oupa6uhexc27rb", - "accessToken": "040xx3qy7s0j28o3q0exrfop579cy20m", - "accessTokenSecret": "7qunl3p505rubmr7u1ijt7odyialnih9" - } - }, - "imageable": { - "namespace": "", - "maxListeners": 512, - "imageSizeLimit": 1024, - "timeouts": { - "convert": 5000, - "identify": 100, - "download": 1000 - }, - "whitelist": { - "allowedHosts": [ - ".*divante.pl", - ".*vuestorefront.io" - ], - "trustedHosts": [ - ".*divante.pl", - ".*vuestorefront.io" - ] - }, - "keepDownloads": true, - "maxDownloadCacheSize": 1000, - "tmpPathRoot": "/tmp" - }, - "elasticsearch": { - "host": "localhost", - "port": "9200", - "indices": [ - "vue_storefront_catalog", - "vue_storefront_catalog_it", - "vue_storefront_catalog_de" - ] - } -} \ No newline at end of file diff --git a/docs/guide/installation/vue-storefront/config/local.json b/docs/guide/installation/vue-storefront/config/local.json deleted file mode 100644 index d1f1407318..0000000000 --- a/docs/guide/installation/vue-storefront/config/local.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "elasticsearch": { - "httpAuth": "", - "host": "https://prod.vuestorefront.io/api/catalog", - "index": "vue_storefront_catalog" - }, - "storeViews": { - "mapStoreUrlsFor": [ - "de", - "it" - ], - "multistore": true, - "de": { - "elasticsearch": { - "httpAuth": "", - "host": "https://prod.vuestorefront.io/api/catalog", - "index": "vue_storefront_catalog_de" - } - }, - "it": { - "elasticsearch": { - "httpAuth": "", - "host": "https://prod.vuestorefront.io/api/catalog", - "index": "vue_storefront_catalog_it" - } - } - }, - "cart": { - "synchronize": true, - "synchronize_totals": true, - "create_endpoint": "https://prod.vuestorefront.io/api/cart/create?token={{token}}", - "updateitem_endpoint": "https://prod.vuestorefront.io/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "https://prod.vuestorefront.io/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "https://prod.vuestorefront.io/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "https://prod.vuestorefront.io/api/cart/totals?token={{token}}&cartId={{cartId}}", - "paymentmethods_endpoint": "https://prod.vuestorefront.io/api/cart/payment-methods?token={{token}}&cartId={{cartId}}", - "shippingmethods_endpoint": "https://prod.vuestorefront.io/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}", - "shippinginfo_endpoint": "https://prod.vuestorefront.io/api/cart/shipping-information?token={{token}}&cartId={{cartId}}", - "collecttotals_endpoint": "https://prod.vuestorefront.io/api/cart/collect-totals?token={{token}}&cartId={{cartId}}", - "deletecoupon_endpoint": "https://prod.vuestorefront.io/api/cart/delete-coupon?token={{token}}&cartId={{cartId}}", - "applycoupon_endpoint": "https://prod.vuestorefront.io/api/cart/apply-coupon?token={{token}}&cartId={{cartId}}&coupon={{coupon}}" - }, - "products": { - "preventConfigurableChildrenDirectAccess": true, - "alwaysSyncPlatformPricesOver": true, - "clearPricesBeforePlatformSync": true, - "waitForPlatformSync": false, - "endpoint": "https://prod.vuestorefront.io/api/product", - "defaultFilters": [ - "color", - "size", - "price" - ] - }, - "orders": { - "endpoint": "https://prod.vuestorefront.io/api/order" - }, - "images": { - "baseUrl": "https://prod.vuestorefront.io/img/" - }, - "users": { - "endpoint": "https://prod.vuestorefront.io/api/user", - "history_endpoint": "https://prod.vuestorefront.io/api/user/order-history?token={{token}}", - "resetPassword_endpoint": "https://prod.vuestorefront.io/api/user/reset-password", - "changePassword_endpoint": "https://prod.vuestorefront.io/api/user/change-password?token={{token}}", - "login_endpoint": "https://prod.vuestorefront.io/api/user/login", - "create_endpoint": "https://prod.vuestorefront.io/api/user/create", - "me_endpoint": "https://prod.vuestorefront.io/api/user/me?token={{token}}", - "refresh_endpoint": "https://prod.vuestorefront.io/api/user/refresh" - }, - "stock": { - "endpoint": "https://prod.vuestorefront.io/api/stock" - }, - "tax": { - "defaultCountry": "PL", - "defaultRegion": "", - "calculateServerSide": true - }, - "i18n": { - "defaultCountry": "US", - "defaultLanguage": "EN" - }, - "mailchimp": { - "endpoint": "https://prod.vuestorefront.io/api/ext/mailchimp-subscribe/subscribe" - }, - "registeredExtensions": [ - "custom_extension", - "mailchimp-subscribe", - "google-analytics" - ], - "demomode": true, - "analytics": { - "id": "UA-108235765-2" - } -} \ No newline at end of file diff --git a/docs/guide/installation/windows.md b/docs/guide/installation/windows.md deleted file mode 100644 index e433945fcd..0000000000 --- a/docs/guide/installation/windows.md +++ /dev/null @@ -1,93 +0,0 @@ -# Installing on Windows - -Vue Storefront is based on open-source technologies, which should (in theory) work perfectly well on most of the leading operating systems. However, we're developing the project using MacOS and Linux machines. - -## Requirements - -1. Please download [Docker for Windows](https://store.docker.com/editions/community/docker-ce-desktop-windows) and install it on your machine. [More Information](https://blog.jayway.com/2017/04/19/running-docker-on-bash-on-windows/). -2. Install [LTS version of Node.js for Windows](https://nodejs.org/en/download/). -3. Instal [Yarn](https://yarnpkg.com/en/docs/install). -4. You can use any editor for development, but we're using [Visual Studio Code](https://code.visualstudio.com/) which is cool, free, and very JS-friendly! -5. You can [download Github Desktop](https://desktop.github.com/) to get access not only for fancy UI, but for the git toolset itself. - -## Installation of vue-storefront-api - -1. Open your command line of choice with [Git](https://git-scm.com/download/win) access or use Github desktop. -2. Clone the [vue-storefront-api](https://github.com/vuestorefront/vue-storefront-api) project: - -```bash -git clone https://github.com/vuestorefront/vue-storefront-api.git vue-storefront-api -``` - -3. Go to `vue-storefront-api` in dir: - -```bash -cd vue-storefront-api -``` - -4. Install dependencies: - -```bash -yarn install -``` - -5. Run Docker containers required by `vue-storefront-api`: - -```bash -docker-compose up -``` - -This step can take a few minutes. - -Note: If it appears that docker-compose is hanging, try opening a new terminal and continue to the next step using that terminal. Allow docker-compose to continue running in the background. - -6. Restore products database and run the latest migrations. - -```bash -yarn restore -yarn migrate -``` - -7. Copy `config/default.json` to `config/local.json`. -8. Run API: - -```bash -yarn dev -``` - -## Installation of vue-storefront - -1. Open your command line of choice with [Git](https://git-scm.com/download/win) access or use Github desktop. -2. Clone the [vue-storefront](https://github.com/vuestorefront/vue-storefront) project: - -```bash -git clone --single-branch --branch master https://github.com/vuestorefront/vue-storefront.git vue-storefront -``` - -3. Go to `vue-storefront` directory: - -``` -cd vue-storefront -``` - -4. Install dependencies: - -```bash -yarn install -``` - -5. Copy `config/default.json` to `config/local.json` - -:::tip NOTE -We're using the powerful node.js library for config files. Check the docs to learn more about it: [https://github.com/lorenwest/node-config](https://github.com/lorenwest/node-config) -::: - -6. Next [install theme](theme.md) - -7. Run Vue Storefront Server: - -```bash -yarn dev -``` - -Now you should have Vue Storefront running on `localhost:3000`. diff --git a/docs/guide/integrations/direct-prices-sync.md b/docs/guide/integrations/direct-prices-sync.md deleted file mode 100644 index 1db1b88f8d..0000000000 --- a/docs/guide/integrations/direct-prices-sync.md +++ /dev/null @@ -1,31 +0,0 @@ -# Direct prices sync with Magento - -As you may have noticed in our architecture, we're synchronizing the whole product catalog with our Elasticsearch data store. There are some edge cases among industries where this kind of synchronization may lead to non-invalidated prices and stock quantity problems. - -Regarding these challenges, we've introduced a special mode that allows vue-storefront to download the prices (in)directly from CMS (Magento or other). -To enable real-time prices sync, please change the following lines in the `config/local.json` - -```json - "products": { - "preventConfigurableChildrenDirectAccess": true, - "alwaysSyncPlatformPricesOver": true, - "clearPricesBeforePlatformSync": true, - "waitForPlatformSync": false, - "endpoint": "http://localhost:8080/api/product" - }, -``` - -This means that each time vue-storefront is downloading the product feed from Elasticsearch, it will call the `vue-storefront-api` unified proxy method to get the prices in real time from Magento. - -To use this feature, you should also modify `config/local.json` within your `vue-storefront-api` installation: - -```json - "tax": { - "defaultCountry": "PL", - "defaultRegion": "", - "calculateServerSide": true, - "alwaysSyncPlatformPricesOver": true - }, -``` - -_Important note_: To use the dynamic Magento 2 prices sync, you should restore the database using `yarn restore` within the `vue-storefront-api` or re-run the `mage2vuestorefront` product sync, because an "ID" field has been added to the `configurable_children` products and it's required for the prices sync. diff --git a/docs/guide/integrations/integrations.md b/docs/guide/integrations/integrations.md deleted file mode 100644 index 961124defa..0000000000 --- a/docs/guide/integrations/integrations.md +++ /dev/null @@ -1,11 +0,0 @@ -# Existing integrations - -Here is a short list of existing Vue Storefront integrations with links to their repositories. - -- [Vue Storefront + Magento](https://github.com/vuestorefront/mage2vuestorefront) -- [Vue Storefront + Magento 1.9](https://github.com/divanteLtd/magento1-vsbridge) -- [Vue Storefront + with Magento checkout](https://github.com/divanteLtd/magento2-external-checkout) -- [Vue Storefront + Pimcore](https://github.com/divanteLtd/pimcore2vuestorefront) -- [Magento2 Product Reviews](https://github.com/vuestorefront/vue-storefront/blob/develop/doc/Reviews.md) -- [Vue Storefront 3rd party platforms integration boilerplate](https://github.com/divanteLtd/vue-storefront-integration-boilerplate) - This is the API you should implement to integrate a third-party platform. -- [Vue Storefront + Fresh Relevance](https://github.com/TriggeredMessaging/vsf-freshrelevance) diff --git a/docs/guide/integrations/multistore.md b/docs/guide/integrations/multistore.md deleted file mode 100644 index cc1d86e6a4..0000000000 --- a/docs/guide/integrations/multistore.md +++ /dev/null @@ -1,422 +0,0 @@ -# Multistore Magento 2 support - -Vue Storefront supports Magento Multistore installations - -## Limitations of Multistore in Vue Storefront 1 -- Multidomain support is possible to achieve right now but it requires tricky changes in core. It will be available in refactored Multistore which should be released in 1.13/1.14 -- Different theme for different store is not supported right now. If you want to achieve that - the easiest way would be to just run 2 instances with different configs. If you want to change only parts of theme based on current store then you can easily achieve it with helpers described below. -- No support for multicurrency per store - -## Multiwebsite indexing - -Multiwebsite support starts with the Elasticsearch indexing. Basically, each store has its own Elasticsearch index and should be populated using the [Magento2 VSBridge Indexer](https://github.com/vuestorefront/magento2-vsbridge-indexer) tool. - -:::warning - -There is legacy node indexer which has been deprecated. You should not use `mage2vuestorefront` anymore in production! The only acceptable reason to using it - is to provide data to your local Elasticsearch cluster from development instance. -::: - -The simplest script to provide data to your local Elasticsearch: - -```bash -export TIME_TO_EXIT=2000 -export MAGENTO_CONSUMER_KEY=byv3730rhoulpopcq64don8ukb8lf2gq -export MAGENTO_CONSUMER_SECRET=u9q4fcobv7vfx9td80oupa6uhexc27rb -export MAGENTO_ACCESS_TOKEN=040xx3qy7s0j28o3q0exrfop579cy20m -export MAGENTO_ACCESS_TOKEN_SECRET=7qunl3p505rubmr7u1ijt7odyialnih9 - -echo 'German store - de' -export MAGENTO_URL=http://demo-magento2.vuestorefront.io/rest/de -export INDEX_NAME=vue_storefront_catalog_de - -node --harmony cli.js categories --partitions=1 --removeNonExistient=true -node --harmony cli.js productcategories --partitions=1 -node --harmony cli.js attributes --partitions=1 --removeNonExistient=true -node --harmony cli.js taxrule --partitions=1 --removeNonExistient=true -node --harmony cli.js products --partitions=1 --removeNonExistient=true - -echo 'Italian store - it' -export MAGENTO_URL=http://demo-magento2.vuestorefront.io/rest/it -export INDEX_NAME=vue_storefront_catalog_it - -node --harmony cli.js categories --partitions=1 --removeNonExistient=true -node --harmony cli.js productcategories --partitions=1 -node --harmony cli.js attributes --partitions=1 --removeNonExistient=true -node --harmony cli.js taxrule --partitions=1 --removeNonExistient=true -node --harmony cli.js products --partitions=1 --removeNonExistient=true - -echo 'Default store - in our case United States / en' -export MAGENTO_URL=http://demo-magento2.vuestorefront.io/rest -export INDEX_NAME=vue_storefront_catalog - -node --harmony cli.js categories --partitions=1 --removeNonExistient=true -node --harmony cli.js productcategories --partitions=1 -node --harmony cli.js attributes --partitions=1 --removeNonExistient=true -node --harmony cli.js taxrule --partitions=1 --removeNonExistient=true -node --harmony cli.js products --partitions=1 --removeNonExistient=true -``` - -As you can see, it's just an **IT** or **DE** store code that is added to the base Magento 2 REST API URLs that makes the difference, and then the **INDEX_NAME** set to the dedicated index name. - -In the result, you should get: - -- _vue_storefront_catalog_it_ - populated with the "it" store data -- _vue_storefront_catalog_de_ - populated with the "de" store data -- _vue_storefront_catalog_ - populated with the "default" store data - -Then, to use these indices in Vue Storefront, you should index the database schema using the `vue-storefront-api` db tool (use only if using mage2vuestorefront!): - -```bash -yarn db rebuild -- --indexName=vue_storefront_catalog_it -yarn db rebuild -- --indexName=vue_storefront_catalog_de -yarn db rebuild -- --indexName=vue_storefront_catalog -``` - -## Vue Storefront and Vue Storefront API configuration - -After this sequence of command, you may add the available ES index to your `vue-storefront-api/config/local.json`: - -```json -{ - "server": { - "host": "localhost", - "port": 8080 - }, - "esHost": "localhost:9200", - "esIndexes": [ - "vue_storefront_catalog", - "vue_storefront_catalog_de", - "vue_storefront_catalog_it" - ], - "availableStores": [ - "de", - "it" - ], -// ... -``` - -The last thing is to change the `vue-storefront/config/local.json` to configure the storeViews that are available. - -```json -"storeViews": { - "multistore": false, - "mapStoreUrlsFor": ["de", "it"], - "de": { - "storeCode": "de", - "storeId": 3, - "name": "German Store", - "url": "/de", - "elasticsearch": { - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog_de" - }, - "tax": { - "defaultCountry": "DE", - "defaultRegion": "", - "sourcePriceIncludesTax": false, - "calculateServerSide": true, - "userGroupId": null, - "useOnlyDefaultUserGroupId": false, - "deprecatedPriceFieldsSupport": true, - "finalPriceIncludesTax": false - }, - "i18n": { - "defaultCountry": "DE", - "defaultLanguage": "DE", - "availableLocale": [ - "de-DE" - ], - "defaultLocale": "de-DE", - "currencyCode": "EUR", - "currencySign": "€", - "currencyDecimal": "", - "currencyGroup": "", - "fractionDigits": 2, - "priceFormat": "{sign}{amount}", - "dateFormat": "HH:mm D/M/YYYY", - "fullCountryName": "Deutschland", - "fullLanguageName": "German", - "bundleAllStoreviewLanguages": false - } - }, - "it": { - "storeCode": "it", - "storeId": 4, - "name": "Italian Store", - "url": "/it", - "elasticsearch": { - "host": "localhost:8080/api/catalog", - "index": "vue_storefront_catalog_it" - }, - "tax": { - "defaultCountry": "IT", - "defaultRegion": "", - "sourcePriceIncludesTax": false, - "calculateServerSide": true, - "userGroupId": null, - "useOnlyDefaultUserGroupId": false, - "deprecatedPriceFieldsSupport": true, - "finalPriceIncludesTax": false - }, - "i18n": { - "defaultCountry": "IT", - "defaultLanguage": "IT", - "availableLocale": [ - "it-IT" - ], - "defaultLocale": "it-IT", - "currencyCode": "EUR", - "currencySign": "€", - "currencyDecimal": "", - "currencyGroup": "", - "fractionDigits": 2, - "priceFormat": "{sign}{amount}", - "dateFormat": "HH:mm D/M/YYYY", - "fullCountryName": "Italy", - "fullLanguageName": "Italian", - "bundleAllStoreviewLanguages": false - } - } -}, -``` - -:::tip -You can find more options available to _multistore_ in [store view](/guide/basics/configuration.html#store-views) section of _Configuration File Explained_. -::: - -After these changes, you'll have a `LanguageSwitcher` component visible on the bottom. - -By default, the language / store is switched by the URL prefix: - -- `http://localhost:3000` is for the default store -- `http://localhost:3000/it` will switch the store to the Italian one -- `http://localhost:3000/de` will switch the store to the German one - -General URL format is: -`http://localhost:3000/{storeCode}` - -The storeCode may be switched by ENV variable set before running `yarn dev` / `yarn start`: - -- `export STORE_CODE=de && yarn dev` will run the shop with the `de` shop loaded - -Another option, useful when using multistore mode with the NGINX/varnish mode, is to set the store code by the `x-vs-store-code` HTTP header. - -## Changing the UI for specific store views - -If you would like to modify the routes or change some particular components regarding the current locale (for example, a different checkout in the German store), please take a look at: `src/themes/default/index.js`: - -```js -export default function(app, router, store) { - // if youre' runing multistore setup this is copying the routed above adding the 'storeCode' prefix to the URLs and the names of the routes - // You can do it on your own and then be able to customize the components used for example for German storeView checkout - // To do so please execlude the desired storeView from the config.storeViews.mapStoreUrlsFor and map the URLs by Your own like: - // { name: 'de-checkout', path: '/checkout', component: CheckoutCustomized }, - router.addRoutes(routes); - setupMultistoreRoutes(config, router, routes); - store.registerModule('ui', UIStore); -} -``` - -Another option is to create a separate theme for a specific storeview. Runtime theme changes are not possible, as themes are compiled in the JS bundles by webpack during the page build process. In that case, you should run separate instances of `vue-storefront` having the proper theme set in the `config/local.json` file. - -## Multi Source Inventory (MSI) support -To support this custom feature you should take care of 2 things. At first please install [Magento2 VSBridge Indexer MSI Extension](https://github.com/divanteLtd/magento2-vsbridge-indexer-msi). Then in `config/local.json` of your VSF-API add part like: -```js -"msi": { - "enabled": true, - "defaultStockId": 1 -}, -``` -Where `defaultStockId` is your default stock ID. In each storeCode, which uses different stock ID you should add part like: -```js -"msi": { - "stockId": 2 -}, -``` - -## Useful _Helpers_ - -### How to get current store view? - -Here is a helper method to get the value of current store view. - -You just need to import `currentStoreView` function from `core/lib/multistore` as example follows: - -```js -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -// ... abridged - return { - currency: currentStoreView().i18n.currencyCode, - value: method.price_incl_tax - } -``` - -`currentStoreView()` returns the object value you set in `local.json` - it is type of StoreView or extended StoreView. -```ts -interface StoreView { - storeCode: string, - extend?: string, - disabled?: boolean, - storeId: any, - name?: string, - url?: string, - appendStoreCode?: boolean, - elasticsearch: { - host: string, - index: string - }, - tax: { - sourcePriceIncludesTax?: boolean, - finalPriceIncludesTax?: boolean, - deprecatedPriceFieldsSupport?: boolean, - defaultCountry: string, - defaultRegion: null | string, - calculateServerSide: boolean, - userGroupId?: number, - useOnlyDefaultUserGroupId: boolean - }, - i18n: { - fullCountryName: string, - fullLanguageName: string, - defaultLanguage: string, - defaultCountry: string, - defaultLocale: string, - currencyCode: string, - currencySign: string, - currencyDecimal: string, - currencyGroup: string, - fractionDigits: number, - priceFormat: string, - dateFormat: string - }, - seo: { - defaultTitle: string - } -} -``` - -### How to remove store code from route - -When you need to remove `storeCode` from route, use `removeStoreCodeFromRoute` as following example : - -```js -import { removeStoreCodeFromRoute } from '@vue-storefront/core/lib/multistore' -// ... abridged - const urlWithoutStorecode1 = removeStoreCodeFromRoute('/gb/home'); // should return '/home` - const urlWithoutStorecode2 = removeStoreCodeFromRoute('gb/home'); // should return 'home` - const urlWithoutStorecode3 = removeStoreCodeFromRoute({ - path: '/gb/home' - }); // should return '/home` -``` - -### Update/append a storeCode to your URL -If you need to append or update `storeCode` query parameter in provided URL you can do it by calling `adjustMultistoreApiUrl` function as following example: - -```js -import { adjustMultistoreApiUrl } from '@vue-storefront/core/lib/multistore' - -// ... abridged -// Let's say current storeCode is `de` -const myUrl1 = adjustMultistoreApiUrl('https://example.com?a=b'); // returns 'https://example.com?a=b&storeCode=de' -const myUrl2 = adjustMultistoreApiUrl('https://example.com?a=b&storeCode=it'); // returns 'https://example.com?a=b&storeCode=de' -``` - -This feature is extra useful when you are sending a request to the VSF-API and you want VSF-API to use endpoint of certain storeCode. - -### Using endpoint of certain storeCode in Vue Storefront API -In `src/api/extensions/example-magento-api/index.js` we have line that creates Magento 2 Client: -```js -const client = Magento2Client(config.magento2.api); -``` - -If you want to support multistore for certain endpoint, you should make it this way: -```js -const client = Magento2Client(multiStoreConfig(config.magento2.api, req)); -``` - -It uses `storeCode` query parameter from the `req` to figure out which store to use. To make it work properly you should also configure different stores in your VSF-API's config. Check this example configuration for `de` and `it` store codes: -```js -"magento2": { - "imgUrl": "http://demo-magento2.vuestorefront.io/media/catalog/product", - "assetPath": "/../var/magento2-sample-data/pub/media", - "api": { - "url": "https://my-magento.com/rest", - "consumerKey": "******", - "consumerSecret": "******", - "accessToken": "******", - "accessTokenSecret": "******" - }, - "api_de": { - "url": "https://my-magento.com/de/rest", - "consumerKey": "******", - "consumerSecret": "******", - "accessToken": "******", - "accessTokenSecret": "******" - }, - "api_it": { - "url": "https://my-magento.com/it/rest", - "consumerKey": "******", - "consumerSecret": "******", - "accessToken": "******", - "accessTokenSecret": "******" - } - }, -``` - -### Localize URL with correct store code -:::tip -Localized Route means denoting store code in URL by convention without a parameter. - -e.g. It's `de` in `https://example.com/de?a=b` -::: - -Method that allows use to do that is `localizedRoute`. Example transformations for `de` as a current storeCode: -```js -localizedRoute('http://example.com/'); // returns http://example.com/de -localizedRoute('/'); // returns /de -localizedRoute('/?a=b'); // returns /de?a=b -localizedRoute('/about'); // returns /de/about -``` - -:::warning -`appendStoreCode` option of the store view configuration should be set to `true` to display store code as tip above -::: - -:::warning -`localizedRoute` is injected to each Vue's instance so you can access it in your component via `this.localizedRoute`. You could also use it in template without additional imports. -::: - -```vue -{{ - page.title -}} -``` - -or - -```vue - -``` - -### How to extract store code from route - -You can extract store code from route as follows : - -```js -import storeCodeFromRoute from '@vue-storefront/core/lib/storeCodeFromRoute' -// abridged -const storeCode = storeCodeFromRoute(currentRoute) -``` - -You should get store code `gb` from route `https://example.com/gb/foo` if storeCode is `gb` \ No newline at end of file diff --git a/docs/guide/integrations/payment-gateway.md b/docs/guide/integrations/payment-gateway.md deleted file mode 100644 index 251569f36f..0000000000 --- a/docs/guide/integrations/payment-gateway.md +++ /dev/null @@ -1,242 +0,0 @@ -# Payment Gateway Integration - -Vue Storefront is a platform-agnostic app. This means it can be connected to virtually any eCommerce backend. At the same time, most of the existing eCommerce platforms integrate the Payment Gateways (PG) using some kind of frontend hooks: - -- Some gateways are integrated by iframe component -- Others are injected by JavaScript snippet etc. - -In this situation, it's a rare case when you can modify your payment gateway with PWA—without creating the integration code by yourself. - -## Community resources - -Our community members created some really cool docs and reference implementation of payment modules You could and probably should base yours on: - -- Blog post: [How to create VS 1.6 payment module](https://www.develodesign.co.uk/news/development-of-the-paypal-module-for-vue-storefront/) -- Paypal integration: [Paypal integration by Develodesign](https://github.com/develodesign/vsf-payment-paypal) -- Braintree integration: [Braintree integration by Daniel Coull](https://github.com/danrcoull/vsf-payment-braintree) - -## Frontend integration - -First step is to create a [Vue Storefront Module](../modules/introduction.md) to integrate the payment provider in the frontend. Any of the payment handling logic UI is handled solely via the Module. For the most basic version of how a Payment Module is, the "src/modules/payment-cash-on-delivery". - -- The Payment Module, where applicable, should catch the `checkout-payment-method-changed`vent. If the payment method code is the desired one, then you have the option to dynamically inject any components into the order review section on the checkout—for example, credit card input fields, payment method information, etc. - -- You are required to catch the `checkout-before-placeOrder` event and do any processing required for the payment method before placing the order. -- You are required to emit the `checkout-do-placeOrder` event with an optional payload to complete the placeorder process. -- For your payment method to display, add it to the Payment Methods collection in storage `app.\$store.state.payment.methods.push(paymentMethodConfig)`. -- Unregister any events when they are no longer required. -- For clarity in growing extensions, payment extensions should be named clearly `payment-{VENDOR}-{PAYMENT_METHOD}` - -### Cash on delivery example - -Here is an example of a "Cash on delivery" payment method main logic. It's placed in the `src/modules/payment-cash-on-deliver/hooks/afterRegistration.ts` which is usually a good entry point for registering custom event hooks: - -```js -import InfoComponent from '../components/Info.vue' -import config from 'config' - -export function afterRegistration({ Vue, config, store, isServer }) { - // Place the order. Payload is empty as we don't have any specific info to add for this payment method '{}' - const placeOrder = function () { - EventBus.$emit('checkout-do-placeOrder', {}) - } - - if (!isServer) { - // Update the methods - let paymentMethodConfig = { - 'title': 'Cash on delivery', - 'code': 'cashondelivery', - 'cost': 0, - 'cost_incl_tax': 0, - 'default': true, - 'offline': true - } - rootStore.dispatch('payment/addMethod', paymentMethodConfig) - - // Mount the info component when required. - EventBus.$on('checkout-payment-method-changed', (paymentMethodCode) => { - if (paymentMethodCode === 'cashondelivery') { - // Register the handler for what happens when they click the place order button. - EventBus.$on('checkout-before-placeOrder', placeOrder) - - // Dynamically inject a component into the order review section (optional) - const Component = Vue.extend(InfoComponent) - const componentInstance = (new Component()) - componentInstance.$mount('#checkout-order-review-additional') - } else { - // unregister the extensions placeorder handler - EventBus.$off('checkout-before-placeOrder', placeOrder) - } - }) - } -} - -``` - -### More examples - -You can find much more sophisticated solutions for [Paypal](https://github.com/develodesign/vsf-payment-paypal) on our partner - **Develodesign** github and [Braintree](https://github.com/danrcoull/vsf-payment-braintree) - -More info: -- [How to create VS 1.6 Payment module](https://www.develodesign.co.uk/news/development-of-the-paypal-module-for-vue-storefront/) - - -## Backend Integration - -After successfully integrating the payments on the frontend, you're sending the users and transactions to the payment integrator. Then, we need to get back the payment token/identifier and update the order status as soon as the payment integrator will let us know that the transaction finished. - -To store the transaction info you'll get from the payment service, you may emit an event: - -```js - placeOrderWithPayload (payload) { - this.$bus.$emit('checkout-do-placeOrder', payload) -``` - -where the payload equals to JSON object with additional order information. This object will be transferred to the server along with the order object in the `order.payment_method_additional` property. - -### The order workflow - server side - -To get it right, first we must understand how Vue Storefront processes orders. Vue Storefront sends orders to the server asynchronously because of the offline orders support (orders can be sent immediately after they’re placed, minutes after or even hours). - -The order is being sent to the `vue-storefront-api/api/order/create` endpoint. This API endpoint pushes the order to the queue from where it's being transferred to the eCommerce backend by a cron-run process called `order_2_magento2.js` (o2m). You will find the source code of `o2m` in the `vue-storefront-api/worker` folder. - -As you can see, we don't have the backend's order number immediately after the order has been placed. To pair the client-side order ID and server-side metadata, `order_2_magento.js` process stores special metadata entries in `Redis` cache: - -```js -api.cart.order(null, cartId, { - "paymentMethod": - { - "method":orderData.addressInformation.payment_method_code, - "additional_data":orderData.addressInformation.payment_method_additional - } -}, isThisAuthOrder).then(result => { - logger.info(THREAD_ID, result)() - if(job) job.progress(currentStep++, TOTAL_STEPS); - - logger.info(THREAD_ID + '[OK] Order placed with ORDER ID', result);() - logger.debug(THREAD_ID + result)() - redisClient.set("order$$id$$" + orderData.order_id, JSON.stringify( - { - platform_order_id: result, - transmited: true, - transmited_at: new Date(), - platform: 'magento2', - order: orderData - })); - redisClient.set("order$$totals$$" + orderData.order_id, JSON.stringify(result[1])); - - if(job) job.progress(currentStep++, TOTAL_STEPS); - return done(null, { magentoOrderId: result, transferedAt: new Date() }); -``` - -As you can see, you can get the order data using the **client-side** order ID by accessing the `order$$id$${clientsideorderid}` in the local Redis instance. - -You can check `vue-storefront-api/src/api/sync.js` with the `check` method: - -```js -export default ({ config, db }) => { - let syncApi = Router(); - - /** - * GET get stock item - */ - syncApi.get('/order/:order_id', (req, res) => { - const Redis = require('redis'); - let redisClient = Redis.createClient(config.redis); // redis client - redisClient.on('error', function(err) { - // workaround for https://github.com/NodeRedis/node_redis/issues/713 - redisClient = Redis.createClient(config.redis); // redis client - }); - - redisClient.get('order$$id$$' + req.param('order_id'), function( - err, - reply, - ) { - const orderMetaData = JSON.parse(reply); - if (orderMetaData) { - orderMetaData.order = null; // for security reasons we're just clearing out the real order data as it's set by `order_2_magento2.js` - } - apiStatus(res, err ? err : orderMetaData, err ? 500 : 200); - }); - }); - - return syncApi; -}; -``` - -As this method returns the order data by non-secured URL with just the client-side order ID, there is an `order` property being removed to return just the platform ID of their order. - -### Status change for the order - -The example shown above was a prep step for updating the Magento (or other platform) order status based on the response from the payments integrator. - -First, you need to add the special endpoint under the public URL that will be getting the notifications / statuses from the payments provider. Here is an example [how to add custom API endpoint to the `vue-storefront-api`](Extending vue-storefront-api.md). - -To add the API extension to `vue-storefront-api`: - -1. Create the folder within `src/api/extensions`. For example 'custom-payment-method` -2. Then add the `index.js` file and put the API methods code inside. We're using Express.js. Here is a boilerplate/example for the extension code: - -```js -import { apiStatus } from '../../../lib/util'; -import { Router } from 'express'; - -module.exports = ({ config, db }) => { - let mcApi = Router(); - - /** - * POST create an user - */ - mcApi.post('/status', (req, res) => { - const notificationData = req.body; - const order_id = ''; // we should extract the client's order id from the notification status - /// ... business logic related to the status verification - - // getting the data from Redis - the original order - const Redis = require('redis'); - const Magento2Client = require('magento2-rest-client').Magento2Client; - - let redisClient = Redis.createClient(config.redis); // redis client - redisClient.on('error', function(err) { - // workaround for https://github.com/NodeRedis/node_redis/issues/713 - redisClient = Redis.createClient(config.redis); // redis client - }); - - redisClient.get('order$$id$$' + order_id, function(err, reply) { - const orderMetaData = JSON.parse(reply); - if (orderMetaData) { - // now we can use the api client to update the order status in Magento - const client = Magento2Client(config.magento2.api); - client.addMethods('invoice', function(restClient) { - var module = {}; - - module.create = function() { - return restClient.post('/invoice/create'); // the real Magento2 endpoint should be here - this is just an example - }; - return module; - }); - client.invoice - .create() - .then(result => { - apiStatus(res, result, 200); // just dump it to the browser, result = JSON object - }) - .catch(err => { - apiStatus(res, err, 500); - }); - } - }); - }); - return mcApi; -}; -``` - -3. Add the extension to `config/local.json`: - -```json - "registeredExtensions": ["custom-payment-method"], -``` - -4. Restart the `vue-storefront-api` -5. Your new API method is available on `localhost:8080/api/ext//` for example: `localhost:8080/api/ext/custom-payment-method/status` - -In this extension above, you may want to get the order data by the client-side order ID (passed to the payment service and probably returned with the notification). Then **you may use the Magento 2 API to update the payment status**, likely by executing the `invoice` method. diff --git a/docs/guide/integrations/paypal-payments.md b/docs/guide/integrations/paypal-payments.md deleted file mode 100644 index c0867940df..0000000000 --- a/docs/guide/integrations/paypal-payments.md +++ /dev/null @@ -1,135 +0,0 @@ -# PayPal payments support - -Vue Storefront is supporting PayPal payments with PayPal Payment extension for [vue-storefront](https://github.com/vuestorefront/vue-storefront), by [Develo Design](https://www.develodesign.co.uk). - -## The architecture - -![Architecture diagram](../images/paypal.svg) - - -## Installation - -By hand (preferer): - -```shell -$ git clone git@github.com:develodesign/vsf-payment-paypal.git ./vue-storefront/src/modules/paypal -``` - -```json -"paypal": { - "clientId": "", - "endpoint": { - "complete": "http://localhost:8080/api/ext/paypal/complete", - "setExpressCheckout": "http://localhost:8080/api/ext/paypal/setExpressCheckout" - } -} -``` - -## Registration the Paypal module - -Open in you editor `./src/modules/index.ts` - -```js -... -import { Paypal } from './paypal'; - -export const registerModules: VueStorefrontModule[] = [ - ..., - Paypal -] -``` - -## Paypal payment Checkout Review - -Under your theme `components/core/blocks/Checkout/OrderReview.vue` add the following import to your script - -```js -import PaypalButton from '@develodesign/vsf-payment-paypal/components/Button' - -export default { - components: { - ... - PaypalButton - }, - ... - computed: { - payment () { - return this.$store.state.checkout.paymentDetails - } - }, -``` - -And to your template add the paypal button before `button-full`: - -```html - - - {{ $t('Place the order') }} - -``` - -## PayPal payment API extension - -Setup dependency to api: -`cd ../vue-storefront-api` -`yarn add -W @paypal/checkout-server-sdk` -`yarn add -W paypal-nvp-api` - -Install extension to `vue-storefront-api`: - -```shell -$ cp -fr src/modules/paypal/api/paypal ../vue-storefront-api/src/api/extensions/ -``` - -Go to api config `./vue-storefront-api/config/local.json` and register the Paypal Api extension: - -```json -"registeredExtensions": [ - ... - "paypal" -] -``` - -And add the `paypal` settings to `extensions` key: - -Add the following also to your `config/local.json` need set `paypal.env` to `sandbox` or `live`. - -```json - "extensions": { - "paypal": { - "env": "sandbox", - "clientId": "", - "secret": "", - "username": "", - "password": "", - "signature": "" - }, - ... - } -``` - -## Magento2 integration - -Turn on Paypal Express and provide the API credentials using the built in Paypal module. Enable only Express Checkout. - -Other Paypal methods are not supported or tested right now. - -## Customization - -Also we can use `paypal.style` option for more customizable PayPal button view. For more info [PayPal](https://developer.paypal.com/demo/checkout/#/pattern/checkout). - -In Button.vue, the button takes prop styling - -```json -"style": { - "size": "small", - "color": "gold", - "shape": "pill" -} -``` diff --git a/docs/guide/integrations/reviews.md b/docs/guide/integrations/reviews.md deleted file mode 100644 index 8a7afcbb6a..0000000000 --- a/docs/guide/integrations/reviews.md +++ /dev/null @@ -1,17 +0,0 @@ -# Product Reviews - -Starting with the 1.4.0 release, Vue Storefront is supporting Magento 2 product reviews. Unfortunately, the Magento 2 REST API doesn't contain any Reviews-related endpoints, so to make it work, you need to install an [additional Magento 2 module](https://github.com/divanteLtd/magento2-review-api). - -Installation steps (in your Magento 2 directory): - -```bash -composer config repositories.divante vcs https://github.com/divanteLtd/magento2-review-api.git -composer require divante/magento2-review-api:dev-master -php bin/magento setup:upgrade -``` - -You should be aware that Reviews are stored in the Elasticsearch. To display Reviews correctly, you need to update your [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront/) and run the Reviews sync: - -```bash -node --harmony cli.js reviews -``` diff --git a/docs/guide/integrations/tier-prices-sync.md b/docs/guide/integrations/tier-prices-sync.md deleted file mode 100644 index 5db6a5bc00..0000000000 --- a/docs/guide/integrations/tier-prices-sync.md +++ /dev/null @@ -1,15 +0,0 @@ -# Tier prices sync with Magento - -This feature allows you to show the user a final price, including tier prices from Magento. It supports a simple, downloadable, configurable bundle and group products. - -To enable tier prices, change the following lines in the - -```json - "usePriceTiers": true -``` - -To use this feature you should also modify `config/local.json` within your `vue-storefront-api` installation: - -```json - "usePriceTiers": true -``` diff --git a/docs/guide/integrations/totals-sync.md b/docs/guide/integrations/totals-sync.md deleted file mode 100644 index 6c40fa7751..0000000000 --- a/docs/guide/integrations/totals-sync.md +++ /dev/null @@ -1,92 +0,0 @@ -# Totals and cart sync with Magento - -One of the key principles of the Vue Storefront architecture is full scalability. We've achieved very good performance results by not relying on the Magento 2/CMS API performance. By implementing the "divide and conquer" rule, we created the middleware and external data store for the catalog using Elasticsearch. - -That was our first goal, but the second was to provide the full data safety and reliability to the business so we never encounter situations like stocks and prices being de-synchronized or the discount rules not being applied. It can ruin the business, no matter the performance 😃 - -## Cart and Totals sync - -This is the reason we have the direct shopping cart sync with Magento 2. Whenever the user adds something to the cart, we're checking the stock and synchronizing the local in-browser cart with CMS. - -![This is the dynamic requests architecture](../images/Vue-storefront-architecture-proxy-requests.png) - -In the backward direction, we're always getting the current totals, after Magento shopping cart rules and discounts are applied to display the proper data to the user. This synchronization is implemented to keep the Vue Storefront platform-agnostic. The `vue-storefront-api` layer is in charge of translating the platform-specific API formats to the Vue Storefront general-data abstraction. - -![This is how the cart sync works](../images/cart-sync.png) - -As you can see, the synchronization works like a sequence of network calls to the `vue-storefront-api`: - -1. The `pull` method is executed to get the current user's Magento cart. -2. On the client side, the logic is checking which elements are new on the client side, server side, removed. -3. In our case, one element doesn't exist on the server side so we're calling an `update` method to add it to the server cart. -4. Then, we call `totals`to get the current row values and general totals for the cart. - -## How to make this feature work - -By default, the cart and totals sync is disabled. To make it work, you just need to follow the steps described below: - -1. Generate the Magento 2 API accesses. In our first tutorial, there is an [explanation on how to do this](../installation/magento.md). - -2. Use the OAuth keys from the previous step to properly configure the `vue-storefront-api` data layer (it should've been installed locally on your computer / server). To do so, you need to modify the `conf/local.json` and paste the authorization data to the `magento2.api` section: - -```json -"magento2": { - "url": "http://magento2.demo-1.xyz.com", - "imgUrl": "http://localhost:8080/media/catalog/product", - "magentoUserName": "", - "magentoUserPassword": "", - "httpUserName": "", - "httpUserPassword": "", - "api": { - "url": "http://demo-magento2.vuestorefront.io/rest", - "consumerKey": "byv3730rhoulpopcq64don8ukb8lf2gq", - "consumerSecret": "u9q4fcobv7vfx9td80oupa6uhexc27rb", - "accessToken": "040xx3qy7s0j28o3q0exrfop579cy20m", - "accessTokenSecret": "7qunl3p505rubmr7u1ijt7odyialnih9" - } - }, -``` - -Please check the [`conf/default.json`](https://github.com/vuestorefront/vue-storefront-api/blob/master/config/default.json) for the reference. - -3. Move to your `vue-storefront` installation catalog and modify the `config/local.json`. You need to change the `cart` section to switch the `synchronize` and `synchronize_totals` flags to true: - -```json - "cart": { - "synchronize": false, - "synchronize_totals": false, - "create_endpoint": "http://localhost:8080/api/cart/create?token={{token}}", - "updateitem_endpoint": "http://localhost:8080/api/cart/update?token={{token}}&cartId={{cartId}}", - "deleteitem_endpoint": "http://localhost:8080/api/cart/delete?token={{token}}&cartId={{cartId}}", - "pull_endpoint": "http://localhost:8080/api/cart/pull?token={{token}}&cartId={{cartId}}", - "totals_endpoint": "http://localhost:8080/api/cart/totals?token={{token}}&cartId={{cartId}}", - "paymentmethods_endpoint": "http://localhost:8080/api/cart/payment-methods?token={{token}}&cartId={{cartId}}", - "shippingmethods_endpoint": "http://localhost:8080/api/cart/shipping-methods?token={{token}}&cartId={{cartId}}", - "shippinginfo_endpoint": "http://localhost:8080/api/cart/shipping-information?token={{token}}&cartId={{cartId}}", - "collecttotals_endpoint": "http://localhost:8080/api/cart/collect-totals?token={{token}}&cartId={{cartId}}" - }, -``` - -Please check the [`conf/default.json`](https://github.com/vuestorefront/vue-storefront/blob/1302ed84561a514beb8c35e45ae1d0aa4dc9f74a/config/default.json#L8) for a reference. - -## Prices sync - -The last missing block is the catalog prices sync. This can be very easily enabled using the feature called Dynamic Prices. Please check [Dynamic Prices howto](direct-prices-sync.md) to switch this feature on. - -## Order sync - -One of the cool features of Vue Storefront is queued order sync. This means whenever a user makes an order in the application, we store the order in the local browser cache (indexedDb instance) and send it to the server as soon as the Internet connection is available. - -![Orders are stored locally before they're send to the server](../images/orders-collection.png) - -On the server side, the `vue-storefront-api` is the first line that the order is crossing on its way back to Magento 2. No matter if the shopping cart was synchronized (as described above) or not, the order will be converted to a Magento 2 object. - -The server API stores the order in the queue where it's processed by the [`order_2_magento`](https://github.com/vuestorefront/vue-storefront-api/blob/master/src/worker/order_to_magento2.js) worker process. We do support multiple types of orders: for guest users and logged in, with already synchronized carts or not, etc. - -This process doesn't require much additional configuration: - -1. You must have the Magento2 API access configures in the `config/local.json` file of `vue-storefront-api` -2. You must have the "Orders" section marked On within the "Permissions" section of Magento Integration ([see the previous tutorial for the reference on how to set it up](../installation/magento.md)). -3. After the configuration step You just run `yarn o2m` inside your `vue-storefront-api` directory. - -![This is the output of o2m after successfull setup](../images/o2m-output.png) diff --git a/docs/guide/modules/cart.md b/docs/guide/modules/cart.md deleted file mode 100644 index 1cb75f13f7..0000000000 --- a/docs/guide/modules/cart.md +++ /dev/null @@ -1,192 +0,0 @@ -# Cart module - -This module contains all the logic, components and store related to cart operations. - -## Components - -### AddToCart - -This component represents a single button that when pressed adds a product to cart. - -**Props** - -- `product` - product that'll be added to cart - -**Methods** - -- `addToCart(product)` - adds passed product to the cart. By default correlates with `product` prop - -### Microcart - -User cart with a products list and price summary. - -**Computed** - -- `productsInCart` - array of products that are currently in the cart -- `appliedCoupon` - return applied cart coupon or `false` if no coupon was applied -- `totals` - cart totals -- `isMicrocartOpen` - returns `true` if microcart is open - -**Methods** - -- `applyCoupon(code)` - applies cart coupon -- `removeCoupon` - removes currently applied cart coupon -- `toggleMicrocart` - open/close microcart - -### MicrocartButton - -Component responsible for opening/closing Microcart - -**Computed** - -- `quantity` - number of products in cart - -**Methods** - -- `toggleMicrocart` - open/close microcart - -### Product - -Component representing product in microcart. Allows to modify it's quantity or remove from cart. - -**Computed** - -- `thumbnail` - returns src of products thumbnail - -**Methods** - -- `removeFromCart` - removes current product (data property `product`) from cart -- `updateQuantity` - updates cart quantity for current product (data property `product`) - -## Store - -Cart Store is designed to handle all actions related the shopping cart. - -### State - -```js - state: { - itemsAfterPlatformTotals: {}, - platformTotals: null, - platformTotalSegments: null, - cartIsLoaded: false, - cartServerToken: '', // server side ID to synchronize with Backend (for example Magento) - shipping: [], - payment: [], - cartItemsHash: '', - bypassCount: 0, - cartItems: [] // TODO: check if it's properly namespaced - }, -``` - -Cart state is automatically loaded from `localForage` collection after page has been loaded whenever `core/components/blocks/Microcart.vue` is included. The cart state is loaded by dispatching `cart/load` action and [stored automatically by any change to the cart state](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/index.js#L118). - -The cart state data: - -- `itemsAfterPlatformTotals` - helper collection, dictionary where the key is Magento cart item `item_id` that stores the totals information per item - received from Magento; it's automatically populated when `config.cart.synchronize_totals` is enabled; -- `platformTotals` - similarly to above item, here we have the full totals from Magento for the current shopping cart. These collections are populated by [`cart/syncTotals`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/actions.js#L49) and the event handler for [`servercart-after-totals`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L30) -- `cartIsLoaded` (bool) - true after dispatching `cart/load` -- `shipping` - (object) currently selected shipping method - only when NOT using `cart.synchronize_totals` (if so, the shipping and payment's data comes from Magento2), -- `payment` - (object) currently selected shipping method - only when NOT using `cart.synchronize_totals` (if so, the shipping and payment's data comes from Magento2), -- `cartItems` - collection of the cart items; the item format is the same as described in [ElasticSearch Data formats](https://github.com/vuestorefront/vue-storefront/blob/master/doc/ElasticSearch%20data%20formats.md) - the `product` class; the only difference is that the (int) `qty` field is added - -### Events - -The following events are published from `cart` store: - -- `EventBus.$emit('cart-after-itemchanged', { item: cartItem })` - executed after [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) - after server cart sync, that signalize the specific shopping cart item has been changed; `Microcart/Product.vue` component is subscribed to this event to refresh the shopping cart UI -- `EventBus.$emit('cart-before-add', { product: item })` - fired after product has been added to the cart, -- `EventBus.$emit('cart-before-save', { items: state.cartItems })` - fired after the product cart has been saved, -- `EventBus.$emit('cart-before-delete', { items: state.cartItems })` - the event fired before the cart item is going to be deleted with the current cart state (before item is deleted) -- `EventBus.$emit('cart-after-delete', { items: state.cartItems })` - the event fired before the cart item has been deleted with the current cart state (after item is deleted) -- `EventBus.$emit('cart-before-itemchanged', { item: record })` - item called before the specific item properties are going to be changed; for example called when [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) is going to change the `server_item_id` property -- `EventBus.$emit('cart-after-itemchanged', { item: record })` - item called after the specific item properites has been changed; for example called when [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) is going to change the `server_item_id` property -- `EventBus.$emit('application-after-loaded')` - event called after `cart/load` action has been dispatched to notify that cart is being available, -- `EventBus.$emit('cart-after-updatetotals', { platformTotals: totals, platformTotalSegments: platformTotalSegments })` - event called after the totals from Magento has been synchronized with current state; it's going to be emitted only when `cart.synchronize_totals` option is enabled. - -### Actions - -The cart store provides following public actions: - -#### `disconnect (context)` - -Helper method used to clear the current server cart id (used for cart synchronization) - -#### `clear (context)` - -This method is called after order has been placed to empty the `cartItems` collection and create the new server cart when the `cart.synchronize_totals` is set to true - -#### `save (context)` - -Method used to save the cart to the `localForage` browser collection - -#### `sync (context, { forceClientState = false })` - -This method is used to synchronize the current state of the cart items back and forth between server and current client state. When the `forceClientState` is set to false the communication is one-way only (client -> server). This action is called automatically on any shopping cart change when the `cart.synchronize` is set to true. - -#### `syncTotals (context, { forceClientState = false })` - -Method is called whenever the cart totals should have been synchronized with the server (after `serverPull`). This method overrides local shopping cart grand totals and specific item values (for example prices after discount). - -#### `connect (context, { guestCart = false })` - -Action is dispatched to create the server cart and store the cart id (for further synchronization) - -#### `load (context)` - -This method loads the cart items from `localForage` browser state management. - -#### `getItem ({ commit, dispatch, state }, sku)` - -This action is used for search the particular item in the shopping cart (by SKU) - -#### `addItem ({ commit, dispatch, state }, { productToAdd, forceServerSilence = false })` - -This action is used to add the `productToAdd` to the cart, if `config.cart.synchronize` is set to true the next action subsequently called will be `serverPull` to synchronize the cart. The event `cart-before-add` is called whenever new product lands in the shopping cart. The option `forceServerSilence` is used to bypass the server synchronization and it's used for example then the item is added during the ... sync process to avoid circular synchronization cycles. - -#### `removeItem ({ commit, dispatch }, product)` - -As you may imagine :) This action simply removes the product from the shopping cart and synchronizes the server cart when set. You must at least specify the `product.sku`. - -#### `updateQuantity ({ commit, dispatch }, { product, qty, forceServerSilence = false })` - -This method is called whenever user changes the quantity of product in the cart (called from `Microcart.vue`). The parameter `qty` is the new quantity of product and by using `forceServerSilence` you may control if the server cart synchronization is being executed or not. - -#### `updateItem ({ commit }, { product })` - -Updates item properties. - -#### `syncPaymentMethods (context)` - -Gets a list of payment methods from the backend and saves them to `cart.payment` store state. - -#### `syncShippingMethods (context, address)` - -Gets a list of shipping methods from the backend and saves them to `cart.shipping` store state. Country ID is passed to this method in a mandatory `address` parameter. - -#### `syncTotals (context, methodsData)` - -This method sends request to the backend to collect cart totals. It calls different backend endpoints depending on if payment and shipping methods information is available or not. - -### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -- `getCartToken` - get the current cart token, if empty it does mean we need to call an action `cart/connect` prior to sync with the server, -- `getLastSyncDate` - this is an integer, timestamp of the last shopping cart sync with the server -- `getLastTotalsSyncDate` - integer, timestamp of the last totals sync with the server, -- `getShippingMethod` - object, gets currently selected shipping method in the Checkout, -- `getPaymentMethod` - object, gets current payment method selected in the checkout, -- `getLastCartHash` - get the last saved hash/HMAC of the cart items + server token that let's you track the changes of the shipping cart. Hash is being saved by the server sync, -- `getCurrentCartHash` - get the current hash/HMAC of the cart items + server token. Coparing it to the `getLastCartHash` value let you know if we need a server sync or not, -- `isCartHashChanged` - comparing the `getLastCartHash` with the `getCurrentCartHash` in order to verify if we need a server sync or not, -- `isSyncRequired` - checking if the `isCartHashChanged` is true OR if this is the first sync attempt (after the SSR), -- `isTotalsSyncRequired` - same as `isSyncRequired` but for the totals (not the cart items), -- `isCartHashEmptyOrChanged` - checks if `isCartHashChanged` or empty, -- `getCartItems` - array of products in the shopping cart, -- `isTotalsSyncEnabled` - check if the `config.cart.synchronize` is true + if we're online + if this is CSR request, -- `isCartConnected` - check if the `getCartToken` is not empty - which means the `cart/connect` action has been called and we're OK to sync with the server, -- `isCartSyncEnabled` - the same as `isTotalsSyncEnabled` but for totals (`config.cart.synchronize_totals` flag), -- `getTotals` - array with the total segments, -- `getItemsTotalQuantity` - get the sum of all the items in the shopping cart, -- `getCoupon` - get the currently applied discount code, diff --git a/docs/guide/modules/catalog.md b/docs/guide/modules/catalog.md deleted file mode 100644 index 0241fe66c4..0000000000 --- a/docs/guide/modules/catalog.md +++ /dev/null @@ -1,328 +0,0 @@ -# Catalog module - -Catalog module is a big one combining all the logic, components and store for attribute, category, product, stock and tax operations - -## Components - -## Store - -### Attribute Store - -Attribute Store is designed to handle all actions related to the attributes management - -#### State - -```js - state: { - list_by_code: {}, - list_by_id: {}, - labels: {} - }, -``` - -As we're using the attributes dictionary for the product management in a very similar way Magento does ([EAV model](http://www.xpertdeveloper.com/2010/10/what-is-eav-model-in-magento/)) we're operating on the attributes, attribute types and dictionaries. - -Attributes are **explicitly** loaded by the user by calling the `attribute/list` method. For example, when you're going to work with customizable attributes of the product or to work on variants you need to prefetch the attributes metadata: - -```js -this.$store.dispatch('attribute/list', { - filterValues: [true], - filterField: 'is_user_defined', -}); -``` - -This is example from [product compare feature](https://github.com/vuestorefront/vue-storefront/blob/c954b96f6633a201e10bed1d2e4c0def1aeb3071/core/pages/Compare.vue). - -The attribute state data: - -- `list_by_code` - this is a dictionary where you can get the specific attribute by just accessing the `list_by_code['color']` etc. -- `list_by_id` - this is a dictionary where you can get the specific attribute by just accessing the `list_by_id[123]` etc. -- `labels` - the preloaded labels of attribute values (the V in EAV) - -#### Actions - -The attribute store provides following public actions: - -**`list (context, { filterValues = null, filterField = 'attribute_code', size = 150, start = 0 })`** - -This method is used to load the attributes metadata. `filterValues` is an array of multiple values like: `['color', 'size']` and the `filterField` is the attribute field to compare the `filterValues` against. Usually is a `attribute_code` or `attribute_id`. The `size` and `start` are just used to limit the list. - -#### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -export default { - attributeListByCode: state => state.list_by_code, - attributeListById: state => state.list_by_id, -}; -``` - -### Category Store - -Category Store is designed to handle all actions related the categories data. - -This module works pretty tightly with Elastic Search and operates on the [Product data format](../data/elasticsearch.md) - -#### State - -```js -const state = { - list: [], - current: {}, - filters: { color: [], size: [], price: [] }, - breadcrumbs: { routes: [] }, - current_path: [], // list of categories from root to current -}; -``` - -Category state is generally populated by just two methods [list](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L38) and [single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L28) - -:::tip Note -The action `category/single` uses `localForage` cache only - no ElasticSearch data store directly; because of this optimization, please do download the categories list by dispatching `category/list` at first. -::: - -The category state data: - -- `breadcrumbs` - this is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - this is the current category object, -- `filters` is a current state of the category filters - dictionary of selected variant attributes; for example it contains dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, that we're using the Magento like EAV attributes structure - so the values here are an attribute value indexes not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference - -- `current_path` - this is the list of category objects: from current category to the top level root, - -#### Events - -The following events are published from `category` store: - -- `EventBus.$emit('category-after-single', { category: mainCategory })` - from [category/single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) after single category is loaded, -- `EventBus.$emit('category-after-current', { category: category })` - after current category has been changed - this is subsequent call of `category/single` action, -- `EventBus.$emit('category-after-reset', { })` - after category has been reset (for example in the process of moving from one category page to another) -- `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - this event emits the current category list as it's returned by `category/list`. - -#### Actions - -The cart store provides following public actions: - -**`list (context, { parent = null, onlyActive = true, onlyNotEmpty = false, size = 4000, start = 0, sort = 'position:asc' })`** - -This is the key method to load the category list. It returns the `Promise` that contains the product list object. This method should be used everywhere you need to get products data. - -**`single (context, { key, value, setCurrentCategory = true, setCurrentCategoryPath = true })`** - -This method gets the single category from `localForage`. - -:::warning Important -To make this method work you should call `category/list` before. This category works only on localFotage and cannot access ElasticSearch directly -::: - -:::warning Important -This method synchronizes products for offline usage by: storing the whole query results object into `localForage` and by caching each category individually (to be used on the Product page for example) -::: - -This method emits category list as `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - -- `parent` - `category` object to load the subcategories only - -- `start`, `size` - both parameters are used for paging; start is the starting index; size is a page size - -- `onlyActive` - (bool) load only the categories marked as active in CMS (for example in Magento) - -- `sort` - category attribute using to sort, this field must be mapped in ElasticSearch as a numeric field - -- `onlyNotEmpty` - (bool) load only the categories that contain any products - -#### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -const getters = { - current: state => state.current, - list: state => state.list, -}; -``` - -### Product Store - -Product Store is designed to handle all actions related the product data. It's responsible for loading the list of products or a single product as well as configuring the configurable products and managing the products attachments. - -This module works pretty tightly with Elastic Search and operates on the [Product data format](../data/elasticsearch.md) - -#### State - -```js -const state = { - breadcrumbs: { routes: [] }, - current: null, // shown product - current_options: { color: [], size: [] }, - current_configuration: {}, - parent: null, - list: [], - original: null, // default, not configured product - related: {}, -}; -``` - -Product state is generally populated by just two methods [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) and [single](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L428) and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L215) - -The product state data: - -- `breadcrumbs` - this is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - this is the product object with selected `configurable_children` variant - so it's the base product with attributes overridden by the values from selected `configurable_children` variant; it's used on [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/pages/Product.vue#L203) this is the product which is added to the cart after "Add to cart" -- `current_options` - it's a list used to populate the variant selector on the [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/themes/default/pages/Product.vue#L56) it contains dictionary of attributes x possible attribute values and labels and it's populated by [setupVariants](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L344) based on the `configurable_children` property -- `current_configuration` is a current product configuration - dictionary of selected variant attributes; for example it contains dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, that we're using the Magento like EAV attributes structure - so the values here are an attribute value indexes not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference - -- `parent` - if the current product is a `type_id="single"` then in this variable the parent, `configurable` product is stored. This data is populated only on `Product.vue` by [checkConfigurableParent](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L323) -- `list` - this is an Array of products loaded by [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) -- `original` - used only for `configurable` products; this is the base product with no variant selected -- `related` - this is dictionary of related products; set outside this store (for [example here](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/blocks/Product/Related.vue)) by calling and [related action](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L528) - -#### Events - -The following events are published from `product` store: - -- `EventBus.$emit('product-after-priceupdate', product)` - from [syncProductPrice](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L33) after product price is synced with Magento; -- `EventBus.$emit('product-after-configure', { product: product, configuration: configuration, selectedVariant: selectedVariant })` from `configureProductAsync` (called by `product/configure` action after `product/single`). This event provides the information about selected product variant on the product page -- `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, result: resp })` - this event emits the current product list as it's returned by `product/list` providing the current filters etc. You can mark specific product list identifier by setting `meta` property; it's important because on single page this event can be executed multiple time for each individual block of products -- `EventBus.$emit('product-after-single', { key: key, options: options, product: cachedProduct })` - after single product has been loaded (invoked by `product/single` action)related products - -#### Actions - -The product store provides following public actions: - -**`setupBreadcrumbs (context, { product })`** - -This method is in charge of setting `state.breadcrumbs` to be used on `Product.vue` page. It's called from `Product.vue:fetchData`. The `product` parameter is a [ElasticSearch product object](../data/elasticsearch.md) - -**`syncPlatformPricesOver(context, { skus })`** - -When the config option `products.alwaysSyncPlatformPricesOver` is on, Vue Storefront will request the current product prices each time when `product/single` or `product/list` action is dispatched. It's called exclusively by these actions and shouldn't be called manually. This method calls `vue-storefront-api` proxy to get the current prices from Magento or any other backend CMS. - -`skus` - this is an Array with product SKU's to be synchronized - -**`setupAssociated (context, { product })`** - -This method is called as a subsequent call of `Product.vue:fetchData` or `product/list` action. It's used to get the child products of `grouped` or `bundle` types of products. - -**`checkConfigurableParent (context, {product})`** - -This method is called by `Product.vue:fetchData` to check if current, simple product has got an configurable parent. If so the redirect is being made to the parent product. It's a fix for [#508](https://github.com/vuestorefront/vue-storefront/issues/508) - -**`setupVariants (context, { product })`** - -This method is subsequently called by `Product.vue:fetchData` to load all configurable attributes defined in `product.configurable_options` and then to populate `state.current_configuration` and `state.current_options`. The main usage of this action is to prepare product to be configured by the user on the product page and to display the product configurator UI properly - -**`list (context, { query, start = 0, size = 50, entityType = 'product', sort = '', cacheByKey = 'sku', prefetchGroupProducts = true, updateState = true, meta = {} })`** - -This is the key method to load the product list. It returns the `Promise` that contains the product list object. This method should be used everywhere you need to get products data. When `config.tax.calculateServerSide=false` this method runs product taxes calculator and synchronizes prices with Magento if it's required. - -This method emits product list as `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, meta: meta, result: resp })` - -:::warning Important -This method synchronizes products for offline usage by: storing the whole query results object into `localForage` and by caching each product individually (to be used on the Product page for example) -::: - -- `query` - this is the `bodybuilder` ElasticSearch query (please check `bodybuilder` package or for example `Home.vue` for a reference how to use it) - -- `start`, `size` - both parameters are used for paging; start is the starting index; size is a page size - -- `entityType` - by default it's of course set to `product` and it's mapped to ElasticSearch entity class - -- `sort` - product attribute using to sort, this field must be mapped in ElasticSearch as a numeric field - -- `prefetchGroupProducts` - by default it's set to true and causes `setupAssociated` action to be dispatched to get all the associated products - -- `updateState` - if you set this to false, the `state.list` will not be updated - just the products will be returned - -- `meta` - this is an optional attribute which is returned with `product-after-list` event; it can be used for example to mark any specific ES call. - -**`single (context, { options, setCurrentProduct = true, selectDefaultVariant = true, key = 'sku' })`** - -This method subsequently dispatched `product/list` action to get the products and synchronize the taxes/prices. When the product has been recently downloaded via `product/list` this method will return the cached version from `localForage` - but update the cache anyway. - -**`configure (context, { product = null, configuration, selectDefaultVariant = true })`** - -This action is used to configure the `configurable` product with specified attributes. It gets the `configuration` object which should have the following format: `{ attribute_code: attribute_value_id }` and finds the `product.configurable_children` item which complies to this configuration. Then it merges this specific `configurable_child` with product itself - for example setting the product.price to the configurable price, color, size etc. The method is used on: `Product.vue` page for allowing user to select color, size etc. The second usage for it is on `Category.vue` page - after user selects some filters, the resulting products are configured to display the proper images (related to selected color and size) and prices. - -If `selectDefaultVariant` is set to true (default), the `state.current` will be altered with configured product. - -**`setCurrent (context, productVariant)`** - -Auxiliary method just to set `state.current` to productVariant - -**`setOriginal (context, originalProduct)`** - -Auxiliary method just to set `state.original` to originalProduct - -**`related (context, { key = 'related-products', items })`** - -Alters `state.related` dictionary to set specific list of related products to be displayed on `Product.vue` page (`RelatedProducts` component is used for this) - -#### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -const getters = { - getParentProduct: state => state.parent, - getCurrentProduct: state => state.current, - getCurrentProductConfiguration: state => state.current_configuration, - getOriginalProduct: state => state.original, - getCurrentProductOptions: state => state.current_options, - breadcrumbs: state => state.breadcrumbs, -}; -``` - -### Stock Store - -Stock Store is designed to handle stock quantity checks. - -#### Events - -The following events are published from `stock` store: - -- `stock-after-check` - emitted just after the stock item has been received from eCommerce backend / Magento - -#### Actions - -The cart store provides following public actions: - -**`check (context, { product, qty = 1 })`** - -Check if the `product` can be added to the shopping cart with a given quantity. - -The resulting promise is expanded to the following object: - -```js -{ - qty: 100, - status: 'ok', // another option is: 'out_of_stock' - onlineCheckTaskId: 14241 -} -``` - -### Tax Store - -## Helpers - -### optionLabel - -Used to get the Label for specific `optionId`. For example, when the user filters products and uses the 165 attribute_value we can call `optionLabel( { attributeKey: 'color', optionId: 165 })` to get back 'Red' label. diff --git a/docs/guide/modules/checkout.md b/docs/guide/modules/checkout.md deleted file mode 100644 index c5503edf4a..0000000000 --- a/docs/guide/modules/checkout.md +++ /dev/null @@ -1,274 +0,0 @@ -# Checkout Module - -Checkout Module is designed to handle all logic related the checkout operations and UI. - -## Components - -### CartSummary - -This component displays the cart summary information - -**Computed** - -- `totals` - mapped getter to show the cart totals - -### OrderReview - -A summary of the current order - -**Props** - -- `isActive` - boolean, required prop - -**Methods** - -- `placeOrder` - checks if current user has an account. If not, will trigger a `register` method, otherwise will emit `checkout-before-placeOrder` bus event -- `register` - dispatches a `user/register` action to register a new user - -### Payment - -A component to handle payment operations - -**Props** - -- `isActive` - boolean, required prop - -**Computed** - -- `currentUser` - the current user mapped from application state -- `paymentMethods` - available payment methods mapped from `payment/paymentMethods` getter - -**Methods** - -- `sendDataToCheckout` - emits `checkout-after-paymentDetails` bus event and sets `isFilled` to `true` -- `edit` - checks `isFilled` and if it's `true`, emits a `checkout-before-edit` bus event -- `hasBillingData` - checks if current user exists and if it has `default_billing_ property -- `initializeBillingAddress` - checks if current user exists and if it has `default_billing` property; if so, populates the `payment` data property with current user address data -- `useShippingAddress` - populates the `payment` data property with `$store.state.checkout.shippingDetails` -- `useBillingAddress` - populates the `payment` data property with `currentUser.addressess` -- `useGenerateInvoice` - negates the `generateInvoice` value and if it becomes `false`, will reset `this.payment.company` and `this.payment.taxId` -- `getCountryName` - gets the country name for the current payment by the country code -- `getPaymentMethod` - gets the payment method title for the current payment by the payment method code -- `notInMethods` - checks if passed method is present in `paymentMethods` -- `changePaymentMethod` - resets the additional payment method component container if exists and emits `checkout-payment-method-changed` bus event - -### Personal Details - -User's personal details component - -**Props** - -- `isActive` - boolean, required prop -- `focusedField` - a string showing which field is focused - -**Computed** - -- `currentUser` - the current user mapped from application state - -**Methods** - -- `onLoggedIn` - populates `personalDetails` with data passed as a parameter -- `sendDataToCheckout` - performs a check if an account is already created and emits `checkout-after-personalDetails` bus event -- `edit` - emits `checkout-before-edit` bus event -- `gotoAccount` - shows a sign-up modal - -### Product - -The component representing a product - -**Props** - -- `product` - current product - -**Computed** - -- `thumbnail` - returns a thumbnail for product image - -**Methods** - -- `onProductChanged` - checks `event.item.sku` and if it's equal to `product.sku`, the force update will be triggered - -### Shipping - -Component handling all the shipping logic - -**Props** - -- `isActive` - boolean, required prop - -**Computed** - -- `currentUser` - the current user mapped from application state -- `shippingMethods` - available payment methods mapped from `payment/paymentMethods` getter -- `checkoutShippingDetails` - mapped from `state.checkout.shippingDetails` -- `paymentMethod` - mapped from `state.payment.methods` - -**Methods** - -- `onAfterShippingSet` - populates the `shipping` data property with a passed parameter -- `onAfterPersonalDetail` - checks `isFilled` data property and if it's false, dispatches `checkout/updatePropValue` with user's first and last names -- `sendDataToCheckout` - emits `checkout-after-shippingDetails` bus event; sets `isFilled` to `true` -- `edit` - is `isFilled` is true, emits `checkout-before-edit` bus event and sets `isFilled` to `false` -- `hasShippingDetails` - checks, if `currentUser` exists and has a property `default_shipping`; if so, populates `myAddressDetails` data property with `currentUser.addresses` -- `useMyAddress` - checks `shipToMyAddress`; if `true`, populates `shipping` data property with `myAddressDetails` -- `getShippingMethod` - gets the shipping method from `shippingMethods` data property -- `getCountryName` - gets country name with country code -- `changeCountry` - emits `checkout-before-shippingMethods` bus event -- `getCurrentShippingMethod` - calculates a current shipping method with shipping method code -- `changeShippingMethod` - if `getCurrentShippingMethod` exists, emits `checkout-after-shippingMethodChanged` bus event -- `notInMethods` - checks if passed method is present in `shippingMethods` - -## How to add a custom checkout step - -We now show an example of how to add a new step to the checkout page of Vue Storefront. - -The step is named `NewStep` and is placed just after the `PersonalDetails` step; changing the step's name and position requires small modifications to the procedure. - -### First, create the NewStep component - -1. **Create the NewStep component** according to your needs. To do it quickly, make a copy of the `PersonalDetails` component, name it `NewStep` and customize it. - -2. **Customize the sendDataToCheckout method** of the `NewStep` component so that it emits the event `checkout-after-newStep`; for example: -```javascript - sendDataToCheckout () { - this.$bus.$emit('checkout-after-newStep', this.newStep, this.$v) - } -``` - -3. **Call the sendDataToCheckout method** when the button to the next section is clicked. This could be achieved in the template like this: -```vue - -``` - -### Then, modify the checkout component - -1. **Insert the NewStep component in the checkout template** at the desired position. For example, you could place it between the Personal Details and Shipping steps: -```vue - - - - - -``` - -2. **Listen for the checkout-after-newStep event** by adding the following listener to the `beforeMount()` function: -```javascript - this.$bus.$on('checkout-after-newStep', this.onAfterNewStep) -``` - -3. **Specify how to jump from the previous step to NewStep**. Modify the `onAfterPersonalDetails()` method in order to activate the `newStep` section instead of the `shipping` step: -```javascript - onAfterPersonalDetails (receivedData, validationResult) { - this.personalDetails = receivedData - this.validationResults.personalDetails = validationResult - this.activateSection('newStep') // show the new step - this.savePersonalDetails() - this.focusedField = null - } -``` -This is assuming that the new checkout step follows the Personal Details step; if this is not the case, you will need to modify the `onAfter` metod of whatever step precedes `NewStep`. - -4. **Specify how to jump from NewStep to the next step** by creating the method `onAfterNewStep`; in this example, the next step is the shipping form: -```javascript - onAfterNewStep (receivedData, validationResult) { - this.newStep = receivedData - this.validationResults.newStep = validationResult - this.activateSection('shipping') // change 'shipping' to whatever you want the next step to be - this.saveNewStep() // include this line only if newStep has state - } -``` -Note that calling `activateSection('shipping')` is what ultimately shows the next checkout step to the user. - -5. **If needed, save NewStep state** by defining a non-empty method `saveNewStep()`; for example: -```javascript - saveNewStep () { - this.$store.dispatch('checkout/saveNewStep', this.newStep) - }, -``` -This is needed only if your new step has state, in which case you will also need to define the `checkout/saveNewStep` action in Vuex. - - -## Store - -The Checkout Store is designed to handle the passage from user's cart to actual order; it defines actions such as saving the information given by the user during checkout, and placing the order. - -### State - -```js - state: { - order: {}, - personalDetails: { - firstName: '', - lastName: '', - emailAddress: '', - password: '', - createAccount: false - }, - shippingDetails: { - firstName: '', - lastName: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - shippingMethod: '' - }, - paymentDetails: { - firstName: '', - lastName: '', - company: '', - country: '', - streetAddress: '', - apartmentNumber: '', - city: '', - state: '', - region_id: 0, - zipCode: '', - phoneNumber: '', - taxId: '', - paymentMethod: '', - paymentMethodAdditional: {} - }, - isThankYouPage: false, - modifiedAt: 0 - } -``` - -The state of the Checkout module contains both the [Order object](https://github.com/vuestorefront/vue-storefront/blob/master/core/models/order.schema.json) and the information given by the user during the checkout process, to be stored for further use in the `localForage`. - -The state is modified by [`placeOrder`](https://github.com/vuestorefront/vue-storefront/blob/1793aaa7afc89b3f08e443f40dd5c6131dd477ba/core/store/modules/checkout/actions.js#L11) action and [`load`](https://github.com/vuestorefront/vue-storefront/blob/1793aaa7afc89b3f08e443f40dd5c6131dd477ba/core/store/modules/checkout/actions.js#L41) which loads the state from browser database. - -The category state data: - -- `order` - this is the last order to be placed, the [schema is defined](https://github.com/vuestorefront/vue-storefront/blob/master/core/models/order.schema.json) in Ajv compliant format -- `shippingDetails`, `paymentDetails` - the address information provided by the user during the [Checkout](https://github.com/vuestorefront/vue-storefront/blob/master/core/pages/Checkout.vue). - -### Actions - -The cart store provides following public actions: - -#### `placeOrder (context, { order })` - -Action called by `Checkout.vue` to complete the order. Data object is validated against the [order schema](https://github.com/vuestorefront/vue-storefront/blob/master/core/models/order.schema.json), stored within the `localForage` collection by subseqent call of [`order/placeOrder`](https://github.com/vuestorefront/vue-storefront/blob/1793aaa7afc89b3f08e443f40dd5c6131dd477ba/core/store/modules/order/actions.js#L12) - -#### `savePersonalDetails ({ commit }, personalDetails)` - -Stores the personal Details (the format is exactly the same as this store `state.personalDetails`) for later use in the browser's storage - -#### `saveShippingDetails ({ commit }, shippingDetails)` - -Stores the shipping Details (the format is exactly the same as this store `state.shippingDetails`) for later use in the browser's storage - -#### `savePaymentDetails ({ commit }, paymentDetails)` - -Stores the payment Details (the format is exactly the same as this store `state.paymentDetails`) for later use in the browser's storage - -#### `load ({ commit })` - -Load the current state from the `localForage` diff --git a/docs/guide/modules/introduction.md b/docs/guide/modules/introduction.md deleted file mode 100644 index f144640fca..0000000000 --- a/docs/guide/modules/introduction.md +++ /dev/null @@ -1,285 +0,0 @@ -# Introduction -## Table of contents - -**Introduction and motivation** -- [What are VS Modules](#what-are-vs-modules) -- [Motivation](#motivation) -- [What is the purpose of VS modules?](#what-is-the-purpose-of-vs-modules) - -**Technical part** -- [Module config and its capabilities](#module-config-and-capabilities) -- [Module file structure](#module-file-structure) -- [Module registration](#module-registration) - -**Patterns and good practices for common use cases** -- [General rules and good practices](#general-rules-and-good-practices) -- [Adding new features as VS modules](#adding-new-features-as-vs-modules) -- [Extending and overriding Vue Storefront modules](#extending-and-overriding-vue-storefront-modules) -- [Creating third party modules](#Creating-3rd-party-modules) - - -## What are VS modules? - -You can think about each module as a one, independent feature available in Vue Storefront with all its logic and dependencies inside. This *one feature* however is a common denominator that links all the features inside. For example, the common denominator for adding a product to the cart, receiving a list of items that is in the cart or applying a cart coupon is obviously a `cart` and `cart` is not a feature of anything bigger than itself (its common denominator is the shop) so it should be a module. Wishlist, Reviews or Newsletter are also good examples of the module as we intuitively think about them as standalone features. - -## Motivation - -I believe that an obvious metaphor can clearly describe the problem, at the same time, the solution. - -To better illustrate the whole concept I'll try to explain it with lego bricks. - -Let's say we have a box with 90 lego bricks that we can use to build some fancy things like Towers, Castles, or Helicopters. Unfortunately due to some stupid EU regulations we can only have 3 different colors of bricks in our box. As we all know, not every color is accurate for every structure that can be built so we need to swap one color with another in a shop from time to time in order to have bricks in colors that are best-suited for our next lego project. - -Cool, but there is one problem - since we have all our bricks in one box they look more or less as follows : - -![lego](../images/pile_of_legos.png) - -When we want to replace the green bricks with, let's say, the black ones we need to look for each green brick separately among all the others which can take a lot of time... and there is still a chance that we will miss some of them! Not to mention that finding the particular green brick that we need to finish the palm tree we are building (this one!) will require looking for it among all the other bricks which can make this task extremely difficult and time-consuming. - -This is obviously not a situation that we want to end up in with our small lego empire. Neither do we want it with Vue Storefront since it's meant to be easily extendable so you can replace your green bricks (or current user cart feature/cms provider/cms content provider) with the black ones (different cart feature with multiple carts, WordPress instead of Prismic for content etc) without hustles and bustles looking for each of them among all the bricks and without worries that you will miss some of them or EU will confiscate all the bricks that you have! We also want to make it easier to find the correct brick that we want right now to finish this damn palm tree! - -So how do we make this horrible situation better? - -Introducing... (drums build up in the background) **_bricks grouped by colors_**! (wows in the background) - -![lego2](../images/organized_lego_bricks.jpeg) - -When we have our bricks grouped by their colors (and in separate boxes - modules) it's much easier to find this green brick that we needed for a palm tree since we only need to search in a small subset of all bricks. Moreover when we want to replace green bricks with the black ones, then instead of looking for all the green representatives one by one we are just replacing their box with the one containing black bricks. We also don't need to worry if something was left behind since we know that all the green bricks were in the box. - -This is the modularity and extendability we are looking for in Vue Storefront and the architecture we are currently rewriting it into. - -## What is the purpose of VS modules? - -The purpose is well described in [this discussion](https://github.com/vuestorefront/vue-storefront/issues/1213). It can be summarized to: - -- **Better extendability**: We can extend each module or replace it completely with the new one. For example, we may want to replace our Cart module with the one that allows to have multiple carts. With module approach, we can just detach the current Cart module and replace it with the new one. Another example can be using different modules for different content CMSes integration etc. -- **Better developer experience**: Along with the modules we are introducing many features focused on delivering better and easier experience for developers to hop on in a more predictable way. We changed the way you can compose components with features, added unit tests, TypeScript interfaces etc. -- **Better upgradability**: Each module is a separate NPM package therefore can be upgraded independently and since it has all the logic encapsulated, it shouldn't break any other part of the application when detached, modified or replaced. - -## Module config and capabilities - -Module config is the object that is required to instantiate VS module. The config object you provide is later used to extend and hook into different parts of the application (e.g. router, Vuex etc). -Please use this object as the only part that is responsible for extending Vue Storefront. Otherwise it may stop working after some breaking core updates. - -Vue Storefront module object with provided config should be exported to `index.ts` entry point. Ideally it should be an *export* named the same as modules key. - -This is how the signature of Vue Storefront Module looks like: - -```js -interface VueStorefrontModuleConfig { - key: string; - store?: { - modules?: { key: string, module: Module }[], - plugin?: Function, - }; - router?: { - routes?: RouteConfig[], - beforeEach?: NavigationGuard, - afterEach?: NavigationGuard, - }; - beforeRegistration?: (VSF) => void; - afterRegistration?: (VSF) => void; -} -``` - -See code [here](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/index.ts) - -### `key` (required) - -A key is an ID of your module. It's used to identify your module and to set keys in all key-based extensions that module is associated (e.g. creating namespaced store). This key should be unique. - -### `store` - -The entry point for Vuex. - -- `modules` - array of Vuex modules to register under given keys -- `plugin` - you can provide your own Vuex plugin here - -### `router` - -The entry point for vue-router. You can provide additional routes and [navigation guards](https://router.vuejs.org/guide/advanced/navigation-guards.html) here. - -### `beforeRegistration` - -A function that'll be called before registering the module both on server and client side. You have access to VSF object here. - -The `VSF` object is an instance of your Vue Storefront shop. It contains following properties -````js - Vue?: VueConstructor, - config?: Object, - store?: Store, - isServer?: boolean -```` -### `afterRegistration` - -A function that'll be called after registering the module both on server and client side. You have access to VSF object here. - -The `VSF` object is an instance of your Vue Storefront shop. It contains following properties -````js - Vue?: VueConstructor, - config?: Object, - store?: Store, - isServer?: boolean -```` -## Module file structure - -Below you can see recommended file structure for VS module. All of the core ones are organised in this way. -Try to have a similar file structure inside the ones that you create. If all the modules are implemented with a similar architecture, it'll be much easier to maintain and understand them. Please avoid unnecessary changes in design unless otherwise required so. - -Not all of this folders and files should exist in every module. The only mandatory file is `index.ts` which is the entry point. The rest depends on your needs and module functionality. - -You can take a look at [module template](https://github.com/vuestorefront/vue-storefront/tree/master/core/modules/module-template) with an example implementation of all features listed in config. - -- `components` - Components logic related to this module (eg. Microcart for Cart module). Normally it contains `.ts` files but you can also create `.vue` files and provide some baseline markup if it is required for the component to work out of the box. -- `pages` - If you want to provide full pages with your module, place them here. It's also a good practice to extend router configuration for these pages -- `store` - Vuex Module associated to this module. You can also place Vuex modules extensions in here - - `index.ts` - Entry point and main export of your Vuex Module. Actions/getters/mutations can be split into different files if the logic is too complex to keep it in one file. Should be used in `store` config property. - - `mutation-types.ts` - Mutation strings represented by variables to use instead of plain strings - - `plugins.ts` - Good place to put vuex plugin. Should be used in `store.plugins` config object -- `types` - TypeScript types associated with the module -- `test` - Folder with unit tests which is _required_ for every new or rewritten module. -- `hooks` - before/after hooks that are called before and after registration of the module. - - `beforeRegistration.ts` - Should be used in `beforeRegistration` config property. - - `afterRegistration.ts` - Should be used in `afterRegistration` config property. -- `router` - routes and navigation guards associated to this module - - `routes.ts`- array of route objects that will be added to the current router configuration. Should be used in `router.routes` config property. - - `beforeEach.ts` - beforeEach navigation guard. Should be used in `router.beforeEach` config property. - - `afterEach.ts`- afterEach navigation guard. Should be used in `router.afterEach` config property. -- `queries` - GraphQL queries -- `helpers` - everything else that is meant to support modules behavior -- `index.js` - entry point for the module. Should export VueStorefrontModule. It's also a good place to instantiate cache storage. - -## Module registration - -All modules including the core ones are registered in `src/modules/index.ts` file. Thanks to this approach you can easily modify any of core modules object before registration (read more [here](#extending-and-overriding-vue-storefront-modules)). - -All VS modules from `registerModules` will be registered during the shop initialisation. - ---- - -## General rules and good practices - -First off, take a look at module template. It contains great examples, good practices and explanations for everything that can be put in a module. - -0. **THE MOST IMPORTANT RULE** Try to isolate all the logic required for a module to work properly and put them inside the module. You can import it from other parts of the app but the logic itself should exist in the module -1. **Try not to rely on any other module. Keep everything encapsulated and only rely on core helpers and libs**. Use other stores only if it's the only way to achieve the functionality and import `rootStore` for this purpose. Modules should work standalone and rely only on themselves. Try to think about each module as a standalone npm package. -1. Place all reusable features as Vuex actions (e.g. `addToCart(product)`, `subscribeNewsletter()` etc) instead of placing them in components. try to use getters for modified or filtered values from state. We are trying to place most of the logic in Vuex stores to allow easier core updates. Here is a good example of such externalisation. - -```js -export const Microcart = { - name: 'Microcart', - computed: { - productsInCart(): Product[] { - return this.$store.state.cart.cartItems; - }, - appliedCoupon(): AppliedCoupon | false { - return this.$store.getters['cart/coupon']; - }, - totals(): CartTotalSegments { - return this.$store.getters['cart/totals']; - }, - isMicrocartOpen(): boolean { - return this.$store.state.ui.microcart; - }, - }, - methods: { - applyCoupon(code: String): Promise { - return this.$store.dispatch('cart/applyCoupon', code); - }, - removeCoupon(): Promise { - return this.$store.dispatch('cart/removeCoupon'); - }, - toggleMicrocart(): void { - this.$store.dispatch('ui/toggleMicrocart'); - }, - }, -}; -``` - -3. If you want to inform of success/failure of core component's method you can either use a callback or scoped event. Omit Promises if you think that function can be called from the template and you'll need the resolved value. This is a good example of method that you can call either on `template` or `script` section: - -```js -addToCart(product, success, failure) { - this.$store.dispatch('cart/addToCart').then(res => - success(res) - ).catch(err => - failure(err) - ) -} -``` - -Try to choose a method based on use cases. [This](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/mailchimp/components/Subscribe.ts#L28) is a good example of using callbacks. - -5. Create pure functions that can be easily called with a different argument. Rely on `data` properties instead of arguments only if it's required (for example, they are validated as [here](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/mailchimp/components/Subscribe.ts#L28)) -6. Make a document for exported components like as follows : [document](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/mailchimp/components/Subscribe.ts) -7. If your module core functionality is an integration with external service, better name it the same as this service (for example `mailchimp`) -8. Use named exports and type check. - -## Adding new features as VS modules - -- If you want to crete a new module, copy content from `src/module-template` and use the parts that you need. -- If you are creating a new feature, then note it's not merely extending currently existing one. If you are sure the feature you want to provide is completely new then it should be introduced as a new VS module. -- Provide unique key that should represent the feature or 3rd party system name (if the module is an integration) -- Try not to rely on data and logic from other modules if your module is not claimed to directly extend it. In doing so, it's guaranteed to remain working and easier to reuse even after extensive VS core updates. - -## Extending and overriding Vue Storefront Modules - -You can extend and modify all parts of any of Vue Storefront module before its registration by providing a`VueStorefrontModuleConfig` object **with the same key** to `extendModule()` function. This config will be deep merged with the module of the same key, which means: -- All Vuex stores with the same keys will be merged (conflicting actions/mutations will be overwritten, others will be added) -- Leafs like before/after hooks, store plugins or router object properties will be overwritten by the new ones if provided. - - - -Let's see an example and assume we want to extend module `cart` by overriding its `beforeRegistration` hook and `load` Vuex action. -1. First we need to prepare a `VueStorefrontModuleConfig` that we will use to extend `cart` module. It must have the same `key` value as the module we want to extend. -2. Next we need to pass this object to `extendModule` function -3. That's all! Now when you register `cart` module it will be extended with provided config. - - -```js -import { Cart } from '@vue-storefront/core/modules/cart' - -// 1. Preparation of new VSMConfig -const extendCartVuex = { - actions: { - load () { - console.info('hey') - } - } -} - -const extendCartAfterRegistration = function (VSF) { - console.info('Hello, im extended now!') - } - -const cartExtend = { - key: 'cart', - afterRegistration: extendCartAfterRegistration, - store: { modules: [{ key: 'cart', module: extendCartVuex }] }, -} - -// 2. After passing the object to extendModule function it will be merged with Cart module during registration -extendModule(cartExtend) - -export const registerModules: VueStorefrontModule[] = [Cart] -``` - -If you want to make complex changes with your own app-specific VS module (which is not an npm package), it's a good practice to keep this module inside `src/modules/{module-name}`. To extend a module with another module just pass its config to `extendModule` function - -```js -import { Cart } from '@vue-storefront/core/modules/cart' -import { ExtendCartModule } from 'extend-cart'; - - -extendModule(ExtendCartModule.config) - -export const registerModules: VueStorefrontModule[] = [Cart] -``` - -## Creating third party modules - -If you want to create a third party module, just copy the `src/modules/module-template` raw code to your repo. Don't use any transpilation and build tools since it prevents proper tree shaking and optimization. A building process is handled by Vue Storefront build tools. A package name needs to start with `vsf-` prefix to be included into Vue Storefront build process. - -## Contributions - -Please introduce every new feature as a standalone, encapsulated module. We also need your help in rewriting Vue Storefront to modular approach - [here](https://github.com/vuestorefront/vue-storefront/issues?q=is%3Aissue+is%3Aopen+label%3A%22API+Module%22) you can find tasks related to this architecture change and [here](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/refactoring-to-modules.md) is the tutorial on how to approach applying these changes. diff --git a/docs/guide/modules/order.md b/docs/guide/modules/order.md deleted file mode 100644 index 04474134de..0000000000 --- a/docs/guide/modules/order.md +++ /dev/null @@ -1,46 +0,0 @@ -# Order module - -This module contains all the logic, components and store related to order operations. - -## Components - -### UserOrder - -**Computed** - -- `ordersHistory` - maps the value from `state.user.orders_history.items` -- `isHistoryEmpty` - checks if `state.user.orders_history.items` array is empty - -**Methods** - -- `reorder (products)` - iterates through passed 'products' array, adding each item to cart -- `skipGrouped (items)` - filters passed 'items' array returning only items without `parent_id` - -### UserSingleOrder - -**Computed** - -- `ordersHistory` - maps the value from `state.user.orders_history.items` -- `order` - finds the order in the `orderHistory` computed property with an id matching to route `orderId` parameter -- `paymentMethod` - returns `payment.additional_information[0]` from the `order` computed property -- `billingAddress` - returns `billing_address` from the `order` computed property -- `shippingAddress` - returns `extension_attributes.shipping_assignments[0].shipping.address` from the `order` computed property -- `singleOrderItems` - returns ordered products without `parent_id` - -**Methods** - -- `remakeOrder (items)` - iterates through passed 'items' array, adding each item to cart as a single product -- `skipGrouped (items)` - filters passed 'items' array returning only items without `parent_id` - -## Store - -Order store is very simple, used just to pass the current order to the backend service. - -## Actions - -The order store provides following public actions: - -### `placeOrder ({ commit }, order)` - -The order object is queued in the local, indexedDb `ordersCollection` to be sent to the server. -Please take a look at the [Working with data](../data/data.md) for the details about data formats and how does `localForage` is being used in this project. diff --git a/docs/guide/modules/review.md b/docs/guide/modules/review.md deleted file mode 100644 index d4161b265f..0000000000 --- a/docs/guide/modules/review.md +++ /dev/null @@ -1,188 +0,0 @@ -# Review module - -This module contains all the logic, components and store related to cart operations. - -## Components - -### AddToCart - -This component represents a single button that when pressed adds a product to cart. - -**Props** - -- `product` - product that'll be added to cart - -**Methods** - -- `addToCart(product)` - adds passed product to the cart. By default correlates with `product` prop - -### Microcart - -User cart with a products list and price summary. - -**Computed** - -- `productsInCart` - array of products that are currently in the cart -- `appliedCoupon` - return applied cart coupon or `false` if no coupon was applied -- `totals` - cart totals -- `isMicrocartOpen` - returns `true` if microcart is open - -**Methods** - -- `applyCoupon(code)` - applies cart coupon -- `removeCoupon` - removes currently applied cart coupon -- `toggleMicrocart` - open/close microcart - -### MicrocartButton - -Component responsible for opening/closing Microcart - -**Computed** - -- `quantity` - number of products in cart - -**Methods** - -- `toggleMicrocart` - open/close microcart - -### Product - -Component representing product in microcart. Allows to modify it's quantity or remove from cart. - -**Computed** - -- `thumbnail` - returns src of products thumbnail - -**Methods** - -- `removeFromCart` - removes current product (data property `product`) from cart -- `updateQuantity` - updates cart quantity for current product (data property `product`) - -## Store - -Cart Store is designed to handle all actions related the shopping cart. - -### State - -```js - state: { - itemsAfterPlatformTotals: {}, - platformTotals: null, - platformTotalSegments: null, - cartIsLoaded: false, - cartServerToken: '', // server side ID to synchronize with Backend (for example Magento) - shipping: [], - payment: [], - cartItemsHash: '', - bypassCount: 0, - cartItems: [] // TODO: check if it's properly namespaced - }, -``` - -Cart state is automatically loaded from `localForage` collection after page has been loaded whenever `core/components/blocks/Microcart.vue` is included. The cart state is loaded by dispatching `cart/load` action and [stored automatically by any change to the cart state](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/index.js#L118). - -The cart state data: - -- `itemsAfterPlatformTotals` - helper collection, dictionary where the key is Magento cart item `item_id` that stores the totals information per item - received from Magento; it's automatically populated when `config.cart.synchronize_totals` is enabled; -- `platformTotals` - similarly to above item, here we have the full totals from Magento for the current shopping cart. These collections are populated by [`cart/serverTotals`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/actions.js#L49) and the event handler for [`servercart-after-totals`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L30) -- `cartIsLoaded` (bool) - true after dispatching `cart/load` -- `shipping` - (object) currently selected shipping method - only when NOT using `cart.synchronize_totals` (if so, the shipping and payment's data comes from Magento2), -- `payment` - (object) currently selected shipping method - only when NOT using `cart.synchronize_totals` (if so, the shipping and payment's data comes from Magento2), -- `cartItems` - collection of the cart items; the item format is the same as described in [ElasticSearch Data formats](https://github.com/vuestorefront/vue-storefront/blob/master/doc/ElasticSearch%20data%20formats.md) - the `product` class; the only difference is that the (int) `qty` field is added - -### Events - -The following events are published from `cart` store: - -- `EventBus.$emit('cart-after-itemchanged', { item: cartItem })` - executed after [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) - after server cart sync, that signalize the specific shopping cart item has been changed; `Microcart/Product.vue` component is subscribed to this event to refresh the shopping cart UI -- `EventBus.$emit('cart-before-add', { product: item })` - fired after product has been added to the cart, -- `EventBus.$emit('cart-before-save', { items: state.cartItems })` - fired after the product cart has been saved, -- `EventBus.$emit('cart-before-delete', { items: state.cartItems })` - the event fired before the cart item is going to be deleted with the current cart state (before item is deleted) -- `EventBus.$emit('cart-after-delete', { items: state.cartItems })` - the event fired before the cart item has been deleted with the current cart state (after item is deleted) -- `EventBus.$emit('cart-before-itemchanged', { item: record })` - item called before the specific item properties are going to be changed; for example called when [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) is going to change the `server_item_id` property -- `EventBus.$emit('cart-after-itemchanged', { item: record })` - item called after the specific item properites has been changed; for example called when [`servercart-after-itemupdated`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L108) is going to change the `server_item_id` property -- `EventBus.$emit('application-after-loaded')` - event called after `cart/load` action has been dispatched to notify that cart is being available, -- `EventBus.$emit('cart-after-updatetotals', { platformTotals: totals, platformTotalSegments: platformTotalSegments })` - event called after the totals from Magento has been synchronized with current state; it's going to be emitted only when `cart.synchronize_totals` option is enabled. - -### Actions - -The cart store provides following public actions: - -#### `serverTokenClear (context)` - -Helper method used to clear the current server cart id (used for cart synchronization) - -#### `clear (context)` - -This method is called after order has been placed to empty the `cartItems` collection and create the new server cart when the `cart.synchronize_totals` is set to true - -#### `save (context)` - -Method used to save the cart to the `localForage` browser collection - -#### `serverPull (context, { forceClientState = false })` - -This method is used to synchronize the current state of the cart items back and forth between server and current client state. When the `forceClientState` is set to false the communication is one-way only (client -> server). This action is called automatically on any shopping cart change when the `cart.synchronize` is set to true. - -#### `serverTotals (context, { forceClientState = false })` - -Method is called whenever the cart totals should have been synchronized with the server (after `serverPull`). This method overrides local shopping cart grand totals and specific item values (for example prices after discount). - -#### `connect (context, { guestCart = false })` - -Action is dispatched to create the server cart and store the cart id (for further synchronization) - -#### `serverUpdateItem (context, cartItem)`, `serverDeleteItem (context, cartItem)` - -Actions called whenever the shopping cart item should be synchronized with server side (pushes changes to the server). Basically this method is called within [`servercart-after-pulled`](https://github.com/vuestorefront/vue-storefront/blob/c43b2966a9ae10661e5a62b10445403ed9789b32/core/store/modules/cart/index.js#L45) - -#### `load (context)` - -This method loads the cart items from `localForage` browser state management. - -#### `getItem ({ commit, dispatch, state }, sku)` - -This action is used for search the particular item in the shopping cart (by SKU) - -#### `addItem ({ commit, dispatch, state }, { productToAdd, forceServerSilence = false })` - -This action is used to add the `productToAdd` to the cart, if `config.cart.synchronize` is set to true the next action subsequently called will be `serverPull` to synchronize the cart. The event `cart-before-add` is called whenever new product lands in the shopping cart. The option `forceServerSilence` is used to bypass the server synchronization and it's used for example then the item is added during the ... sync process to avoid circular synchronization cycles. - -#### `removeItem ({ commit, dispatch }, product)` - -As you may imagine :) This action simply removes the product from the shopping cart and synchronizes the server cart when set. You must at least specify the `product.sku`. - -#### `removeNonConfirmedVariants ({ commit, dispatch }, product)` - -This action simply removes the non-confirmed product from the shopping cart and synchronizes the server cart when set. You must at least specify the `product.sku`. - -#### `updateQuantity ({ commit, dispatch }, { product, qty, forceServerSilence = false })` - -This method is called whenever user changes the quantity of product in the cart (called from `Microcart.vue`). The parameter `qty` is the new quantity of product and by using `forceServerSilence` you may control if the server cart synchronization is being executed or not. - -#### `updateItem ({ commit }, { product })` - -Updates item properties. - -#### `getPaymentMethods (context)` - -Gets a list of payment methods from the backend and saves them to `cart.payment` store state. - -#### `getShippingMethods (context, address)` - -Gets a list of shipping methods from the backend and saves them to `cart.shipping` store state. Country ID is passed to this method in a mandatory `address` parameter. - -#### `syncTotals (context, methodsData)` - -This method sends request to the backend to collect cart totals. It calls different backend endpoints depending on if payment and shipping methods information is available or not. - -### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -const getters = { - current: state => state.current, - list: state => state.list, -}; -``` diff --git a/docs/guide/modules/user.md b/docs/guide/modules/user.md deleted file mode 100644 index b3edb7cf82..0000000000 --- a/docs/guide/modules/user.md +++ /dev/null @@ -1,197 +0,0 @@ -# User Module - -This module contains all the logic, components and store related to the user account - -## Components - -### AccountButton - -A component to handle redirects to user account page and user logout. Usually used in header. - -**Computed** - -- `isLoggedIn` - represents if user is logged in; -- `user` - current user. - -**Methods** - -- `goToAccount` - is user is logged in, redirects user to account page. Otherwise shows sign-up modal -- `logout` - emits `user-before-logout` event and redirects user to home page - -### Login - -**Methods** - -- `close` - closes sign-up modal -- `callLogin` - starts authentication process with emitting `notification-progress-start`, calls `user/login` action with user's email and password. -- `switchElem` - triggers `setAuthElem` mutation with `register` parameter -- `callForgotPassword` - triggers `setAuthElem` mutation with `forgot-pass` parameter - -### Register - -**Methods** - -- `switchElem` - triggers `setAuthElem` mutation with `register` parameter -- `close` - closes sign-up modal -- `callRegister` - starts registration process with emitting `notification-progress-start`, calls `user/register` action with user's email, password, first name and last name. - -### UserAccount - -**Methods** - -- `onLoggedIn` - sets `currentUser` and `userCompany`. This method is called on `user-after-loggedin` bus event -- `edit` - sets `isEdited` flag to `true` -- `objectsEqual (a, b, excludedFields = [])` - checks if two passed objects are equal to each other -- `updateProfile` - updates user profile with new data. Calls a method `exitSection(null, updatedProfile)` -- `exitSection` - emits `myAccount-before-updateUser` bus event with updated user profile. Resets component user data to default values. -- `getUserCompany` - finds user company -- `getCountryName` - finds user country name - -### UserShippingDetails - -**Methods** - -- `onLoggedIn` - sets `currentUser` and `shippingDetails`. This method is called on `user-after-loggedin` bus event -- `edit` - sets `isEdited` flag to `true` -- `updateDetails` - updates shipping details with new data. Calls a method `updatedShippingDetails` -- `exitSection` - emits `myAccount-before-updateUser` bus event with updated shipping details. Resets component user data to default values -- `fillCompanyAddress` - finds shipping details -- `getCountryName` - finds country name -- `hasBillingAddres` - returns `true` if user has a billing address - -## Store - -User Store is designed to handle all actions related to the user account. -All user related data is stored in the original eCommerce CMS/Magento and the modifying actions are executed directly against the platform API. - -### State - -```js - state: { - token: '', - current: null - }, -``` - -The user state data: - -- `token` - this is the current user token got from the [`user/login`](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L64). It's used to authorize all subsequent calls with the current user identity. If this token is not empty it does mean that the user is authorized. -- `current` - this is the current user object received from [`user/me`](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L105) - immediately called after the login action. - -The user data format: - -```json -{ - "code": 200, - "result": { - "id": 58, - "group_id": 1, - "default_billing": "62", - "default_shipping": "48", - "created_at": "2018-01-23 15:30:00", - "updated_at": "2018-03-04 06:39:28", - "created_in": "Default Store View", - "email": "pkarwatka28@example.pl", - "firstname": "Piotr", - "lastname": "Karwatka", - "store_id": 1, - "website_id": 1, - "addresses": [ - { - "id": 48, - "customer_id": 58, - "region": { - "region_code": null, - "region": null, - "region_id": 0 - }, - "region_id": 0, - "country_id": "PL", - "street": ["Street", "12"], - "telephone": "", - "postcode": "51-169", - "city": "City", - "firstname": "Piotr", - "lastname": "Karwatka", - "default_shipping": true - }, - { - "id": 62, - "customer_id": 58, - "region": { - "region_code": null, - "region": null, - "region_id": 0 - }, - "region_id": 0, - "country_id": "PL", - "street": ["Street", "12"], - "company": "example", - "telephone": "", - "postcode": "51-169", - "city": "City", - "firstname": "Piotr", - "lastname": "Karwatka", - "vat_id": "PL8951930748", - "default_billing": true - } - ], - "disable_auto_group_change": 0 - } -} -``` - -### Events - -The following events are published from `user` store: - -- `EventBus.$emit('session-after-started')` - executed just [after the application has been loaded](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L22) and the User UI session has started -- `EventBus.$emit('user-after-loggedin', res)` - executed after the successful [`user/me` action call](https://github.com/vuestorefront/vue-storefront/blob/fabea12dd6ab4f8824b58812b0cfdabce94cde70/core/store/modules/user/actions.js#L123) - so the user has been authorized and the profile loaded - -### Actions - -The user store provides the following public actions: - -#### `startSession (context)` - -Just to mark that the session is started and loading the current user token from the `localForage` - for the further usage. - -#### `resetPassword (context, { email })` - -Calls the `vue-storefront-api` endpoint to send the password reset link to specified `email` address - -#### `login (context, { username, password })` - -Called to login the user and receive the current token that can be used to authorize subsequent API calls. After user is successfully authorized the `user/me` action is dispatched to load the user profile data. - -#### `register (context, { email, firstname, lastname, password, addresses })` - -Registers the user account in the eCommerce platform / Magento. - -#### `me (context, { refresh = true, useCache = true })` - -Loads the user profile from eCommerce CMS; when `userCache` is set to true the result will be stored in the `localForage` and if it's stored before - returned from cache using the `fastest` strategy (network vs cache). If `refresh` is set to true - the user data will be pulled from the server despite the cached copy is available. - -#### `update (context, userData)` - -This action is used to update various user profile data. Please check the [user schema](https://github.com/vuestorefront/vue-storefront/blob/master/core/store/modules/user/userProfile.schema.json) for the data format details. - -#### `changePassword (context, passwordData)` - -Tries to change the user password to `passwordData.newPassword`. - -#### `logout (context)` - -This is used to log out the user, close the session and clear the user token. Please notice - the current shopping cart is closed after this call. - -### Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -const getters = { - isLoggedIn(state) { - return state.current !== null; - }, -}; -``` diff --git a/docs/guide/security/README.md b/docs/guide/security/README.md deleted file mode 100644 index 6a9ae6dbf8..0000000000 --- a/docs/guide/security/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Headless Security - -External links : [Headless Security](https://headless-security.org/) \ No newline at end of file diff --git a/docs/guide/upgrade-notes/README.md b/docs/guide/upgrade-notes/README.md deleted file mode 100644 index df93304b23..0000000000 --- a/docs/guide/upgrade-notes/README.md +++ /dev/null @@ -1,631 +0,0 @@ -# Upgrade notes - -We're trying to keep the upgrade process as easy as possible. Unfortunately, sometimes manual code changes are required. Before pulling out the latest version, please take a look at the upgrade notes below: - -## 1.11 -> 1.12 - -Most of the changes added to 1.12 are backward compatible. To enable the new features (mostly the optimization features) please follow the steps described below. - -**Remove bodybuilder and compact API responses** - -The new search adapter `api-search-query` has been added. When you switch to it, by setting the `config.server.api = "api-search-query"` the ElasticSearch query is being built in the [`vue-storefront-api`](https://github.com/vuestorefront/vue-storefront-api/pull/390) which saves around 400kB in the bundle size as `bodybuilder` is no longer needed in the frontend. - -This new `api-search-query` adapter supports the `response_format` query parameter which now is sent to the `/api/catalog` endpoint. Currently there is just one additional format supported: `response_format=compact`. When used, the response format got optimized by: a) remapping the results, removing the `_source` from the `hits.hits`; b) compressing the JSON fields names according to the `config.products.fieldsToCompact`; c) removing the JSON fields from the `product.configurable_children` when their values === parent product values; overall response size reduced over -70%. - -**Re-enable amp-renderer** - -The `amp-renderer` module has been disabled by default to save the bundle size; If you'd like to enable it uncomment the module from the `src/modules` and uncomment the `product-amp` and `category-amp` links that are added to the `` section in the `src/themes/default/Product.vue` and `src/themes/default/Category.vue` - -**Check entity optimization settings** - -Cart optimization was earlier disabled automatically if entity optimization was disabled. Now they can be used independently from each other. If you don't want to use cart optimization, make sure that the `entities.optimizeShoppingCart` configuration entry is disabled explicitly. - -**deprecated actions and helpers** - -Product module has been refactored, here is list of actions that are not used anymore and you can remove them to reduce bundle. - -- deprecated actions: - - product/reset - - product/setupBreadcrumbs - - product/syncPlatformPricesOver - - product/setupAssociated - - product/loadConfigurableAttributes - - product/setupVariants - - product/filterUnavailableVariants - - product/list - - product/preConfigureAssociated - - product/preConfigureProduct - - product/configureLoadedProducts - - product/configureBundleAsync - - product/configureGroupedAsync - - product/configure - - product/setCurrentOption - - product/setCurrentErrors - - product/setOriginal - - product/loadProductAttributes - - category/list (new action is category-next/fetchMenuCategories) - -- deprecated helpers: - - configureProductAsync - - populateProductConfigurationAsync - - setConfigurableProductOptionsAsync - -Here is list of actions that are used from 1.12 in product module: -- product/doPlatformPricesSync -- product/single -- product/checkConfigurableParent -- product/findProducts -- product/findConfigurableParent -- product/setCustomOptions -- product/setBundleOptions -- product/setCurrent -- product/loadProduct -- product/addCustomOptionValidator -- product/setProductGallery -- product/loadProductBreadcrumbs -- product/getProductVariant - -All of those actions and helpers that are deprecated, can be removed so you will have smaller bundle. -Comment those lines: -- core/modules/catalog/store/product/actions.ts:318 -- core/modules/catalog/helpers/index.ts:14-18 -- core/modules/catalog-next/store/category/actions.ts:265 - -## 1.10 -> 1.11 - -This is the last major release of Vue Storefront 1.x before 2.0 therefore more manual updates are required to keep external packages compatible with 1.x as long as possible. -- `src/modules/index.ts` was renamed to `client.ts`, exported property was renamed to `registerClientModules` -- Output compression module has been added; it's enabled by default on production builds; to disable it please switch the `src/modules/server.ts` configuration -- The [`formatCategoryLink`](https://github.com/vuestorefront/vue-storefront/blob/develop/core/modules/url/helpers/index.ts) now supports multistore - adding the `storeCode` when necessary; it could have caused double store prefixes like `/de/de` - but probably only in the Breadcrumbs (#3359) -- All modules were refactored to new API. You can still register modules in previous format until 2.0 -- `DroppointShipping` and `magento-2-cms `modules were deleted -- example modules moved to https://github.com/vuestorefront/vsf-samples -- `core/helpers/initCacheStorage.ts` merged with `StorageManager.ts` (import path alias for backward compatibility added) -- Old extensions mechanism (before VS 1.4) was finally removed after being deprecated for almost a year (`src/extensions` removal) -- Cache collections were reorganized. In most cases Local Storage keys remained untouched, only collection keys were unified. also they're used only in the core. Posting changes in case someone is using those collections in their modules; - - `syncTaskCollection` renamed to `syncTasks` - - `compareCollection` renamed to `compare` - - `cmsData` renamed to `cms` - - `cartsCollection` renamed to `carts` - - `checkoutFieldValues`, `checkoutFieldsCollection` renamed to `checkout` (`checkoutFieldsCollection` wasn’t used) - - `ordersCollection` and `orders` renamed to just `orders` (`ordersCollection` wasn’t used) - - `elasticCacheCollection` renamed to `elasticCache` - - `usersCollection` `usersData` merged and renamed to `user` - - `attributesCollection`, `attributes` renamed to just `attributes` - - `ordersHistoryCollection` merged to `user` cache where it belongs - - `categoriesCollection` renamed to categories - - Collections in theme like `claimsCollection` (claims modules) remained untouched -- `UserOrder` component has been renamed to `UserOrderHistory` and moved from `src/modules/order-history/components/UserOrders` to `@vue-storefront/core/modules/order/components/UserOrdersHistory`. This component was used in `MyOrders` component found here: `src/themes/default/components/core/blocks/MyAccount/MyOrders.vue`. In this file the `import` path has to be updated. -- `claims`, `promoted-offers`, `homepage` and `ui` modules have been moved from `@vue-storefront/src/modules` to `src/themes/default/store/` and reduced to stores only.
-Delete those folders:
- -- `src/modules/claims`
- -- `src/modules/promoted-offers`
- -- `src/modules/homepage`
- -- `src/modules/ui-store`
-Copy folder `theme/store/` from `theme default`.
-Register the stores copied in previous step in `src/themes/default/index.js`. To do that, import them along with `StorageManager` method, used to replace `claims beforeRegistration hook`. -```js -import { StorageManager } from '@vue-storefront/core/lib/storage-manager'; -import { store as claimsStore } from 'theme/store/claims' -import { store as homeStore } from 'theme/store/homepage' -import { store as uiStore } from 'theme/store/ui' -import { store as promotedStore } from 'theme/store/promoted-offers' -``` -Next, inside `initTheme` method use `store.registerModule` method to register the stores. -```js -StorageManager.init('claims'); -store.registerModule('claims', claimsStore); -store.registerModule('homepage', homeStore); -store.registerModule('ui', uiStore); -store.registerModule('promoted', promotedStore); -``` -- `WebShare` moved from `@vue-storefront/core/modules/social-share/components/WebShare.vue` to `@vue-storefront/src/themes/default/components/theme/WebShare.vue`. This component was used in `Product` component found here: `src/themes/default/pages/Product.vue`. In this file the `import` path has to be updated. - -- We've fixed the naming strategy for product prices; The following fields were renamed: `special_priceInclTax` -> `special_price_incl_tax`, `priceInclTax` -> `price_incl_tax`, `priceTax` -> `price_tax`; The names have been kept and marked as @deprecated. These fields will be **removed with Vue Storefront 2.0rc-1**. -- We've decreased the `localStorage` quota usage + error handling by introducing new config variables: -- `config.products.disablePersistentProductsCache` to not store products by SKU (by default it's on). Products are cached in ServiceWorker cache anyway so the `product/list` will populate the in-memory cache (`cache.setItem(..., memoryOnly = true)`); -- `config.seo.disableUrlRoutesPersistentCache` - to not store the url mappings; they're stored in in-memory cache anyway so no additional requests will be made to the backend for url mapping; however it might cause some issues with url routing in the offline mode (when the offline mode PWA installed on homescreen got reloaded, the in-memory cache will be cleared so there won't potentially be the url mappings; however the same like with `product/list` the ServiceWorker cache SHOULD populate url mappings anyway); -- `config.syncTasks.disablePersistentTaskQueue` to not store the network requests queue in service worker. Currently only the stock-check and user-data changes were using this queue. The only downside it introduces can be related to the offline mode and these tasks will not be re-executed after connectivity established, but just in a case when the page got reloaded while offline (yeah it might happen using ServiceWorker; `syncTasks` can't be re-populated in cache from SW) -- We've moved files from /store/lib to /lib. Basically to use it from the new directory you have to import now from `@vue-storefront/core/lib/store/` instead of `@vue-storefront/core/store/lib/`. These core files got changed: -```js -core/build/webpack.base.config.ts -core/lib/sync/task.ts -core/lib/storage-manager.ts -core/modules/catalog/helpers/search.ts -core/modules/catalog/store/attribute/mutations.ts -core/modules/catalog/store/category/actions.ts -core/modules/catalog/store/category/mutations.ts -core/modules/catalog/store/product/actions.ts -core/modules/catalog/store/tax/mutations.ts -core/modules/compare/store/actions.ts -core/modules/order/store/mutations.ts -core/modules/order/index.ts -core/modules/wishlist/store/actions.ts -``` -If by some reasons you wan't to have the `localStorage` back on for `Products by SKU`, `Url Routes` and `SyncTasks` - please just set these variables back to `false` in your `config/local.json`. - -- New page-not-found handling requires to update router/index.js in the theme. -- The option `config.ssr.lazyHydrateFor` with `category-next.products` value was introduced which is responsible for hydrating products list and loading them only on client side. It means there is no category products in the `__INITIAL__STATE__`. It's enabled by default. -- The modules: `Review`, `Mailer`, `Order`, `RecentlyViewed`, `InstantCheckout` are no longer loaded by default in the main bundle as they are loading on-demand on the related pages. -- Authentication guard was moved from user module router to `MyAccount` pages mixin. -- The getters `cmsBlocks`, `cmsBlockIdentifier`, `cmsBlockId` are deprecated. Please use `getCmsBlocks`, `getCmsBlockIdentifier`, `getCmsBlockId` instead. -- Translations for "Order #", "Price ", "Select size ", "You are logged in as" and "items" changed, they now include a placeholder for the value. Please refer to [this commit](https://github.com/vuestorefront/vue-storefront/pull/3550/commits/366d31bf28a1e27a7f14b222369cba8fe0a6d3e0) in order to adjust them, otherwise they might get lost. -- `i18n.currencySignPlacement` config value is replaced by `i18n.priceFormat` so price format becomes more flexible -- Theme initialization needs to be modified in customized themes - - Delete the line `RouterManager.addRoutes(routes, router, true)`. This is now handled in `setupMultistoreRoutes`, including the default store. - - Optionally give theme routes priority, to ensure they override module routes if there are any conflicts. For example `setupMultistoreRoutes(config, router, routes, 10)`. - - See `/src/themes/default/index.js` for a complete example. -- In `storeView` config there is no more `disabled` flag for specific language config. Links for other languages will be displayed if specific `storeView` config exist. -- Categories can be filtered globally, to never be loaded, by setting `entities.category.filterFields` in local.json, e.g. `"filterFields": { "is_active": true }`. -- Categories can be filtered in the Breadcrumbs, by setting `entities.category.breadcrumbFilterFields` in local.json, e.g. `"breadcrumbFilterFields": { "include_in_menu": true }`. -## 1.10 -> 1.10.4 - -We've decreased the `localStorage` quota usage + error handling by introducing new config variables: - -- `config.products.disablePersistentProductsCache` to not store products by SKU (by default it's on). Products are cached in ServiceWorker cache anyway so the `product/list` will populate the in-memory cache (`cache.setItem(..., memoryOnly = true)`); -- `config.seo.disableUrlRoutesPersistentCache` - to not store the url mappings; they're stored in in-memory cache anyway so no additional requests will be made to the backend for url mapping; however it might cause some issues with url routing in the offline mode (when the offline mode PWA installed on homescreen got reloaded, the in-memory cache will be cleared so there won't potentially be the url mappings; however the same like with `product/list` the ServiceWorker cache SHOULD populate url mappings anyway); -- `config.syncTasks.disablePersistentTaskQueue` to not store the network requests queue in service worker. Currently only the stock-check and user-data changes were using this queue. The only downside it introuces can be related to the offline mode and these tasks will not be re-executed after connectivity established, but just in a case when the page got reloaded while offline (yeah it might happen using ServiceWorker; `syncTasks` can't be re-populated in cache from SW) - -If by some reasons you wan't to have the `localStorage` back on for `Products by SKU`, `Url Routes` and `SyncTasks` - please juset set these variables back to `false` in your `config/local.json`. - - -## 1.9 -> 1.10 -- Event `application-after-init` is now emitted by event bus instead of root Vue instance (app), so you need to listen to `Vue.prototype.$bus` (`EventBus.$on()`) now -- The lowest supported node version is currently 8.10.0, -- Module Mailchimp is removed in favor of Newsletter. `local.json` configuration under key `mailchimp` moved to key `newsletter`. -- In multistore mode now there is a possibility to skip appending storecode to url with `appendStoreCode` config option. To keep the original behavior, it should be set to true. - @lukeromanowicz (#3048). -- The `cart/addItem` is no longer displaying the error messages - please use the `diffLog.clientNorifications` to update the UI instead (take a look at the `AddToCart.ts` for a reference) -- Please make sure your `AddToCart.vue` component has the `notifyUser` method defined for showing the errors / success messages while adding to the cart. The default implementation is: - -```js - notifyUser (notificationData) { - this.$store.dispatch('notification/spawnNotification', notificationData, { root: true }) - } -``` -- The getter `cart/totals` has been replaced with `cart/getTotals` - @pkarw (#2522) -- The getter `cart/coupon` has been replaced with `cart/getCoupon` - @pkarw (#2522) -- The getter `cart/totalQuantity` has been replaced with `cart/getItemsTotalQuantity` - @pkarw (#2522) - -## 1.8 -> 1.9 -- The Url Dispatcher feature added for friendly URLs. When `config.seo.useUrlDispatcher` set to true the `product.url_path` and `category.url_path` fields are used as absolute URL addresses (no `/c` and `/p` prefixes anymore). Check the latest `mage2vuestorefront` snapshot and **reimport Your products** to properly set `url_path` fields -- `cart.multisiteCommonCart` config property changed to `storeViews.commonCache` -- Way of creating VS Modules was changed to use factory method instead of explict object creation. Even though the feature is backward compatible we highly encourage all developers to refactor their modules to use new syntax. - -The process of creating a new module with the factory method looks like the following: -````js -import { createModule } from '@vue-storefront/core/lib/module' - -const moduleConfig: VueStorefrontModuleConfig = { - // VS module config -} - -const module = createModule(moduleConfig) -```` -- `@vue-storefront/store` package has been depreciated. Just change imports to `@vue-storefront/core/store`. -- `breadCrumbRoutes` helper has been refactored to `formatBreadCrumbRoutes` -- orders which fail validation in API are assumed to have http code 400 instead of 500 -- notification message about invalid order address now uses email configured in mailer section instead of hardcoded one -- Added validation for UTF8 alpha and alphanumeric characters in most checkout fields -- Update your local.json config and set default `api.url` path, without it you may have problems with elasticsearch queries. - -### Troubleshooting -- In case of CORS problem after upgrade check your elasticsearch url in config file. Best practice for that change can be found [here](https://github.com/vuestorefront/vue-storefront/commit/77fc9c2765068303879c75ef9ed4a4b98f6763b6) - -- In case of app crashing, especially when opening `vue devtools` in browser, try setting `storeViews.commonCache` to `false`. - -## 1.7 -> 1.8 -Full changelog is available [here](https://github.com/vuestorefront/vue-storefront/blob/master/CHANGELOG.md) - -- `store/types` have moved to new module named `core/types`. -- `store/lib/search` has been moved to `core/lib/search`. -- `store/lib/multistore.ts` has been [moved] to (https://github.com/patzick/vue-storefront/commit/d42cdc44fc204dd10b173894d52dbeff244913f5#diff-87917f882ffc57fb755b1cc82ffa9e28L11) to `core/lib/multistore.ts` -- new [styles](https://github.com/patzick/vue-storefront/commit/d42cdc44fc204dd10b173894d52dbeff244913f5#diff-ae72dc903f169eb56d716cd5ac99df35R1) file for form elements -- Removed unused `src/themes/default/filters/index.js` file. Check if you're not using it as well. -- `src/themes/default/resource/head.js` has been moved to `src/themes/default/head.js` -- `src/themes/default/index.basic.template.html` has been moved to `src/themes/default/templates/index.basic.template.html` -- `src/themes/default/index.minimal.template.html` has been moved to `src/themes/default/templates/index.minimal.template.html` -- `src/themes/default/index.template.html` has been moved to `src/themes/default/templates/index.template.html` - -## 1.6 -> 1.7 - -Starting from Vue Storefront 1.7, we changed the caching strategy and offline-ready features: -- By default, the Elasticsearch Queries are executed using `GET` method and therefore are cached by Service Worker (`config.elasticsearch.queryMethod` — set it to POST for the previous behavior and if you're using graphql). -- By default, products and queries cache is set in `LocalStorage` with a quota set to 4MB (`config.server.elasticCacheQuota`). If the storage quota is set, the cache purging is executed every 30 seconds using the LRU algorithm. Local Storage is limited to 5MB in most browsers. -- We added `config.server. disablePersistentQueriesCache`, which is set to `true` by default. When this option is on, we're not storing the Elasticsearch results in the local cache because results are by default cached in the Service Worker cache anyway. -- `module.extend` has been changed to `extendModule`. You can find usage examples in `src/modules/index.ts`. -- [routes](https://github.com/patzick/vue-storefront/commit/a97eb11868de2915e86d57c4279caf944d4de422#diff-a334a7caeb7f61836f8c1178d92de3e0), [layouts](https://github.com/patzick/vue-storefront/commit/a97eb11868de2915e86d57c4279caf944d4de422#diff-48863b9fe31d7713222ec5709ef5a4fa), and component, which are not visible at page rendering are now loaded when they are needed. -- Every store manipulation should be done by dispatching actions. Invoke store dispatch on `category/mergeSearchOptions` when manipulating `store.state.category.current_product_query` somewhere. -- [here](https://github.com/patzick/vue-storefront/commit/a97eb11868de2915e86d57c4279caf944d4de422) are all changes in default themes - -Backward compatibility: To reverse to the 1.0–1.6 behavior: -- Set `config.server.disablePersistentQueriesCache` = `false`, -- Set `config.elasticsearch.queryMethod` = `POST` -- Set `config.localForage.defaultDrivers.elasticCache` = `INDEXEDDB` - -**NOTE:** Offline mode may not work properly in development mode (localhost) because of Service Workers and lack of bundle prefetching (bundles lazy loading). - -With 1.7, the number of attribute descriptors that are loaded on the product page is limited and dynamically adjusted to the fields used in the product. This behavior shouldn't have any negative impact on your app; however, if you haven’t used the `attribute/list` action explicitly based on all attributes loaded by default (up to 1.6), this may cause problems. You can switch off the dynamic loader by setting the `config.entities.product.useDynamicAttributeLoader=false`. Details: [#2137](https://github.com/vuestorefront/vue-storefront/pull/2137/files) - -Dynamic Categories prefetching (#2076). Starting with Vue Storefront 1.7, we added a configuration option `config.entities.category.categoriesDynamicPrefetch` (by default set to `true`). TThis option switches the way the category tree is being fetched. Previously, we were fetching the full categories tree. In some cases, it can generate even a few MB of payload. Currently with this option in place, we're pre-fetching the categories on demand while the user is browsing the category tree. - -**NOTE:** Since we're no longer generating `category.slug` client-side, we need to have `category.url_key` field unique. If Your Magento2 url_keys are unique it will work without any changes. If not - please do use [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) to re-import the categories. There is a new `categories` importer option `--generateUniqueUrlKeys` which is set to true by default. - -With the new modules architecture available from 1.6 we've [updated the payment modules guide](https://github.com/vuestorefront/vue-storefront/pull/2135). If You've used the custom payment (and basically any other) extensions please make sure You've already ported them to [new modules architecture](https://docs.vuestorefront.io/guide/modules/introduction.html). - - -## 1.5 -> 1.6 - -With 1.6, we introduced new modular architecture and moved most of the theme-specific logic from the core to the default theme. It's probably the biggest update in VS history and the first step to making future upgrades more seamless. - -Due to architectural changes, `core/components` and `core/store/modules` folders were removed and reorganised into modules ( `core/modules`). In most cases, the components API remained the same (if not, we provided an API bridge in `core/compatibility/components` folder which allows you to benefit from new features without making changes in your theme). It's a good idea to look for imports referring to deleted folders after migration to be sure that we made a full update. - -Overall, the theme upgrade for the default theme requires 105 files to be changed, but 85% of these changes are just a new import paths for the core component, which makes this update time-consuming, but easy to follow and not risky. - -[Here](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f) you can find detailed information (as an upgrade commit with notes) about what needs to be changed in the theme to support VS 1.6. - -#### Global changes -- Notifications are now based on Vuex Store instead of Event Bus. We also pulled hardcoded notifications from core to theme. - -Change every component: -````js -this.$bus.$emit('notification', -```` -to: -````js -this.$store.dispatch('notification/spawnNotification', -```` -Change every store: -````js -EventBus.$emit('notification', -```` -to: -````js -rootStore.dispatch('notification/spawnNotification', -```` -and make sure you are importing `rootStore`. - -- Lazy loading for non-SSR routes is now available. - -You can now [use dynamic imports to lazy load non-SSR routes](https://router.vuejs.org/guide/advanced/lazy-loading.html). You can find examples from the default theme [here](https://github.com/vuestorefront/vue-storefront/tree/develop/src/themes/default/router) - -- Extensions are now rewritten to modules (and the extensions system will be depreciated in 1.7). - -If you haven't modified any extensions directly, you don't need to change anything. If you made such changes, you probably need to rewrite your extension to a module. - -- The old event bus is moved to the compatibility folder. From now we are trying to create new features without it and slowly depreciate the event bus whenever possible. It'll be replaced with some enhanced module-based mechanism with event autosuggestion support. - -Change all `@vue-storefront/core/plugins/event-bus` imports to `@vue-storefront/core/compatibility/plugins/event-bus` - -#### Components that were moved or the API was changed and the compatibility component was created. - -Required action: Change the import path. In case of additional changes click on a component name to see how to update. - -- [`AddToCart.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-16a4dd1cbf1aaf74e001e6541fb27725) -- [`Breadcrumbs.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-fa33732560b7c39ea7854f701c4187bf) -- [`ColorSelector.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-551ae89c6d9e297a662749ee02676d45) -- [`GenericSelector.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-cbd08cdda068c587e3146bb3441e2161) -- [`NewsletterPopup.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-7b6cae3e664a647ff7397d6692e61cd6) -- [`Notification.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-d91127b094ff36e25dd94834c3aded6a) + `exec` changed to `execAction`, added `execAction` -- [`Overlay.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-66a3ae90ace32b95ebe4513c496f76ce) -- [`PriceSelector.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-f564bd3cf6c2687c23c442d1363fe97b) -- [`ProductAttribute.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-b9783243d8666d5b2e46df608e6ace48) -- [`ProductCustomOptions.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-14db476c8821b640e674f1e655340de5) -- [`ProductGallery.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-66a4dfcf61217934307df12e9e3f14c6) -- [`SizeSelector.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-a83c265a63b7b594b5052ec61907cde1) -- [`SortBy.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-5dab270f8342facd10835e828e5d7509) + change from dropdown to select -- [`ValidationError.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-e80a538151f16af10b20efa810ae0bc3) -- [`Login.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-3ac818c5cd6c41a9c9d7a3acba41cdb5) -- [`Category/Sidebar.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-88e78ea0dd0f96c15c4a00d29922e44c) + added possibility to clear all filters -- [`CartSummary.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-bacd489920bede6d60bc9ce0b165e517) -- [`OrderReview.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-958fa5f4d0d271422146a86119bde82d) + notifications moved from core to theme -- [`Payment.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-878b5e4180d8f997d0b57a7dc5d28154) -- [`PersonalDetails.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-d8651354142bff547c667cdab2e87a9f) -- [`Checkout/Product.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-a08ecdd137cc1b32d02d458e3ae22079) -- [`Shipping.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-a83c265a63b7b594b5052ec61907cde1) -- [`AccountIcon.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-b25a4326cbb2102f3293084aefe6d4c8) -- [`CompareIcon.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-4180179a247fd8ebb816f4d8e94e6c5b) -- [`HamburgerIcon.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-39d690ab85a291546f5ebe1606250fb5) -- [`MicrocartIcon.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-ddcf89d0ca46b06824190f07c87f6031) -- [`Returnicon.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-68dfc0097a84464e2f8196d0948b4a03) -- [`WishlistIcon.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-fabd1714a4872a2bcf26619adbe0709c) -- [`Microcart.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-ffba5dcba1456c20f565e314790cd450) -- [`Microcart/Product.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-5715f58dacfe621ce8d056e382f1be8b) -- [`MyNewsletter.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-48b9d7ff2e2e8e6cf6965fd582e51957) -- [`MyOrder.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-a8dbaeacd2cf4fb6440959d7827372fa) -- [`MyOrders.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-4d60c889f1362249fc19756d60f8f9b1) -- [`MyProfile.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-677b7e3129afc1c974c3bd08069662c7) -- [`MyShippingDetails.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-5ed705e03a497368b98d9ce41ec378de) -- [`Related.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-c1703e08c80fcacd8135eeb6d707aa95) + `refreshList` method changed -- [`SearchPanel.gql.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-2020399a5c1abfc37c176bfcc5912293) -- [`SearchPanel.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-e2ff52db282530838ffce57893ed4a77) -- [`SidebarMenu.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-c341122656ef878fc532a5c34348a1ec) + bugfix (hide longer menus that are below currently active one), direct router link instead of event -- [`Wishlist/Product.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-7c0514d730223832fd2e1fae9d5f2068) -- [`Wishlist.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-8dc4f61d36ae2b2ffc2a4c4603e844b8) -- [`Collection.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-26a650b112a3b01efd1ff3a5c752aba1) -- [`Home.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-91bc0c9fe9fa95dd88900beff8975200) - -#### Components that were moved from core to theme - -Required action: Add moved content and remove core import. In case of additional changes, click on a component name to see how to update. - -- [`Loader.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-a2791ef5c57fb2459362720b4a624e53) -- [`Modal.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-527c2bb9d04213a2aaf1aac75673bc71) + static content removed -- [`ProductLinks.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-9ccb222e8d3decebb067729ee935899a) -- [`ProductListing.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-000bc323621dca4883033c0e91f9125a) -- [`ProductTile.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-4049e5b38efd1a5d0215fd71e32136a3) + core import moved to module -- [`ProductSlider.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-624e75f15bdb9b2f7d5f417138c9f0ec) -- [`ForgotPass.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-e2fbfb707a274bea8b9f7af3e3c57032) -- [`Register.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-f5266afaec9d55f2fc93cf3b874b7288) + core import moved to module -- [`SignUp.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-df0c798d67a63c4eef4d3141393db5f9) -- [`BaseCheckbox.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-23e3b8989b402a011ee4cb3f985fa3ce) -- [`BaseInput.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-d209385c453bdf31b89bc51772bd2bda) -- [`BaseRadioButton.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-1493dcb05a06ce313807003d1774fa14) -- [`BaseSelect.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-c4c37e440fd8ec3deeacef085b48ed9f) -- [`BaseTextArea.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-0ea48ec8c1545db8cbdacfd348f8bf75) -- [`Header.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-5f4560c69df1a5f6d97f0b064b9b792f) -- [`MainSlider.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-19c964e13db826a1358f30839627986b) -- [`Reviews.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-d8861516b2c7dfc547b91e1066ca4755) + empty array instead of null, core import path changed -- [`Compare.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-0aa476fa2f0314806d4afd620c80be54) - -#### Other -- [`ProductBundleOption.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-32809917812e7c8c4571be70a693d65b) - splitted single option from `ProductBundleOptions.vue` component. -- [`ProductBundleOptions.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-7ccee94c636406b1a82feddea3a7f520) - single option moved to separate component `ProductBundleOption.vue`, moved to module. -- [`ThankYouPage.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-84c29c5b22568c31b021dc864221563f) added order id display, order confirmation, pulled notifications from core and added mail confirmation -- [`main.scss`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-c65e47159738f3552a22f16ec5c5974f) removed duplicated flexbox grid -- [`index.template.html`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-bf0804a2329350f8e9d9071e40cf1429) (+ all other templates that you may have like minimal, basic etc), added ` output.appendHead(), renderStyles()` -- [`Category.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-eb709969add1ca4a266ac072cddde954) notifications moved to theme -- [`Checkout.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-1c6544c28d075f275812201fa42755de) notifications moved to theme -- [`MyAccount.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-bb873f532ed9a2efbb157af79a70e0f7) notifications moved to theme -- [`Product.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-174db65df8e0c43df20b73b5bf16881b) minor changes in attribute var names that may influence the markup, notifications moved to theme -- [`Static.vue`](https://github.com/vuestorefront/vue-storefront/commit/cc17b5bfa43a9510815aea14dce8bafac382bc7f#diff-a3a9b6eeeba4c915c1ea1aae1c489ecc) static pages are no longed using markdown files. - -## 1.4 -> 1.5 - -### Modifications - -#### New Modules API - -With 1.5.0, we introduced a new, heavily refactored modules API. We tried to keep the old theme components backward compatible, so now you can view some "mock" components in the `/core/components` referencing to the `/modules//components` original. Please read [how modules work and are structured](../modules/introduction.md) to check if it implies any changes to your theme. Though it may seem like a lot of changes (a lot of files were added/removed/renamed), it should not impact your custom code. - -#### New Newsletter module - -The existing newsletter-integration module was pretty chaotic and messy. @filrak has rewritten it from scratch. If you've relied on existing newsletter module logic / events / etc., it could have affected your code (low probability). - -#### Memory leaks fixed - -We fixed SSR memory leaks with #1882. It should not affect your custom code, but if you've modified any SSR features, please make sure that everything still works just fine. - -## 1.3 -> 1.4 - -### Modifications - -#### GraphQL - -We added GraphQL support. Please read more on the [GraphQL Action Plan](/guide/basics/graphql.html). Starting from this release, the **bodybuilder** package is **deprecated**. You should use the **SearchQuery** internal class that can be used against API and GraphQL endpoints. Read more on [how to query data](/guide/data/elastic-queries.html). - -#### SSR: Advanced output and cache - -This change does not involve any required actions to port the code, but please be aware that we're supporting [SSR Cache](https://github.com/vuestorefront/vue-storefront/blob/develop/doc/SSR%20Cache.md) + [dynamic layout changes](https://github.com/vuestorefront/vue-storefront/blob/develop/doc/Layouts%20and%20advanced%20output%20operations.md) etc. If you're using the modified version of the theme, you can hardly use these without updating `themes/YourTheme/App.vue` to the new format (check the default theme for details). - -#### Reviews - -We added Reviews support, however, Magento 2 is still lacking Reviews support in the REST API. To have reviews up and running, please add the https://github.com/divanteLtd/magento2-review-api to your Magento 2 instance. - -#### Microcart - -1. We moved core functionalities of coupon codes to API modules: - - - **Coupon** computed value is now **appliedCoupon** ([read more](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/cart.md)) - - **removeCoupon** ([read more](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/cart.md)) - - **applyCoupon** ([read more](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/cart.md)) - - **totals** -> **cartTotals** ([read more](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/cart.md)) - - **shipping** -> **cartShipping** ([read more](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/cart.md)) - - **payment** -> **cartPayment** ([read more](https://github.com/vuestorefront/vue-storefront/blob/master/doc/api-modules/cart.md)) - -2. We moved/renamed methods responsible for UI to the default theme: - - **addDiscountCoupon** - toggle coupon form - - **removeCoupon** -> **clearCoupon** - removing coupon by dispatch removeCoupon API method and toggle coupon form - - **applyCoupon** -> **setCoupon** - submit coupon form by dispatch applyCoupon API method - - **enterCoupon** - was removed, because @keyup="enterCoupon" we changed to @keyup.enter="setCoupon" - -3. We moved $emit with notification about appliedCoupon and removedCoupon from Vuex store to the default theme. Now applyCoupon and removeCoupon returns promise, which you can handle by yourself. - -4. We moved VueOfflineMixin and onEscapePress mixins to the theme component. The core component is clean from UI stuff now. - -5. We've replaced one method `Microcart` - `cartTotals` -> `totals` - -#### Assets - -1. We removed the default assets from `core/assets`. From now on, we only use the assets from `your-theme/assets`. - -#### Store - -1. We moved the socialTiles Vuex store from the core to the theme, because it's specific to the theme. - -#### i18n - -1. We removed all the theme-specific translations for the core. - -## 1.2 -> 1.3 - -### Changes - -1. We removed event emit from client-entry.js with online status information. Instead, we are now using [vue-offline](https://github.com/filrak/vue-offline) mixin. [#1494](https://github.com/vuestorefront/vue-storefront/issues/1494) -2. We removed the isOnline variable from Microcart.js. Instead, we are now using variables from [vue-offline](https://github.com/filrak/vue-offline) mixin. [#1494](https://github.com/vuestorefront/vue-storefront/issues/1494) - -### Upgrade step-by-step - -#### `global.$VS` replaced with `rootStore` and `config` was moved to `config` - -To get access to rootStore, import it by - -`import config from 'config'` - -#### cms extenstion was renamed to extension-magento2-cms - -Import of CmsData must be changed in `CustomCmsPage.vue` component to: - -`import CmsData from '@vue-storefront/extension-magento2-cms/components/CmsData'` - -## 1.1 -> 1.2 ([release notes](https://github.com/vuestorefront/vue-storefront/releases/tag/v1.2.0)) - -There were no breaking-changes introduced. No special treatment needed 😃 - -## 1.0 -> 1.1 ([release notes](https://github.com/vuestorefront/vue-storefront/releases/tag/v1.1.0)) - -### Modifications - -#### Plugins registration simplified - -Instead of exporting an object in `{theme}/plugins/index.js` just use `Vue.use(pugin)` directly in this file ( [docs](https://github.com/vuestorefront/vue-storefront/blob/master/doc/Working%20with%20plugins.md) ) - -#### Microcart logic moved to API module (partially) - -Starting from Microcart, we are moving most of the logic to core modules along with unit testing them. [Read more](https://github.com/vuestorefront/vue-storefront/issues/1213). - -Changes that happened in `Microcart.js` core component and `Microcart.vue` component from default theme: - -- `closeMicrocart` renamed to `closeMicrocartExtend` -- `items` renamed to `productsInCart` -- `removeFromCart`method added to core Microcart - -#### `theme/app-extend.js` removed - -It was redundant. - -#### `{theme}/service-worker-ext.js` moved to `{theme}/service-worker/index.js` - -Now it mirrors `core/` folder structure, which is desired behaviour. - -### vue-storefront-api docker support has been extended - -We added the possibility to run the `vue-storefront-api` fully in Docker (previously, just the Elastic and Redis images were present in the `docker-compose.yml`. Please read the [README.md](https://github.com/vuestorefront/vue-storefront-api) for more details. - -**PLEASE NOTE:** We changed the structure of the `elasticsearch` section of the config files, moving `esIndexes` to `elasticsearch.indices` etc. There is an automatic migration that will update your config files automatically by running: `yarn migrate` in the `vue-storefront-api` folder. - -### Default storage of the shopping carts and user data moved to localStorage - -Currently, there is a config option to set up the default local storage configs: https://github.com/vuestorefront/vue-storefront/blob/271a33fc6e712b978e10b91447b05529b6d04801/config/default.json#L148. If you like the previous behavior of storing the carts in the indexedDb, please change the config backend to `INDEXEDDB`. - -### mage2vuestorefront improvements - -A lot of improvements have been added to the [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) importer. importer. For example, fixed special_price sync. For such changes, please update [mage2vuestorefront](https://github.com/vuestorefront/mage2vuestorefront) and re-import your products. We also added the dynamic on/demand indexing. - -### New features - -We added [`vue-progressbar`](https://github.com/hilongjw/vue-progressbar) to default theme, which can be found in `App.vue` file. - -## 1.0RC-3 -> 1.0([release notes](https://github.com/vuestorefront/vue-storefront/releases/tag/v1.0.0)) - -This is the official, stable release of Vue Storefront. - -1. We renamed `the core/components/*.vue` -> `the core/components/*.js` -2. We renamed `the core/pages/*.vue` -> `the core/pages/*.js` -3. We removed `corePage` and `coreComponent` helpers and created an es-lint rule to migrate the `import` statements automatically (with `--fix` parameter) - -You should replace the mixin declarations from the previous version: - -```vue - -``` - -to - -```vue - -``` - -4. We've added Multistore support. It shouldn't imply any breaking changes to the existing themes / extensions (by default it's just disabled). - -## 1.0RC-2 -> 1.0RC-3 ([release notes](https://github.com/vuestorefront/vue-storefront/releases/tag/v1.0.0-rc.3)) - -This release contains three important refactoring efforts: - -1. We changed the user-account endpoints and added the token-reneval mechanism, which is configured by -`config.users.autoRefreshTokens`. If set to true and user token will expire, VS will try to refresh it. - -2. We separated the user-account endpoints, so please copy the following defaults from `default.json` to Your `local.json` and set the correct API endpoints: - -```json - "users": { - "autoRefreshTokens": true, - "endpoint": "http://localhost:8080/api/user", - "history_endpoint": "http://localhost:8080/api/user/order-history?token={{token}}", - "resetPassword_endpoint": "http://localhost:8080/api/user/reset-password", - "changePassword_endpoint": "http://localhost:8080/api/user/change-password?token={{token}}", - "login_endpoint": "http://localhost:8080/api/user/login", - "create_endpoint": "http://localhost:8080/api/user/create", - "me_endpoint": "http://localhost:8080/api/user/me?token={{token}}", - "refresh_endpoint": "http://localhost:8080/api/user/refresh" - }, -``` - -The endpoints are also set by the `yarn installer` so You can try to reinstall VS using this command. - -3. We optimized the performance by limiting the fields loaded each time in JSON objects from the backend. Please review the `config/default.json`and if some fields required / used by your app are missing, copy the following fragment to the `config/local.json` and add the required fields: - -```json - "entities": { - "optimize": true, - "twoStageCaching": true, - "category": { - "includeFields": [ "children_data", "id", "children_count", "sku", "name", "is_active", "parent_id", "level" ] - }, - "attribute": { - "includeFields": [ "attribute_code", "id", "entity_type_id", "options", "default_value", "is_user_defined", "frontend_label", "attribute_id", "default_frontend_label", "is_visible_on_front", "is_visible", "is_comparable" ] - }, - "productList": { - "includeFields": [ "type_id", "sku", "name", "price", "priceInclTax", "original_price_incl_tax", "id", "image", "sale", "new" ], - "excludeFields": [ "configurable_children", "description", "configurable_options", "sgn", "tax_class_id" ] - }, - "productListWithChildren": { - "includeFields": [ "type_id", "sku", "name", "price", "priceInclTax", "original_price_incl_tax", "id", "image", "sale", "new", "configurable_children.image", "configurable_children.sku", "configurable_children.price", "configurable_children.special_price", "configurable_children.price_incl_tax", "configurable_children.special_price_incl_tax", "configurable_children.original_price", "configurable_children.original_price_incl_tax", "configurable_children.color", "configurable_children.size" ], - "excludeFields": [ "description", "sgn", "tax_class_id" ] - }, - "product": { - "excludeFields": [ "updated_at", "created_at", "attribute_set_id", "status", "visibility", "tier_prices", "options_container", "url_key", "msrp_display_actual_price_type", "has_options", "stock.manage_stock", "stock.use_config_min_qty", "stock.use_config_notify_stock_qty", "stock.stock_id", "stock.use_config_backorders", "stock.use_config_enable_qty_inc", "stock.enable_qty_increments", "stock.use_config_manage_stock", "stock.use_config_min_sale_qty", "stock.notify_stock_qty", "stock.use_config_max_sale_qty", "stock.use_config_max_sale_qty", "stock.qty_increments", "small_image"], - "includeFields": null - } - }, -``` - -If `optimize` is set to false, it's a fallback to the previous behaviour (getting all fields). - -4. Another cool feature is `twoStageCaching` enabled by default. It means that for the Category page, VS is getting only the minimum number of JSON fields required to display the ProductTiles. Shortly after, it downloads the full objects by the second request to store them in the local cache. - -5. We tweaked the Service Worker to better cache the app; it sometimes can generate frustration if your home page is now cached in the SW (previously was not). Feel free to use `Clear Storage` in your Developers tools :) - -6. The `mage2vuestorefront` tool got update and now it's loading the `media_gallery` with additional media per product. We also put some MediaGallery components on the product page. - -7.Product and Category pages got refactored. It's a massive refactoring moving all the logic to the Vuex stores, so if you played with the core `fetchData`/`loadData` functions, your code may be affected by this change. - -## 1.0RC -> 1.0RC-2 ([release notes](https://github.com/vuestorefront/vue-storefront/releases/tag/v1.0.0-rc.2)) - -This release brings some cool new features (Magento 1.x support, Magento 2 external checkout, My Orders, Discount Codes) together with some minor refactors. - -Unfortunately, with the refactors comes two manual changes that need to be applied to your custom themes after the update. - -Here You can check an **[example how did we migrated our own default_m1 theme to RC-2](https://github.com/vuestorefront/vue-storefront/commit/111519c04acec272657e7eefec7ea8405da95f13)**. - -1. We changed `ColorButton`, `SizeButton`, `PriceButton` in the `core` to `ColorSelector`, `SizeSelector`, `PriceSelector` and added the `GenericSelector` for all other attribute types. Because of this change, the `coreComponent('ColorButton')` must be changed to `coreComponent('ColorSelector')` etc. - -2. We added the Vuex Stores extensibility to the themes. If You're getting the following build error: - -``` -ERROR in ./core/store/index.js -Module not found: Error: Can't resolve 'theme/store' in '***/vue-storefront/core/store' -``` - -It means, that you need to copy the template store to: `/store`. diff --git a/docs/guide/vuex/attribute-store.md b/docs/guide/vuex/attribute-store.md deleted file mode 100644 index a1f4523a1a..0000000000 --- a/docs/guide/vuex/attribute-store.md +++ /dev/null @@ -1,59 +0,0 @@ -# Attribute Vuex Store - -Attribute Store is designed to handle all actions related to attributes management. - -## State - -```js - state: { - list_by_code: {}, - list_by_id: {}, - labels: {} - }, -``` - -As we're using the attributes dictionary for the product management in a very similar way as Magento ([EAV model](http://www.xpertdeveloper.com/2010/10/what-is-eav-model-in-magento/)), we're operating on the attributes, attribute types, and dictionaries. - -Attributes are **explicitly** loaded by the user by calling the `attribute/list` method. For example, when you're going to work with customizable attributes of the product, or to work on variants, you need to prefetch the attributes metadata: - -```js -this.$store.dispatch('attribute/list', { - filterValues: [true], - filterField: 'is_user_defined', -}); -``` - -This is an example from [product compare feature](https://github.com/vuestorefront/vue-storefront/blob/c954b96f6633a201e10bed1d2e4c0def1aeb3071/core/pages/Compare.vue). - -The attribute state data: - -- `list_by_code` - This is a dictionary where you can get the specific attribute just by accessing the `list_by_code['color']` etc. -- `list_by_id` - This is a dictionary where you can get the specific attribute just by accessing the `list_by_id[123]` etc. -- `labels` - The preloaded labels of attribute values (the V in EAV). - -## Actions - -The attribute store provides the following public actions: - -### `list (context, { filterValues = null, filterField = 'attribute_code', size = 150, start = 0 })`` - -This method is used to load the attributes metadata. `filterValues` is an array of multiple values like: `['color', 'size']` and the `filterField` is the attribute field to compare the `filterValues` against. Usually, it is a `attribute_code` or `attribute_id`. The `size` and `start` are just used to limit the list. - -## Helpers - -Attribute module exports one very popular helper method: - -### `export function optionLabel (state, { attributeKey, searchBy = 'code', optionId })` - -This is used to get the label for specific `optionId`. For example, when the user filters products and uses the 165 attribute_value we can call `optionLabel( { attributeKey: 'color', optionId: 165 })` to get back 'Red' label. - -## Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats - -```js -export default { - attributeListByCode: state => state.list_by_code, - attributeListById: state => state.list_by_id, -}; -``` diff --git a/docs/guide/vuex/category-store.md b/docs/guide/vuex/category-store.md deleted file mode 100644 index 74a35f02c1..0000000000 --- a/docs/guide/vuex/category-store.md +++ /dev/null @@ -1,92 +0,0 @@ -# Category Vuex Store - -The Category Store is designed to handle all actions related to category data. - -This module works pretty tightly with Elasticsearch and operates on the [Product data format](../data/elasticsearch.md) - -## State - -```js -const state = { - list: [], - current: {}, - filters: { color: [], size: [], price: [] }, - breadcrumbs: { routes: [] }, - current_path: [], // list of categories from root to current -}; -``` - -The category state is generally populated by just two methods- [list](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L38) and [single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) - and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L28) - -:::tip Note -The action `category/single` uses `localForage` cache only—no Elasticsearch data store directly. Because of this optimization, download the categories list by dispatching the `category/list` at first. -::: - -The category state data: - -- `breadcrumbs` - This is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - This is the current category object. -- `filters` is a current state of the category filters, a dictionary of selected variant attributes; for example it contains dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, we're using the Magento-like EAV attributes structure so the values here are attribute value indexes, not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference - -- `current_path` - this is the list of category objects: from current category to the top level root, - -## Events - -The following events are published from `category` store: - -- `EventBus.$emit('category-after-single', { category: mainCategory })` - from [category/single](https://github.com/vuestorefront/vue-storefront/blob/06fbb89a5a8bc2c607847f65a7bca9ad54ed7146/core/store/modules/category.js#L70) after single category is loaded. -- `EventBus.$emit('category-after-current', { category: category })` - After current category has been changed - this is subsequent call of `category/single` action. -- `EventBus.$emit('category-after-reset', { })` - After the category has been reset (for example, in the process of moving from one category page to another). -- `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - This event emits the current category list as it's returned by `category/list`. - -## Actions - -The cart store provides following public actions: - -### `list (context, { parent = null, onlyActive = true, onlyNotEmpty = false, size = 4000, start = 0, sort = 'position:asc' })` - -This is the key method to load the category list. It returns the Promise that contains the product list object. This method should be used everywhere you need to get products data. - -### `single (context, { key, value, setCurrentCategory = true, setCurrentCategoryPath = true })` - -This method gets the single category from `localForage`. - -:::warning Important -To make this method work, you should call `category/list` before. This category works only on localForage and cannot access Elasticsearch directly -::: - -:::warning Important -This method synchronizes products for offline usage by storing the whole query results object into `localForage` and by caching each category individually (to be used on the product page, for example). -::: - -**Events**: this method emits category list as `EventBus.$emit('category-after-list', { query: qrObj, sort: sort, size: size, start: start, list: resp })` - -- `parent` - `category` - Object to load the subcategories only. - -- `start`, `size` - Both parameters are used for paging; start is the starting index; size is a page size. - -- `onlyActive` - (bool) load only the categories marked as active in CMS (for example, in Magento). - -- `sort` - Category attribute used to sort. This field must be mapped in Elasticsearch as a numeric field. - -- `onlyNotEmpty` - (bool) load only the categories that contain any products. - -## Getters - -All state members should be accessed only by getters. Please take a look at the state reference for data formats. - -```js -const getters = { - current: state => state.current, - list: state => state.list, -}; -``` diff --git a/docs/guide/vuex/introduction.md b/docs/guide/vuex/introduction.md deleted file mode 100644 index db5399966a..0000000000 --- a/docs/guide/vuex/introduction.md +++ /dev/null @@ -1,64 +0,0 @@ -# Introduction - -All data processing and remote requests should be managed by Vuex data stores. The core modules generally contain `store` folder inside. -You can modify the existing store actions by responding to events. Events are specified in the docs below and can be found in the [core module](https://github.com/vuestorefront/vue-storefront/tree/master/core), where `EventBus.$emit` has been mostly used for Vuex Actions. - -**You should put all the REST calls, Elasticsearch data queries inside the Vuex Actions.** This is our default design pattern for managing the data. - -## Vuex conventions - -Before you start working with Vuex, it's recommended to get familiar with our [vuex conventions](./vuex-conventions.md) - -## Vuex modules - -- [Product](product-store.md) -- [Category](category-store.md) -- [Cart](Cart%20Store.md) -- [Checkout](Checkout%20Store.md) -- [Order](Order%20Store.md) -- [Stock](stock-store.md) -- [Sync](sync-store.md) -- [User](User%20Store.md) -- [Attribute](attribute-store.md) -- [UI Store]() - -## Override existing core modules - -Existing core modules can be overridden in the themes store. Just import any core store modules and override them using the `extendStore()` utility method like the example given below in `src/modules/ui-store/index.ts`. - -``` -import coreStore from '@vue-storefront/core/store/modules/ui-store' -import { extendStore } from '@vue-storefront/core/lib/themes' - -const state = { - // override state of core ui module... -} - -const mutations = { - // override mutations of core ui module... -} - -const actions = { - // override actions of core ui module... -} - -export default extendStore(coreStore, { - state, - mutations, - actions -}) -``` - -And then import it in `src/modules/index.ts` - -``` -import ui from './ui-store' - -export default { - ui -} -``` - -## Related - -[Working with data](data.md) diff --git a/docs/guide/vuex/product-store.md b/docs/guide/vuex/product-store.md deleted file mode 100644 index 7c3e9b44a7..0000000000 --- a/docs/guide/vuex/product-store.md +++ /dev/null @@ -1,139 +0,0 @@ -# Product Vuex Store - -The Product Store is designed to handle all actions related product data. It's responsible for loading the list of products or a single product as well as configuring the configurable products and managing the product attachments. - -This module works pretty tightly with Elasticsearch and operates on the [Product data format](../data/elasticsearch.md) - -## State - -```js -const state = { - breadcrumbs: { routes: [] }, - current: null, // shown product - current_options: { color: [], size: [] }, - current_configuration: {}, - parent: null, - list: [], - original: null, // default, not configured product - related: {}, -}; -``` - -The product state is generally populated by just two methods - [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) and [single](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L428) - and cleared to the defaults by [reset](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L215). - -The product state data: - -- `breadcrumbs` - This is the list of routes used by the [Breadcrumbs component](https://github.com/vuestorefront/vue-storefront/blob/master/core/components/Breadcrumbs.js) -- `current` - This is the product object with selected `configurable_children` variant, so it's the base product with attributes overridden by the values from selected `configurable_children` variant; it's used on [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/pages/Product.vue#L203). This is the product which is added to the cart after "Add to cart" -- `current_options` - The list used to populate the variant selector on the [Product.vue page](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/themes/default/pages/Product.vue#L56). It contains dictionary of attributes and possible attribute values and labels, and it's populated by [setupVariants](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L344) based on the `configurable_children` property. -- `current_configuration` The current product configuration. A dictionary of selected variant attributes; for example, it contains a dictionary of selected product attributes: - -```json -{ - "color": 123, - "size": 24 -} -``` - -Please note, we're using the Magento-like EAV attributes structure so the values here are attribute value indexes, not the values itself. Please take a look at [Data formats](../data/elasticsearch.md) for a reference. - -- `parent` - If the current product is a `type_id="single"`, then in this variable the parent, `configurable` product is stored. This data is populated only on `Product.vue` by [checkConfigurableParent](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L323) -- `list` - This is an array of products loaded by [list](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L395) -- `original` - Used only for `configurable` products, this is the base product with no selected variant. -- `related` - This is dictionary of related products; set outside this store (for [example here](https://github.com/vuestorefront/vue-storefront/blob/master/src/themes/default/components/core/blocks/Product/Related.vue)) by calling and [related action](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L528) - -## Events - -The following events are published from `product` store: - -- `EventBus.$emit('product-after-priceupdate', product)` - from [syncProductPrice](https://github.com/vuestorefront/vue-storefront/blob/bd559f1baad7cd392bc5bae7b935a60484e2e6e5/src/store/modules/product.js#L33) after product price is synced with Magento. -- `EventBus.$emit('product-after-configure', { product: product, configuration: configuration, selectedVariant: selectedVariant })` from `configureProductAsync` (called by `product/configure` action after `product/single`). This event provides the information about selected product variant on the product page. -- `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, result: resp })` - this event emits the current product list as it's returned by `product/list`providing the current filters, etc. You can mark the specific product list identifier by setting the `meta` property; it's important because on a single page, this event can be executed multiple time for each individual block of products. -- `EventBus.$emit('product-after-single', { key: key, options: options, product: cachedProduct })` - After single product has been loaded (invoked by `product/single` action). - -## Actions - -The product store provides following public actions: - -### `setupBreadcrumbs (context, { product })` - -This method is in charge of setting `state.breadcrumbs` to be used on the `Product.vue` page. It's called from `Product.vue:fetchData`. The `product` parameter is an [Elasticsearch product object](../data/elasticsearch.md). - -### `syncPlatformPricesOver(context, { skus })` - -When the config option `products.alwaysSyncPlatformPricesOver` is on, Vue Storefront will request the current product prices each time when `product/single` or `product/list` action is dispatched. It's called exclusively by these actions and shouldn't be called manually. This method calls `vue-storefront-api` proxy to get the current prices from Magento or any other backend CMS. - -`skus` - this is an array with product SKUs to be synchronized. - -### `setupAssociated (context, { product })` - -This method is called as a subsequent call of `Product.vue:fetchData` or `product/list` action. It's used to get the child products of `grouped` or `bundle` types of products. - -### `checkConfigurableParent (context, {product})` - -This method is called by `Product.vue:fetchData` to check if current, simple product has got an configurable parent. If so, the redirect is being made to the parent product. It's a fix for [#508](https://github.com/vuestorefront/vue-storefront/issues/508) - -### `setupVariants (context, { product })` - -This method is subsequently called by `Product.vue:fetchData` to load all configurable attributes defined in `product.configurable_options` and then to populate `state.current_configuration` and `state.current_options`. The main usage of this action is to prepare a product to be configured by the user on the product page and to display the product configurator UI properly. - -### `list (context, { query, start = 0, size = 50, entityType = 'product', sort = '', cacheByKey = 'sku', prefetchGroupProducts = true, updateState = true, meta = {} })` - -This is the key method to load the product list. It returns the Promise that contains the product list object. This method should be used everywhere you need to get product data. When `config.tax.calculateServerSide=false` this method runs product taxes calculator and synchronizes prices with Magento if it's required. - -**Events**: this method emits product list as `EventBus.$emit('product-after-list', { query: query, start: start, size: size, sort: sort, entityType: entityType, meta: meta, result: resp })` - -:::warning Important -This method synchronizes products for offline usage by storing the whole query results object into `localForage` and by caching each product individually (to be used on the product page, for example). -::: - -- `query` - This is the `bodybuilder` Elasticsearch query (please check `bodybuilder` package or for example `Home.vue` for a reference how to use it). - -- `start`, `size` - Both parameters are used for paging; start is the starting index; size is a page size. - -- `entityType` - By default it's of course set to `product` and it's mapped to Elasticsearch entity class. - -- `sort` - Product attribute using to sort. This field must be mapped in Elasticsearch as a numeric field, - -- `prefetchGroupProducts` - By default, it's set to true and causes the `setupAssociated` action to be dispatched to get all the associated products - -- `updateState` - If you set this to false, the `state.list` will not be updated, just the products will be returned. - -- `meta` - This is an optional attribute which is returned with the `product-after-list` event; it can be used for example to mark any specific ES call. - -### `single (context, { options, setCurrentProduct = true, selectDefaultVariant = true, key = 'sku' })` - -This method subsequently dispatches the `product/list` action to get the products and synchronize the taxes/prices. When the product has been recently downloaded via `product/list` this method will return the cached version from `localForage`, but update the cache anyway. - -### `configure (context, { product = null, configuration, selectDefaultVariant = true })` - -This action is used to configure the `configurable` product with specified attributes. It gets the `configuration` object, which should have the following format: `{ attribute_code: attribute_value_id }` and finds the `product.configurable_children` item which complies with this configuration. Then, it merges this specific `configurable_child` with the product itself - for example, setting the product.price to the configurable price, color, size etc. This method is used on: `Product.vue` page for allowing user to select color, size etc. The second usage for it is on `Category.vue` page after user selects some filters - the resulting products are configured to display the proper images (related to selected color and size) and prices. - -If `selectDefaultVariant` is set to true (default), the `state.current` will be altered with configured product. - -### `setCurrent (context, productVariant)` - -Auxiliary method just to set `state.current` to productVariant. - -### `setOriginal (context, originalProduct)` - -Auxiliary method just to set `state.original` to originalProduct. - -### `related (context, { key = 'related-products', items })` - -Alters `state.related` dictionary to set specific list of related products to be displayed on `Product.vue` page (`RelatedProducts` component is used for this). - -## Getters - -All state members should have been accessed only by getters. Please take a look at the state reference for data formats. - -```js -const getters = { - getParentProduct: state => state.parent, - getCurrentProduct: state => state.current, - getCurrentProductConfiguration: state => state.current_configuration, - getOriginalProduct: state => state.original, - getCurrentProductOptions: state => state.current_options, - breadcrumbs: state => state.breadcrumbs, -}; -``` diff --git a/docs/guide/vuex/stock-store.md b/docs/guide/vuex/stock-store.md deleted file mode 100644 index abda8efa54..0000000000 --- a/docs/guide/vuex/stock-store.md +++ /dev/null @@ -1,28 +0,0 @@ -# Stock Vuex Store - -Stock Store is designed to handle stock-quantity checks. - -## Events - -The following events are published from `stock` store: - -- `stock-after-check` - Emitted just after the stock item has been received from eCommerce backend / Magento. - - -## Actions - -The cart store provides the following public actions: - -### `check (context, { product, qty = 1 })` - -Check if the `product` can be added to the shopping cart with a given quantity. - -The resulting promise is expanded to the following object: - -```js -{ - qty: 100, - status: 'ok', // another option is: 'out_of_stock' - onlineCheckTaskId: 14241 -} -``` diff --git a/docs/guide/vuex/sync-store.md b/docs/guide/vuex/sync-store.md deleted file mode 100644 index 4052b8092f..0000000000 --- a/docs/guide/vuex/sync-store.md +++ /dev/null @@ -1,80 +0,0 @@ -# Sync Vuex Store - -Sync Store is an auxiliary module for synchronizing the data with the backend. Tasks can be queued in the `localForage` in case the Internet access is not available. - -## Actions - -The sync store provides following public actions: - -### `queue ({ commit }, task)` - -Queue network sync method. This is very similar to standard browser's `fetch()` call but the difference is that the network call is queued and executed when the Internet is available. - -Example usage: - -```js -context - .dispatch( - 'sync/queue', - { - url: config.stock.endpoint + '/check/' + encodeURIComponent(product.sku), - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - }, - product_sku: product.sku, - callback_event: 'store:stock/stockAfterCheck', - }, - { root: true }, - ) - .then(task => { - resolve({ - qty: product.stock.qty, - status: product.stock.is_in_stock ? 'ok' : 'out_of_stock', - onlineCheckTaskId: task.task_id, - }); // if not online, cannot check the source of true here - }); -``` - -The `callback_event` is emitted after the results are received from the server party. -The `url` can contain two dynamic variable placeholders that will be expanded to the actual values just before the execution: - -- `{{token}}` - current user token -- `{{cartId}}` - current cart id - -An example URL with variables: `http://localhost:8080/api/cart/totals?token={{token}}&cartId={{cartId}}` - -:::tip Note -The task object and then the results are stored within the `syncTasks` indexedDb/Local storage data table under the key of `task.task_id` -::: - -![syncTasks local collection stores the tasks and the results](../images/syncTasks-example.png) - -## Helpers - -Some types of network calls shouldn't have been queued. For example the shopping cart synchronization or other tasks that operate on the volatile data/state. - -In this case some modules (`cart.js` for example) do use just some helper methods from sync store to synchronize the data immediately but keeping the common interface for the network calls: - -```js -context - .dispatch( - 'sync/execute', - { - url: config.cart.pull_endpoint, // sync the cart - payload: { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - mode: 'cors', - }, - silent: true, - force_client_state: forceClientState, - callback_event: 'store:cart/servercartAfterPulled', - }, - { root: true }, - ) - .then(task => {}); -``` - -`sync/execute` can be used as an equivalent of `fetch()`. diff --git a/docs/guide/vuex/vuex-conventions.md b/docs/guide/vuex/vuex-conventions.md deleted file mode 100644 index 24063bd27e..0000000000 --- a/docs/guide/vuex/vuex-conventions.md +++ /dev/null @@ -1,138 +0,0 @@ -# Vuex conventions - -## Module -The Vuex module should be created for a specific set of functionalities. It should also have only absolutely necessary dependencies to other modules. The name of module should be short, quite clear about it’s destination, and have words separated by a dash. - -Good examples: - -- products -- product -- user -- checkout -- compare-products -- notifications -- order - -Bad examples: - -- next-module -- compare (because it’s not saying what it compares) - -## State -State properties should be simple and their structure should not be nested. Their names are written in underscore-case notation and indicate what they contain. We should avoid having more than one instance of an object, even between modules. In the vast majority of cases, they can be referenced by their unique ID property. Example: - -``` -{ - "products_map": { - "WS08": { - "sku": "WS08", - "name": "Minerva LumaTech™ V-Tee" - // other options - }, - "WS12": { - "sku": "WS12", - "name": "Radiant Tee" - // other options - }, - "WS08-XS-Black": { - "sku": "WS08-XS-Black", - "name": "Minerva LumaTech™ V-Tee" - // other options - } - // maaaaaaaany more products - }, - "current_product_id": "WS08-XS-Black", - "wishlist": ["MP01-32-Black", "MSH05-32-Black"], - "cart_items": [ - { - "sku": "WH09-XS-Green", - "qty": 3 - }, - { - "sku": "WH09-S-Red", - "qty": 1 - } - ] -} -``` - -Good examples: - - - categories_map -- current_category_id -- order -- product_parent_id - -Bad examples -- list -- elements - -``` -filters: { - available: {}, - chosen: {} -}, -``` - -## Getters -The Vuex state, except of mutations, should always be accessed by getters, including actions. Getter should: - -* Start from `is` when returns Boolean, or `get` otherwise -* Answer to question `what am I returning?` -* Contain module name to ensure that getter is unique through whole Vuex, but it doesn’t have to start with that name. First, it should have a natural name, so for example we have module `category` and in the state `availableFilters`. So `what am I returning?` -> `available Filters` and these filters are `category filters`. It's not a Boolean, it’s an array or map so we’re starting with `get` -> `getAvailableCategoryFilters` - -Good examples: - -- For state user -> isUserLoggedIn, getUser -- For state availableFilters -> getAvailableCategoryFilters -- For state currentProductId -> getCurrentProduct (because it gets product object from map), getCurrentProductId - -Bad examples: - -- totals -- product -- current -- list - -## Actions - -Every state change from outside of a module should be invoked as an action. Actions are meant to: - -- Fetch something from the server(or cache) — in this case, they have to be asynchronous (return promise). -- Mutate state of current module. -- Dispatch actions from the same module (to avoid repeating logic). -- Dispatch actions from another module (only if it’s absolutely required). -- Their names should be as unique as possible and simply describe what specific action will happen. **Almost every action should return promise.** We allow you to replicate conventions for existing methods like list or single in new modules to have a consistent API. - -Good examples: - -- fetchProduct - Gets product by ID from server or cache, sets it in products map, and returns it by getter. -- findProducts - Fetches products by specific query, sets them in products map, and returns them as array. -- setCurrentProduct - Param could be ID, it could dispatch fetchProduct, mutate it to productsMap, and mutate its ID to currentProductId. Also if productId is null, then it removes currentProduct. -- addCartItem -- toggleMicrocart - -Bad examples: - -- products -- reset - -## Mutations - -Finally we have mutations. Only mutations can change the state of the module. They should be synchronous (never return promise), not contain any logic (be extremely fast), except one needed to keep the state as it should be (for example, sets default value for state). Mutations should be invoked only by actions from the same module. In most cases, it should only be a single action that invokes a specific mutation. Types of mutations: - -- SET_ - The most common type of mutation. It can set an object (or whole array), set default value of object (or maybe clean array), -- ADD_ - It can add a new element to the state property, which is an array or add new element to map. -- REMOVE_ - An opposite to ADD. It can remove the map element or array element by index (or by finding object, which is not recommended on big arrays, as mutation could be slow). - -Good examples: - -- ADD_PRODUCT -- SET_CURRENT_PRODUCT_ID -- ADD_CATEGORY_FILTER -- REMOVE_WISHLIST_PRODUCT_ID - -Bad examples: - -- CATEGORY_UPD_CURRENT_CATEGORY -- TAX_UPDATE_RULES diff --git a/docs/now.json b/docs/now.json deleted file mode 100644 index 7bd3be3d49..0000000000 --- a/docs/now.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 2, - "name": "vuepress", - "builds": [ - { "src": "package.json", "use": "@now/static-build"} - ] -} diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index b0cbd76e80..0000000000 --- a/docs/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@vue-storefront/docs", - "private": true, - "version": "1.12.2", - "scripts": { - "docs:dev": "vuepress dev", - "docs:build": "vuepress build", - "build": "vuepress build && mv .vuepress/dist dist" - }, - "dependencies": { - "diff2html": "^2.12.1", - "vuepress": "^1.0.1" - } -} diff --git a/ecosystem.json b/ecosystem.json deleted file mode 100644 index 7098247e71..0000000000 --- a/ecosystem.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "apps": [ - { - "name": "server", - "max_memory_restart": "1G", - "instances": "4", - "exec_mode": "cluster", - "env": { - "TS_NODE_PROJECT": "tsconfig-build.json", - "NODE_ENV": "production" - }, - "interpreter": "./node_modules/.bin/ts-node", - "script": "./node_modules/.bin/ts-node", - "args": "-P tsconfig-build.json ./core/scripts/server.ts", - "node_args": "--max_old_space_size=1024", - "log_date_format": "YYYY-MM-DD HH:mm:ss", - "ignore_watch": [ - "core/build/config.json", - "node_modules" - ] - } - ] -} diff --git a/kubernetes/nginx-configmap.yaml b/kubernetes/nginx-configmap.yaml deleted file mode 100644 index 07ed0c4eee..0000000000 --- a/kubernetes/nginx-configmap.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: nginx-config -data: - default.conf: | - server { - listen 80; - - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag none; - - gzip on; - gzip_proxied any; - gzip_types - text/css - text/javascript - text/xml - application/javascript - application/json - text/json - text/html; - - location / { - proxy_pass http://vue-storefront:3000/; - } - - location /assets/ { - proxy_pass http://vue-storefront:3000/assets/; - } - - location /api/ { - proxy_pass http://vue-storefront-api:8080/api/; - } - - location /img/ { - proxy_pass http://vue-storefront-api:8080/img/; - } - } diff --git a/kubernetes/nginx-deployment.yaml b/kubernetes/nginx-deployment.yaml deleted file mode 100644 index c448cd52ff..0000000000 --- a/kubernetes/nginx-deployment.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx -spec: - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:alpine - ports: - - containerPort: 80 - volumeMounts: - - mountPath: /etc/nginx/conf.d/default.conf - name: config - subPath: default.conf - volumes: - - name: config - configMap: - name: nginx-config - items: - - key: default.conf - path: default.conf diff --git a/kubernetes/nginx-service.yaml b/kubernetes/nginx-service.yaml deleted file mode 100644 index 62bf060e60..0000000000 --- a/kubernetes/nginx-service.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: nginx - labels: - app: nginx -spec: - selector: - app: nginx - type: NodePort - ports: - - port: 80 - targetPort: 80 - nodePort: 30080 diff --git a/kubernetes/vue-storefront-configmap.yaml b/kubernetes/vue-storefront-configmap.yaml deleted file mode 100644 index caa259dd83..0000000000 --- a/kubernetes/vue-storefront-configmap.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: vue-storefront-config -data: - NODE_CONFIG_ENV: docker - BIND_HOST: 0.0.0.0 - PM2_ARGS: --no-daemon - VS_ENV: dev diff --git a/kubernetes/vue-storefront-deployment.yaml b/kubernetes/vue-storefront-deployment.yaml deleted file mode 100644 index 9e06f02a2d..0000000000 --- a/kubernetes/vue-storefront-deployment.yaml +++ /dev/null @@ -1,69 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: vue-storefront -spec: - selector: - matchLabels: - app: vue-storefront - template: - metadata: - labels: - app: vue-storefront - spec: - containers: - - name: vue-storefront - image: divante/vue-storefront:latest - envFrom: - - configMapRef: - name: vue-storefront-config - ports: - - containerPort: 3000 - volumeMounts: - - mountPath: /var/www/.babelrc - name: code - subPath: .babelrc - readOnly: true - - mountPath: /var/www/config - name: code - subPath: config - readOnly: true - - mountPath: /var/www/core - name: code - subPath: core - - mountPath: /var/www/ecosystem.json - name: code - subPath: ecosystem.json - readOnly: true - - mountPath: /var/www/.eslintignore - name: code - subPath: .eslintignore - readOnly: true - - mountPath: /var/www/.eslintrc.js - name: code - subPath: .eslintrc.js - readOnly: true - - mountPath: /var/www/lerna.json - name: code - subPath: lerna.json - readOnly: true - - mountPath: /var/www/package.json - name: code - subPath: package.json - readOnly: true - - mountPath: /var/www/src - name: code - subPath: src - readOnly: true - - mountPath: /var/www/var - name: code - subPath: var - - mountPath: /var/www/dist - name: dist - volumes: - - name: code - hostPath: - path: "/root/vue-storefront" - - name: dist - emptyDir: - medium: Memory diff --git a/kubernetes/vue-storefront-service.yaml b/kubernetes/vue-storefront-service.yaml deleted file mode 100644 index 950254cdb3..0000000000 --- a/kubernetes/vue-storefront-service.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: vue-storefront - labels: - app: vue-storefront -spec: - selector: - app: vue-storefront - ports: - - port: 3000 - targetPort: 3000 diff --git a/lerna.json b/lerna.json index b083c58a8d..deb185c208 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,5 @@ { - "lerna": "3.14.1", - "version": "independent", - "npmClient": "yarn", "useWorkspaces": true, - "registry": "https://registry.npmjs.org/" + "npmClient": "yarn", + "version": "independent" } diff --git a/lighthouserc.json b/lighthouserc.json new file mode 100644 index 0000000000..aad697ed6e --- /dev/null +++ b/lighthouserc.json @@ -0,0 +1,10 @@ +{ + "ci": { + "upload": { + "target": "temporary-public-storage" + } + }, + "assert": { + "preset": "lighthouse:recommended" + } +} diff --git a/now.json b/now.json new file mode 100644 index 0000000000..80e57248ac --- /dev/null +++ b/now.json @@ -0,0 +1,5 @@ +{ + "github": { + "enabled": false + } +} diff --git a/package.json b/package.json index 38d88ccf68..ef70943adc 100644 --- a/package.json +++ b/package.json @@ -1,206 +1,109 @@ { - "name": "vue-storefront", - "version": "1.12.3", - "description": "A Vue.js, PWA eCommerce frontend", + "name": "root", "private": true, - "engines": { - "node": ">=10.x" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/vuestorefront/vue-storefront" - }, - "keywords": [ - "vue", - "vuex", - "vue-router", - "webpack", - "starter", - "server-side", - "boilerplate" - ], - "author": "pkarw and contributors", - "license": "MIT", - "bugs": { - "url": "https://github.com/vuestorefront/vue-storefront/issues" - }, - "homepage": "https://github.com/vuestorefront/vue-storefront/README.md", "scripts": { - "static-server": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/static-server.ts", - "generate": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/generate.ts", - "generate-files": "cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/generate-files.ts", - "start": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" pm2 start ecosystem.json $PM2_ARGS", - "start:inspect": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" node --inspect -r ts-node/register ./core/scripts/server", - "installer": "node ./core/scripts/installer", - "installer:ci": "yarn installer --default-config", - "all": "cross-env NODE_ENV=development node ./core/scripts/all", - "cache": "node ./core/scripts/cache", - "dev": "yarn generate-files && cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" ts-node ./core/scripts/server.ts", - "dev:sw": "yarn generate-files && cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" yarn build:sw && yarn dev", - "dev:inspect": "yarn generate-files && cross-env TS_NODE_PROJECT=\"tsconfig-build.json\" node --inspect -r ts-node/register ./core/scripts/server", - "build:sw": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" webpack --config ./core/build/webpack.prod.sw.config.ts --mode production --progress --hide-modules", - "build:client": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" webpack --config ./core/build/webpack.prod.client.config.ts --mode production --progress --hide-modules", - "build:server": "cross-env NODE_ENV=production TS_NODE_PROJECT=\"tsconfig-build.json\" webpack --config ./core/build/webpack.prod.server.config.ts --mode production --progress --hide-modules", - "build": "rimraf dist && yarn generate-files && npm-run-all -p build:*", - "test:unit": "jest -c test/unit/jest.conf.js", - "test:unit:watch": "jest -c test/unit/jest.conf.js --watch", - "test:e2e": "cypress open", - "test:e2e:ci": "cypress run", - "lint": "cross-env NODE_ENV=production eslint --ext .js,.vue,.ts core src --fix", - "lerna": "lerna" - }, - "dependencies": { - "@types/webpack": "^4.41", - "@types/webpack-dev-server": "^3.10", - "apollo-cache-inmemory": "^1.6.5", - "apollo-client": "^2.6.8", - "apollo-link": "^1.2.13", - "apollo-link-http": "^1.5.16", - "body-scroll-lock": "^2.6.4", - "bodybuilder": "2.2.21", - "config": "^1.30.0", - "cross-env": "^3.1.4", - "dayjs": "^1.8.21", - "es6-promise": "^4.2.8", - "express": "^4.14.0", - "fork-ts-checker-webpack-plugin": "^6.2.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.6", - "graphql": "^0.13.2", - "graphql-tag": "^2.10.3", - "intl": "^1.2.5", - "intl-locales-supported": "^1.8.2", - "isomorphic-fetch": "^2.2.1", - "js-sha3": "^0.8.0", - "localforage": "^1.7.2", - "magento2-rest-client": "github:DivanteLtd/magento2-rest-client", - "ms": "^2.1.2", - "pm2": "^2.10.4", - "proxy-polyfill": "^0.3.0", - "redis-tag-cache": "^1.2.1", - "reflect-metadata": "^0.1.12", - "register-service-worker": "^1.5.2", - "storefront-query-builder": "https://github.com/vuestorefront/storefront-query-builder.git", - "ts-node": "^8.6.2", - "vue": "^2.6.11", - "vue-analytics": "^5.16.1", - "vue-apollo": "^3.0.3", - "vue-carousel": "^0.18", - "vue-gtm": "^2.0.0", - "vue-i18n": "^8.0.0", - "vue-lazy-hydration": "^1.0.0-beta.9", - "vue-lazyload": "^1.2.6", - "vue-meta": "^2.3.3", - "vue-no-ssr": "^0.2.2", - "vue-observe-visibility": "^0.4.1", - "vue-offline": "^1.0.8", - "vue-router": "3.1.6", - "vue-server-renderer": "^2.6.11", - "vuelidate": "^0.7.5", - "vuex": "^3.1.2", - "vuex-router-sync": "^5.0.0" + "build:bp": "yarn build:bp:tools && yarn build:bp:theme", + "build:bp:api-client": "cd packages/boilerplate/api-client && yarn build", + "build:bp:composables": "cd packages/boilerplate/composables && yarn build", + "build:bp:theme": "cd packages/boilerplate/theme && yarn build", + "build:bp:tools": "yarn build:core && yarn build:bp:api-client && yarn build:bp:composables", + "build:core": "yarn build:core:core && yarn build:core:cache && yarn build:core:middleware", + "build:core:core": "cd packages/core/core && yarn build", + "build:core:cache": "cd packages/core/cache && yarn build", + "build:core:middleware": "cd packages/core/middleware && yarn build", + "build:ct": "yarn build:ct:tools && yarn build:ct:theme", + "build:ct:api-client": "cd packages/commercetools/api-client && yarn build", + "build:ct:composables": "cd packages/commercetools/composables && yarn build", + "build:ct:theme": "cd packages/commercetools/theme && yarn build", + "build:ct:tools": "yarn build:core && yarn build:ct:api-client && yarn build:ct:composables", + "build:docs": "cd packages/core/docs && yarn build", + "build:middleware": "cd packages/core/middleware && yarn build", + "cli": "cd packages/core/cli && yarn cli", + "commit": "cz", + "core:changelog": "cd packages/core/docs/scripts && node changelog", + "ct:changelog": "cd packages/core/docs/scripts && node changelog --in ../commercetools/changelog --out ../commercetools/changelog.md", + "dev:bp": "cd packages/boilerplate/theme && yarn dev", + "dev:ct": "cd packages/commercetools/theme && yarn dev", + "dev:ct:api-client": "cd packages/commercetools/api-client && yarn dev", + "dev:ct:composables": "cd packages/commercetools/composables && yarn dev", + "dev:docs": "cd packages/core/docs && yarn dev", + "link-packages": "lerna link --force-local", + "lint": "eslint . --ext .js,.ts,.vue", + "start:bp": "cd packages/boilerplate/theme && yarn start", + "start:ct": "cd packages/commercetools/theme && yarn start", + "test:cache": "cd packages/core/cache && yarn test", + "test:cli": "cd packages/core/cli && yarn test", + "test:core": "cd packages/core/core && yarn test", + "test:middleware": "cd packages/core/middleware && yarn test", + "test:ct": "yarn test:ct:api-client && yarn test:ct:composables", + "test:ct:api-client": "cd packages/commercetools/api-client && yarn test", + "test:ct:composables": "cd packages/commercetools/composables && yarn test", + "test:e2e": "./node_modules/.bin/cypress open", + "test:e2e:bp": "cd packages/boilerplate/theme && yarn run test:e2e", + "test:e2e:bp:generate:report": "cd packages/boilerplate/theme && yarn run test:e2e:generate:report", + "test:e2e:bp:hl": "cd packages/boilerplate/theme && yarn run test:e2e:hl", + "test:e2e:ct": "cd packages/commercetools/theme && yarn run test:e2e", + "test:e2e:ct:generate:report": "cd packages/commercetools/theme && yarn run test:e2e:generate:report", + "test:e2e:ct:hl": "cd packages/commercetools/theme && yarn run test:e2e:hl" }, "devDependencies": { - "@babel/core": "^7.9.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/polyfill": "^7.8.3", - "@babel/preset-env": "^7.9.0", - "@types/jest": "^25.1.3", - "@types/node": "^13.7.7", - "@typescript-eslint/eslint-plugin": "^1.7.1-alpha.17", - "@typescript-eslint/parser": "^1.7.1-alpha.17", - "@vue/test-utils": "^1.0.0-beta.29", - "app-root-path": "^2.0.1", - "autoprefixer": "^8.6.2", - "babel-core": "^7.0.0-bridge.0", - "babel-eslint": "^9.0.0", - "babel-jest": "^24.1.0", - "babel-loader": "^8.0.6", - "babel-plugin-dynamic-import-node": "^2.2.0", - "case-sensitive-paths-webpack-plugin": "^2.1.2", - "command-exists": "^1.2.2", - "commander": "^2.18.0", - "css-loader": "^1.0.0", - "cypress": "^3.1.5", - "d3-dsv": "^1.0.8", - "detect-installed": "^2.0.4", - "empty-dir": "^1.0.0", - "eslint": "^5.0.0", - "eslint-config-standard": "^12.0.0", - "eslint-friendly-formatter": "^4.0.1", - "eslint-loader": "^2.0.0", - "eslint-plugin-import": "^2.13.0", - "eslint-plugin-node": "^6.0.1", - "eslint-plugin-promise": "^3.7.0", - "eslint-plugin-standard": "^3.1.0", - "eslint-plugin-vue": "^5.2.2", - "eslint-plugin-vue-storefront": "^0.0.1", - "file-loader": "^1.1.11", - "fs-exists-sync": "^0.1.0", - "html-webpack-plugin": "^3.2.0", - "husky": "^2.6.0", - "inquirer": "^3.3.0", - "is-windows": "^1.0.1", - "jest": "^25.1.0", - "jest-fetch-mock": "^3.0.1", - "jest-serializer-vue": "^2.0.2", - "jsonfile": "^4.0.0", - "lerna": "^3.14.1", - "lint-staged": "^8.2.1", - "mkdirp": "^0.5.1", - "node-sass": "^4.12.0", - "npm-run-all": "^4.1.5", - "phantomjs-prebuilt": "^2.1.10", - "postcss-flexbugs-fixes": "^4.1.0", - "postcss-loader": "^3.0.0", - "print-message": "^2.1.0", - "rimraf": "^2.6.0", - "sass-loader": "^7.1.0", - "shelljs": "^0.8.1", - "sw-precache-webpack-plugin": "^0.11.5", - "ts-jest": "^25.2.1", - "ts-loader": "^5.3.0", - "typescript": "^3.1.6", - "url-loader": "^1.1.2", - "url-parse": "^1.4.4", - "vue-eslint-parser": "^2.0.3", - "vue-jest": "^3.0.5", - "vue-loader": "^15.4.2", - "vue-ssr-webpack-plugin": "^3.0.0", - "vue-template-compiler": "^2.6.11", - "webpack": "^4.25.1", - "webpack-bundle-analyzer": "^3.3.2", - "webpack-cli": "^3.3.11", - "webpack-dev-middleware": "^3.4.0", - "webpack-hot-middleware": "^2.24.3", - "webpack-merge": "^4.2.2" + "@babel/core": "^7.10.5", + "@commitlint/cli": "^13.1.0", + "@commitlint/config-conventional": "^13.1.0", + "@commitlint/config-lerna-scopes": "^13.1.0", + "@rollup/plugin-babel": "^5.1.0", + "@rollup/plugin-replace": "^2.3.3", + "@types/jest": "^26.0.24", + "@types/node": "^12.12.14", + "@typescript-eslint/eslint-plugin": "^4.15.2", + "@typescript-eslint/parser": "^4.15.2", + "@vue/eslint-config-typescript": "^7.0.0", + "commitizen": "^4.2.4", + "cross-env": "^6.0.3", + "cz-conventional-changelog": "^3.3.0", + "eslint": "^7.20.0", + "eslint-config-standard": "^16.0.2", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.3.1", + "eslint-plugin-standard": "^5.0.0", + "eslint-plugin-vue": "^7.6.0", + "husky": "^4.2.3", + "jest": "^27.0.6", + "lerna": "^3.15.0", + "lint-staged": "^10.0.7", + "rimraf": "^3.0.2", + "rollup": "^1.25.2", + "rollup-plugin-terser": "^5.1.2", + "rollup-plugin-typescript2": "^0.30.0", + "ts-jest": "^27.0.3", + "ts-node": "^8.4.1", + "tslib": "^2.1.0", + "typescript": "^4.2.2", + "vue-eslint-parser": "^7.0.0", + "webpack-bundle-analyzer": "^3.5.2" }, - "peerDependencies": { - "vue-template-compiler": "^2.6.11" - }, - "browserslist": { - "development": [ - "last 2 chrome versions", - "last 2 firefox versions", - "last 2 edge versions", - "> 5%" - ], - "production": [ - ">5%", - "last 4 versions", - "Firefox ESR" + "workspaces": { + "packages": [ + "packages/**/*" ] }, - "workspaces": [ - "core", - "core/i18n", - "src/extensions/*", - "src/modules/*", - "src/trace/*", - "src/themes/*", - "docs", - "core/modules/*", - "test/unit", - "packages/*" - ] + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{js,ts,vue}": "eslint --fix" + }, + "config": { + "commitizen": { + "path": "cz-conventional-changelog", + "maxHeaderWidth": 100, + "maxLineWidth": 100 + } + }, + "engines": { + "node": ">=12.x" + } } diff --git a/packages/api-extractor.base.json b/packages/api-extractor.base.json new file mode 100644 index 0000000000..9f9fe79743 --- /dev/null +++ b/packages/api-extractor.base.json @@ -0,0 +1,48 @@ +/** + * Config file for API Extractor. For more info, please visit: https://api-extractor.com + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "projectFolder": ".", + "compiler": { + "tsconfigFilePath": "/tsconfig.base.json" + }, + "docModel": { + "enabled": true + }, + "dtsRollup": { + "enabled": true + }, + "tsdocMetadata": { + "enabled": false + }, + "apiReport": { + "enabled": false + }, + "messages": { + "compilerMessageReporting": { + "default": { + "logLevel": "warning" + } + }, + "extractorMessageReporting": { + "default": { + "logLevel": "none", + "addToApiReportFile": false + }, + "ae-extra-release-tag": { + "logLevel": "none", + "addToApiReportFile": false + }, + "ae-forgotten-export": { + "logLevel": "none" + } + }, + "tsdocMessageReporting": { + "default": { + "logLevel": "none", + "addToApiReportFile": false + } + } + } +} \ No newline at end of file diff --git a/packages/boilerplate/api-client/.gitignore b/packages/boilerplate/api-client/.gitignore new file mode 100644 index 0000000000..4730fa6492 --- /dev/null +++ b/packages/boilerplate/api-client/.gitignore @@ -0,0 +1,4 @@ +node_modules +coverage +lib +server diff --git a/packages/boilerplate/api-client/jest.config.js b/packages/boilerplate/api-client/jest.config.js new file mode 100644 index 0000000000..4333eb5fde --- /dev/null +++ b/packages/boilerplate/api-client/jest.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('./../jest.base.config'); + +module.exports = baseConfig; diff --git a/packages/boilerplate/api-client/package.json b/packages/boilerplate/api-client/package.json new file mode 100644 index 0000000000..102a11129f --- /dev/null +++ b/packages/boilerplate/api-client/package.json @@ -0,0 +1,29 @@ +{ + "name": "@vue-storefront/boilerplate-api", + "version": "2.4.1", + "private": true, + "sideEffects": false, + "server": "server/index.js", + "main": "lib/index.cjs.js", + "module": "lib/index.es.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "rimraf lib server && rollup -c", + "dev": "rollup -c -w", + "test": "jest", + "prepublish": "yarn build" + }, + "dependencies": { + "@vue-storefront/core": "~2.4.1" + }, + "devDependencies": { + "rollup-plugin-typescript2": "^0.30.0", + "@rollup/plugin-node-resolve": "^13.0.0" + }, + "files": [ + "lib/**/*" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/boilerplate/api-client/rollup.config.js b/packages/boilerplate/api-client/rollup.config.js new file mode 100644 index 0000000000..a76d6a223a --- /dev/null +++ b/packages/boilerplate/api-client/rollup.config.js @@ -0,0 +1,35 @@ +import nodeResolve from '@rollup/plugin-node-resolve'; +import typescript from 'rollup-plugin-typescript2'; +import pkg from './package.json'; +import { generateBaseConfig } from '../../rollup.base.config'; + +const extensions = ['.ts', '.js']; + +const server = { + input: 'src/index.server.ts', + output: [ + { + file: pkg.server, + format: 'cjs', + sourcemap: true + } + ], + external: [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ], + plugins: [ + nodeResolve({ + extensions + }), + typescript({ + // eslint-disable-next-line global-require + typescript: require('typescript') + }) + ] +}; + +export default [ + generateBaseConfig(pkg), + server +]; diff --git a/packages/boilerplate/api-client/src/index.server.ts b/packages/boilerplate/api-client/src/index.server.ts new file mode 100644 index 0000000000..cffb531f9a --- /dev/null +++ b/packages/boilerplate/api-client/src/index.server.ts @@ -0,0 +1,20 @@ +import { apiClientFactory } from '@vue-storefront/core'; +import type { Setttings, Endpoints } from './types'; + +function onCreate(settings: Setttings) { + return { + config: settings, + client: {} + }; +} + +const { createApiClient } = apiClientFactory({ + onCreate, + api: { + + } +}); + +export { + createApiClient +}; diff --git a/packages/boilerplate/api-client/src/index.ts b/packages/boilerplate/api-client/src/index.ts new file mode 100644 index 0000000000..fcb073fefc --- /dev/null +++ b/packages/boilerplate/api-client/src/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/packages/boilerplate/api-client/src/types.ts b/packages/boilerplate/api-client/src/types.ts new file mode 100644 index 0000000000..9c872fa29f --- /dev/null +++ b/packages/boilerplate/api-client/src/types.ts @@ -0,0 +1,57 @@ +export type TODO = unknown; + +export type Setttings = TODO; + +export type Endpoints = TODO; + +export type BillingAddress = TODO; + +export type Cart = TODO; + +export type CartItem = TODO; + +export type Category = TODO; + +export type Coupon = TODO; + +export type Facet = TODO; + +export type FacetSearchCriteria = TODO; + +export type Order = TODO; + +export type OrderItem = TODO; + +export type PasswordResetResult = TODO; + +export type Product = TODO; + +export type ProductFilter = TODO; + +export type Review = TODO; + +export type ReviewItem = TODO; + +export type User = TODO; + +export type UserBillingAddress = TODO; + +export type UserBillingAddressItem = TODO; + +export type UserBillingAddressSearchCriteria = TODO; + +export type UserShippingAddress = TODO; + +export type UserShippingAddressItem = TODO; + +export type UserShippingAddressSearchCriteria = TODO; + +export type ShippingAddress = TODO; + +export type ShippingMethod = TODO; + +export type ShippingProvider = TODO; + +export type Wishlist = TODO; + +export type WishlistItem = TODO; diff --git a/packages/boilerplate/api-client/tsconfig.json b/packages/boilerplate/api-client/tsconfig.json new file mode 100644 index 0000000000..444b0a5d2b --- /dev/null +++ b/packages/boilerplate/api-client/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["src"], + "exclude": ["node_modules", "lib"], + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "ES2017", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "preserveSymlinks": true, + } +} diff --git a/packages/boilerplate/composables/jest.config.js b/packages/boilerplate/composables/jest.config.js new file mode 100644 index 0000000000..4333eb5fde --- /dev/null +++ b/packages/boilerplate/composables/jest.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('./../jest.base.config'); + +module.exports = baseConfig; diff --git a/packages/boilerplate/composables/nuxt/README.MD b/packages/boilerplate/composables/nuxt/README.MD new file mode 100644 index 0000000000..1da6ed3c40 --- /dev/null +++ b/packages/boilerplate/composables/nuxt/README.MD @@ -0,0 +1,6 @@ +# Example config +```js +export const config = { + // Put expected config example here +}; +``` \ No newline at end of file diff --git a/packages/boilerplate/composables/nuxt/index.js b/packages/boilerplate/composables/nuxt/index.js new file mode 100644 index 0000000000..20d988927c --- /dev/null +++ b/packages/boilerplate/composables/nuxt/index.js @@ -0,0 +1,8 @@ +import path from 'path'; + +export default function integrationModule(moduleOptions) { + this.addPlugin({ + src: path.resolve(__dirname, './plugin.js'), + options: moduleOptions + }); +} diff --git a/packages/boilerplate/composables/nuxt/plugin.js b/packages/boilerplate/composables/nuxt/plugin.js new file mode 100644 index 0000000000..4c0da4856f --- /dev/null +++ b/packages/boilerplate/composables/nuxt/plugin.js @@ -0,0 +1,10 @@ +import { integrationPlugin } from '@vue-storefront/core'; + +const moduleOptions = <%= serialize(options) %>; + +export default integrationPlugin(({ integration }) => { + integration.configure('boilerplate', { + ...moduleOptions + // other options + }); +}); diff --git a/packages/boilerplate/composables/package.json b/packages/boilerplate/composables/package.json new file mode 100644 index 0000000000..a961d40817 --- /dev/null +++ b/packages/boilerplate/composables/package.json @@ -0,0 +1,32 @@ +{ + "name": "@vue-storefront/boilerplate", + "version": "2.4.1", + "private": true, + "sideEffects": false, + "main": "lib/index.cjs.js", + "module": "lib/index.es.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "rimraf lib && rollup -c", + "dev": "rollup -c -w", + "test": "jest", + "prepublish": "yarn build" + }, + "dependencies": { + "@vue-storefront/boilerplate-api": "~2.4.1", + "@vue-storefront/core": "~2.4.1" + }, + "devDependencies": { + "rollup-plugin-typescript2": "^0.30.0", + "@rollup/plugin-node-resolve": "^13.0.0" + }, + "peerDependencies": { + "@vue/composition-api": "1.0.0-beta.21" + }, + "files": [ + "lib/**/*" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/boilerplate/composables/rollup.config.js b/packages/boilerplate/composables/rollup.config.js new file mode 100644 index 0000000000..e834ad8073 --- /dev/null +++ b/packages/boilerplate/composables/rollup.config.js @@ -0,0 +1,4 @@ +import pkg from './package.json'; +import { generateBaseConfig } from '../rollup.base.config'; + +export default generateBaseConfig(pkg); diff --git a/packages/boilerplate/composables/src/getters/cartGetters.ts b/packages/boilerplate/composables/src/getters/cartGetters.ts new file mode 100644 index 0000000000..dbde34c213 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/cartGetters.ts @@ -0,0 +1,101 @@ +import { + CartGetters, + AgnosticPrice, + AgnosticTotals, + AgnosticCoupon, + AgnosticDiscount, + AgnosticAttribute +} from '@vue-storefront/core'; +import type { Cart, CartItem } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItems (cart: Cart): CartItem[] { + return [ + {} + ]; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemName(item: CartItem): string { + return 'Name'; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemImage(item: CartItem): string { + return 'https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg'; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemPrice(item: CartItem): AgnosticPrice { + return { + regular: 12, + special: 10 + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemQty(item: CartItem): number { + return 1; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemAttributes(item: CartItem, filterByAttributeName?: Array): Record { + return { + color: 'red' + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemSku(item: CartItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotals(cart: Cart): AgnosticTotals { + return { + total: 12, + subtotal: 12, + special: 10 + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getShippingPrice(cart: Cart): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotalItems(cart: Cart): number { + return 1; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFormattedPrice(price: number): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCoupons(cart: Cart): AgnosticCoupon[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getDiscounts(cart: Cart): AgnosticDiscount[] { + return []; +} + +export const cartGetters: CartGetters = { + getTotals, + getShippingPrice, + getItems, + getItemName, + getItemImage, + getItemPrice, + getItemQty, + getItemAttributes, + getItemSku, + getFormattedPrice, + getTotalItems, + getCoupons, + getDiscounts +}; diff --git a/packages/boilerplate/composables/src/getters/categoryGetters.ts b/packages/boilerplate/composables/src/getters/categoryGetters.ts new file mode 100644 index 0000000000..ec933b3ff0 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/categoryGetters.ts @@ -0,0 +1,16 @@ +import { CategoryGetters, AgnosticCategoryTree } from '@vue-storefront/core'; +import type { Category } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTree(category: Category): AgnosticCategoryTree { + return { + label: '', + slug: '', + items: [], + isCurrent: false + }; +} + +export const categoryGetters: CategoryGetters = { + getTree +}; diff --git a/packages/boilerplate/composables/src/getters/facetGetters.ts b/packages/boilerplate/composables/src/getters/facetGetters.ts new file mode 100644 index 0000000000..a71b90bcf8 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/facetGetters.ts @@ -0,0 +1,89 @@ +import { + FacetsGetters, + FacetSearchResult, + AgnosticCategoryTree, + AgnosticGroupedFacet, + AgnosticPagination, + AgnosticSort, + AgnosticBreadcrumb, + AgnosticFacet +} from '@vue-storefront/core'; +import type { Facet, FacetSearchCriteria } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getAll(params: FacetSearchResult, criteria?: FacetSearchCriteria): AgnosticFacet[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getGrouped(params: FacetSearchResult, criteria?: FacetSearchCriteria): AgnosticGroupedFacet[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getSortOptions(params: FacetSearchResult): AgnosticSort { + return { + options: [], + selected: '' + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCategoryTree(params: FacetSearchResult): AgnosticCategoryTree { + return { + label: '', + slug: '', + items: null, + isCurrent: false, + count: 0 + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getProducts(params: FacetSearchResult): any { + return [ + { + _id: 1, + _description: 'Some description', + _categoriesRef: [ + '1', + '2' + ], + name: 'Black jacket', + sku: 'black-jacket', + images: [ + 'https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg' + ], + price: { + original: 12.34, + current: 10.00 + } + } + ]; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPagination(params: FacetSearchResult): AgnosticPagination { + return { + currentPage: 1, + totalPages: 1, + totalItems: 1, + itemsPerPage: 10, + pageOptions: [] + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getBreadcrumbs(params: FacetSearchResult): AgnosticBreadcrumb[] { + return []; +} + +export const facetGetters: FacetsGetters = { + getSortOptions, + getGrouped, + getAll, + getProducts, + getCategoryTree, + getBreadcrumbs, + getPagination +}; diff --git a/packages/boilerplate/composables/src/getters/forgotPasswordGetters.ts b/packages/boilerplate/composables/src/getters/forgotPasswordGetters.ts new file mode 100644 index 0000000000..bcf326c6f4 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/forgotPasswordGetters.ts @@ -0,0 +1,17 @@ +import { ForgotPasswordGetters } from '@vue-storefront/core'; +import type { PasswordResetResult } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getResetPasswordToken(result: PasswordResetResult): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function isPasswordChanged(result: PasswordResetResult): boolean { + return true; +} + +export const forgotPasswordGetters: ForgotPasswordGetters = { + getResetPasswordToken, + isPasswordChanged +}; diff --git a/packages/boilerplate/composables/src/getters/orderGetters.ts b/packages/boilerplate/composables/src/getters/orderGetters.ts new file mode 100644 index 0000000000..a91c8cd8d5 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/orderGetters.ts @@ -0,0 +1,71 @@ +import { UserOrderGetters } from '@vue-storefront/core'; +import type { Order, OrderItem } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getDate(order: Order): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getId(order: Order): string { + return '1'; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getStatus(order: Order): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPrice(order: Order): number | null { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItems(order: Order): OrderItem[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemSku(item: OrderItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemName(item: OrderItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemQty(item: OrderItem): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemPrice(item: OrderItem): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFormattedPrice(price: number): string { + return ''; +} + +// eslint-disable-next-line +function getOrdersTotal(orders: any): number { + return 1; +} + +export const orderGetters: UserOrderGetters = { + getDate, + getId, + getStatus, + getPrice, + getItems, + getItemSku, + getItemName, + getItemQty, + getItemPrice, + getFormattedPrice, + getOrdersTotal +}; diff --git a/packages/boilerplate/composables/src/getters/productGetters.ts b/packages/boilerplate/composables/src/getters/productGetters.ts new file mode 100644 index 0000000000..24ff627061 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/productGetters.ts @@ -0,0 +1,115 @@ +import { + AgnosticMediaGalleryItem, + AgnosticAttribute, + AgnosticPrice, + ProductGetters +} from '@vue-storefront/core'; +import type { Product, ProductFilter } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getName(product: Product): string { + return 'Name'; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getSlug(product: Product): string { + return 'slug'; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPrice(product: Product): AgnosticPrice { + return { + regular: 0, + special: 0 + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getGallery(product: Product): AgnosticMediaGalleryItem[] { + return [ + { + small: 'https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg', + normal: 'https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg', + big: 'https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg' + } + ]; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCoverImage(product: Product): string { + return 'https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg'; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFiltered(products: Product[], filters: ProductFilter): Product[] { + return [ + { + _id: 1, + _description: 'Some description', + _categoriesRef: [ + '1', + '2' + ], + name: 'Black jacket', + sku: 'black-jacket', + images: [ + 'https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg' + ], + price: { + original: 12.34, + current: 10.00 + } + } + ]; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getAttributes(products: Product[] | Product, filterByAttributeName?: string[]): Record { + return {}; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getDescription(product: Product): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCategoryIds(product: Product): string[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getId(product: Product): string { + return '1'; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFormattedPrice(price: number): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotalReviews(product: Product): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getAverageRating(product: Product): number { + return 0; +} + +export const productGetters: ProductGetters = { + getName, + getSlug, + getPrice, + getGallery, + getCoverImage, + getFiltered, + getAttributes, + getDescription, + getCategoryIds, + getId, + getFormattedPrice, + getTotalReviews, + getAverageRating +}; diff --git a/packages/boilerplate/composables/src/getters/reviewGetters.ts b/packages/boilerplate/composables/src/getters/reviewGetters.ts new file mode 100644 index 0000000000..52ddd1b130 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/reviewGetters.ts @@ -0,0 +1,65 @@ +import { ReviewGetters, AgnosticRateCount } from '@vue-storefront/core'; +import type { Review, ReviewItem } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItems (review: Review): ReviewItem[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getReviewId(item: ReviewItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getReviewAuthor(item: ReviewItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getReviewMessage(item: ReviewItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getReviewRating(item: ReviewItem): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getReviewDate(item: ReviewItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotalReviews(review: Review): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getAverageRating(review: Review): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getRatesCount(review: Review): AgnosticRateCount[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getReviewsPage(review: Review): number { + return 0; +} + +export const reviewGetters: ReviewGetters = { + getItems, + getReviewId, + getReviewAuthor, + getReviewMessage, + getReviewRating, + getReviewDate, + getTotalReviews, + getAverageRating, + getRatesCount, + getReviewsPage +}; diff --git a/packages/boilerplate/composables/src/getters/userBillingGetters.ts b/packages/boilerplate/composables/src/getters/userBillingGetters.ts new file mode 100644 index 0000000000..8d78800f64 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/userBillingGetters.ts @@ -0,0 +1,117 @@ +import { UserBillingGetters } from '@vue-storefront/core'; +import type { + UserBillingAddress as Address, + UserBillingAddressItem as AddressItem, + UserBillingAddressSearchCriteria +} from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getAddresses(billing: Address, criteria?: UserBillingAddressSearchCriteria): AddressItem[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getDefault(billing: Address): Address { + return {}; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotal(billing: Address): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPostCode(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getStreetName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getStreetNumber(address: AddressItem): string | number { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCity(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFirstName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getLastName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCountry(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPhone(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getEmail(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getProvince(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCompanyName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTaxNumber(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getId(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getApartmentNumber(address: AddressItem): string | number { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function isDefault(address: AddressItem): boolean { + return false; +} + +export const userBillingGetters: UserBillingGetters = { + getAddresses, + getDefault, + getTotal, + getPostCode, + getStreetName, + getStreetNumber, + getCity, + getFirstName, + getLastName, + getCountry, + getPhone, + getEmail, + getProvince, + getCompanyName, + getTaxNumber, + getId, + getApartmentNumber, + isDefault +}; diff --git a/packages/boilerplate/composables/src/getters/userGetters.ts b/packages/boilerplate/composables/src/getters/userGetters.ts new file mode 100644 index 0000000000..5b93146097 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/userGetters.ts @@ -0,0 +1,29 @@ +import { UserGetters } from '@vue-storefront/core'; +import type { User } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFirstName(user: User): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getLastName(user: User): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFullName(user: User): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getEmailAddress(user: User): string { + return ''; +} + +export const userGetters: UserGetters = { + getFirstName, + getLastName, + getFullName, + getEmailAddress +}; diff --git a/packages/boilerplate/composables/src/getters/userShippingGetters.ts b/packages/boilerplate/composables/src/getters/userShippingGetters.ts new file mode 100644 index 0000000000..157c95bc05 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/userShippingGetters.ts @@ -0,0 +1,117 @@ +import { UserShippingGetters } from '@vue-storefront/core'; +import type { + UserShippingAddress as Address, + UserShippingAddressItem as AddressItem, + UserShippingAddressSearchCriteria +} from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getAddresses(shipping: Address, criteria?: UserShippingAddressSearchCriteria): AddressItem[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getDefault(shipping: Address): Address { + return {}; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotal(shipping: Address): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPostCode(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getStreetName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getStreetNumber(address: AddressItem): string | number { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCity(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFirstName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getLastName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCountry(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPhone(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getEmail(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getProvince(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getCompanyName(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTaxNumber(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getId(address: AddressItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getApartmentNumber(address: AddressItem): string | number { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function isDefault(address: AddressItem): boolean { + return false; +} + +export const userShippingGetters: UserShippingGetters = { + getAddresses, + getDefault, + getTotal, + getPostCode, + getStreetName, + getStreetNumber, + getCity, + getFirstName, + getLastName, + getCountry, + getPhone, + getEmail, + getProvince, + getCompanyName, + getTaxNumber, + getId, + getApartmentNumber, + isDefault +}; diff --git a/packages/boilerplate/composables/src/getters/wishlistGetters.ts b/packages/boilerplate/composables/src/getters/wishlistGetters.ts new file mode 100644 index 0000000000..12fac51855 --- /dev/null +++ b/packages/boilerplate/composables/src/getters/wishlistGetters.ts @@ -0,0 +1,84 @@ +import { + WishlistGetters, + AgnosticAttribute, + AgnosticPrice, + AgnosticTotals +} from '@vue-storefront/core'; +import type { Wishlist, WishlistItem } from '@vue-storefront/boilerplate-api'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItems(wishlist: Wishlist): WishlistItem[] { + return []; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotals(wishlist: Wishlist): AgnosticTotals { + return { + total: 10, + subtotal: 10 + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemName(item: WishlistItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemImage(item: WishlistItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemPrice(item: WishlistItem): AgnosticPrice { + return { + regular: 12, + special: 10 + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemQty(item: WishlistItem): number { + return 1; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemAttributes(item: WishlistItem, filters?: string[]): Record { + return { + color: 'red' + }; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getItemSku(item: WishlistItem): string { + return ''; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getShippingPrice(wishlist: Wishlist): number { + return 0; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getTotalItems(wishlist: Wishlist): number { + return 1; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getFormattedPrice(price: number): string { + return ''; +} + +export const wishlistGetters: WishlistGetters = { + getItems, + getTotals, + getItemName, + getItemImage, + getItemPrice, + getItemQty, + getItemAttributes, + getShippingPrice, + getItemSku, + getTotalItems, + getFormattedPrice +}; diff --git a/packages/boilerplate/composables/src/index.ts b/packages/boilerplate/composables/src/index.ts new file mode 100644 index 0000000000..3d9324e153 --- /dev/null +++ b/packages/boilerplate/composables/src/index.ts @@ -0,0 +1,33 @@ +// Composables +export { useBilling } from './useBilling'; +export { useCart } from './useCart'; +export { useCategory } from './useCategory'; +export { useContent } from './useContent'; +export { useFacet } from './useFacet'; +export { useForgotPassword } from './useForgotPassword'; +export { useMakeOrder } from './useMakeOrder'; +export { useProduct } from './useProduct'; +export { useReview } from './useReview'; +export { useShipping } from './useShipping'; +export { useShippingProvider } from './useShippingProvider'; +export { useUser } from './useUser'; +export { useUserBilling } from './useUserBilling'; +export { useUserOrder } from './useUserOrder'; +export { useUserShipping } from './useUserShipping'; +export { useWishlist } from './useWishlist'; + +// Getters +export { cartGetters } from './getters/cartGetters'; +export { categoryGetters } from './getters/categoryGetters'; +export { facetGetters } from './getters/facetGetters'; +export { forgotPasswordGetters } from './getters/forgotPasswordGetters'; +export { orderGetters } from './getters/orderGetters'; +export { productGetters } from './getters/productGetters'; +export { reviewGetters } from './getters/reviewGetters'; +export { userBillingGetters } from './getters/userBillingGetters'; +export { userGetters } from './getters/userGetters'; +export { userShippingGetters } from './getters/userShippingGetters'; +export { wishlistGetters } from './getters/wishlistGetters'; + +// Types +export * from './types'; diff --git a/packages/boilerplate/composables/src/types.ts b/packages/boilerplate/composables/src/types.ts new file mode 100644 index 0000000000..6819a4ae2a --- /dev/null +++ b/packages/boilerplate/composables/src/types.ts @@ -0,0 +1,25 @@ +import { + ProductsSearchParams +} from '@vue-storefront/core'; + +export type TODO = any; + +export type UseBillingAddParams = TODO; + +export type UseCategorySearchParams = TODO; + +export type UseFacetSearchParams = TODO; + +export type UseProductSearchParams = ProductsSearchParams; + +export type UseReviewSearchParams = TODO; + +export type UseReviewAddParams = TODO; + +export type UseShippingAddParams = TODO; + +export type UseUserUpdateParams = TODO; + +export type UseUserRegisterParams = TODO; + +export type useUserOrderSearchParams = TODO; diff --git a/packages/boilerplate/composables/src/useBilling/index.ts b/packages/boilerplate/composables/src/useBilling/index.ts new file mode 100644 index 0000000000..d6aeeb0e93 --- /dev/null +++ b/packages/boilerplate/composables/src/useBilling/index.ts @@ -0,0 +1,25 @@ +import { + Context, + useBillingFactory, + UseBillingParams +} from '@vue-storefront/core'; +import type { BillingAddress } from '@vue-storefront/boilerplate-api'; +import type { + UseBillingAddParams as AddParams +} from '../types'; + +const params: UseBillingParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, { customQuery }) => { + console.log('Mocked: useBilling.load'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + save: async (context: Context, { params, billingDetails, customQuery }) => { + console.log('Mocked: useBilling.save'); + return {}; + } +}; + +export const useBilling = useBillingFactory(params); diff --git a/packages/boilerplate/composables/src/useCart/index.ts b/packages/boilerplate/composables/src/useCart/index.ts new file mode 100644 index 0000000000..cab692a0f4 --- /dev/null +++ b/packages/boilerplate/composables/src/useCart/index.ts @@ -0,0 +1,67 @@ +import { + Context, + useCartFactory, + UseCartFactoryParams +} from '@vue-storefront/core'; +import type { + Cart, + CartItem, + Product +} from '@vue-storefront/boilerplate-api'; + +const params: UseCartFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, { customQuery }) => { + console.log('Mocked: useCart.load'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addItem: async (context: Context, { currentCart, product, quantity, customQuery }) => { + console.log('Mocked: useCart.addItem'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + removeItem: async (context: Context, { currentCart, product, customQuery }) => { + console.log('Mocked: useCart.removeItem'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateItemQty: async (context: Context, { currentCart, product, quantity, customQuery }) => { + console.log('Mocked: useCart.updateItemQty'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clear: async (context: Context, { currentCart }) => { + console.log('Mocked: useCart.clear'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + applyCoupon: async (context: Context, { currentCart, couponCode, customQuery }) => { + console.log('Mocked: useCart.applyCoupon'); + return { + updatedCart: {}, + updatedCoupon: {} + }; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + removeCoupon: async (context: Context, { currentCart, couponCode, customQuery }) => { + console.log('Mocked: useCart.removeCoupon'); + return { + updatedCart: {} + }; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isInCart: (context: Context, { currentCart, product }) => { + console.log('Mocked: useCart.isInCart'); + return false; + } +}; + +export const useCart = useCartFactory(params); diff --git a/packages/boilerplate/composables/src/useCategory/index.ts b/packages/boilerplate/composables/src/useCategory/index.ts new file mode 100644 index 0000000000..66ceb23e20 --- /dev/null +++ b/packages/boilerplate/composables/src/useCategory/index.ts @@ -0,0 +1,39 @@ +import { + Context, + useCategoryFactory, + UseCategoryFactoryParams +} from '@vue-storefront/core'; +import type { Category } from '@vue-storefront/boilerplate-api'; +import type { + UseCategorySearchParams as SearchParams +} from '../types'; + +const params: UseCategoryFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + categorySearch: async (context: Context, { customQuery, ...params }) => { + console.log('Mocked: useCategory.categorySearch'); + + return [ + { + id: 1, + name: 'Women', + slug: 'women', + items: [] + }, + { + id: 2, + name: 'Men', + slug: 'men', + items: [] + }, + { + id: 3, + name: 'Kids', + slug: 'kids', + items: [] + } + ]; + } +}; + +export const useCategory = useCategoryFactory(params); diff --git a/packages/boilerplate/composables/src/useContent/index.ts b/packages/boilerplate/composables/src/useContent/index.ts new file mode 100644 index 0000000000..b98c90a97a --- /dev/null +++ b/packages/boilerplate/composables/src/useContent/index.ts @@ -0,0 +1 @@ +export function useContent(): any {} diff --git a/packages/boilerplate/composables/src/useFacet/index.ts b/packages/boilerplate/composables/src/useFacet/index.ts new file mode 100644 index 0000000000..b0bedf3d07 --- /dev/null +++ b/packages/boilerplate/composables/src/useFacet/index.ts @@ -0,0 +1,18 @@ +import { + Context, + useFacetFactory, + FacetSearchResult +} from '@vue-storefront/core'; +import type { + UseFacetSearchParams as SearchParams +} from '../types'; + +const factoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + search: async (context: Context, params: FacetSearchResult) => { + console.log('Mocked: useFacet.search'); + return {}; + } +}; + +export const useFacet = useFacetFactory(factoryParams); diff --git a/packages/boilerplate/composables/src/useForgotPassword/index.ts b/packages/boilerplate/composables/src/useForgotPassword/index.ts new file mode 100644 index 0000000000..f05f6d80c9 --- /dev/null +++ b/packages/boilerplate/composables/src/useForgotPassword/index.ts @@ -0,0 +1,21 @@ +import { + Context, + useForgotPasswordFactory, + UseForgotPasswordFactoryParams +} from '@vue-storefront/core'; + +const factoryParams: UseForgotPasswordFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + resetPassword: async (context: Context, { email, customQuery }) => { + console.log('Mocked: resetPassword'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setNewPassword: async (context: Context, { tokenValue, newPassword, customQuery }) => { + console.log('Mocked: setNewPassword'); + return {}; + } +}; + +export const useForgotPassword = useForgotPasswordFactory(factoryParams); diff --git a/packages/boilerplate/composables/src/useMakeOrder/index.ts b/packages/boilerplate/composables/src/useMakeOrder/index.ts new file mode 100644 index 0000000000..4ebdc03057 --- /dev/null +++ b/packages/boilerplate/composables/src/useMakeOrder/index.ts @@ -0,0 +1,16 @@ +import { + Context, + useMakeOrderFactory, + UseMakeOrderFactoryParams +} from '@vue-storefront/core'; +import type { Order } from '@vue-storefront/boilerplate-api'; + +const factoryParams: UseMakeOrderFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + make: async (context: Context, { customQuery }) => { + console.log('Mocked: useMakeOrder.make'); + return {}; + } +}; + +export const useMakeOrder = useMakeOrderFactory(factoryParams); diff --git a/packages/boilerplate/composables/src/useProduct/index.ts b/packages/boilerplate/composables/src/useProduct/index.ts new file mode 100644 index 0000000000..4392899941 --- /dev/null +++ b/packages/boilerplate/composables/src/useProduct/index.ts @@ -0,0 +1,20 @@ +import { + Context, + useProductFactory, + UseProductFactoryParams +} from '@vue-storefront/core'; +import type { Product } from '@vue-storefront/boilerplate-api'; +import type { + UseProductSearchParams as SearchParams +} from '../types'; + +const params: UseProductFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + productsSearch: async (context: Context, params) => { + console.log('Mocked: useProduct.productsSearch'); + + return {}; + } +}; + +export const useProduct = useProductFactory(params); diff --git a/packages/boilerplate/composables/src/useReview/index.ts b/packages/boilerplate/composables/src/useReview/index.ts new file mode 100644 index 0000000000..ffcb52cfba --- /dev/null +++ b/packages/boilerplate/composables/src/useReview/index.ts @@ -0,0 +1,26 @@ +import { + Context, + useReviewFactory, + UseReviewFactoryParams +} from '@vue-storefront/core'; +import type { Review } from '@vue-storefront/boilerplate-api'; +import type { + UseReviewSearchParams as SearchParams, + UseReviewAddParams as AddParams +} from '../types'; + +const params: UseReviewFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + searchReviews: async (context: Context, params) => { + console.log('Mocked: useReview.searchReviews'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addReview: async (context: Context, params) => { + console.log('Mocked: useReview.addReview'); + return {}; + } +}; + +export const useReview = useReviewFactory(params); diff --git a/packages/boilerplate/composables/src/useShipping/index.ts b/packages/boilerplate/composables/src/useShipping/index.ts new file mode 100644 index 0000000000..d7a7a818f8 --- /dev/null +++ b/packages/boilerplate/composables/src/useShipping/index.ts @@ -0,0 +1,25 @@ +import { + Context, + useShippingFactory, + UseShippingParams +} from '@vue-storefront/core'; +import type { ShippingAddress } from '@vue-storefront/boilerplate-api'; +import type { + UseShippingAddParams as AddParams +} from '../types'; + +const params: UseShippingParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, { customQuery }) => { + console.log('Mocked: useShipping.load'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + save: async (context: Context, { shippingDetails, customQuery }) => { + console.log('Mocked: useShipping.save'); + return {}; + } +}; + +export const useShipping = useShippingFactory(params); diff --git a/packages/boilerplate/composables/src/useShippingProvider/index.ts b/packages/boilerplate/composables/src/useShippingProvider/index.ts new file mode 100644 index 0000000000..7e64df39ae --- /dev/null +++ b/packages/boilerplate/composables/src/useShippingProvider/index.ts @@ -0,0 +1,18 @@ +import { useShippingProviderFactory, UseShippingProviderParams, Context } from '@vue-storefront/core'; +import type { ShippingProvider, ShippingMethod } from '@vue-storefront/boilerplate-api'; + +const params: UseShippingProviderParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, { customQuery }) => { + console.log('Mocked: loadShippingProvider'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + save: async (context: Context, { shippingMethod, customQuery }) => { + console.log('Mocked: saveShippingProvider'); + return {}; + } +}; + +export const useShippingProvider = useShippingProviderFactory(params); diff --git a/packages/boilerplate/composables/src/useUser/index.ts b/packages/boilerplate/composables/src/useUser/index.ts new file mode 100644 index 0000000000..79bcd054d6 --- /dev/null +++ b/packages/boilerplate/composables/src/useUser/index.ts @@ -0,0 +1,49 @@ +import { + Context, + useUserFactory, + UseUserFactoryParams +} from '@vue-storefront/core'; +import type { User } from '@vue-storefront/boilerplate-api'; +import type { + UseUserUpdateParams as UpdateParams, + UseUserRegisterParams as RegisterParams +} from '../types'; + +const params: UseUserFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context) => { + console.log('Mocked: useUser.load'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + logOut: async (context: Context) => { + console.log('Mocked: useUser.logOut'); + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateUser: async (context: Context, { currentUser, updatedUserData }) => { + console.log('Mocked: useUser.updateUser'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + register: async (context: Context, { email, password, firstName, lastName }) => { + console.log('Mocked: useUser.register'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + logIn: async (context: Context, { username, password }) => { + console.log('Mocked: useUser.logIn'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + changePassword: async (context: Context, { currentUser, currentPassword, newPassword }) => { + console.log('Mocked: useUser.changePassword'); + return {}; + } +}; + +export const useUser = useUserFactory(params); diff --git a/packages/boilerplate/composables/src/useUserBilling/index.ts b/packages/boilerplate/composables/src/useUserBilling/index.ts new file mode 100644 index 0000000000..4978b83d3e --- /dev/null +++ b/packages/boilerplate/composables/src/useUserBilling/index.ts @@ -0,0 +1,43 @@ +import { + Context, + useUserBillingFactory, + UseUserBillingFactoryParams +} from '@vue-storefront/core'; +import type { + UserBillingAddress as Address, + UserBillingAddressItem as AddressItem +} from '@vue-storefront/boilerplate-api'; + +const params: UseUserBillingFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addAddress: async (context: Context, params) => { + console.log('Mocked: useUserBilling.addAddress'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + deleteAddress: async (context: Context, params) => { + console.log('Mocked: useUserBilling.deleteAddress'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateAddress: async (context: Context, params) => { + console.log('Mocked: useUserBilling.updateAddress'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, params) => { + console.log('Mocked: useUserBilling.load'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setDefaultAddress: async (context: Context, params) => { + console.log('Mocked: useUserBilling.setDefaultAddress'); + return {}; + } +}; + +export const useUserBilling = useUserBillingFactory(params); diff --git a/packages/boilerplate/composables/src/useUserOrder/index.ts b/packages/boilerplate/composables/src/useUserOrder/index.ts new file mode 100644 index 0000000000..2843a66c38 --- /dev/null +++ b/packages/boilerplate/composables/src/useUserOrder/index.ts @@ -0,0 +1,19 @@ +import { + Context, + useUserOrderFactory, + UseUserOrderFactoryParams +} from '@vue-storefront/core'; +import type { Order } from '@vue-storefront/boilerplate-api'; +import type { + useUserOrderSearchParams as SearchParams +} from '../types'; + +const params: UseUserOrderFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + searchOrders: async (context: Context, params) => { + console.log('Mocked: searchOrders'); + return {}; + } +}; + +export const useUserOrder = useUserOrderFactory(params); diff --git a/packages/boilerplate/composables/src/useUserShipping/index.ts b/packages/boilerplate/composables/src/useUserShipping/index.ts new file mode 100644 index 0000000000..36983ab6c3 --- /dev/null +++ b/packages/boilerplate/composables/src/useUserShipping/index.ts @@ -0,0 +1,43 @@ +import { + Context, + useUserShippingFactory, + UseUserShippingFactoryParams +} from '@vue-storefront/core'; +import type { + UserShippingAddress as Address, + UserShippingAddressItem as AddressItem +} from '@vue-storefront/boilerplate-api'; + +const params: UseUserShippingFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addAddress: async (context: Context, params) => { + console.log('Mocked: useUserShipping.addAddress'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + deleteAddress: async (context: Context, params) => { + console.log('Mocked: useUserShipping.deleteAddress'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + updateAddress: async (context: Context, params) => { + console.log('Mocked: useUserShipping.updateAddress'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, params) => { + console.log('Mocked: useUserShipping.load'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setDefaultAddress: async (context: Context, params) => { + console.log('Mocked: useUserShipping.setDefaultAddress'); + return {}; + } +}; + +export const useUserShipping = useUserShippingFactory(params); diff --git a/packages/boilerplate/composables/src/useWishlist/index.ts b/packages/boilerplate/composables/src/useWishlist/index.ts new file mode 100644 index 0000000000..b642d0bbdc --- /dev/null +++ b/packages/boilerplate/composables/src/useWishlist/index.ts @@ -0,0 +1,41 @@ +/* istanbul ignore file */ +import { + Context, + useWishlistFactory, + UseWishlistFactoryParams +} from '@vue-storefront/core'; +import type { Wishlist, WishlistItem, Product } from '@vue-storefront/boilerplate-api'; + +const params: UseWishlistFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context) => { + console.log('Mocked: useWishlist.load'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addItem: async (context: Context, { currentWishlist, product }) => { + console.log('Mocked: useWishlist.addItem'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + removeItem: async (context: Context, { currentWishlist, product }) => { + console.log('Mocked: useWishlist.removeItem'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clear: async (context: Context, { currentWishlist }) => { + console.log('Mocked: useWishlist.clear'); + return {}; + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isInWishlist: (context: Context, { currentWishlist, product }) => { + console.log('Mocked: useWishlist.isInWishlist'); + return false; + } +}; + +export const useWishlist = useWishlistFactory(params); diff --git a/packages/boilerplate/composables/tsconfig.json b/packages/boilerplate/composables/tsconfig.json new file mode 100644 index 0000000000..2ab53ecfdc --- /dev/null +++ b/packages/boilerplate/composables/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["src"], + "exclude": ["node_modules", "lib"] +} diff --git a/packages/boilerplate/jest.base.config.js b/packages/boilerplate/jest.base.config.js new file mode 100644 index 0000000000..d246220ffd --- /dev/null +++ b/packages/boilerplate/jest.base.config.js @@ -0,0 +1,13 @@ +// For a detailed explanation regarding each configuration property, visit: +// https://jestjs.io/docs/en/configuration.html + +module.exports = { + transform: { + '^.+\\.(ts)$': 'ts-jest' + }, + coverageDirectory: './coverage/', + collectCoverageFrom: [ + 'src/**/*.ts' + ], + collectCoverage: true +}; diff --git a/packages/boilerplate/rollup.base.config.js b/packages/boilerplate/rollup.base.config.js new file mode 100644 index 0000000000..efeeb97710 --- /dev/null +++ b/packages/boilerplate/rollup.base.config.js @@ -0,0 +1,35 @@ +import nodeResolve from '@rollup/plugin-node-resolve'; +import typescript from 'rollup-plugin-typescript2'; +import { terser } from 'rollup-plugin-terser'; + +const extensions = ['.ts', '.js']; + +export function generateBaseConfig(pkg) { + return { + input: 'src/index.ts', + output: [ + { + file: pkg.main, + format: 'cjs' + }, + { + file: pkg.module, + format: 'es' + } + ], + external: [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ], + plugins: [ + nodeResolve({ + extensions + }), + typescript({ + // eslint-disable-next-line global-require + typescript: require('typescript') + }), + terser() + ] + }; +} diff --git a/packages/boilerplate/theme/.editorconfig b/packages/boilerplate/theme/.editorconfig new file mode 100644 index 0000000000..5d12634847 --- /dev/null +++ b/packages/boilerplate/theme/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/boilerplate/theme/.gitignore b/packages/boilerplate/theme/.gitignore new file mode 100644 index 0000000000..b747047256 --- /dev/null +++ b/packages/boilerplate/theme/.gitignore @@ -0,0 +1,99 @@ +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# theme +_theme + +# Nuxt generate +dist + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# IDE / Editor +.idea + +# Service worker +sw.* + +# Mac OSX +.DS_Store + +# Vim swap files +*.swp + +version + +# e2e reports +tests/e2e/report.json +tests/e2e/report diff --git a/packages/boilerplate/theme/README.md b/packages/boilerplate/theme/README.md new file mode 100644 index 0000000000..9592cb07b1 --- /dev/null +++ b/packages/boilerplate/theme/README.md @@ -0,0 +1,51 @@ +# Vue Storefront 2 + +## Build Setup + +```bash +# install dependencies +$ yarn install + +# serve with hot reload at localhost:3000 +$ yarn dev + +# build for production and launch server +$ yarn build +$ yarn start +``` + +For detailed explanation on how things work, check out the [documentation](https://docs.vuestorefront.io/v2/). + +## Special Directories + +You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality. + +### `assets` + +The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts. + +### `components` + +The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components. + +### `layouts` + +Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop. + +### `pages` + +This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically. + +### `plugins` + +The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`. + +### `static` + +This directory contains your static files. Each file inside this directory is mapped to `/`. + +Example: `/static/robots.txt` is mapped as `/robots.txt`. + +### `store` + +This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex. diff --git a/packages/boilerplate/theme/jest.config.js b/packages/boilerplate/theme/jest.config.js new file mode 100644 index 0000000000..af71b05fe4 --- /dev/null +++ b/packages/boilerplate/theme/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + modulePathIgnorePatterns: ['tests/e2e/'] +}; diff --git a/packages/boilerplate/theme/lang/.gitkeep b/packages/boilerplate/theme/lang/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/boilerplate/theme/lang/de.js b/packages/boilerplate/theme/lang/de.js new file mode 100644 index 0000000000..8d83e80a01 --- /dev/null +++ b/packages/boilerplate/theme/lang/de.js @@ -0,0 +1,155 @@ +/* eslint-disable */ + +export default { + 'Categories': 'Kategorien', + 'Filters': 'Filters', + 'Sort by': 'Sortieren nach', + 'Products found': 'Produkte gefunden', + 'About us': 'Über uns', + 'Who we are': 'Wer wir sind', + 'Quality in the details': 'Qualität im Detail', + 'Customer Reviews': 'Kundenbewertungen', + 'Departments': 'Abteilungen', + 'Women fashion': 'Damenmode', + 'Men fashion': 'Herrenmode', + 'Kidswear': 'Kinderkleidung', + 'Home': 'Zuhause', + 'Help': 'Hilfe', + 'Customer service': 'Kundendienst', + 'Size guide': 'Größentabelle', + 'Contact us': 'Kontaktiere uns', + 'Payment & Delivery': 'Zahlung & Lieferung', + 'Purchase terms': 'Kaufbedingungen', + 'Guarantee': 'Garantie', + 'Description': 'Beschreibung', + 'Read reviews': 'Bewertungen lesen', + 'Additional Information': 'Zusätzliche Information', + 'Save for later': 'Für später speichern', + 'Add to compare': 'Hinzufügen zum vergleichen', + 'Match it with': 'Kombiniere es mit', + 'Share your look': 'Teile deinen Look', + 'Product description': 'Das Karissa V-Neck Tee hat eine halb taillierte Form schmeichelhaft für jede Figur. Sie können mit ins Fitnessstudio gehen Vertrauen, während es Kurven umarmt und häufiges "Problem" verbirgt Bereiche. Finden Sie atemberaubende Cocktailkleider für Frauen und feiern Sie Kleider.', + 'Brand': 'Marke', + 'Instruction1': 'Um mich kümmern', + 'Instruction2': 'Nur hier für die Pflegehinweise?', + 'Instruction3': 'Ja, das haben wir uns gedacht', + 'Items': 'Gegenstände', + 'View': 'Ansicht', + 'Show on page': 'Auf Seite anzeigen', + 'Done': 'Fertig', + 'Clear all': 'Alles löschen', + 'Empty': 'Sieht so aus, als hätten Sie der Tasche noch keine Artikel hinzugefügt. Beginnen Sie mit dem Einkaufen, um es auszufüllen.', + 'Help & FAQs': 'Hilfe & FAQs', + 'Download': 'Laden Sie unsere Anwendung herunter', + 'Find out more': 'Finde mehr heraus', + 'Login': 'Anmeldung', + 'Forgotten password?': 'Passwort vergessen?', + 'No account': `Sie haben noch keinen Account?`, + 'Register today': 'Melde dich noch heute an', + 'Go to checkout': 'Zur Kasse gehen', + 'Go back shopping': 'Zurück einkaufen', + 'Personal details': 'Persönliche Daten', + 'Edit': 'Bearbeiten', + 'Shipping details': 'Versanddetails', + 'Billing address': 'Rechnungsadresse', + 'Same as shipping address': 'Wie Versandadresse', + 'Payment method': 'Zahlungsmethode', + 'Apply': 'Übernehmen', + 'Update password': 'Passwort aktualisieren', + 'Update personal data': 'Persönliche Daten aktualisieren', + 'Item': 'Artikel', + 'Go back': 'Go back', + 'Continue to shipping': 'Weiter zum Versand', + 'I agree to': 'Ich stimme zu', + 'Terms and conditions': 'Allgemeine Geschäftsbedingungen', + 'Pay for order': 'Für Bestellung bezahlen', + 'Log into your account': 'In dein Konto einloggen', + 'or fill the details below': 'oder füllen Sie die Details unten', + 'Enjoy your free account': 'Enjoy these perks with your free account!', + 'Continue to payment': 'Weiter zur Zahlung', + 'Order No.': 'Bestellnummer', + 'Successful placed order': 'Sie haben die Bestellung erfolgreich aufgegeben. Sie können den Status Ihres Bestellen Sie über unsere Lieferstatusfunktion. Sie erhalten eine Bestellung Bestätigungs-E-Mail mit Details Ihrer Bestellung und einem Link zum Verfolgen der Bestellung Fortschritt.', + 'Info after order': 'Sie können sich mit E-Mail und definiertem Passwort in Ihrem Konto anmelden vorhin. Überprüfen Sie in Ihrem Konto Ihre Profildaten Transaktionsverlauf, Abonnement für Newsletter bearbeiten.', + 'Allow order notifications': 'Bestellbenachrichtigungen zulassen', + 'Feedback': 'Ihr Feedback ist uns wichtig. Lassen Sie uns wissen, was wir verbessern können.', + 'Send my feedback': 'Senden Sie mein Feedback', + 'Go back to shop': 'Zurück zum Einkaufen', + 'Read all reviews': 'Alle Bewertungen lesen', + 'Color': 'Farbe', + 'Contact details updated': 'Halten Sie Ihre Adressen und Kontaktdaten auf dem neuesten Stand.', + 'Manage billing addresses': 'Alle gewünschten Rechnungsadressen verwalten (Arbeitsplatz, Privatadresse ...) Auf diese Weise müssen Sie die Rechnungsadresse nicht bei jeder Bestellung manuell eingeben.', + 'Change': 'Änderungsänderung', + 'Delete': 'Löschen', + 'Add new address': 'Neue Adresse hinzufügen', + 'Set up newsletter': 'Richten Sie Ihren Newsletter ein und wir senden Ihnen wöchentlich Informationen zu neuen Produkten und Trends aus den von Ihnen ausgewählten Bereichen', + 'Sections that interest you': 'Abschnitte, die Sie interessieren', + 'Save changes': 'Änderungen speichern', + 'Read and understand': 'Ich habe das gelesen und verstanden', + 'Privacy': 'Datenschutz', + 'Cookies Policy': 'Cookie-Richtlinie', + 'Commercial information': 'und erklären sich damit einverstanden, personalisierte Handelsinformationen vom Markennamen per E-Mail zu erhalten', + 'Feel free to edit': 'Fühlen Sie sich frei, Ihre unten stehenden Daten zu bearbeiten, damit Ihr Konto immer auf dem neuesten Stand ist', + 'Use your personal data': 'Bei Markennamen legen wir großen Wert auf Datenschutzfragen und verpflichten uns, die persönlichen Daten unserer Benutzer zu schützen. Erfahren Sie mehr darüber, wie wir Ihre persönlichen Daten pflegen und verwenden', + 'Privacy Policy': 'Datenschutzrichtlinie', + 'Change password your account': 'Wenn Sie das Passwort ändern möchten, um auf Ihr Konto zuzugreifen, geben Sie die folgenden Informationen ein', + 'Your current email address is': 'Ihre aktuelle E-Mail-Adresse lautet', + 'Product': 'Produkt', + 'Details and status orders': 'Überprüfen Sie die Details und den Status Ihrer Bestellungen im Online-Shop. Sie können Ihre Bestellung auch stornieren oder eine Rücksendung anfordern. ', + 'You currently have no orders': 'Sie haben derzeit keine Bestellungen', + 'Start shopping': 'Einkaufen starten', + 'Download': 'Herunterladen', + 'Download all': 'Alle herunterladen', + 'View details': 'Details anzeigen', + 'Manage shipping addresses': 'Alle gewünschten Versandadressen verwalten (Arbeitsplatz, Privatadresse ...) Auf diese Weise müssen Sie die Versandadresse nicht bei jeder Bestellung manuell eingeben.', + 'Quantity': 'Menge', + 'Price': 'Preis', + 'Back to homepage': 'Zurück zur Homepage', + 'Select shipping method': 'Versandart auswählen', + 'Review my order': 'Meine Bestellung überprüfen', + 'Select payment method': 'Zahlungsmethode auswählen', + 'Make an order': 'Bestellung aufgeben', + 'or': 'oder', + 'login in to your account': 'Anmelden bei Ihrem Konto', + 'Create an account': 'Konto erstellen', + 'Your bag is empty': 'Ihre Tasche ist leer', + 'Cancel': 'Abbrechen', + 'See all results': 'Alle Ergebnisse anzeigen', + 'You haven’t searched for items yet': 'Sie haben noch nicht nach Artikeln gesucht.', + 'Let’s start now – we’ll help you': 'Fangen wir jetzt an - wir helfen Ihnen.', + 'Search results': 'Suchergebnisse', + 'Product suggestions': 'Produktvorschläge', + 'Search for items': 'Nach Artikeln suchen', + 'Enter promo code': 'Geben Sie den Promo-Code ein', + 'Shipping method': 'Versandart', + 'Continue to billing': 'Weiter zur Abrechnung', + 'Payment methods': 'Zahlungsmethoden', + 'Shipping address': 'Lieferanschrift', + 'Subtotal': 'Zwischensumme', + 'Shipping': 'Versand', + 'Total price': 'Gesamtpreis', + 'Payment': 'Zahlung', + 'Order summary': 'Bestellübersicht', + 'Products': 'Produkte', + 'Total': 'Gesamt', + 'Reset Password': 'Passwort Zurücksetzen', + 'Save Password': 'Passwort Speichern', + 'Back to home': 'Zurück Zur Startseite', + 'Forgot Password': 'Wenn Sie Ihr Passwort vergessen haben, können Sie es zurücksetzen.', + 'Thank You Inbox': 'Wenn die Nachricht nicht in Ihrem Posteingang ankommt, versuchen Sie es mit einer anderen E-Mail-Adresse, mit der Sie sich möglicherweise registriert haben.', + 'Sign in': 'Einloggen', + 'Register': 'Registrieren', + 'Password Changed': 'Passwort erfolgreich geändert. Sie können nun zur Startseite zurückkehren und sich anmelden.', + 'Password': 'Passwort', + 'Repeat Password': 'Wiederhole das Passwort', + 'Forgot Password Modal Email': 'E-Mail, mit der Sie sich anmelden:', + forgotPasswordConfirmation: 'Vielen Dank! Wenn ein Konto mit der E-Mail-Adresse {0} registriert ist, finden Sie in Ihrem Posteingang eine Nachricht mit einem Link zum Zurücksetzen des Passworts.', + subscribeToNewsletterModalContent: + 'Wenn Sie sich für den Newsletter angemeldet haben, erhalten Sie spezielle Angebote und Nachrichten von VSF per E-Mail. Wir werden Ihre E-Mail zu keinem Zeitpunkt an Dritte verkaufen oder weitergeben. Bitte beachten Sie unsere {0}.', + 'Subscribe': 'Abonnieren', + 'Subscribe to newsletter': 'Anmeldung zum Newsletter', + 'Email address': 'E-Mail Adresse', + 'I confirm subscription': 'Ich bestätige das Abonnement', + 'You can unsubscribe at any time': 'Sie können sich jederzeit abmelden', + 'show more': 'mehr anzeigen', + 'hide': 'ausblenden' +}; diff --git a/packages/boilerplate/theme/lang/en.js b/packages/boilerplate/theme/lang/en.js new file mode 100644 index 0000000000..482677f5a8 --- /dev/null +++ b/packages/boilerplate/theme/lang/en.js @@ -0,0 +1,155 @@ +/* eslint-disable */ + +export default { + 'Categories': 'Categories', + 'Filters': 'Filter', + 'Sort by': 'Sort by', + 'Products found': 'Products found', + 'About us': 'About us', + 'Who we are': 'Who we are', + 'Quality in the details': 'Quality in the details', + 'Customer Reviews': 'Customer Reviews', + 'Departments': 'Departments', + 'Women fashion': 'Women fashion', + 'Men fashion': 'Men fashion', + 'Kidswear': 'Kidswear', + 'Home': 'Home', + 'Help': 'Help', + 'Customer service': 'Customer service', + 'Size guide': 'Size guide', + 'Contact us': 'Contact us', + 'Payment & Delivery': 'Payment & Delivery', + 'Purchase terms': 'Purchase terms', + 'Guarantee': 'Guarantee', + 'Description': 'Description', + 'Read reviews': 'Read reviews', + 'Additional Information': 'Additional Information', + 'Save for later': 'Save for later', + 'Add to compare': 'Add to compare', + 'Match it with': 'Match it with', + 'Share your look': 'Share your look', + 'Product description': `The Karissa V-Neck Tee features a semi-fitted shape that's flattering for every figure. You can hit the gym with confidence while it hugs curves and hides common "problem" areas. Find stunning women's cocktail dresses and party dresses.`, + 'Brand': 'Brand', + 'Instruction1': 'Take care of me', + 'Instruction2': 'Just here for the care instructions?', + 'Instruction3': 'Yeah, we thought so', + 'Items': 'Items', + 'View': 'View', + 'Show on page': 'Show on page', + 'Done': 'Done', + 'Clear all': 'Clear all', + 'Empty': 'Looks like you haven’t added any items to the bag yet. Start shopping to fill it in.', + 'Help & FAQs': 'Help & FAQs', + 'Download': 'Download our application.', + 'Find out more': 'Find out more', + 'Login': 'Login', + 'Forgotten password?': 'Forgotten password?', + 'No account': `Don't have an account yet?`, + 'Register today': 'Register today', + 'Go to checkout': 'Go to checkout', + 'Go back shopping': 'Go back shopping', + 'Personal details': 'Personal details', + 'Edit': 'Edit', + 'Shipping details': 'Shipping details', + 'Billing address': 'Billing address', + 'Same as shipping address': 'Same as shipping address', + 'Payment method': 'Payment method', + 'Apply': 'Apply', + 'Update password': 'Update password', + 'Update personal data': 'Update personal data', + 'Item': 'Item', + 'Go back': 'Go back', + 'Continue to shipping': 'Continue to shipping', + 'I agree to': 'I agree to', + 'Terms and conditions': 'Terms and conditions', + 'Pay for order': 'Pay for order', + 'Log into your account': 'Log into your account', + 'or fill the details below': 'or fill the details below', + 'Enjoy your free account': 'Enjoy these perks with your free account!', + 'Continue to payment': 'Continue to payment', + 'Order No.': 'Order No.', + 'Successful placed order': 'You have successfully placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.', + 'Info after order': 'You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.', + 'Allow order notifications': 'Allow order notifications', + 'Feedback': 'Your feedback is important to us. Let us know what we could improve.', + 'Send my feedback': 'Send my feedback', + 'Go back to shop': 'Go back to shop', + 'Read all reviews': 'Read all reviews', + 'Color': 'Color', + 'Contact details updated': 'Keep your addresses and contact details updated.', + 'Manage billing addresses': 'Manage all the billing addresses you want (work place, home address...) This way you won"t have to enter the billing address manually with each order.', + 'Change': 'Change', + 'Delete': 'Delete', + 'Add new address': 'Add new address', + 'Set up newsletter': 'Set up your newsletter and we will send you information about new products and trends from the sections you selected every week.', + 'Sections that interest you': 'Sections that interest you', + 'Save changes': 'Save changes', + 'Read and understand': 'I have read and understand the', + 'Privacy': 'Privacy', + 'Cookies Policy': 'Cookies Policy', + 'Commercial information': 'and agree to receive personalized commercial information from Brand name by email', + 'Feel free to edit': 'Feel free to edit any of your details below so your account is always up to date', + 'Use your personal data': 'At Brand name, we attach great importance to privacy issues and are committed to protecting the personal data of our users. Learn more about how we care and use your personal data in the', + 'Privacy Policy': 'Privacy Policy', + 'Change password your account': 'If you want to change the password to access your account, enter the following information', + 'Your current email address is': 'Your current email address is', + 'Product': 'Product', + 'Details and status orders': 'Check the details and status of your orders in the online store. You can also cancel your order or request a return.', + 'You currently have no orders': 'You currently have no orders', + 'Start shopping': 'Start shopping', + 'Download': 'Download', + 'Download all': 'Download all', + 'View details': 'View details', + 'Manage shipping addresses': 'Manage all the shipping addresses you want (work place, home address...) This way you won"t have to enter the shipping address manually with each order.', + 'Quantity': 'Quantity', + 'Price': 'Price', + 'Back to homepage': 'Back to homepage', + 'Select shipping method': 'Select shipping method', + 'Review my order': 'Review my order', + 'Select payment method': 'Select payment method', + 'Make an order': 'Make an order', + 'or': 'or', + 'login in to your account': 'login in to your account', + 'Create an account': 'Create an account', + 'Your bag is empty': 'Your bag is empty', + 'Cancel': 'Cancel', + 'See all results': 'See all results', + 'You haven’t searched for items yet': 'You haven’t searched for items yet.', + 'Let’s start now – we’ll help you': 'Let’s start now – we’ll help you.', + 'Search results': 'Search results', + 'Product suggestions': 'Product suggestions', + 'Search for items': 'Search for items', + 'Enter promo code': 'Enter promo code', + 'Shipping method': 'Shipping method', + 'Continue to billing': 'Continue to billing', + 'Payment methods': 'Payment methods', + 'Shipping address': 'Shipping address', + 'Subtotal': 'Subtotal', + 'Shipping': 'Shipping', + 'Total price': 'Total price', + 'Payment': 'Payment', + 'Order summary': 'Order summary', + 'Products': 'Products', + 'Total': 'Total', + 'Reset Password': 'Reset Password', + 'Save Password': 'Save Password', + 'Back to home': 'Back to home', + 'Forgot Password': 'If you can’t remember your password, you can reset it.', + 'Thank You Inbox': 'If the message is not arriving in your inbox, try another email address you might’ve used to register.', + 'Sign in': 'Sign in', + 'Register': 'Register', + 'Password Changed': 'Password successfuly changed. You can now go back to homepage and sign in.', + 'Password': 'Password', + 'Repeat Password': 'Repeat Password', + 'Forgot Password Modal Email': 'Email you are using to sign in:', + forgotPasswordConfirmation: 'Thanks! If there is an account registered with the {0} email, you will find message with a password reset link in your inbox.', + subscribeToNewsletterModalContent: + 'After signing up for the newsletter, you will receive special offers and messages from VSF via email. We will not sell or distribute your email to any third party at any time. Please see our {0}.', + 'Subscribe': 'Subscribe', + 'Subscribe to newsletter': 'Subscribe to newsletter', + 'Email address': 'Email address', + 'I confirm subscription': 'I confirm subscription', + 'You can unsubscribe at any time': 'You can unsubscribe at any time', + 'show more': 'show more', + 'hide': 'hide' +}; diff --git a/packages/boilerplate/theme/middleware.config.js b/packages/boilerplate/theme/middleware.config.js new file mode 100644 index 0000000000..927831dcd7 --- /dev/null +++ b/packages/boilerplate/theme/middleware.config.js @@ -0,0 +1,8 @@ +module.exports = { + integrations: { + boilerplate: { + location: '@vue-storefront/boilerplate-api/server', + configuration: {} + } + } +}; diff --git a/packages/boilerplate/theme/nuxt.config.js b/packages/boilerplate/theme/nuxt.config.js new file mode 100644 index 0000000000..ce61cf63f0 --- /dev/null +++ b/packages/boilerplate/theme/nuxt.config.js @@ -0,0 +1,165 @@ +import webpack from 'webpack'; +import { VSF_LOCALE_COOKIE } from '@vue-storefront/core'; +import theme from './themeConfig'; + +export default { + server: { + port: 3000, + host: '0.0.0.0' + }, + + // Global page headers: https://go.nuxtjs.dev/config-head + head: { + title: 'Vue Storefront', + meta: [ + { charset: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } + ], + link: [ + { + rel: 'icon', + type: 'image/x-icon', + href: '/favicon.ico' + }, + { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossorigin: 'crossorigin' + }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css?family=Raleway:300,400,400i,500,600,700|Roboto:300,300i,400,400i,500,700&display=swap' + } + ] + }, + + loading: { color: '#fff' }, + + // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins + plugins: [], + + // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules + buildModules: [ + // to core + '@nuxt/typescript-build', + '@nuxtjs/style-resources', + ['@vue-storefront/nuxt', { + // @core-development-only-start + coreDevelopment: true, + // @core-development-only-end + useRawSource: { + dev: [ + '@vue-storefront/boilerplate', + '@vue-storefront/core' + ], + prod: [ + '@vue-storefront/boilerplate', + '@vue-storefront/core' + ] + } + }], + // @core-development-only-start + ['@vue-storefront/nuxt-theme', { + generate: { + replace: { + apiClient: '@vue-storefront/boilerplate-api', + composables: '@vue-storefront/boilerplate' + } + } + }], + // @core-development-only-end + /* project-only-start + ['@vue-storefront/nuxt-theme'], + project-only-end */ + ['@vue-storefront/boilerplate/nuxt', {}] + ], + + // Modules: https://go.nuxtjs.dev/config-modules + modules: [ + 'nuxt-i18n', + 'cookie-universal-nuxt', + 'vue-scrollto/nuxt', + '@vue-storefront/middleware/nuxt' + ], + + i18n: { + currency: 'USD', + country: 'US', + countries: [ + { name: 'US', label: 'United States', states: ['California', 'Nevada'] }, + { name: 'AT', label: 'Austria' }, + { name: 'DE', label: 'Germany' }, + { name: 'NL', label: 'Netherlands' } + ], + currencies: [ + { name: 'EUR', label: 'Euro' }, + { name: 'USD', label: 'Dollar' } + ], + locales: [ + { code: 'en', label: 'English', file: 'en.js', iso: 'en' }, + { code: 'de', label: 'German', file: 'de.js', iso: 'de' } + ], + defaultLocale: 'en', + lazy: true, + seo: true, + langDir: 'lang/', + vueI18n: { + fallbackLocale: 'en', + numberFormats: { + en: { + currency: { + style: 'currency', currency: 'USD', currencyDisplay: 'symbol' + } + }, + de: { + currency: { + style: 'currency', currency: 'EUR', currencyDisplay: 'symbol' + } + } + } + }, + detectBrowserLanguage: { + cookieKey: VSF_LOCALE_COOKIE + } + }, + + styleResources: { + scss: [require.resolve('@storefront-ui/shared/styles/_helpers.scss', { paths: [process.cwd()] })] + }, + + // Build Configuration: https://go.nuxtjs.dev/config-build + build: { + babel: { + plugins: [ + ['@babel/plugin-proposal-private-methods', { loose: true }] + ] + }, + transpile: [ + 'vee-validate/dist/rules' + ], + plugins: [ + new webpack.DefinePlugin({ + 'process.VERSION': JSON.stringify({ + // eslint-disable-next-line global-require + version: require('./package.json').version, + lastCommit: process.env.LAST_COMMIT || '' + }) + }) + ] + }, + + router: { + middleware: ['checkout'], + scrollBehavior (_to, _from, savedPosition) { + if (savedPosition) { + return savedPosition; + } else { + return { x: 0, y: 0 }; + } + } + }, + publicRuntimeConfig: { + theme + } +}; diff --git a/packages/boilerplate/theme/package.json b/packages/boilerplate/theme/package.json new file mode 100644 index 0000000000..c11e58bb60 --- /dev/null +++ b/packages/boilerplate/theme/package.json @@ -0,0 +1,41 @@ +{ + "name": "@vue-storefront/boilerplate-theme", + "version": "2.4.1", + "private": true, + "scripts": { + "dev": "nuxt", + "build": "nuxt build -m", + "build:analyze": "nuxt build -a -m", + "start": "nuxt start", + "test": "jest", + "test:e2e": "cypress open --config-file tests/e2e/cypress.json", + "test:e2e:hl": "cypress run --headless --config-file tests/e2e/cypress.json", + "test:e2e:generate:report": "yarn -s mochawesome-merge \"tests/e2e/report/*.json\" > \"tests/e2e/report.json\" && yarn -s marge tests/e2e/report.json -o \"tests/e2e/report\"" + }, + "dependencies": { + "@storefront-ui/vue": "^0.10.3", + "@vue-storefront/boilerplate": "~2.4.1", + "@vue-storefront/middleware": "~2.4.1", + "@vue-storefront/nuxt": "~2.4.1", + "@vue-storefront/nuxt-theme": "~2.4.1", + "cookie-universal-nuxt": "^2.1.3", + "core-js": "^2.6.5", + "nuxt": "^2.13.3", + "nuxt-i18n": "^6.5.0", + "vee-validate": "^3.2.3", + "vue-scrollto": "^2.17.1" + }, + "devDependencies": { + "@nuxt/types": "^0.7.9", + "@vue/test-utils": "^1.0.0-beta.27", + "babel-jest": "^24.1.0", + "cypress": "^6.6.0", + "cypress-pipe": "^2.0.0", + "cypress-tags": "^0.0.20", + "jest": "^24.1.0", + "mochawesome": "^6.2.2", + "mochawesome-merge": "^4.2.0", + "mochawesome-report-generator": "^5.2.0", + "vue-jest": "^4.0.0-0" + } +} diff --git a/packages/boilerplate/theme/static/error/error.svg b/packages/boilerplate/theme/static/error/error.svg new file mode 100644 index 0000000000..e2d0756788 --- /dev/null +++ b/packages/boilerplate/theme/static/error/error.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/boilerplate/theme/static/favicon.ico b/packages/boilerplate/theme/static/favicon.ico new file mode 100644 index 0000000000..479229d87b Binary files /dev/null and b/packages/boilerplate/theme/static/favicon.ico differ diff --git a/packages/boilerplate/theme/static/homepage/apple.png b/packages/boilerplate/theme/static/homepage/apple.png new file mode 100644 index 0000000000..3b67e06976 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/apple.png differ diff --git a/packages/boilerplate/theme/static/homepage/bannerA.webp b/packages/boilerplate/theme/static/homepage/bannerA.webp new file mode 100644 index 0000000000..4e3b12a328 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerA.webp differ diff --git a/packages/boilerplate/theme/static/homepage/bannerB.webp b/packages/boilerplate/theme/static/homepage/bannerB.webp new file mode 100644 index 0000000000..a0169a0908 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerB.webp differ diff --git a/packages/boilerplate/theme/static/homepage/bannerC.webp b/packages/boilerplate/theme/static/homepage/bannerC.webp new file mode 100644 index 0000000000..832dfcd18d Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerC.webp differ diff --git a/packages/boilerplate/theme/static/homepage/bannerD.png b/packages/boilerplate/theme/static/homepage/bannerD.png new file mode 100644 index 0000000000..244482e941 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerD.png differ diff --git a/packages/boilerplate/theme/static/homepage/bannerE.webp b/packages/boilerplate/theme/static/homepage/bannerE.webp new file mode 100644 index 0000000000..1181b8f0e6 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerE.webp differ diff --git a/packages/boilerplate/theme/static/homepage/bannerF.webp b/packages/boilerplate/theme/static/homepage/bannerF.webp new file mode 100644 index 0000000000..46c74549e4 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerF.webp differ diff --git a/packages/boilerplate/theme/static/homepage/bannerG.webp b/packages/boilerplate/theme/static/homepage/bannerG.webp new file mode 100644 index 0000000000..0a9bebf579 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerG.webp differ diff --git a/packages/boilerplate/theme/static/homepage/bannerH.webp b/packages/boilerplate/theme/static/homepage/bannerH.webp new file mode 100644 index 0000000000..7d01c20c76 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/bannerH.webp differ diff --git a/packages/boilerplate/theme/static/homepage/google.png b/packages/boilerplate/theme/static/homepage/google.png new file mode 100644 index 0000000000..6d5cd62679 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/google.png differ diff --git a/packages/boilerplate/theme/static/homepage/imageAd.webp b/packages/boilerplate/theme/static/homepage/imageAd.webp new file mode 100644 index 0000000000..1a79e56731 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageAd.webp differ diff --git a/packages/boilerplate/theme/static/homepage/imageAm.webp b/packages/boilerplate/theme/static/homepage/imageAm.webp new file mode 100644 index 0000000000..bb267f7944 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageAm.webp differ diff --git a/packages/boilerplate/theme/static/homepage/imageBd.webp b/packages/boilerplate/theme/static/homepage/imageBd.webp new file mode 100644 index 0000000000..1920527fa9 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageBd.webp differ diff --git a/packages/boilerplate/theme/static/homepage/imageBm.webp b/packages/boilerplate/theme/static/homepage/imageBm.webp new file mode 100644 index 0000000000..a7c98740c4 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageBm.webp differ diff --git a/packages/boilerplate/theme/static/homepage/imageCd.webp b/packages/boilerplate/theme/static/homepage/imageCd.webp new file mode 100644 index 0000000000..18f82af02a Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageCd.webp differ diff --git a/packages/boilerplate/theme/static/homepage/imageCm.webp b/packages/boilerplate/theme/static/homepage/imageCm.webp new file mode 100644 index 0000000000..811116d6e2 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageCm.webp differ diff --git a/packages/boilerplate/theme/static/homepage/imageDd.webp b/packages/boilerplate/theme/static/homepage/imageDd.webp new file mode 100644 index 0000000000..2e37f209f9 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageDd.webp differ diff --git a/packages/boilerplate/theme/static/homepage/imageDm.webp b/packages/boilerplate/theme/static/homepage/imageDm.webp new file mode 100644 index 0000000000..c8ab58c43d Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/imageDm.webp differ diff --git a/packages/boilerplate/theme/static/homepage/newsletter.webp b/packages/boilerplate/theme/static/homepage/newsletter.webp new file mode 100644 index 0000000000..b3592b87da Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/newsletter.webp differ diff --git a/packages/boilerplate/theme/static/homepage/productA.webp b/packages/boilerplate/theme/static/homepage/productA.webp new file mode 100644 index 0000000000..fb5141e301 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/productA.webp differ diff --git a/packages/boilerplate/theme/static/homepage/productB.webp b/packages/boilerplate/theme/static/homepage/productB.webp new file mode 100644 index 0000000000..b5d10b9dd8 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/productB.webp differ diff --git a/packages/boilerplate/theme/static/homepage/productC.webp b/packages/boilerplate/theme/static/homepage/productC.webp new file mode 100644 index 0000000000..a12f3f56d3 Binary files /dev/null and b/packages/boilerplate/theme/static/homepage/productC.webp differ diff --git a/packages/boilerplate/theme/static/icons/facebook.svg b/packages/boilerplate/theme/static/icons/facebook.svg new file mode 100644 index 0000000000..8dcb72b3f9 --- /dev/null +++ b/packages/boilerplate/theme/static/icons/facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/boilerplate/theme/static/icons/google.svg b/packages/boilerplate/theme/static/icons/google.svg new file mode 100644 index 0000000000..c3fe1f0035 --- /dev/null +++ b/packages/boilerplate/theme/static/icons/google.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/boilerplate/theme/static/icons/langs/de.webp b/packages/boilerplate/theme/static/icons/langs/de.webp new file mode 100644 index 0000000000..fee9a24388 Binary files /dev/null and b/packages/boilerplate/theme/static/icons/langs/de.webp differ diff --git a/packages/boilerplate/theme/static/icons/langs/en.webp b/packages/boilerplate/theme/static/icons/langs/en.webp new file mode 100644 index 0000000000..891a0e301b Binary files /dev/null and b/packages/boilerplate/theme/static/icons/langs/en.webp differ diff --git a/packages/boilerplate/theme/static/icons/logo.svg b/packages/boilerplate/theme/static/icons/logo.svg new file mode 100644 index 0000000000..c9efd085f5 --- /dev/null +++ b/packages/boilerplate/theme/static/icons/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/boilerplate/theme/static/icons/pinterest.svg b/packages/boilerplate/theme/static/icons/pinterest.svg new file mode 100644 index 0000000000..f0527a7a57 --- /dev/null +++ b/packages/boilerplate/theme/static/icons/pinterest.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/boilerplate/theme/static/icons/twitter.svg b/packages/boilerplate/theme/static/icons/twitter.svg new file mode 100644 index 0000000000..338994fd10 --- /dev/null +++ b/packages/boilerplate/theme/static/icons/twitter.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/boilerplate/theme/static/icons/youtube.svg b/packages/boilerplate/theme/static/icons/youtube.svg new file mode 100644 index 0000000000..795eaedbf8 --- /dev/null +++ b/packages/boilerplate/theme/static/icons/youtube.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/boilerplate/theme/static/productpage/productA.jpg b/packages/boilerplate/theme/static/productpage/productA.jpg new file mode 100644 index 0000000000..866c1ffd66 Binary files /dev/null and b/packages/boilerplate/theme/static/productpage/productA.jpg differ diff --git a/packages/boilerplate/theme/static/productpage/productB.jpg b/packages/boilerplate/theme/static/productpage/productB.jpg new file mode 100644 index 0000000000..c518dedc8f Binary files /dev/null and b/packages/boilerplate/theme/static/productpage/productB.jpg differ diff --git a/packages/boilerplate/theme/static/productpage/productM.jpg b/packages/boilerplate/theme/static/productpage/productM.jpg new file mode 100644 index 0000000000..a1cb15a18c Binary files /dev/null and b/packages/boilerplate/theme/static/productpage/productM.jpg differ diff --git a/packages/boilerplate/theme/static/thankyou/bannerD.png b/packages/boilerplate/theme/static/thankyou/bannerD.png new file mode 100644 index 0000000000..de97105a36 Binary files /dev/null and b/packages/boilerplate/theme/static/thankyou/bannerD.png differ diff --git a/packages/boilerplate/theme/static/thankyou/bannerM.png b/packages/boilerplate/theme/static/thankyou/bannerM.png new file mode 100644 index 0000000000..72b909ca87 Binary files /dev/null and b/packages/boilerplate/theme/static/thankyou/bannerM.png differ diff --git a/packages/boilerplate/theme/tests/e2e/cypress.json b/packages/boilerplate/theme/tests/e2e/cypress.json new file mode 100644 index 0000000000..c96ef98f03 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/cypress.json @@ -0,0 +1,24 @@ +{ + "baseUrl": "http://localhost:3000", + "fixturesFolder": "tests/e2e/fixtures", + "integrationFolder": "tests/e2e/integration", + "pluginsFile": "tests/e2e/plugins/index.js", + "supportFile": "tests/e2e/support/index.js", + "viewportHeight": 1080, + "viewportWidth": 1920, + "pageLoadTimeout": 180000, + "screenshotOnRunFailure": true, + "screenshotsFolder": "tests/e2e/report/assets/screenshots", + "video": false, + "reporter": "../../../../node_modules/mochawesome", + "reporterOptions": { + "reportDir": "tests/e2e/report", + "reportFilename": "report", + "overwrite": false, + "html": false + }, + "retries": { + "runMode": 2, + "openMode": 0 + } +} diff --git a/packages/boilerplate/theme/tests/e2e/fixtures/test-data/e2e-place-order.json b/packages/boilerplate/theme/tests/e2e/fixtures/test-data/e2e-place-order.json new file mode 100644 index 0000000000..da87fc6b00 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/fixtures/test-data/e2e-place-order.json @@ -0,0 +1,26 @@ +{ + "customer": { + "firstName": "John", + "lastName": "Doe", + "address": { + "shipping": { + "streetName": "1 VueStorefront Rd.", + "apartment": "23", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "zipcode": "45678", + "phone": "123456789" + }, + "billing": { + "streetName": "1 VueStorefront Rd.", + "apartment": "23", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "zipcode": "45678", + "phone": "123456789" + } + } + } +} diff --git a/packages/boilerplate/theme/tests/e2e/integration/e2e-place-order.spec.ts b/packages/boilerplate/theme/tests/e2e/integration/e2e-place-order.spec.ts new file mode 100644 index 0000000000..2c638a5988 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/integration/e2e-place-order.spec.ts @@ -0,0 +1,33 @@ +import page from '../pages/factory'; + +context('Order placement', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-place-order').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it(['happypath', 'regression'], 'Should successfully place an order', function () { + const data = this.fixtures.data; + page.home.visit(); + page.home.header.categories.first().click(); + page.category.products.first().click(); + page.product.addToCartButton.click(); + page.product.header.openCart(); + page.cart.goToCheckoutButton.click(); + page.checkout.shipping.heading.should('be.visible'); + page.checkout.shipping.fillForm(data.customer); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.first().click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.fillForm(data.customer); + page.checkout.billing.continueToPaymentButton.click(); + page.checkout.payment.paymentMethods.first().click(); + page.checkout.payment.terms.click(); + page.checkout.payment.makeAnOrderButton.click(); + page.checkout.thankyou.heading.should('be.visible'); + }); +}); diff --git a/packages/boilerplate/theme/tests/e2e/pages/base.ts b/packages/boilerplate/theme/tests/e2e/pages/base.ts new file mode 100644 index 0000000000..614224ed92 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/base.ts @@ -0,0 +1,15 @@ +import Header from './components/header'; + +export default class Base { + get path(): string { + return '/'; + } + + get header() { + return Header; + } + + visit(): Cypress.Chainable { + return cy.visit(this.path); + } +} diff --git a/packages/boilerplate/theme/tests/e2e/pages/category.ts b/packages/boilerplate/theme/tests/e2e/pages/category.ts new file mode 100644 index 0000000000..a1a7a9f8d4 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/category.ts @@ -0,0 +1,9 @@ +import { el } from './utils/element'; + +class Category { + get products(): Cypress.Chainable { + return el('category-product-card', 'a'); + } +} + +export default new Category(); diff --git a/packages/boilerplate/theme/tests/e2e/pages/checkout.ts b/packages/boilerplate/theme/tests/e2e/pages/checkout.ts new file mode 100644 index 0000000000..b2366f09d7 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/checkout.ts @@ -0,0 +1,155 @@ +import { Customer } from '../types/customer'; +import { el } from './utils/element'; + +class Shipping { + + get firstName(): Cypress.Chainable { + return el('shipping-firstName', 'input'); + } + + get lastName(): Cypress.Chainable { + return el('shipping-lastName'); + } + + get streetName(): Cypress.Chainable { + return el('shipping-streetName'); + } + + get apartment(): Cypress.Chainable { + return el('shipping-apartment'); + } + + get city(): Cypress.Chainable { + return el('shipping-city'); + } + + get state(): Cypress.Chainable { + return el('shipping-state', 'input'); + } + + get country(): Cypress.Chainable { + return el('shipping-country', 'select'); + } + + get zipcode(): Cypress.Chainable { + return el('shipping-zipcode'); + } + + get phone(): Cypress.Chainable { + return el('shipping-phone'); + } + + get continueToBillingButton(): Cypress.Chainable { + return el('continue-to-billing'); + } + + get heading(): Cypress.Chainable { + return el('shipping-heading'); + } + + get selectShippingButton(): Cypress.Chainable { + return el('select-shipping'); + } + + get shippingMethods(): Cypress.Chainable { + return el('shipping-method', 'label'); + } + + public fillForm(customer: Customer) { + this.firstName.type(customer.firstName); + this.lastName.type(customer.lastName); + this.streetName.type(customer.address.shipping.streetName); + this.apartment.type(customer.address.shipping.apartment); + this.city.type(customer.address.shipping.city); + this.country.select(customer.address.shipping.country); + this.state.type(customer.address.shipping.state); + this.zipcode.type(customer.address.shipping.zipcode); + this.phone.type(customer.address.shipping.phone); + } +} + +class Billing { + get firstName(): Cypress.Chainable { + return el('billing-firstName'); + } + + get lastName(): Cypress.Chainable { + return el('billing-lastName'); + } + + get streetName(): Cypress.Chainable { + return el('billing-streetName'); + } + + get apartment(): Cypress.Chainable { + return el('billing-apartment'); + } + + get city(): Cypress.Chainable { + return el('billing-city'); + } + + get state(): Cypress.Chainable { + return el('billing-state', 'input'); + } + + get country(): Cypress.Chainable { + return el('billing-country', 'select'); + } + + get zipcode(): Cypress.Chainable { + return el('billing-zipcode'); + } + + get phone(): Cypress.Chainable { + return el('billing-phone'); + } + + get continueToPaymentButton(): Cypress.Chainable { + return el('continue-to-payment'); + } + + get heading(): Cypress.Chainable { + return el('billing-heading'); + } + + public fillForm(customer: Customer) { + this.firstName.type(customer.firstName); + this.lastName.type(customer.lastName); + this.streetName.type(customer.address.billing.streetName); + this.apartment.type(customer.address.billing.apartment); + this.city.type(customer.address.billing.city); + this.country.select(customer.address.billing.country); + this.state.type(customer.address.billing.state); + this.zipcode.type(customer.address.billing.zipcode); + this.phone.type(customer.address.billing.phone); + } + +} + +class Payment { + get makeAnOrderButton(): Cypress.Chainable { + return el('make-an-order'); + } + + get paymentMethods(): Cypress.Chainable { + return el('payment-method'); + } + + get terms(): Cypress.Chainable { + return el('terms', 'label'); + } +} + +class ThankYou { + get heading(): Cypress.Chainable { + return el('thank-you-banner', 'h2'); + } +} + +export { + Shipping, + Billing, + Payment, + ThankYou +}; diff --git a/packages/boilerplate/theme/tests/e2e/pages/components/cart-sidebar.ts b/packages/boilerplate/theme/tests/e2e/pages/components/cart-sidebar.ts new file mode 100644 index 0000000000..b9477eaef3 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/components/cart-sidebar.ts @@ -0,0 +1,9 @@ +import { el } from '../utils/element'; + +class Cart { + get goToCheckoutButton(): Cypress.Chainable { + return el('go-to-checkout-btn'); + } +} + +export default new Cart(); diff --git a/packages/boilerplate/theme/tests/e2e/pages/components/header.ts b/packages/boilerplate/theme/tests/e2e/pages/components/header.ts new file mode 100644 index 0000000000..8bbea180ea --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/components/header.ts @@ -0,0 +1,27 @@ +import { el } from '../utils/element'; + +class Header { + get cart(): Cypress.Chainable { + return el('app-header-cart'); + } + + get categories(): Cypress.Chainable { + return cy.get('[data-e2e*="app-header"]'); + } + + get category() { + return { + women: () => el('app-header-url_women'), + men: () => el('app-header-url_men') + }; + } + + openCart(): Cypress.Chainable { + const click = $el => $el.click(); + return this.cart.pipe(click).should(() => { + expect(Cypress.$('[data-e2e="sidebar-cart"]')).to.exist; + }); + } +} + +export default new Header(); diff --git a/packages/boilerplate/theme/tests/e2e/pages/factory.ts b/packages/boilerplate/theme/tests/e2e/pages/factory.ts new file mode 100644 index 0000000000..a022bb7ebb --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/factory.ts @@ -0,0 +1,30 @@ +import Category from './category'; +import { Billing, Payment, Shipping, ThankYou } from './checkout'; +import Cart from './components/cart-sidebar'; +import Home from './home'; +import Product from './product'; + +const page = { + get cart() { + return Cart; + }, + get category() { + return Category; + }, + get checkout() { + return { + shipping: new Shipping(), + billing: new Billing(), + payment: new Payment(), + thankyou: new ThankYou() + }; + }, + get home() { + return Home; + }, + get product() { + return Product; + } +}; + +export default page; diff --git a/packages/boilerplate/theme/tests/e2e/pages/home.ts b/packages/boilerplate/theme/tests/e2e/pages/home.ts new file mode 100644 index 0000000000..b162f16e11 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/home.ts @@ -0,0 +1,14 @@ +import Base from './base'; +import Header from './components/header'; + +class Home extends Base { + get header() { + return Header; + } + + visit(): Cypress.Chainable { + return cy.visit('/'); + } +} + +export default new Home(); diff --git a/packages/boilerplate/theme/tests/e2e/pages/product.ts b/packages/boilerplate/theme/tests/e2e/pages/product.ts new file mode 100644 index 0000000000..2f344df4b1 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/product.ts @@ -0,0 +1,10 @@ +import Base from './base'; +import { el } from './utils/element'; + +class Product extends Base { + get addToCartButton(): Cypress.Chainable { + return el('product_add-to-cart'); + } +} + +export default new Product(); diff --git a/packages/boilerplate/theme/tests/e2e/pages/utils/element.ts b/packages/boilerplate/theme/tests/e2e/pages/utils/element.ts new file mode 100644 index 0000000000..c7496801f6 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/pages/utils/element.ts @@ -0,0 +1,3 @@ +export function el(selector: string, children?: string) { + return children ? cy.get(`[data-e2e="${selector}"] ${children}`) : cy.get(`[data-e2e="${selector}"]`); +} diff --git a/packages/boilerplate/theme/tests/e2e/plugins/index.js b/packages/boilerplate/theme/tests/e2e/plugins/index.js new file mode 100644 index 0000000000..70b6427fe6 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/plugins/index.js @@ -0,0 +1,25 @@ +// eslint-disable-next-line spaced-comment +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +const tagify = require('cypress-tags'); + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config + on('file:preprocessor', tagify(config)); +}; diff --git a/packages/boilerplate/theme/tests/e2e/support/commands.js b/packages/boilerplate/theme/tests/e2e/support/commands.js new file mode 100644 index 0000000000..9ce9016cc1 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/support/commands.js @@ -0,0 +1,26 @@ +/* eslint-disable no-undef */ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/packages/boilerplate/theme/tests/e2e/support/index.d.ts b/packages/boilerplate/theme/tests/e2e/support/index.d.ts new file mode 100644 index 0000000000..b34f8e01a9 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/support/index.d.ts @@ -0,0 +1,9 @@ +/* eslint-disable spaced-comment */ +/// +/// + +declare namespace Cypress { + interface Chainable { + fixtures?: any; + } +} diff --git a/packages/boilerplate/theme/tests/e2e/support/index.js b/packages/boilerplate/theme/tests/e2e/support/index.js new file mode 100644 index 0000000000..9500306864 --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/support/index.js @@ -0,0 +1,35 @@ +/* eslint-disable no-undef */ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands.js'; +import 'cypress-pipe'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') + +import addContext from 'mochawesome/addContext'; + +Cypress.on('test:after:run', (test, runnable) => { + if (test.state === 'failed') { + const screenshot = `assets/screenshots/${Cypress.spec.name}/${runnable.parent.title} -- ${test.title} (failed).png`; + addContext({test}, { + title: 'Screenshot', + value: screenshot + }); + } +}); + diff --git a/packages/boilerplate/theme/tests/e2e/types/address.ts b/packages/boilerplate/theme/tests/e2e/types/address.ts new file mode 100644 index 0000000000..d415b85a4e --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/types/address.ts @@ -0,0 +1,9 @@ +export type Address = { + streetName: string; + apartment: string; + city: string; + state: string; + country: string; + zipcode: string; + phone: string; +} diff --git a/packages/boilerplate/theme/tests/e2e/types/customer.ts b/packages/boilerplate/theme/tests/e2e/types/customer.ts new file mode 100644 index 0000000000..0c0fdc9bed --- /dev/null +++ b/packages/boilerplate/theme/tests/e2e/types/customer.ts @@ -0,0 +1,10 @@ +import { Address } from './address'; + +export type Customer = { + firstName?: string; + lastName?: string; + address?: { + shipping: Address, + billing: Address + } +} diff --git a/packages/boilerplate/theme/themeConfig.js b/packages/boilerplate/theme/themeConfig.js new file mode 100644 index 0000000000..234f4c2e47 --- /dev/null +++ b/packages/boilerplate/theme/themeConfig.js @@ -0,0 +1,23 @@ +export default { + home: { + bannerA: { + link: '/', + image: { + mobile: '/homepage/bannerB.webp', + desktop: '/homepage/bannerF.webp' + } + }, + bannerB: { + link: '/', + image: '/homepage/bannerE.webp' + }, + bannerC: { + link: '/', + image: '/homepage/bannerC.webp' + }, + bannerD: { + link: '/', + image: '/homepage/bannerG.webp' + } + } +}; diff --git a/packages/boilerplate/theme/tsconfig.json b/packages/boilerplate/theme/tsconfig.json new file mode 100644 index 0000000000..83ca83fc87 --- /dev/null +++ b/packages/boilerplate/theme/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "esnext", + "moduleResolution": "node", + "lib": [ + "esnext", + "esnext.asynciterable", + "dom" + ], + "esModuleInterop": true, + "allowJs": true, + "sourceMap": true, + "strict": false, + "noEmit": true, + "baseUrl": ".", + "paths": { + "~/*": [ + "./*" + ], + "@/*": [ + "./*" + ] + }, + "types": [ + "@types/node", + "@nuxt/types", + "nuxt-i18n" + ] + }, + "exclude": [ + "node_modules", + ".nuxt", + "dist", + "_theme" + ] +} diff --git a/packages/boilerplate/tsconfig.base.json b/packages/boilerplate/tsconfig.base.json new file mode 100644 index 0000000000..55f81c31e0 --- /dev/null +++ b/packages/boilerplate/tsconfig.base.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "es5", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "lib": ["es6", "es7"], + "strict": false, + }, + "exclude": ["node_modules", "**/*.spec.ts"] +} diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/packages/cli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/packages/cli/README.md b/packages/cli/README.md deleted file mode 100644 index 370b9970da..0000000000 --- a/packages/cli/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Storefront CLI - -This is a beta version of CLI for Vue Storefront. - -## Installation - -``` -npm i -g @vue-storefront/cli -``` -## Commands - -After installing CLI you'll get access to following commands: -- `vsf init [dirname]` - Use it to set up new VS project. The CLI will ask you a few questions and set up new, ready to work with Vue Storefront instance. -- `vsf init:module [module-name]` - generates boilerplate for Vue Storefront module named `vsf-module-name` in a current directory -- `vsf init:theme` - generates theme for Vue Storefront -- `vsf --help` - available commands -- `vsf --version` - current version of cli diff --git a/packages/cli/boilerplates/module/.gitignore b/packages/cli/boilerplates/module/.gitignore deleted file mode 100644 index 04c01ba7ba..0000000000 --- a/packages/cli/boilerplates/module/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -dist/ \ No newline at end of file diff --git a/packages/cli/boilerplates/module/README.md b/packages/cli/boilerplates/module/README.md deleted file mode 100644 index af1a160657..0000000000 --- a/packages/cli/boilerplates/module/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# vsf-package - - -## Installation - -``` -yarn add vsf-package -``` - -## API - - - -## Development - -- `yarn build` - bundles package into main.js file with typescript/es6 transpilations -- `npm publish` - publishes package to NPM registry (automatically runs `yarn build` before publishing) \ No newline at end of file diff --git a/packages/cli/boilerplates/module/package.json b/packages/cli/boilerplates/module/package.json deleted file mode 100644 index 121eeecd9f..0000000000 --- a/packages/cli/boilerplates/module/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "vsf-package", - "version": "1.0.11", - "description": "", - "main": "dist/main.js", - "scripts": { - "build": "webpack --config node_modules/@vue-storefront/core/lib/modules/tools/module-build.config.js", - "prePublishOnly": "yarn build" - }, - "keywords": ["vue-storefront"], - "author": "", - "license": "MIT", - "dependencies": {}, - "devDependencies": { - "@vue-storefront/core": "^1.12.2", - "ts-loader": "^6.0.4", - "typescript": "^3.5.2", - "webpack": "^4.35.2", - "webpack-cli": "^3.3.11" - }, - "peerDependencies": { - "@vue-storefront/core": "^1.12.2" - } -} diff --git a/packages/cli/boilerplates/module/src/index.ts b/packages/cli/boilerplates/module/src/index.ts deleted file mode 100644 index 9dc90cd1d7..0000000000 --- a/packages/cli/boilerplates/module/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { coreHooks } from '@vue-storefront/core/hooks' -import { extendStore } from '@vue-storefront/core/helpers' -import { ExampleStore, ExtendProductStore } from './store' - -export const ExampleModule: StorefrontModule = function ({store}) { - // You can access config passed to registerModule via moduleConfig variable - // This is how you register new Vuex modules - store.registerModule('example', ExampleStore) - // This is how you override properties of currently existing Vuex modules - extendStore('product', ExtendProductStore) - // This is how you can hook into various palces of the application - coreHooks.afterAppInit(() => console.log('Do something when application is initialized!')) -} diff --git a/packages/cli/boilerplates/module/src/store.ts b/packages/cli/boilerplates/module/src/store.ts deleted file mode 100644 index ba44c5f5cb..0000000000 --- a/packages/cli/boilerplates/module/src/store.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const ExampleStore = { - state: { - message: 'Hello World' - } -} - -export const ExtendProductStore = { - actions: { - state: { - newprop: null - }, - list () { - console.log('Hello from extended action') - } - } -} diff --git a/packages/cli/boilerplates/module/tsconfig.json b/packages/cli/boilerplates/module/tsconfig.json deleted file mode 100644 index bcf0447cd8..0000000000 --- a/packages/cli/boilerplates/module/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "outDir": "./dist", - "strict": false, - "moduleResolution": "node" - }, - "include": [ - "**/*.ts" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file diff --git a/packages/cli/consts.js b/packages/cli/consts.js deleted file mode 100644 index 6044d4a332..0000000000 --- a/packages/cli/consts.js +++ /dev/null @@ -1,34 +0,0 @@ -const options = { - version: { - stable: 'Stable version (recommended for production)', - rc: 'Release Candidate', - nightly: 'In development branch (could be unstable!)' - }, - installation: { - installer: 'Installer (MacOS/Linux only)', - manual: 'Manual installation' - } -} - -const themes = { - capybara: { - label: 'Capybara - based on Storefront UI', - branches: { - master: options.version.stable, - develop: options.version.nightly - }, - minVsfVersion: '^1.11.0' - }, - default: { - label: 'Default', - branches: { - master: options.version.stable - }, - minVsfVersion: '*' - } -} - -module.exports = { - options, - themes -} diff --git a/packages/cli/helpers.js b/packages/cli/helpers.js deleted file mode 100644 index b8f1196a08..0000000000 --- a/packages/cli/helpers.js +++ /dev/null @@ -1,16 +0,0 @@ -const fs = require('fs') -const path = require('path') - -const getVsfPackageJSON = (installationDir, quiet) => { - try { - return JSON.parse(fs.readFileSync(path.join(installationDir, '/package.json'))) - } catch (err) { - if (quiet) return {} - console.error(err) - process.exit(1) - } -} - -module.exports = { - getVsfPackageJSON -} diff --git a/packages/cli/index.js b/packages/cli/index.js deleted file mode 100755 index 19af904002..0000000000 --- a/packages/cli/index.js +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node - -const command = process.argv[2] - -switch (command) { - case 'init': - require('./scripts/install.js')(process.argv[3]) - break; - case 'init:module': - require('./scripts/generateModule.js')(process.argv[3]) - break; - case 'init:theme': - require('./scripts/installTheme.js')() - break; - case '-h': - case '--help': - require('./scripts/manual.js')() - break; - case '-v': - case '--version': - console.log('v' + require('./package.json').version) - break; - default: - console.log('Unknown command. try one of those:\n') - require('./scripts/manual.js')() -} diff --git a/packages/cli/package.json b/packages/cli/package.json deleted file mode 100644 index 8f421f355c..0000000000 --- a/packages/cli/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@vue-storefront/cli", - "version": "0.2.1", - "description": "", - "main": "index.js", - "bin": { - "vsf": "./index.js" - }, - "publishConfig": { - "access": "public" - }, - "preferGlobal": true, - "author": "Filip Rakowski (@filrak)", - "license": "MIT", - "dependencies": { - "execa": "^4.0.2", - "fs-extra": "^8.1.0", - "inquirer": "^6.3.1", - "listr": "^0.14.3", - "lodash": "^4.17.15", - "replace-in-file": "^4.1.1", - "semver": "^7.1.3" - } -} diff --git a/packages/cli/scripts/generateModule.js b/packages/cli/scripts/generateModule.js deleted file mode 100644 index fd6c4c4bee..0000000000 --- a/packages/cli/scripts/generateModule.js +++ /dev/null @@ -1,24 +0,0 @@ -const fse = require('fs-extra') -const path = require('path') -const replace = require('replace-in-file') -const cwd = process.cwd() -const boilerplatePath = path.resolve(__dirname, '../boilerplates/module') - -module.exports = function (moduleName) { - const modulePath = cwd + '/vsf-' + moduleName - const replacementOptions = { - files: [modulePath + '/**/*.*'], - from: 'vsf-package', - to: 'vsf-' + moduleName - } - - fse.copy(boilerplatePath, modulePath, (err) => { - if (err) { - console.error(err) - } else { - replace(replacementOptions) - .catch(error => console.error('Error occurred:', error)) - console.log('Module vsf-' + moduleName + ' has been succesfully created!\n cd vsf-' + moduleName) - } - }) -} diff --git a/packages/cli/scripts/install.js b/packages/cli/scripts/install.js deleted file mode 100644 index 2d7a8b8fd3..0000000000 --- a/packages/cli/scripts/install.js +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -const inquirer = require('inquirer') -const Listr = require('listr') -const execa = require('execa') -const spawn = require('child_process') -const fs = require('fs') -const semverSortDesc = require('semver/functions/rsort') -const { options } = require('./../consts') -const { createThemeTasks, createThemePrompt } = require('./../themeTasks') - -module.exports = function (installationDir) { - installationDir = installationDir || 'vue-storefront' - let allTags = [] - let availableBranches = [ - 'develop', - 'master' - ] - - const tasks = { - installDeps: { - title: 'Installing dependencies', - task: () => execa.command('cd ' + installationDir + ' && yarn cache clean && yarn', { shell: true }) - }, - cloneVersion: { - title: 'Copying Vue Storefront files', - task: answers => { - return execa.command(`git clone --quiet --single-branch --branch ${answers.specificVersion} https://github.com/vuestorefront/vue-storefront.git ${installationDir} && cd ${installationDir}/core/scripts && git remote rm origin`, { shell: true }) - } - }, - ...createThemeTasks(installationDir), - runInstaller: { - title: 'Running installer', - task: () => spawn.execFileSync('yarn', ['installer'], { stdio: 'inherit', cwd: installationDir }) - }, - getStorefrontVersions: { - title: 'Check available versions', - task: () => execa('git', ['ls-remote', '--tags', 'https://github.com/vuestorefront/vue-storefront.git']).then(({ stdout }) => { - allTags = stdout.match(/refs\/tags\/v1.([0-9.]+)(-rc.[0-9])?/gm).map(tag => tag.replace('refs/tags/', '')) - allTags = semverSortDesc(allTags) - execa('git', ['ls-remote', '--heads', 'https://github.com/vuestorefront/vue-storefront.git']).then(({ stdout }) => { - let rcBranches = stdout.match(/refs\/heads\/release\/v1.([0-9.x]+)/gm).map(tag => tag.replace('refs/heads/', '')) - availableBranches = [...rcBranches, ...availableBranches] - }) - }).catch(e => { - console.error('Problem with checking versions\n', e) - }) - } - } - - if (fs.existsSync(installationDir)) { - console.error('Vue Storefront is already installed in directory ./' + installationDir + '. Aborting.') - } else { - new Listr([ - tasks.getStorefrontVersions - ]).run().then(() => { - inquirer - .prompt([ - { - type: 'list', - name: 'version', - message: 'Which version of Vue Storefront you\'d like to install?', - choices: [ - options.version.stable, - options.version.rc, - options.version.nightly - ] - }, - { - type: 'list', - name: 'specificVersion', - message: 'Select specific version', - choices: function (answers) { - if (answers.version === options.version.stable) return allTags.filter(tag => !tag.includes('rc')).slice(0, 10) - if (answers.version === options.version.rc) return allTags.filter(tag => tag.includes('rc')).slice(0, 5) - return availableBranches - } - }, - ...createThemePrompt(), - { - type: 'list', - name: 'installation', - message: 'Would you like to use friendly installer or install Vue Storefront manually?', - choices: [ - options.installation.installer, - options.installation.manual - ] - } - ]) - .then(answers => { - const taskQueue = [] - taskQueue.push(tasks.cloneVersion) - taskQueue.push(tasks.cloneTheme) - taskQueue.push(tasks.installDeps) // we need to install deps for theme - taskQueue.push(tasks.configureTheme) - if (answers.installation === options.installation.installer) { - taskQueue.push(tasks.runInstaller) - } - new Listr(taskQueue).run(answers) - }) - }) - } -} diff --git a/packages/cli/scripts/installTheme.js b/packages/cli/scripts/installTheme.js deleted file mode 100644 index b8a1a47752..0000000000 --- a/packages/cli/scripts/installTheme.js +++ /dev/null @@ -1,34 +0,0 @@ -const inquirer = require('inquirer') -const Listr = require('listr') -const { createThemeTasks, createThemePrompt } = require('./../themeTasks') -const { getVsfPackageJSON } = require('./../helpers') - -module.exports = function () { - inquirer - .prompt([ - { - type: 'input', - name: 'vsf_dir', - message: 'Please provide path to vue-storefront directory', - default: '.', - validate: function (value) { - const { name } = getVsfPackageJSON(value, true) - if (name !== 'vue-storefront') { - return 'Provided directory is not vue-storefront directory' - } - - return true - } - }, - ...createThemePrompt() - ]) - .then(answers => { - const { cloneTheme, installDeps, configureTheme } = createThemeTasks(answers.vsf_dir) - - new Listr([ - cloneTheme, - installDeps, - configureTheme - ]).run(answers) - }) -} diff --git a/packages/cli/scripts/manual.js b/packages/cli/scripts/manual.js deleted file mode 100644 index 99914b7082..0000000000 --- a/packages/cli/scripts/manual.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function () { - console.log('Usage: vsf [command] [options]\n') - console.log('Options:') - console.log(' --help | -h available commands') - console.log(' --version | -v CLI version\n') - console.log('Commands:') - console.log(' init [dir] setup new VS project') - console.log(' init:module [name] generate vs module boilerplate') - console.log(' init:theme adds theme in specified directory') -} diff --git a/packages/cli/themeTasks.js b/packages/cli/themeTasks.js deleted file mode 100644 index 1d8046e4b4..0000000000 --- a/packages/cli/themeTasks.js +++ /dev/null @@ -1,122 +0,0 @@ -const fs = require('fs') -const execa = require('execa') -const path = require('path') -const semverInc = require('semver/functions/inc') -const semverSatisfies = require('semver/functions/satisfies') -const semverCoerce = require('semver/functions/coerce') -const merge = require('lodash/merge') -const { themes } = require('./consts') -const { getVsfPackageJSON } = require('./helpers') - -const createThemeTasks = (installationDir = 'vue-storefront') => ({ - installDeps: { - title: 'Installing dependencies', - task: (answers) => { - const _installationDir = answers.vsf_dir || installationDir - return execa.command('cd ' + _installationDir + ' && yarn cache clean && yarn', { shell: true }) - } - }, - cloneTheme: { - title: 'Copying Vue Storefront theme', - task: answers => { - const _installationDir = answers.vsf_dir || installationDir - return execa.command([ - `git clone --quiet --single-branch --branch ${answers.themeBranch} https://github.com/vuestorefront/vsf-${answers.themeName}.git ${_installationDir}/src/themes/${answers.themeName}`, - `cd ${_installationDir}/src/themes/${answers.themeName}`, - `git remote rm origin` - ].join(' && '), { shell: true }) - }, - skip: answers => { - const _installationDir = answers.vsf_dir || installationDir - if (fs.existsSync(`${_installationDir}/src/themes/${answers.themeName}`)) { - return `Chosen theme already exists in Vue Storefront installation directory ${_installationDir}/src/themes/` - } - } - }, - configureTheme: { - title: 'Configuring Vue Storefront theme', - task: answers => { - const _installationDir = answers.vsf_dir || installationDir - const configurationFiles = ['local.config.js', 'local.json'] - const [themeLocalConfigJsPath, themeLocalJsonPath] = configurationFiles.map( - file => `${_installationDir}/src/themes/${answers.themeName}/${file}` - ) - const vsfLocalJsonPath = path.join(_installationDir, '/config/local.json') - const vsfPackageJsonPath = path.join(_installationDir, '/package.json') - - try { - const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion || getVsfPackageJSON(_installationDir).version) - const vsfVersionFromPackageJson = JSON.parse(fs.readFileSync(vsfPackageJsonPath)).version - const vsfVersion = isVsfVersionAsBranch - ? semverInc(vsfVersionFromPackageJson, 'minor') - : vsfVersionFromPackageJson - - const vsfLocalJson = fs.existsSync(vsfLocalJsonPath) - ? JSON.parse(fs.readFileSync(vsfLocalJsonPath)) - : {} - - const themeLocalJson = fs.existsSync(themeLocalConfigJsPath) - ? require(fs.realpathSync(themeLocalConfigJsPath))(vsfVersion) - : fs.existsSync(themeLocalJsonPath) - ? JSON.parse(fs.readFileSync(themeLocalJsonPath)) - : null - - if (themeLocalJson) { - fs.writeFileSync(vsfLocalJsonPath, JSON.stringify(merge(vsfLocalJson, themeLocalJson), null, 2)) - } - } catch (e) { - console.error(`Problem with parsing or merging configurations (${configurationFiles})\n`, e) - } - }, - skip: answers => { - const _installationDir = answers.vsf_dir || installationDir - const configurationFiles = ['local.config.js', 'local.json', 'local.config'] - const themePath = `${_installationDir}/src/themes/${answers.themeName}` - - if (configurationFiles.every(file => !fs.existsSync(`${themePath}/${file}`))) { - return ` - Missing configuration file in theme folder - nothing to configure. - Theme path: ${themePath}. - Configuration files: ${configurationFiles}. - ` - } - } - } -}) - -const createThemePrompt = (installationDir = 'vue-storefront') => [ - { - type: 'list', - name: 'themeName', - message: 'Select theme for Vue Storefront', - choices: answers => { - const _installationDir = answers.vsf_dir || installationDir - const isVsfVersionAsBranch = ['master', 'develop'].includes(answers.specificVersion || getVsfPackageJSON(_installationDir).version) - const selectedVsfVersion = semverCoerce(answers.specificVersion || getVsfPackageJSON(_installationDir).version) - - return Object.entries(themes) - .filter(([, themeConfig]) => isVsfVersionAsBranch || semverSatisfies(selectedVsfVersion, themeConfig.minVsfVersion, { includePrerelease: true })) - .map(([themeName, themeConfig]) => ({ - name: themeConfig.label, - value: themeName - })) - }, - default: 'default' - }, - { - type: 'list', - name: 'themeBranch', - message: 'Select theme version', - choices: answers => Object.entries(themes[answers.themeName].branches) - .map(([branchName, branchLabel]) => ({ - name: branchLabel, - value: branchName - })), - default: 'master' - } -] - -module.exports = { - createThemeTasks, - createThemePrompt -} diff --git a/packages/commercetools/api-client/.gitignore b/packages/commercetools/api-client/.gitignore new file mode 100644 index 0000000000..77e5a4f9f8 --- /dev/null +++ b/packages/commercetools/api-client/.gitignore @@ -0,0 +1,4 @@ +lib +node_modules +coverage +server diff --git a/packages/commercetools/api-client/.npmignore b/packages/commercetools/api-client/.npmignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/packages/commercetools/api-client/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/commercetools/api-client/__tests__/api/addToCart.spec.ts b/packages/commercetools/api-client/__tests__/api/addToCart.spec.ts new file mode 100644 index 0000000000..13e886e509 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/addToCart.spec.ts @@ -0,0 +1,54 @@ +import addToCart from '../../src/api/addToCart'; + +const cart = { + id: 1, + version: 1 +} as any; + +describe('[commercetools-api-client] addToCart', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('adds product to the cart', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + mutate: () => ({ + actions: [ + { addLineItem: {quantity: 2, sku: '123', variantId: 1} } + ], + id: 1, + version: 1 + }) + }, + extendQuery: (customQuery, args) => args + }; + const product = { id: 1, + sku: '123' } as any; + + const response = await addToCart(context, cart, { + product, + quantity: 2 + }); + + expect(response).toEqual({ + id: 1, + version: 1, + actions: [ + { + addLineItem: { + variantId: 1, + sku: '123', + quantity: 2 + } + } + ] + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/applyCartCoupon.spec.ts b/packages/commercetools/api-client/__tests__/api/applyCartCoupon.spec.ts new file mode 100644 index 0000000000..a4b3cce625 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/applyCartCoupon.spec.ts @@ -0,0 +1,47 @@ +import applyCartCoupon from '../../src/api/applyCartCoupon'; + +const cart = { + id: 1, + version: 1 +} as any; + +describe('[commercetools-api-client] applyCartCoupon', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('applies coupon to the cart', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + mutate: () => ({ + actions: [ + { addDiscountCode: { code: 'coupon' } } + ], + id: 1, + version: 1 + }) + }, + extendQuery: (customQuery, args) => args + }; + + const response = await applyCartCoupon(context, cart, 'coupon'); + + expect(response).toEqual({ + id: 1, + version: 1, + actions: [ + { + addDiscountCode: { + code: 'coupon' + } + } + ] + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/createCart.spec.ts b/packages/commercetools/api-client/__tests__/api/createCart.spec.ts new file mode 100644 index 0000000000..a150b2f204 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/createCart.spec.ts @@ -0,0 +1,85 @@ +import createCart from '../../src/api/createCart'; +import defaultMutation from '../../src/api/createCart/defaultMutation'; + +describe('[commercetools-api-client] createCart', () => { + it('creates a new cart with draft', async () => { + const givenVariables = { + acceptLanguage: ['en', 'de'], + locale: 'en', + currency: 'USD', + draft: { + currency: 'USD', + items: [], + id: 'cart-id' + } + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + mutate: ({ variables, mutation }) => { + expect(variables).toEqual(givenVariables); + expect(mutation).toEqual(defaultMutation); + + return { + data: { + items: [], + id: 'cart-id' + } + }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await createCart(context, { + items: [], + id: 'cart-id' + } as any); + + expect(data).toEqual({ + items: [], + id: 'cart-id' + }); + }); + + it('creates a new cart without draft', async () => { + const givenVariables = { + acceptLanguage: ['en', 'de'], + locale: 'en', + currency: 'USD', + draft: { + country: 'US', + currency: 'USD' + } + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'US' + }, + client: { + mutate: ({ variables, mutation }) => { + expect(variables).toEqual(givenVariables); + expect(mutation).toEqual(defaultMutation); + + return { + data: {} + }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await createCart(context); + + expect(data).toEqual({}); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/createMyOrderFromCart.spec.ts b/packages/commercetools/api-client/__tests__/api/createMyOrderFromCart.spec.ts new file mode 100644 index 0000000000..216e43ba55 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/createMyOrderFromCart.spec.ts @@ -0,0 +1,40 @@ +import createMyOrderFromCart from '../../src/api/createMyOrderFromCart'; +import defaultMutation from '../../src/api/createMyOrderFromCart/defaultMutation'; + +jest.unmock('../../src/api/createMyOrderFromCart'); + +const givenVariables = { + draft: { + id: '123123', + version: 2 + }, + acceptLanguage: ['en', 'de'], + locale: 'en', + currency: 'USD' +}; + +describe('[commercetools-api-client] createMyOrderFromCart', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('creates a new order', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + mutate: ({ variables, mutation }) => { + expect(variables).toEqual(givenVariables); + expect(mutation).toEqual(defaultMutation); + return { data: 'order response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await createMyOrderFromCart(context, { id: '123123', version: 2 }); + expect(data).toBe('order response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/customerChangeMyPassword.spec.ts b/packages/commercetools/api-client/__tests__/api/customerChangeMyPassword.spec.ts new file mode 100644 index 0000000000..ac03e9f517 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/customerChangeMyPassword.spec.ts @@ -0,0 +1,28 @@ +import customerChangeMyPassword from '../../src/api/customerChangeMyPassword'; +import defaultMutation from '../../src/api/customerChangeMyPassword/defaultMutation'; + +describe('[commercetools-api-client] customerChangeMyPassword', () => { + it('changes user password', async () => { + const givenVariables = { + version: 364964457, + currentPassword: 'currentPassword', + newPassword: 'newPassword' + }; + + const context = { + config: {}, + client: { + mutate: ({ variables, mutation }) => { + expect(variables).toEqual(givenVariables); + expect(mutation).toEqual(defaultMutation); + + return { data: 'user response' }; + } + } + }; + + const { data } = await customerChangeMyPassword(context, givenVariables.version, givenVariables.currentPassword, givenVariables.newPassword); + + expect(data).toBe('user response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/customerSignMeIn.spec.ts b/packages/commercetools/api-client/__tests__/api/customerSignMeIn.spec.ts new file mode 100644 index 0000000000..88b96e5395 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/customerSignMeIn.spec.ts @@ -0,0 +1,38 @@ +import customerSignMeIn from '../../src/api/customerSignMeIn'; +import defaultMutation from '../../src/api/customerSignMeIn/defaultMutation'; + +describe('[commercetools-api-client] customerSignMeIn', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates user session', async () => { + const givenVariables = { + draft: { + email: 'john@doe.com', + password: 'xxxxx' + }, + acceptLanguage: ['en', 'de'], + locale: 'en', + currency: 'USD' + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + mutate: ({ variables, mutation }) => { + expect(variables).toEqual(givenVariables); + expect(mutation).toEqual(defaultMutation); + return { data: 'user response' }; + } + } + }; + + const { data } = await customerSignMeIn(context, givenVariables.draft); + expect(data).toBe('user response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/customerSignMeUp.spec.ts b/packages/commercetools/api-client/__tests__/api/customerSignMeUp.spec.ts new file mode 100644 index 0000000000..eb4b2bdfa2 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/customerSignMeUp.spec.ts @@ -0,0 +1,37 @@ +import customerSignMeUp from '../../src/api/customerSignMeUp'; +import defaultMutation from '../../src/api/customerSignMeUp/defaultMutation'; + +describe('[commercetools-api-client] customerSignMeUp', () => { + it('creates user account', async () => { + const givenVariables = { + draft: { + firstName: 'John', + lastName: 'Doe', + email: 'john@doe.com', + password: 'xxxxx' + }, + acceptLanguage: ['en', 'de'], + locale: 'en', + currency: 'USD' + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + mutate: ({ variables, mutation }) => { + expect(variables).toEqual(givenVariables); + expect(mutation).toEqual(defaultMutation); + + return { data: 'user response' }; + } + } + }; + + const { data } = await customerSignMeUp(context, givenVariables.draft); + expect(data).toBe('user response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/customerSignOut.spec.ts b/packages/commercetools/api-client/__tests__/api/customerSignOut.spec.ts new file mode 100644 index 0000000000..96a0c8c89b --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/customerSignOut.spec.ts @@ -0,0 +1,45 @@ +import customerSignOut from '../../src/api/customerSignOut'; + +const mockContext = { + config: { + auth: { + onTokenRemove: null + } + }, + client: { + tokenProvider: null + } +}; + +describe('[commercetools-api-client] customerSignOut', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls "onTokenRemove" if provided', () => { + const onTokenRemove = jest.fn().mockImplementation(() => {}); + mockContext.config.auth.onTokenRemove = onTokenRemove; + + customerSignOut(mockContext); + expect(onTokenRemove).toBeCalled(); + }); + + it('calls "invalidateTokenInfo" if provided', () => { + const tokenProvider = { invalidateTokenInfo: jest.fn().mockImplementation(() => {}) }; + mockContext.client.tokenProvider = tokenProvider; + + customerSignOut(mockContext); + expect(tokenProvider.invalidateTokenInfo).toBeCalled(); + }); + + it('calls "onTokenRemove" and "invalidateTokenInfo" if both are provided', () => { + const onTokenRemove = jest.fn().mockImplementation(() => {}); + const tokenProvider = { invalidateTokenInfo: jest.fn().mockImplementation(() => {}) }; + mockContext.config.auth.onTokenRemove = onTokenRemove; + mockContext.client.tokenProvider = tokenProvider; + + customerSignOut(mockContext); + expect(onTokenRemove).toBeCalled(); + expect(tokenProvider.invalidateTokenInfo).toBeCalled(); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/customerUpdateMe.spec.ts b/packages/commercetools/api-client/__tests__/api/customerUpdateMe.spec.ts new file mode 100644 index 0000000000..7a6b311843 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/customerUpdateMe.spec.ts @@ -0,0 +1,47 @@ +import customerUpdateMe from '../../src/api/customerUpdateMe'; + +jest.mock('../../src/api/customerUpdateMe/defaultMutation.ts', () => ''); + +const mockContext = { + client: { + mutate: jest.fn().mockImplementation(() => ({ data: 'MOCK_DATA' })) + }, + config: {} +}; + +describe('[commercetools-api-client] customerUpdateMe', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('updates customer data', async () => { + const currentUser = { id: 1, version: 2 }; + const updatedUserData = { firstName: 'First', lastName: 'Last', email: 'email' }; + + const response = await customerUpdateMe( + mockContext, + currentUser, + updatedUserData + ); + + expect(mockContext.client.mutate).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { + version: currentUser.version, + actions: expect.arrayContaining([ + { + setFirstName: { firstName: updatedUserData.firstName } + }, + { + setLastName: { lastName: updatedUserData.lastName } + }, + { + changeEmail: { email: updatedUserData.email } + } + ]) + } + }) + ); + expect(response).toBe('MOCK_DATA'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/getCart.spec.ts b/packages/commercetools/api-client/__tests__/api/getCart.spec.ts new file mode 100644 index 0000000000..620db93aad --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/getCart.spec.ts @@ -0,0 +1,33 @@ +import getCart from '../../src/api/getCart'; +import defaultQuery from '../../src/api/getCart/defaultQuery'; + +describe('[commercetools-api-client] getCart', () => { + it('fetches cart', async () => { + const givenVariables = { + acceptLanguage: ['en', 'de'], + locale: 'en', + cartId: 'cart id', + currency: 'USD' + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + query: ({ variables, query }) => { + expect(variables).toEqual(givenVariables); + expect(query).toEqual(defaultQuery); + + return { data: 'cart response' }; + } + } + }; + + const { data } = await getCart(context, 'cart id'); + + expect(data).toBe('cart response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/getCategory.spec.ts b/packages/commercetools/api-client/__tests__/api/getCategory.spec.ts new file mode 100644 index 0000000000..91c8a95a70 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/getCategory.spec.ts @@ -0,0 +1,59 @@ +import getCategory from '../../src/api/getCategory'; +import defaultQuery from '../../src/api/getCategory/defaultQuery'; + +describe('[commercetools-api-client] getCategory', () => { + it('fetches categories without search parameters', async () => { + const givenVariables = { + acceptLanguage: ['en', 'de'] + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + query: ({ variables, query }) => { + expect(variables).toEqual(givenVariables); + expect(query).toEqual(defaultQuery); + + return { data: 'category response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await getCategory(context, null); + + expect(data).toBe('category response'); + }); + + it('fetches categories with default query', async () => { + const givenVariables = { + where: 'id="724b250d-9805-4657-ae73-3c02a63a9a13"', + acceptLanguage: ['en', 'de'] + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + query: ({ variables, query }) => { + expect(variables).toEqual(givenVariables); + expect(query).toEqual(defaultQuery); + + return { data: 'category response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await getCategory(context, { catId: '724b250d-9805-4657-ae73-3c02a63a9a13' }); + + expect(data).toBe('category response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/getMe.spec.ts b/packages/commercetools/api-client/__tests__/api/getMe.spec.ts new file mode 100644 index 0000000000..34cce8b0d4 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/getMe.spec.ts @@ -0,0 +1,33 @@ +import getMe from '../../src/api/getMe'; +import { basicProfile } from '../../src/api/getMe/defaultQuery'; + +describe('[commercetools-api-client] getMe', () => { + it('fetches current user data', async () => { + const givenVariables = { + acceptLanguage: ['en', 'de'], + locale: 'en', + currency: 'USD' + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + query: ({ variables, query }) => { + expect(variables).toEqual(givenVariables); + expect(query).toEqual(basicProfile); + + return { data: 'me response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await getMe(context); + + expect(data).toBe('me response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/getOrders.spec.ts b/packages/commercetools/api-client/__tests__/api/getOrders.spec.ts new file mode 100644 index 0000000000..cbf3b7794e --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/getOrders.spec.ts @@ -0,0 +1,70 @@ +import getOrders from '../../src/api/getOrders'; +import defaultQuery from '../../src/api/getOrders/defaultQuery'; +import { OrderWhereSearch } from '../../src/types/Api'; + +describe('[commercetools-api-client] getOrders', () => { + let givenVariables: any; + let params: OrderWhereSearch; + + beforeEach(() => { + givenVariables = { + acceptLanguage: ['en', 'de'], + locale: 'en', + where: null, + limit: 10, + offset: 0, + sort: undefined, + currency: 'USD' + }; + params = { + limit: 10, + offset: 0 + }; + }); + + it('fetches current user orders data', async () => { + givenVariables.where = 'id="fvdrt8gaw4r"'; + params.id = 'fvdrt8gaw4r'; + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + query: ({ variables, query }) => { + expect(variables).toEqual(givenVariables); + expect(query).toEqual(defaultQuery); + return { data: 'me response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await getOrders(context, params); + expect(data).toBe('me response'); + }); + + it('fetches current user orders data by orderNumber', async () => { + givenVariables.where = 'orderNumber="1234"'; + params.orderNumber = '1234'; + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + query: ({ variables, query }) => { + expect(variables).toEqual(givenVariables); + expect(query).toEqual(defaultQuery); + return { data: 'me response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await getOrders(context, params); + expect(data).toBe('me response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/getProducts.spec.ts b/packages/commercetools/api-client/__tests__/api/getProducts.spec.ts new file mode 100644 index 0000000000..45243a51a9 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/getProducts.spec.ts @@ -0,0 +1,36 @@ +import getProduct from '../../src/api/getProduct'; +import defaultQuery from '../../src/api/getProduct/defaultQuery'; + +describe('[commercetools-api-client] getProduct', () => { + it('fetches product with default query', async () => { + const givenVariables = { + where: 'masterData(current(categories(id in ("724b250d-9805-4657-ae73-3c02a63a9a13"))))', + acceptLanguage: ['en', 'de'], + locale: 'en', + currency: 'USD', + country: 'UK' + }; + + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + query: ({ variables, query }) => { + expect(variables).toEqual(givenVariables); + expect(query).toEqual(defaultQuery); + + return { data: 'product response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await getProduct(context, { catId: ['724b250d-9805-4657-ae73-3c02a63a9a13'] }); + + expect(data).toBe('product response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/getShippingMethods.spec.ts b/packages/commercetools/api-client/__tests__/api/getShippingMethods.spec.ts new file mode 100644 index 0000000000..c8a8598644 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/getShippingMethods.spec.ts @@ -0,0 +1,26 @@ +import getShippingMethods from '../../src/api/getShippingMethods'; +import defaultQuery from '../../src/api/getShippingMethods/defaultQuery'; + +describe('[commercetools-api-client] getShippingMethods', () => { + it('fetches shipping methods', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' + }, + client: { + query: ({ query }) => { + expect(query).toEqual(defaultQuery); + + return { data: 'shipping response' }; + } + }, + extendQuery: (customQuery, args) => args + }; + + const { data } = await getShippingMethods(context); + + expect(data).toBe('shipping response'); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/getStores.spec.ts b/packages/commercetools/api-client/__tests__/api/getStores.spec.ts new file mode 100644 index 0000000000..4fa67e1653 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/getStores.spec.ts @@ -0,0 +1,41 @@ +import getStores from '../../src/api/getStores'; +import { storesData } from '../../src/api/getStores/defaultQuery'; + +describe('[commercetools-api-client] getStores', () => { + it('fetches store data', async () => { + + const response = { + data: { + stores: 'stores response' + } + }; + + const extendQuery = jest.fn().mockImplementation( + function (_, args) { + return args; + } + ); + + const query = jest.fn().mockReturnValue( + response + ); + + const config = { + locale: 'en' + }; + + const client = { + query + }; + + const context = { + config, + client, + extendQuery + }; + + expect(await getStores(context)).toBe(response.data.stores); + expect(extendQuery).toHaveBeenCalled(); + expect(query).toHaveBeenCalledWith({ variables: { ...config }, query: storesData, fetchPolicy: 'no-cache' }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/isGuest.spec.ts b/packages/commercetools/api-client/__tests__/api/isGuest.spec.ts new file mode 100644 index 0000000000..01ee4d2946 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/isGuest.spec.ts @@ -0,0 +1,58 @@ +import isGuest from '../../src/api/isGuest'; + +const getMockContext = () => ({ + client: { + tokenProvider: true + }, + config: { + handleIsGuest: null, + auth: { + onTokenRead: null + } + } +}); + +describe('[commercetools-api-client] isGuest', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('defaults to false', () => { + const context = getMockContext(); + context.client.tokenProvider = false; + + expect(isGuest(context)).toBeFalsy(); + }); + + it('calls "handleIsGuest" from config', () => { + const context = getMockContext(); + context.config.handleIsGuest = jest.fn().mockImplementation(() => true); + + expect(isGuest(context)).toBeTruthy(); + expect(context.config.handleIsGuest).toBeCalled(); + }); + + it('returns true if visitor is a guest', () => { + const context = getMockContext(); + context.config.auth.onTokenRead = jest.fn().mockImplementation(() => ({ scope: '' })); + + expect(isGuest(context)).toBeTruthy(); + expect(context.config.auth.onTokenRead).toBeCalled(); + }); + + it('returns false if visitor has user session', () => { + const context = getMockContext(); + context.config.auth.onTokenRead = jest.fn().mockImplementation(() => ({ scope: 'customer_id' })); + + expect(isGuest(context)).toBeFalsy(); + expect(context.config.auth.onTokenRead).toBeCalled(); + }); + + it('returns false if visitor has anonymous session', () => { + const context = getMockContext(); + context.config.auth.onTokenRead = jest.fn().mockImplementation(() => ({ scope: 'anonymous_id' })); + + expect(isGuest(context)).toBeFalsy(); + expect(context.config.auth.onTokenRead).toBeCalled(); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/removeCartCoupon.spec.ts b/packages/commercetools/api-client/__tests__/api/removeCartCoupon.spec.ts new file mode 100644 index 0000000000..2b202109aa --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/removeCartCoupon.spec.ts @@ -0,0 +1,50 @@ +import removeCartCoupon from '../../src/api/removeCartCoupon'; + +const cart = { + id: 1, + version: 1 +} as any; + +describe('[commercetools-api-client] removeCartCoupon', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('removes coupon from the cart', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + mutate: () => ({ + actions: [ + { removeDiscountCode: { discountCode: { id: '123', typeId: '123' } } } + ], + id: 1, + version: 1 + }) + }, + extendQuery: (customQuery, args) => args + }; + + const response = await removeCartCoupon(context, cart, { typeId: '123', id: '123'}); + + expect(response).toEqual({ + id: 1, + version: 1, + actions: [ + { + removeDiscountCode: { + discountCode: { + id: '123', + typeId: '123' + } + } + } + ] + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/removeFromCart.spec.ts b/packages/commercetools/api-client/__tests__/api/removeFromCart.spec.ts new file mode 100644 index 0000000000..10f18e3cc1 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/removeFromCart.spec.ts @@ -0,0 +1,53 @@ +import removeFromCart from '../../src/api/removeFromCart'; + +const cart = { + id: 1, + version: 1 +} as any; + +describe('[commercetools-api-client] removeFromCart', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('removes product from cart', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + mutate: () => ({ + actions: [ + { removeLineItem: { quantity: 2, lineItemId: 1} } + ], + id: 1, + version: 1 + }) + }, + extendQuery: (customQuery, args) => args + }; + + const product = { + id: 1, + sku: '123', + quantity: 2 + } as any; + const response = await removeFromCart(context, cart, product); + + expect(response).toEqual({ + id: 1, + version: 1, + actions: [ + { + removeLineItem: { + lineItemId: 1, + quantity: 2 + } + } + ] + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/setShippingMethod.spec.ts b/packages/commercetools/api-client/__tests__/api/setShippingMethod.spec.ts new file mode 100644 index 0000000000..c1dadeb302 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/setShippingMethod.spec.ts @@ -0,0 +1,22 @@ +import { setShippingMethodAction } from '../../src/helpers/cart/actions'; + +describe('[commercetools-api-client] setShippingMethod', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('updates cart shipping method', async () => { + const shippingMethodId = 'some-id'; + + const action1 = setShippingMethodAction(shippingMethodId); + const action2 = setShippingMethodAction(); + + expect(action1).toEqual({ + setShippingMethod: { shippingMethod: { id: shippingMethodId } } + }); + + expect(action2).toEqual({ + setShippingMethod: { shippingMethod: null } + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/updateCart.spec.ts b/packages/commercetools/api-client/__tests__/api/updateCart.spec.ts new file mode 100644 index 0000000000..9a8e2d5c9d --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/updateCart.spec.ts @@ -0,0 +1,111 @@ +import updateCart from '../../src/api/updateCart'; +import defaultMutation from '../../src/api/updateCart/defaultMutation'; + +jest.unmock('../../src/api/updateCart'); + +const givenVariables = { + acceptLanguage: ['en', 'de'], + locale: 'en', + id: 'cart id', + currency: 'USD', + version: 1, + actions: [{ addLineItem: {} }] +}; + +const createContext = (client) => ({ + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client, + extendQuery: (customQuery, args) => args +}); + +describe('[commercetools-api-client] updateCart', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('updates cart', async () => { + const context = createContext({ + mutate: ({ variables, mutation }) => { + expect(variables).toEqual(givenVariables); + expect(mutation).toEqual(defaultMutation); + + return { data: 'cart response' }; + } + }); + + const { data } = await updateCart(context, { + id: 'cart id', + version: 1, + actions: [{ addLineItem: {} }] + }); + + expect(data).toBe('cart response'); + }); + + it('retries by default if error is caused by version mismatch', async () => { + const requestMock = jest.fn() + .mockImplementationOnce(() => { + const error: any = new Error('Mismatch'); + error.graphQLErrors = [{ code: 'ConcurrentModification', currentVersion: 10 }]; + throw error; + }) + .mockImplementationOnce(() => 'SECOND_RETRY'); + + const context = createContext({ mutate: requestMock }); + + const params = { + id: 'cart id', + version: 1, + actions: [{ addLineItem: {} }] + }; + + await expect(updateCart(context, params)).resolves.toBe('SECOND_RETRY'); + expect(requestMock).toHaveBeenCalledTimes(2); + expect(requestMock).toHaveBeenNthCalledWith(1, expect.objectContaining({ variables: expect.objectContaining({ version: 1 }) })); + expect(requestMock).toHaveBeenNthCalledWith(2, expect.objectContaining({ variables: expect.objectContaining({ version: 10 }) })); + }); + + it('doesnt retry if it was disabled', async () => { + const requestMock = jest.fn().mockImplementation(() => { + const error: any = new Error('Mismatch'); + error.graphQLErrors = [{ code: 'ConcurrentModification', currentVersion: 10 }]; + throw error; + }); + + const context = createContext({ mutate: requestMock }); + + const params = { + id: 'cart id', + version: 1, + actions: [{ addLineItem: {} }], + versionFallback: false + }; + + await expect(updateCart(context, params)).rejects.toThrow(/Mismatch/); + expect(requestMock).toHaveBeenCalledTimes(1); + }); + + it('doesnt retry if error was not caused by mismatch', async () => { + const requestMock = jest.fn().mockImplementation(() => { + const error: any = new Error('Some error'); + error.graphQLErrors = [{ code: 'SomeRandomErrorCode' }]; + throw error; + }); + + const context = createContext({ mutate: requestMock }); + + const params = { + id: 'cart id', + version: 1, + actions: [{ addLineItem: {} }] + }; + + await expect(updateCart(context, params)).rejects.toThrow(/Some error/); + expect(requestMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/updateCartQuantity.spec.ts b/packages/commercetools/api-client/__tests__/api/updateCartQuantity.spec.ts new file mode 100644 index 0000000000..0994c057f6 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/updateCartQuantity.spec.ts @@ -0,0 +1,54 @@ +import updateCartQuantity from '../../src/api/updateCartQuantity'; + +const cart = { + id: 1, + version: 1 +} as any; + +describe('[commercetools-api-client] updateCartQuantity', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('updates cart product quantity', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + mutate: () => ({ + actions: [ + { changeLineItemQuantity: { quantity: 2, lineItemId: 1} } + ], + id: 1, + version: 1 + }) + }, + extendQuery: (customQuery, args) => args + }; + + const product = { + id: 1, + sku: '123', + quantity: 2 + } as any; + + const response = await updateCartQuantity(context, cart, product); + + expect(response).toEqual({ + id: 1, + version: 1, + actions: [ + { + changeLineItemQuantity: { + lineItemId: 1, + quantity: 2 + } + } + ] + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/api/updateShippingDetails.spec.ts b/packages/commercetools/api-client/__tests__/api/updateShippingDetails.spec.ts new file mode 100644 index 0000000000..f2273066fb --- /dev/null +++ b/packages/commercetools/api-client/__tests__/api/updateShippingDetails.spec.ts @@ -0,0 +1,145 @@ +import updateShippingDetails from '../../src/api/updateShippingDetails'; + +const cart = { + id: 1, + version: 1 +} as any; + +describe('[commercetools-api-client] updateShippingDetails', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('updates cart shipping details', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + mutate: () => ({ + actions: [ + { + setShippingAddress: { + address: { + firstName: 'John', + lastName: 'Doe', + phone: '123', + country: 'US', + city: 'New York', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } + } + } + ], + id: 1, + version: 1 + }) + }, + extendQuery: (customQuery, args) => args + }; + + const shippingDetails = { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + contactInfo: { phone: '123' }, + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } as any; + + const response = await updateShippingDetails(context, cart, shippingDetails); + + expect(response).toEqual({ + id: 1, + version: 1, + actions: [ + { + setShippingAddress: { + address: { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + phone: '123', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } + } + } + ] + }); + }); + + it('updates cart shipping details without contact info', async () => { + const context = { + config: { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK' + }, + client: { + mutate: () => ({ + actions: [ + { + setShippingAddress: { + address: { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } + } + } + ], + id: 1, + version: 1 + }) + }, + extendQuery: (customQuery, args) => args + }; + + const shippingDetails = { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } as any; + + const response = await updateShippingDetails(context, cart, shippingDetails); + + expect(response).toEqual({ + id: 1, + version: 1, + actions: [ + { + setShippingAddress: { + address: { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } + } + } + ] + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/helpers/actions/updateShippingDetails.spec.ts b/packages/commercetools/api-client/__tests__/helpers/actions/updateShippingDetails.spec.ts new file mode 100644 index 0000000000..5475c7e9f5 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/helpers/actions/updateShippingDetails.spec.ts @@ -0,0 +1,65 @@ +import { setBillingAddressAction } from './../../../src/helpers/cart/actions'; + +describe('[commercetools-api-client] setBillingAddressAction', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates action for setting billing address', () => { + const billingDetails = { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + contactInfo: { phone: '123' }, + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } as any; + + const actionPayload = setBillingAddressAction(billingDetails); + + expect(actionPayload).toEqual({ + setBillingAddress: { + address: { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + phone: '123', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } + } + }); + }); + + it('creates action for setting billing address without contact info', () => { + const billingDetails = { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } as any; + + const actionPayload = setBillingAddressAction(billingDetails); + + expect(actionPayload).toEqual({ + setBillingAddress: { + address: { + firstName: 'John', + lastName: 'Doe', + country: 'US', + city: 'New York', + postalCode: '11-111', + streetName: 'Street 1', + streetNumber: '' + } + } + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/helpers/commercetoolsLink/linkHandlers.spec.ts b/packages/commercetools/api-client/__tests__/helpers/commercetoolsLink/linkHandlers.spec.ts new file mode 100644 index 0000000000..18756a6893 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/helpers/commercetoolsLink/linkHandlers.spec.ts @@ -0,0 +1,170 @@ +import { handleBeforeAuth, handleAfterAuth, handleRetry } from '../../../src/helpers/commercetoolsLink/linkHandlers'; + +const getSdkAuth = (scope) => ({ + anonymousFlow: jest.fn().mockImplementation(() => ({ scope, access_token: 'GUEST_TOKEN' })), + customerPasswordFlow: jest.fn().mockImplementation(() => ({ scope, access_token: 'LOGIN_TOKEN' })) +}); + +const getTokenProvider = (scope) => ({ + setTokenInfo: jest.fn().mockImplementation(() => {}), + getTokenInfo: jest.fn().mockImplementation(() => ({ scope, access_token: 'ACCESS_TOKEN' })), + invalidateTokenInfo: jest.fn().mockImplementation(() => {}) +}); + +describe('[commercetools-helpers] handleBeforeAuth', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('doesnt generate access token for guest on users related operations', async () => { + const scope = ''; + const result = await handleBeforeAuth({ + sdkAuth: getSdkAuth(scope), + tokenProvider: getTokenProvider(scope), + apolloReq: { operationName: 'customerSignMeIn' }, + currentToken: { scope } + }); + + expect(result).toMatchObject({ access_token: 'ACCESS_TOKEN' }); + }); + + it('generates access token for guest on anonymous-session allowed operations', async () => { + const scope = ''; + const result = await handleBeforeAuth({ + sdkAuth: getSdkAuth(scope), + tokenProvider: getTokenProvider(scope), + apolloReq: { operationName: 'createCart' }, + currentToken: { scope } + }); + + expect(result).toMatchObject({ access_token: 'GUEST_TOKEN' }); + }); + + it('returns current token for anonymous user', async () => { + const scope = 'anonymous_id'; + const result = await handleBeforeAuth({ + sdkAuth: getSdkAuth(scope), + tokenProvider: getTokenProvider(scope), + apolloReq: { operationName: 'customerSignMeIn' }, + currentToken: { scope } + }); + + expect(result).toMatchObject({ scope, access_token: 'ACCESS_TOKEN' }); + }); + + it('returns current token for logged in user', async () => { + const scope = 'customer_id'; + const result = await handleBeforeAuth({ + sdkAuth: getSdkAuth(scope), + tokenProvider: getTokenProvider(scope), + apolloReq: { operationName: 'customerSignMeIn' }, + currentToken: { scope } + }); + + expect(result).toMatchObject({ scope, access_token: 'ACCESS_TOKEN' }); + }); +}); + +describe('[commercetools-helpers] handleAfterAuth', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('doesnt fetch access token for non-user related operations', async () => { + const scope = ''; + const result = await handleAfterAuth({ + sdkAuth: getSdkAuth(scope), + tokenProvider: getTokenProvider(scope), + apolloReq: { operationName: 'createCart' }, + currentToken: { scope }, + response: { errors: [] } + }); + + expect(result).toMatchObject({ scope }); + }); + + it('doesnt fetch access token for logged in user', async () => { + const scope = 'customer_id'; + const result = await handleAfterAuth({ + sdkAuth: getSdkAuth(scope), + tokenProvider: getTokenProvider(scope), + apolloReq: { operationName: 'customerSignMeIn' }, + currentToken: { scope }, + response: { errors: [] } + }); + + expect(result).toMatchObject({ scope }); + }); + + it('fetches access token for anonymous session', async () => { + const scope = 'anonymous_id'; + const sdkAuth = getSdkAuth(scope); + const result = await handleAfterAuth({ + sdkAuth, + tokenProvider: getTokenProvider(scope), + apolloReq: { + operationName: 'customerSignMeIn', + variables: { draft: { email: 'EMAIL', password: 'PASSWORD' } } + }, + currentToken: { scope }, + response: { errors: [] } + }); + + expect(result).toMatchObject({ scope, access_token: 'LOGIN_TOKEN' }); + expect(sdkAuth.customerPasswordFlow).toBeCalledWith({ username: 'EMAIL', password: 'PASSWORD' }); + }); + + it('fetches access token for guest', async () => { + const scope = ''; + const sdkAuth = getSdkAuth(scope); + const result = await handleAfterAuth({ + sdkAuth, + tokenProvider: getTokenProvider(scope), + apolloReq: { + operationName: 'customerSignMeIn', + variables: { draft: { email: 'EMAIL', password: 'PASSWORD' } } + }, + currentToken: { scope }, + response: { errors: [] } + }); + + expect(result).toMatchObject({ scope, access_token: 'LOGIN_TOKEN' }); + expect(sdkAuth.customerPasswordFlow).toBeCalledWith({ username: 'EMAIL', password: 'PASSWORD' }); + }); +}); + +describe('[commercetools-helpers] handleRetry', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('defaults to false', () => { + const tokenProvider = getTokenProvider(''); + const handler = handleRetry({ tokenProvider }); + const operation = { operationName: 'any' }; + const error = { result: { message: '' } }; + + expect(handler(1, operation, error)).toBeFalsy(); + expect(tokenProvider.invalidateTokenInfo).not.toBeCalled(); + }); + + it('doesnt run more than 3 times', () => { + const tokenProvider = getTokenProvider(''); + const handler = handleRetry({ tokenProvider }); + const operation = { operationName: 'any' }; + const error = { result: { message: 'invalid_token' } }; + + expect(handler(4, operation, error)).toBeFalsy(); + expect(tokenProvider.invalidateTokenInfo).not.toBeCalled(); + }); + + it('calls "invalidateTokenInfo" on "invalid_token" error', () => { + const tokenProvider = getTokenProvider(''); + const handler = handleRetry({ tokenProvider }); + const operation = { operationName: 'any' }; + const error = { result: { message: 'invalid_token' } }; + + expect(handler(1, operation, error)).toBeTruthy(); + expect(tokenProvider.invalidateTokenInfo).toBeCalled(); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/helpers/search.spec.ts b/packages/commercetools/api-client/__tests__/helpers/search.spec.ts new file mode 100644 index 0000000000..a9285d7f68 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/helpers/search.spec.ts @@ -0,0 +1,178 @@ +import { + buildProductWhere, + buildCategoryWhere, + buildOrderWhere +} from './../../src/helpers/search'; +import { AttributeType, ProductWhereSearch } from '../../src/types/Api'; + +const settings = { + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD' +} as any; + +describe('[commercetools-api-client] search', () => { + it('returns undefined when parameters are not supported', () => { + expect(buildProductWhere(settings, null)).toBe(''); + }); + + it('returns undefined string when parameters are not supported', () => { + expect(buildCategoryWhere(settings, null)).toBe(undefined); + }); + + it('returns undefined string when parameters are not supported', () => { + expect(buildOrderWhere(null)).toBe(null); + }); + + describe('returns product search query by cat id', () => { + it('single one', () => { + expect(buildProductWhere(settings, { catId: 'cat id' })).toBe('masterData(current(categories(id in ("cat id"))))'); + }); + it('multiple', () => { + expect(buildProductWhere(settings, { catId: ['cat id', 'dog id'] })).toBe('masterData(current(categories(id in ("cat id","dog id"))))'); + }); + }); + + it('returns category search query by id', () => { + expect(buildCategoryWhere(settings, { catId: 'cat id' })).toBe('id="cat id"'); + }); + + it('returns category search query by key', () => { + expect(buildCategoryWhere(settings, { key: 'Shoes' })).toBe('key="Shoes"'); + }); + + it('returns category search query by slug', () => { + expect(buildCategoryWhere(settings, { slug: 'cat slug' })).toBe('slug(en="cat slug" or de="cat slug")'); + }); + + it('returns product search query by slug', () => { + expect(buildProductWhere(settings, { slug: 'product-slug' })).toBe('masterData(current(slug(en="product-slug" or de="product-slug")))'); + }); + + it('returns product search query by id', () => { + expect(buildProductWhere(settings, { id: 'product-id' })).toBe('id="product-id"'); + }); + + it('returns product search query by key', () => { + expect(buildProductWhere(settings, { key: 'Shoes' })).toBe('key="Shoes"'); + }); + + it('returns order search query by id', () => { + expect(buildOrderWhere({ id: 'orderid' })).toBe('id="orderid"'); + }); + + describe('using filters', () => { + it('returns empty string for empty filters', () => { + expect(buildProductWhere(settings, { filters: [] })).toEqual(''); + }); + + it(`returns product search query by ${AttributeType.STRING}`, () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.STRING, value: 'stringValue', name: 'whatever' } + ] + }; + + expect(buildProductWhere(settings, search)).toEqual('masterData(current(masterVariant(attributes(name = "whatever" and value = "stringValue"))))'); + }); + + describe(`returns product search query by ${AttributeType.DATE}`, () => { + it('when single value', () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.STRING, value: 'dateValue', name: 'whatever' } + ] + }; + + expect(buildProductWhere(settings, search)).toEqual('masterData(current(masterVariant(attributes(name = "whatever" and value = "dateValue"))))'); + }); + + it('when multiple value', () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.DATE, value: ['dateValue1', 'dateValue2'], name: 'whatever' } + ] + }; + expect(buildProductWhere(settings, search)) + .toEqual('masterData(current(masterVariant(attributes(name = "whatever" and value >= "dateValue1" and value <= "dateValue2"))))'); + }); + }); + + describe(`returns product search query by ${AttributeType.NUMBER}`, () => { + it('when single value', () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.NUMBER, value: 1, name: 'whatever' } + ] + }; + + expect(buildProductWhere(settings, search)).toEqual('masterData(current(masterVariant(attributes(name = "whatever" and value = 1))))'); + }); + it('when pair of values', () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.NUMBER, value: [100, 200], name: 'whatever' } + ] + }; + expect(buildProductWhere(settings, search)).toEqual('masterData(current(masterVariant(attributes(name = "whatever" and value >= 100 and value <= 200))))'); + }); + }); + + it(`returns product search query by ${AttributeType.ENUM}`, () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.ENUM, value: 'enumValue', name: 'whatever' } + ] + }; + + expect(buildProductWhere(settings, search)).toEqual('masterData(current(masterVariant(attributes(name = "whatever" and value(key = "enumValue")))))'); + }); + + it(`returns product search query by ${AttributeType.LOCALIZED_STRING}`, () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.LOCALIZED_STRING, value: 'locStringValue', name: 'whatever' } + ] + }; + + const { locale } = settings; + expect(buildProductWhere(settings, search)).toEqual(`masterData(current(masterVariant(attributes(name = "whatever" and value(${locale} = "locStringValue")))))`); + }); + + describe(`returns product search query by ${AttributeType.MONEY}`, () => { + it('when single value', () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.MONEY, value: 200, name: 'whatever' } + ] + }; + + const { currency } = settings; + + expect(buildProductWhere(settings, search)).toEqual(`masterData(current(masterVariant(attributes(name = "whatever" and value(centAmount = 200 and currencyCode = "${currency.toUpperCase()}")))))`); + }); + it('when pair of values', () => { + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.MONEY, value: [100, 200], name: 'whatever' } + ] + }; + + const { currency } = settings; + expect(buildProductWhere(settings, search)) + .toEqual(`masterData(current(masterVariant(attributes(name = "whatever" and value(centAmount >= 10000 and centAmount <= 20000 and currencyCode = "${currency.toUpperCase()}")))))`); + }); + }); + + it(`returns product search query by ${AttributeType.BOOLEAN}`, () => { + + const search: ProductWhereSearch = { + filters: [ + { type: AttributeType.BOOLEAN, value: true, name: 'whatever' } + ] + }; + + expect(buildProductWhere(settings, search)).toEqual('masterData(current(masterVariant(attributes(name = "whatever" and value = true))))'); + }); + }); +}); diff --git a/packages/commercetools/api-client/__tests__/helpers/setup.spec.ts b/packages/commercetools/api-client/__tests__/helpers/setup.spec.ts new file mode 100644 index 0000000000..0908a1a2f4 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/helpers/setup.spec.ts @@ -0,0 +1,15 @@ +import { createApiClient } from './../../src/index.server'; + +jest.mock('apollo-client'); +jest.mock('@commercetools/sdk-auth'); + +describe('[commercetools-api-client] setup', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates instance for direct connection', () => { + createApiClient({ api: 'api-config' } as any); + }); + +}); diff --git a/packages/commercetools/api-client/__tests__/setup.ts b/packages/commercetools/api-client/__tests__/setup.ts new file mode 100644 index 0000000000..66663c21c4 --- /dev/null +++ b/packages/commercetools/api-client/__tests__/setup.ts @@ -0,0 +1,29 @@ +import { createApiClient } from './../src/index.server'; + +jest.mock('../src/helpers/createCommerceToolsLink'); +jest.mock('../src/api/updateCart', () => jest.fn((arg) => arg)); +jest.mock('../src/api/createMyOrderFromCart', () => jest.fn((arg) => arg)); +jest.mock('apollo-client'); +jest.mock('@commercetools/sdk-auth'); +jest.mock('../src/helpers/createAccessToken', () => jest.fn()); + +export default createApiClient({ + api: {} as any, + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'UK', + cookies: { + currencyCookieName: 'test-vsf-currency', + countryCookieName: 'test-vsf-country', + localeCookieName: 'test-vsf-locale', + storeCookieName: 'test-vsf-store' + }, + auth: { + onTokenChange: jest.fn(), + onTokenRemove: jest.fn() + }, + storeService: { + changeCurrentStore: jest.fn() + } +} as any); diff --git a/packages/commercetools/api-client/api-extractor.json b/packages/commercetools/api-client/api-extractor.json new file mode 100644 index 0000000000..19fe4cc85b --- /dev/null +++ b/packages/commercetools/api-client/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../api-extractor.base.json", + "mainEntryPointFilePath": "./lib/api-extractor-data.d.ts", + "dtsRollup": { + "untrimmedFilePath": "./lib/.d.ts" + }, + "docModel": { + "apiJsonFilePath": "/core/docs/commercetools/api-reference/.api.json" + } +} diff --git a/packages/commercetools/api-client/jest.config.js b/packages/commercetools/api-client/jest.config.js new file mode 100644 index 0000000000..ee1dc3518a --- /dev/null +++ b/packages/commercetools/api-client/jest.config.js @@ -0,0 +1,12 @@ +const baseConfig = require('./../../jest.base.config'); + +module.exports = { + ...baseConfig, + transform: { + ...baseConfig.transform, + '\\.(gql|graphql)$': 'jest-transform-graphql' + }, + moduleNameMapper: { + 'api-client(.*)$': '$1' + } +}; diff --git a/packages/commercetools/api-client/package.json b/packages/commercetools/api-client/package.json new file mode 100644 index 0000000000..a111eb4a6c --- /dev/null +++ b/packages/commercetools/api-client/package.json @@ -0,0 +1,41 @@ +{ + "name": "@vue-storefront/commercetools-api", + "version": "1.3.1", + "sideEffects": false, + "server": "server/index.js", + "main": "lib/index.cjs.js", + "module": "lib/index.es.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "rimraf lib server && rollup -c", + "dev": "rollup -c -w", + "test": "cross-env APP_ENV=test jest --rootDir .", + "prepublish": "yarn build" + }, + "dependencies": { + "@apollo/client": "^3.3.21", + "@commercetools/sdk-auth": "^3.0.12", + "@vue-storefront/core": "~2.4.1", + "apollo-cache-inmemory": "^1.6.6", + "apollo-client": "^2.6.10", + "apollo-link": "^1.2.14", + "apollo-link-context": "^1.0.20", + "apollo-link-error": "^1.1.13", + "apollo-link-http": "^1.5.17", + "apollo-link-retry": "^2.2.16", + "graphql": "^15.5.1", + "graphql-tag": "^2.12.5", + "isomorphic-fetch": "^3.0.0" + }, + "devDependencies": { + "apollo-link-schema": "^1.2.5", + "graphql-tools": "^7.0.5", + "jest-transform-graphql": "^2.1.0", + "@rollup/plugin-graphql": "^1.0.0", + "rollup-plugin-typescript2": "^0.30.0", + "@rollup/plugin-node-resolve": "^13.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/commercetools/api-client/rollup.config.js b/packages/commercetools/api-client/rollup.config.js new file mode 100644 index 0000000000..dfad9062db --- /dev/null +++ b/packages/commercetools/api-client/rollup.config.js @@ -0,0 +1,38 @@ +import nodeResolve from '@rollup/plugin-node-resolve'; +import typescript from 'rollup-plugin-typescript2'; +import graphql from '@rollup/plugin-graphql'; +import pkg from './package.json'; +import { generateBaseConfig } from '../../rollup.base.config'; + +const extensions = ['.ts', '.graphql', '.js']; + +const server = { + input: 'src/index.server.ts', + output: [ + { + file: pkg.server, + format: 'cjs', + sourcemap: true + } + ], + external: [ + '@apollo/client/utilities', + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ], + plugins: [ + nodeResolve({ + extensions + }), + typescript({ + // eslint-disable-next-line global-require + typescript: require('typescript') + }), + graphql() + ] +}; + +export default [ + generateBaseConfig(pkg, true), + server +]; diff --git a/packages/commercetools/api-client/schema.graphql b/packages/commercetools/api-client/schema.graphql new file mode 100644 index 0000000000..54b5d082ea --- /dev/null +++ b/packages/commercetools/api-client/schema.graphql @@ -0,0 +1,7562 @@ +""" +Direct the client to resolve this field locally, either from the cache or local resolvers. +""" +directive @client( + """ + When true, the client will never use the cache for this value. See + https://www.apollographql.com/docs/react/essentials/local-state/#forcing-resolvers-with-clientalways-true + """ + always: Boolean +) on FIELD | FRAGMENT_DEFINITION | INLINE_FRAGMENT + +""" +Export this locally resolved field as a variable to be used in the remainder of this query. See +https://www.apollographql.com/docs/react/essentials/local-state/#using-client-fields-as-variables +""" +directive @export( + """The variable name to export this field as.""" + as: String! +) on FIELD + +""" +Specify a custom store key for this result. See +https://www.apollographql.com/docs/react/advanced/caching/#the-connection-directive +""" +directive @connection( + """Specify the store key.""" + key: String! + + """ + An array of query argument names to include in the generated custom store key. + """ + filter: [String!] +) on FIELD + +type AbsoluteDiscountValue implements CartDiscountValue & ProductDiscountValue { + money: [Money!]! + type: String! +} + +input AbsoluteDiscountValueInput { + money: [MoneyInput!]! +} + +"""A field to access the active cart.""" +interface ActiveCartInterface { + activeCart: Cart +} + +input addAttributeDefinition { + attributeDefinition: AttributeDefinitionDraft! +} + +input AddCartCustomLineItem { + shippingDetails: ItemShippingDetailsDraft + custom: CustomFieldsDraft + quantity: Long + externalTaxRate: ExternalTaxRateDraft + taxCategory: ResourceIdentifierInput + slug: String! + money: BaseMoneyInput! + name: [LocalizedStringItemInputType!]! +} + +input AddCartDiscountCode { + code: String! + validateDuplicates: Boolean = false +} + +input AddCartItemShippingAddress { + address: AddressInput! +} + +input AddCartLineItem { + shippingDetails: ItemShippingDetailsDraft + externalTotalPrice: ExternalLineItemTotalPriceDraft + externalPrice: BaseMoneyInput + externalTaxRate: ExternalTaxRateDraft + custom: CustomFieldsDraft + catalog: ReferenceInput + distributionChannel: ResourceIdentifierInput + supplyChannel: ResourceIdentifierInput + variantId: Int + quantity: Long + sku: String + productId: String +} + +input AddCartPayment { + payment: ResourceIdentifierInput! +} + +input AddCartShoppingList { + shoppingList: ResourceIdentifierInput! + supplyChannel: ResourceIdentifierInput + distributionChannel: ResourceIdentifierInput +} + +input AddCategoryAsset { + position: Int + asset: AssetDraftInput! +} + +input AddCustomerAddress { + address: AddressInput! +} + +input AddCustomerBillingAddressId { + addressId: String! +} + +input AddCustomerShippingAddressId { + addressId: String! +} + +input AddCustomerStore { + store: ResourceIdentifierInput! +} + +input AddInventoryEntryQuantity { + quantity: Long! +} + +input addLocalizedEnumValue { + attributeName: String! + value: LocalizedEnumValueDraft! +} + +input AddMyCartLineItem { + shippingDetails: ItemShippingDetailsDraft + custom: CustomFieldsDraft + catalog: ReferenceInput + distributionChannel: ResourceIdentifierInput + supplyChannel: ResourceIdentifierInput + variantId: Int + quantity: Long + sku: String + productId: String +} + +input AddOrderDelivery { + items: [DeliveryItemDraftType!] = [] + parcels: [DeliveryItemDraftType!] = [] + address: AddressInput +} + +input AddOrderItemShippingAddress { + address: AddressInput! +} + +input AddOrderParcelToDelivery { + deliveryId: String! + measurements: ParcelMeasurementsDraftType + trackingData: TrackingDataDraftType + items: [DeliveryItemDraftType!] = [] +} + +input AddOrderPayment { + payment: ResourceIdentifierInput! +} + +input AddOrderReturnInfo { + items: [ReturnItemDraftType!]! + returnDate: DateTime + returnTrackingId: String +} + +input addPlainEnumValue { + attributeName: String! + value: PlainEnumValueDraft! +} + +input AddProductAsset { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + position: Int + asset: AssetDraftInput! +} + +input AddProductExternalImage { + variantId: Int + sku: String + image: ImageInput! + staged: Boolean = true +} + +input AddProductPrice { + variantId: Int + sku: String + price: ProductPriceDataInput! + catalog: ReferenceInput + staged: Boolean = true +} + +input AddProductToCategory { + category: ResourceIdentifierInput! + orderHint: String + staged: Boolean = true +} + +input AddProductVariant { + assets: [AssetDraftInput!] = [] + attributes: [ProductAttributeInput!] = [] + images: [ImageInput!] = [] + prices: [ProductPriceDataInput!] = [] + key: String + sku: String + staged: Boolean = true +} + +"""An address represents a postal address.""" +type Address { + id: String + title: String + salutation: String + firstName: String + lastName: String + streetName: String + streetNumber: String + additionalStreetInfo: String + postalCode: String + city: String + region: String + state: String + country: Country! + company: String + department: String + building: String + apartment: String + pOBox: String + contactInfo: AddressContactInfo! + additionalAddressInfo: String + externalId: String + key: String +} + +type AddressContactInfo { + phone: String + mobile: String + email: String + fax: String +} + +input AddressInput { + id: String + title: String + salutation: String + firstName: String + lastName: String + streetName: String + streetNumber: String + additionalStreetInfo: String + postalCode: String + city: String + region: String + state: String + country: Country! + company: String + department: String + building: String + apartment: String + pOBox: String + phone: String + mobile: String + email: String + fax: String + additionalAddressInfo: String + externalId: String + key: String +} + +input AddShippingMethodShippingRate { + zone: ResourceIdentifierInput! + shippingRate: ShippingRateDraft! +} + +input AddShippingMethodZone { + zone: ResourceIdentifierInput! +} + +input AddShoppingListLineItem { + addedAt: DateTime + custom: CustomFieldsDraft + quantity: Int = 1 + variantId: Int + sku: String + productId: String +} + +input AddShoppingListTextLineItem { + addedAt: DateTime + custom: CustomFieldsDraft + quantity: Int = 1 + description: [LocalizedStringItemInputType!] + name: [LocalizedStringItemInputType!]! +} + +input AddZoneLocation { + location: ZoneLocation! +} + +enum AnonymousCartSignInMode { + """ + The anonymous cart is used as new active customer cart. No `LineItem`s get merged. + """ + UseAsNewActiveCustomerCart + + """ + `LineItem`s of the anonymous cart will be copied to the customer’s active cart that has been modified most recently. + + The `CartState` of the anonymous cart gets changed to `Merged` while the + `CartState` of the customer’s cart remains `Active`. + + `CustomLineItems` and `CustomFields` of the anonymous cart will not be copied to the customers cart. + + If a `LineItem` in the anonymous cart matches an existing line item in the + customer’s cart (same product ID and variant ID), the maximum quantity of both + LineItems is used as the new quantity. In that case `CustomFields` on the + `LineItem` of the anonymous cart will not be in the resulting `LineItem`. + """ + MergeWithExistingCustomerCart +} + +"""API Clients can be used to obtain OAuth 2 access tokens""" +type APIClientWithoutSecret { + id: String! + name: String! + scope: String! + createdAt: DateTime + lastUsedAt: Date +} + +type APIClientWithoutSecretQueryResult { + offset: Int! + count: Int! + total: Long! + results: [APIClientWithoutSecret!]! +} + +""" +API Clients can be used to obtain OAuth 2 access tokens. The secret is only +shown once in the response of creating the API Client. +""" +type APIClientWithSecret { + id: String! + name: String! + scope: String! + createdAt: DateTime + lastUsedAt: Date + secret: String! +} + +input ApplyCartDeltaToCustomLineItemShippingDetailsTargets { + customLineItemId: String! + targetsDelta: [ShippingTargetDraft!]! +} + +input ApplyCartDeltaToLineItemShippingDetailsTargets { + lineItemId: String! + targetsDelta: [ShippingTargetDraft!]! +} + +type Asset { + id: String! + key: String + sources: [AssetSource!]! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + descriptionAllLocales: [LocalizedString!] + tags: [String!]! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +type AssetDimensions { + width: Int! + height: Int! +} + +input AssetDimensionsInput { + width: Int! + height: Int! +} + +input AssetDraftInput { + key: String + name: [LocalizedStringItemInputType!]! + description: [LocalizedStringItemInputType!] + custom: CustomFieldsDraft + sources: [AssetSourceInput!] + tags: [String!] + type: ResourceIdentifierInput +} + +type AssetSource { + uri: String! + key: String + dimensions: AssetDimensions + contentType: String +} + +input AssetSourceInput { + uri: String! + key: String + dimensions: AssetDimensionsInput + contentType: String +} + +interface Attribute { + name: String! +} + +enum AttributeConstraint { + """No constraints are applied to the attribute""" + None + + """Attribute value should be different in each variant""" + Unique + + """ + A set of attributes, that have this constraint, should have different combinations in each variant + """ + CombinationUnique + + """Attribute value should be the same in all variants""" + SameForAll +} + +type AttributeDefinition { + type: AttributeDefinitionType! + name: String! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + isRequired: Boolean! + attributeConstraint: AttributeConstraint! + inputTip( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + inputHint: TextInputHint! + isSearchable: Boolean! + labelAllLocales: [LocalizedString!]! + inputTipAllLocales: [LocalizedString!] +} + +input AttributeDefinitionDraft { + type: AttributeTypeDraft! + name: String! + label: [LocalizedStringItemInputType!]! + isRequired: Boolean! + attributeConstraint: AttributeConstraint + inputTip: [LocalizedStringItemInputType!] + inputHint: TextInputHint + isSearchable: Boolean! +} + +type AttributeDefinitionResult { + limit: Int + offset: Int + total: Int! + results: [AttributeDefinition!]! +} + +""" +(https://dev.commercetools.com/http-api-projects-productTypes.html#attributetype)[https://dev.commercetools.com/http-api-projects-productTypes.html#attributetype] +""" +interface AttributeDefinitionType { + name: String! +} + +input AttributeSetElementTypeDraft { + text: SimpleAttributeTypeDraft + number: SimpleAttributeTypeDraft + money: SimpleAttributeTypeDraft + date: SimpleAttributeTypeDraft + time: SimpleAttributeTypeDraft + datetime: SimpleAttributeTypeDraft + boolean: SimpleAttributeTypeDraft + reference: ReferenceTypeDefinitionDraft + enum: EnumTypeDraft + lenum: LocalizableEnumTypeDraft + ltext: SimpleAttributeTypeDraft +} + +input AttributeSetTypeDraft { + elementType: AttributeSetElementTypeDraft! +} + +input AttributeTypeDraft { + set: AttributeSetTypeDraft + text: SimpleAttributeTypeDraft + number: SimpleAttributeTypeDraft + money: SimpleAttributeTypeDraft + date: SimpleAttributeTypeDraft + time: SimpleAttributeTypeDraft + datetime: SimpleAttributeTypeDraft + boolean: SimpleAttributeTypeDraft + reference: ReferenceTypeDefinitionDraft + enum: EnumTypeDraft + lenum: LocalizableEnumTypeDraft + ltext: SimpleAttributeTypeDraft +} + +interface BaseMoney { + type: String! + currencyCode: Currency! + centAmount: Long! + fractionDigits: Int! +} + +input BaseMoneyInput { + centPrecision: MoneyInput + highPrecision: HighPrecisionMoneyInput +} + +input BaseSearchKeywordInput { + whitespace: WhitespaceSuggestTokenizerInput + custom: CustomSuggestTokenizerInput +} + +""" +The `BigDecimal` scalar type represents signed fractional values with arbitrary precision. +""" +scalar BigDecimal + +type BooleanAttribute implements Attribute { + value: Boolean! + name: String! +} + +type BooleanAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +type BooleanField implements CustomField { + value: Boolean! + name: String! +} + +type BooleanType implements FieldType { + name: String! +} + +""" +A shopping cart holds product variants and can be ordered. Each cart either +belongs to a registered customer or is an anonymous cart. +""" +type Cart implements Versioned { + customerId: String + customer: Customer + customerEmail: String + anonymousId: String + lineItems: [LineItem!]! + customLineItems: [CustomLineItem!]! + totalPrice: Money! + taxedPrice: TaxedPrice + shippingAddress: Address + billingAddress: Address + inventoryMode: InventoryMode! + taxMode: TaxMode! + taxRoundingMode: RoundingMode! + taxCalculationMode: TaxCalculationMode! + customerGroup: CustomerGroup + customerGroupRef: Reference + country: Country + shippingInfo: ShippingInfo + discountCodes: [DiscountCodeInfo!]! + refusedGifts: [CartDiscount!]! + refusedGiftsRefs: [Reference!]! + paymentInfo: PaymentInfo + locale: Locale + shippingRateInput: ShippingRateInput + origin: CartOrigin! + storeRef: KeyReference @deprecated(reason: "beta feature") + store: Store @deprecated(reason: "beta feature") + itemShippingAddresses: [Address!]! + cartState: CartState! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + deleteDaysAfterLastModification: Int + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input CartClassificationInput { + values: [LocalizedEnumValueInput!]! +} + +type CartClassificationType implements ShippingRateInputType { + values: [ShippingRateInputLocalizedEnumValue!]! + type: String! +} + +""" +Cart discounts are recalculated every time LineItems or CustomLineItems are +added or removed from the Cart or an order is created from the cart. + +The number of active cart discounts that do not require a discount code +(isActive=true and requiresDiscountCode=false) is limited to 100. +""" +type CartDiscount implements Versioned { + cartPredicate: String! + validFrom: DateTime + validUntil: DateTime + stackingMode: StackingMode! + isActive: Boolean! + requiresDiscountCode: Boolean! + sortOrder: String! + key: String + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + descriptionAllLocales: [LocalizedString!] + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + value: CartDiscountValue! + target: CartDiscountTarget + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input CartDiscountDraft { + value: CartDiscountValueInput! + cartPredicate: String! + target: CartDiscountTargetInput + sortOrder: String! + name: [LocalizedStringItemInputType!]! + description: [LocalizedStringItemInputType!] + validFrom: DateTime + validUntil: DateTime + stackingMode: StackingMode = Stacking + requiresDiscountCode: Boolean = false + isActive: Boolean = true + custom: CustomFieldsDraft + key: String +} + +type CartDiscountQueryResult { + offset: Int! + count: Int! + total: Long! + results: [CartDiscount!]! +} + +interface CartDiscountTarget { + type: String! +} + +input CartDiscountTargetInput { + lineItems: LineItemsTargetInput + customLineItems: CustomLineItemsTargetInput + shipping: ShippingTargetInput + multiBuyLineItems: MultiBuyLineItemsTargetInput + multiBuyCustomLineItems: MultiBuyCustomLineItemsTargetInput +} + +input CartDiscountUpdateAction { + changeCartPredicate: ChangeCartDiscountCartPredicate + changeIsActive: ChangeCartDiscountIsActive + changeName: ChangeCartDiscountName + changeRequiresDiscountCode: ChangeCartDiscountRequiresDiscountCode + changeSortOrder: ChangeCartDiscountSortOrder + changeStackingMode: ChangeCartDiscountStackingMode + changeTarget: ChangeCartDiscountTarget + changeValue: ChangeCartDiscountValue + setCustomField: SetCartDiscountCustomField + setCustomType: SetCartDiscountCustomType + setDescription: SetCartDiscountDescription + setKey: SetCartDiscountKey + setValidFrom: SetCartDiscountValidFrom + setValidFromAndUntil: SetCartDiscountValidFromAndUntil + setValidUntil: SetCartDiscountValidUntil +} + +interface CartDiscountValue { + type: String! +} + +input CartDiscountValueInput { + relative: RelativeDiscountValueInput + absolute: AbsoluteDiscountValueInput + giftLineItem: GiftLineItemValueInput +} + +input CartDraft { + currency: Currency! + country: Country + inventoryMode: InventoryMode = None + custom: CustomFieldsDraft + customerEmail: String + shippingAddress: AddressInput + billingAddress: AddressInput + shippingMethod: ResourceIdentifierInput + taxMode: TaxMode = Platform + locale: Locale + deleteDaysAfterLastModification: Int + itemShippingAddresses: [AddressInput!] = [] + discountCodes: [String!] = [] + lineItems: [LineItemDraft!] = [] + customLineItems: [CustomLineItemDraft!] = [] + customerId: String + externalTaxRateForShippingMethod: ExternalTaxRateDraft + anonymousId: String + taxRoundingMode: RoundingMode = HalfEven + taxCalculationMode: TaxCalculationMode = LineItemLevel + customerGroup: ResourceIdentifierInput + shippingRateInput: ShippingRateInputDraft + origin: CartOrigin = Customer + store: ResourceIdentifierInput +} + +enum CartOrigin { + """The cart was created by the merchant on behalf of the customer""" + Merchant + + """The cart was created by the customer. This is the default value""" + Customer +} + +""" +Fields to access carts. Includes direct access to a single cart and searching for carts. +""" +interface CartQueryInterface { + cart(id: String!): Cart + carts(where: String, sort: [String!], limit: Int, offset: Int): CartQueryResult! +} + +type CartQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Cart!]! +} + +input CartScoreInput { + dummy: String +} + +type CartScoreType implements ShippingRateInputType { + type: String! +} + +enum CartState { + """The cart was ordered. No further operations on the cart are allowed.""" + Ordered + + """ + Anonymous cart whose content was merged into a customers cart on signin. No further operations on the cart are allowed. + """ + Merged + + """The cart can be updated and ordered. It is the default state.""" + Active +} + +input CartUpdateAction { + addCustomLineItem: AddCartCustomLineItem + addDiscountCode: AddCartDiscountCode + addItemShippingAddress: AddCartItemShippingAddress + addLineItem: AddCartLineItem + addPayment: AddCartPayment + addShoppingList: AddCartShoppingList + applyDeltaToCustomLineItemShippingDetailsTargets: ApplyCartDeltaToCustomLineItemShippingDetailsTargets + applyDeltaToLineItemShippingDetailsTargets: ApplyCartDeltaToLineItemShippingDetailsTargets + changeCustomLineItemMoney: ChangeCartCustomLineItemMoney + changeCustomLineItemQuantity: ChangeCartCustomLineItemQuantity + changeLineItemQuantity: ChangeCartLineItemQuantity + changeTaxCalculationMode: ChangeCartTaxCalculationMode + changeTaxMode: ChangeCartTaxMode + changeTaxRoundingMode: ChangeCartTaxRoundingMode + recalculate: RecalculateCart + removeCustomLineItem: RemoveCartCustomLineItem + removeDiscountCode: RemoveCartDiscountCode + removeItemShippingAddress: RemoveCartItemShippingAddress + removeLineItem: RemoveCartLineItem + removePayment: RemoveCartPayment + setAnonymousId: SetCartAnonymousId + setBillingAddress: SetCartBillingAddress + setCartTotalTax: SetCartTotalTax + setCountry: SetCartCountry + setCustomField: SetCartCustomField + setCustomLineItemCustomField: SetCartCustomLineItemCustomField + setCustomLineItemCustomType: SetCartCustomLineItemCustomType + setCustomLineItemShippingDetails: SetCartCustomLineItemShippingDetails + setCustomLineItemTaxAmount: SetCartCustomLineItemTaxAmount + setCustomLineItemTaxRate: SetCartCustomLineItemTaxRate + setCustomShippingMethod: SetCartCustomShippingMethod + setCustomType: SetCartCustomType + setCustomerEmail: SetCartCustomerEmail + setCustomerGroup: SetCartCustomerGroup + setCustomerId: SetCartCustomerId + setDeleteDaysAfterLastModification: SetCartDeleteDaysAfterLastModification + setLineItemCustomField: SetCartLineItemCustomField + setLineItemCustomType: SetCartLineItemCustomType + setLineItemPrice: SetCartLineItemPrice + setLineItemShippingDetails: SetCartLineItemShippingDetails + setLineItemTaxAmount: SetCartLineItemTaxAmount + setLineItemTaxRate: SetCartLineItemTaxRate + setLineItemTotalPrice: SetCartLineItemTotalPrice + setLocale: SetCartLocale + setShippingAddress: SetCartShippingAddress + setShippingMethod: SetCartShippingMethod + setShippingMethodTaxAmount: SetCartShippingMethodTaxAmount + setShippingMethodTaxRate: SetCartShippingMethodTaxRate + setShippingRateInput: SetCartShippingRateInput + updateItemShippingAddress: UpdateCartItemShippingAddress +} + +input CartValueInput { + dummy: String +} + +type CartValueType implements ShippingRateInputType { + type: String! +} + +type Category implements Versioned { + id: String! + key: String + version: Long! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + descriptionAllLocales: [LocalizedString!] + slug( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + slugAllLocales: [LocalizedString!]! + ancestorsRef: [Reference!]! + ancestors: [Category!]! + parentRef: Reference + parent: Category + orderHint: String! + externalId: String + metaTitle( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + metaKeywords( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + metaDescription( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + + """Number of a products in the category subtree.""" + productCount: Int! @deprecated(reason: "The returned number is representing only staged products. Use 'stagedProductCount' instead") + + """Number of staged products in the category subtree.""" + stagedProductCount: Int! + + """Number of direct child categories.""" + childCount: Int! + + """Direct child categories.""" + children: [Category!] + createdAt: DateTime! + lastModifiedAt: DateTime! + assets: [Asset!]! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input CategoryDraft { + key: String + name: [LocalizedStringItemInputType!]! + description: [LocalizedStringItemInputType!] + custom: CustomFieldsDraft + slug: [LocalizedStringItemInputType!]! + externalId: String + metaTitle: [LocalizedStringItemInputType!] + metaDescription: [LocalizedStringItemInputType!] + metaKeywords: [LocalizedStringItemInputType!] + orderHint: String + parent: ResourceIdentifierInput + assets: [AssetDraftInput!] = [] +} + +type CategoryOrderHint { + categoryId: String! + orderHint: String! +} + +input CategoryOrderHintInput { + uuid: String! + orderHint: String! +} + +type CategoryQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Category!]! +} + +type CategorySearch { + id: String! + key: String + version: Long! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + descriptionAllLocales: [LocalizedString!] + slug( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + slugAllLocales: [LocalizedString!]! + ancestorsRef: [Reference!]! + ancestors: [CategorySearch!]! + parentRef: Reference + parent: CategorySearch + externalId: String + productCount: Int! @deprecated(reason: "The returned number is representing only staged products. Use 'stagedProductCount' instead") + stagedProductCount: Int! + childCount: Int! + productTypeNames: [String!]! + + """Direct child categories.""" + children: [CategorySearch!]! + createdAt: DateTime! + lastModifiedAt: DateTime! + orderHint: String! + assets: [Asset!]! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +type CategorySearchResult { + offset: Int! + count: Int! + total: Int! + results: [CategorySearch!]! +} + +input CategoryUpdateAction { + addAsset: AddCategoryAsset + changeAssetName: ChangeCategoryAssetName + changeAssetOrder: ChangeCategoryAssetOrder + changeName: ChangeCategoryName + changeOrderHint: ChangeCategoryOrderHint + changeSlug: ChangeCategorySlug + changeParent: ChangeCategoryParent + removeAsset: RemoveCategoryAsset + setAssetCustomField: SetCategoryAssetCustomField + setAssetCustomType: SetCategoryAssetCustomType + setAssetDescription: SetCategoryAssetDescription + setAssetKey: SetCategoryAssetKey + setAssetSources: SetCategoryAssetSources + setAssetTags: SetCategoryAssetTags + setCustomField: SetCategoryCustomField + setCustomType: SetCategoryCustomType + setDescription: SetCategoryDescription + setKey: SetCategoryKey + setMetaDescription: SetCategoryMetaDescription + setMetaKeywords: SetCategoryMetaKeywords + setMetaTitle: SetCategoryMetaTitle + setExternalId: SetCategoryExternalId +} + +input changeAttributeName { + attributeName: String! + newAttributeName: String! +} + +input changeAttributeOrder { + attributeDefinitions: [AttributeDefinitionDraft!]! +} + +input changeAttributeOrderByName { + attributeNames: [String!]! +} + +input ChangeCartCustomLineItemMoney { + customLineItemId: String! + money: BaseMoneyInput! +} + +input ChangeCartCustomLineItemQuantity { + customLineItemId: String! + quantity: Long! +} + +input ChangeCartDiscountCartPredicate { + cartPredicate: String! +} + +input ChangeCartDiscountIsActive { + isActive: Boolean! +} + +input ChangeCartDiscountName { + name: [LocalizedStringItemInputType!]! +} + +input ChangeCartDiscountRequiresDiscountCode { + requiresDiscountCode: Boolean! +} + +input ChangeCartDiscountSortOrder { + sortOrder: String! +} + +input ChangeCartDiscountStackingMode { + stackingMode: StackingMode! +} + +input ChangeCartDiscountTarget { + target: CartDiscountTargetInput! +} + +input ChangeCartDiscountValue { + value: CartDiscountValueInput! +} + +input ChangeCartLineItemQuantity { + lineItemId: String! + quantity: Long! + externalPrice: BaseMoneyInput + externalTotalPrice: ExternalLineItemTotalPriceDraft +} + +input ChangeCartTaxCalculationMode { + taxCalculationMode: TaxCalculationMode! +} + +input ChangeCartTaxMode { + taxMode: TaxMode! +} + +input ChangeCartTaxRoundingMode { + taxRoundingMode: RoundingMode! +} + +input ChangeCategoryAssetName { + name: [LocalizedStringItemInputType!]! + assetKey: String + assetId: String +} + +input ChangeCategoryAssetOrder { + assetOrder: [String!]! +} + +input ChangeCategoryName { + name: [LocalizedStringItemInputType!]! +} + +input ChangeCategoryOrderHint { + orderHint: String! +} + +input ChangeCategoryParent { + parent: ResourceIdentifierInput! +} + +input ChangeCategorySlug { + slug: [LocalizedStringItemInputType!]! +} + +input ChangeCustomerAddress { + addressId: String! + address: AddressInput! +} + +input ChangeCustomerEmail { + email: String! +} + +input ChangeCustomerGroupName { + name: String! +} + +input changeDescription { + description: String! +} + +input ChangeDiscountCodeCartDiscounts { + cartDiscounts: [ReferenceInput!]! +} + +input ChangeDiscountCodeGroups { + groups: [String!]! +} + +input ChangeDiscountCodeIsActive { + isActive: Boolean! +} + +input changeEnumKey { + attributeName: String! + key: String! + newKey: String! +} + +input changeInputHint { + attributeName: String! + newValue: TextInputHint! +} + +input ChangeInventoryEntryQuantity { + quantity: Long! +} + +input changeIsSearchable { + attributeName: String! + isSearchable: Boolean! +} + +input changeLabel { + attributeName: String! + label: [LocalizedStringItemInputType!]! +} + +input changeLocalizedEnumValueLabel { + attributeName: String! + newValue: LocalizedEnumValueDraft! +} + +input changeLocalizedEnumValueOrder { + attributeName: String! + values: [LocalizedEnumValueDraft!]! +} + +input ChangeMyCartTaxMode { + taxMode: TaxMode! +} + +input changeName { + name: String! +} + +input ChangeOrderPaymentState { + paymentState: PaymentState! +} + +input ChangeOrderShipmentState { + shipmentState: ShipmentState! +} + +input ChangeOrderState { + orderState: OrderState! +} + +input changePlainEnumValueLabel { + attributeName: String! + newValue: PlainEnumValueDraft! +} + +input changePlainEnumValueOrder { + attributeName: String! + values: [PlainEnumValueDraft!]! +} + +input ChangeProductAssetName { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + name: [LocalizedStringItemInputType!]! + assetKey: String + assetId: String +} + +input ChangeProductAssetOrder { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + assetOrder: [String!]! +} + +input ChangeProductDiscountIsActive { + isActive: Boolean! +} + +input ChangeProductDiscountName { + name: [LocalizedStringItemInputType!]! +} + +input ChangeProductDiscountPredicate { + predicate: String! +} + +input ChangeProductDiscountSortOrder { + sortOrder: String! +} + +input ChangeProductDiscountValue { + value: ProductDiscountValueInput! +} + +input ChangeProductImageLabel { + variantId: Int + sku: String + imageUrl: String! + label: String + staged: Boolean = true +} + +input ChangeProductMasterVariant { + variantId: Int + sku: String + staged: Boolean = true +} + +input ChangeProductName { + name: [LocalizedStringItemInputType!]! + staged: Boolean = true +} + +input ChangeProductPrice { + priceId: String + variantId: Int + sku: String + price: ProductPriceDataInput! + catalog: ReferenceInput + staged: Boolean = true +} + +input ChangeProductSlug { + slug: [LocalizedStringItemInputType!]! + staged: Boolean = true +} + +input ChangeProjectSettingsCountries { + countries: [Country!]! +} + +input ChangeProjectSettingsCurrencies { + currencies: [Currency!]! +} + +input ChangeProjectSettingsLanguages { + languages: [Locale!]! +} + +input ChangeProjectSettingsMessagesConfiguration { + messagesConfiguration: MessagesConfigurationDraft! +} + +input ChangeProjectSettingsMessagesEnabled { + messagesEnabled: Boolean! +} + +input ChangeProjectSettingsName { + name: String! +} + +input ChangeShippingMethodIsDefault { + isDefault: Boolean! +} + +input ChangeShippingMethodName { + name: String! +} + +input ChangeShippingMethodTaxCategory { + taxCategory: ResourceIdentifierInput! +} + +input ChangeShoppingListLineItemQuantity { + lineItemId: String! + quantity: Int! +} + +input ChangeShoppingListLineItemsOrder { + lineItemOrder: [String!]! +} + +input ChangeShoppingListName { + name: [LocalizedStringItemInputType!]! +} + +input ChangeShoppingListTextLineItemName { + textLineItemId: String! + name: [LocalizedStringItemInputType!]! +} + +input ChangeShoppingListTextLineItemQuantity { + textLineItemId: String! + quantity: Int! +} + +input ChangeShoppingListTextLineItemsOrder { + textLineItemOrder: [String!]! +} + +input ChangeZoneName { + name: String! +} + +type Channel implements Versioned { + id: String! + version: Long! + key: String! + roles: [ChannelRole!]! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!] + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + descriptionAllLocales: [LocalizedString!] + address: Address + geoLocation: Geometry + createdAt: DateTime! + lastModifiedAt: DateTime! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + createdBy: Initiator + lastModifiedBy: Initiator +} + +type ChannelQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Channel!]! +} + +type ChannelReferenceIdentifier { + typeId: String! + id: String + key: String +} + +enum ChannelRole { + """ + Role tells that this channel can be used to track inventory entries.Channels with this role can be treated as warehouses + """ + InventorySupply + + """ + Role tells that this channel can be used to expose products to a specific + distribution channel. It can be used by the cart to select a product price. + """ + ProductDistribution + + """ + Role tells that this channel can be used to track order export activities. + """ + OrderExport + + """ + Role tells that this channel can be used to track order import activities. + """ + OrderImport + + """ + This role can be combined with some other roles (e.g. with `InventorySupply`) + to represent the fact that this particular channel is the primary/master + channel among the channels of the same type. + """ + Primary +} + +type ClassificationShippingRateInput implements ShippingRateInput { + key: String! + type: String! + labelAllLocales: [LocalizedString!]! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String +} + +input ClassificationShippingRateInputDraft { + key: String! +} + +"""[ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) country code.""" +scalar Country + +input CreateApiClient { + name: String! + scope: String! +} + +input CreateStore { + key: String! + name: [LocalizedStringItemInputType!] + languages: [Locale!] +} + +input CreateZone { + name: String! + key: String + description: String + locations: [ZoneLocation!] = [] +} + +""" +Represents a currency. Currencies are identified by their [ISO +4217](http://www.iso.org/iso/home/standards/currency_codes.htm) currency codes. +""" +scalar Currency + +""" +A customer is a person purchasing products. Carts, Orders and Reviews can be associated to a customer. +""" +type Customer implements Versioned { + customerNumber: String + email: String! + password: String! + addresses: [Address!]! + defaultShippingAddressId: String + defaultBillingAddressId: String + shippingAddressIds: [String!]! + billingAddressIds: [String!]! + isEmailVerified: Boolean! + customerGroupRef: Reference + externalId: String + key: String + firstName: String + lastName: String + middleName: String + title: String + locale: Locale + salutation: String + dateOfBirth: Date + companyName: String + vatId: String + customerGroup: CustomerGroup + defaultShippingAddress: Address + defaultBillingAddress: Address + shippingAddresses: [Address!]! + billingAddresses: [Address!]! + storesRef: [KeyReference!]! @deprecated(reason: "beta feature") + stores: [Store!]! @deprecated(reason: "beta feature") + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +"""A field to access a customer's active cart.""" +interface CustomerActiveCartInterface { + customerActiveCart(customerId: String!): Cart +} + +""" +A customer can be a member in a customer group (e.g. reseller, gold member). A +customer group can be used in price calculations with special prices being +assigned to certain customer groups. +""" +type CustomerGroup implements Versioned { + id: String! + version: Long! + name: String! + key: String + createdAt: DateTime! + lastModifiedAt: DateTime! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input CustomerGroupDraft { + groupName: String! + key: String + custom: CustomFieldsDraft +} + +type CustomerGroupQueryResult { + offset: Int! + count: Int! + total: Long! + results: [CustomerGroup!]! +} + +input CustomerGroupUpdateAction { + changeName: ChangeCustomerGroupName + setKey: SetCustomerGroupKey + setCustomType: SetCustomerGroupCustomType + setCustomField: SetCustomerGroupCustomField +} + +""" +Fields to access customer accounts. Includes direct access to a single customer and searching for customers. +""" +interface CustomerQueryInterface { + customer( + """Queries a customer with specified email token""" + emailToken: String + + """Queries a customer with specified password token""" + passwordToken: String + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Customer + customers(where: String, sort: [String!], limit: Int, offset: Int): CustomerQueryResult! +} + +type CustomerQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Customer!]! +} + +input CustomerSignInDraft { + email: String! + password: String! + anonymousCartId: String + anonymousCartSignInMode: AnonymousCartSignInMode = MergeWithExistingCustomerCart + anonymousId: String + updateProductData: Boolean = false +} + +type CustomerSignInResult { + customer: Customer! + cart: Cart +} + +input CustomerSignMeInDraft { + email: String! + password: String! + activeCartSignInMode: AnonymousCartSignInMode = MergeWithExistingCustomerCart + updateProductData: Boolean = false +} + +input CustomerSignMeUpDraft { + email: String! + password: String! + firstName: String + lastName: String + middleName: String + title: String + dateOfBirth: Date + companyName: String + vatId: String + addresses: [AddressInput!] = [] + + """ + The index of the address in the `addresses` list. The + `defaultBillingAddressId` of the customer will be set to the ID of that address. + """ + defaultBillingAddress: Int + + """ + The index of the address in the `addresses` list. The + `defaultShippingAddressId` of the customer will be set to the ID of that address. + """ + defaultShippingAddress: Int + + """ + The indices of the shipping addresses in the `addresses` list. The + `shippingAddressIds` of the `Customer` will be set to the IDs of that addresses. + """ + shippingAddresses: [Int!] = [] + + """ + The indices of the billing addresses in the `addresses` list. The + `billingAddressIds` of the customer will be set to the IDs of that addresses. + """ + billingAddresses: [Int!] = [] + custom: CustomFieldsDraft + locale: Locale + salutation: String + key: String + stores: [ResourceIdentifierInput!] +} + +input CustomerSignUpDraft { + email: String! + password: String! + firstName: String + lastName: String + middleName: String + title: String + dateOfBirth: Date + companyName: String + vatId: String + addresses: [AddressInput!] = [] + + """ + The index of the address in the `addresses` list. The + `defaultBillingAddressId` of the customer will be set to the ID of that address. + """ + defaultBillingAddress: Int + + """ + The index of the address in the `addresses` list. The + `defaultShippingAddressId` of the customer will be set to the ID of that address. + """ + defaultShippingAddress: Int + + """ + The indices of the shipping addresses in the `addresses` list. The + `shippingAddressIds` of the `Customer` will be set to the IDs of that addresses. + """ + shippingAddresses: [Int!] = [] + + """ + The indices of the billing addresses in the `addresses` list. The + `billingAddressIds` of the customer will be set to the IDs of that addresses. + """ + billingAddresses: [Int!] = [] + custom: CustomFieldsDraft + locale: Locale + salutation: String + key: String + stores: [ResourceIdentifierInput!] + customerNumber: String + anonymousCartId: String + externalId: String + customerGroup: ResourceIdentifierInput + isEmailVerified: Boolean + anonymousId: String +} + +type CustomerToken { + id: String! + customerId: String! + createdAt: DateTime! + expiresAt: DateTime! + value: String! +} + +input CustomerUpdateAction { + addAddress: AddCustomerAddress + addBillingAddressId: AddCustomerBillingAddressId + addShippingAddressId: AddCustomerShippingAddressId + addStore: AddCustomerStore + changeAddress: ChangeCustomerAddress + changeEmail: ChangeCustomerEmail + removeAddress: RemoveCustomerAddress + removeBillingAddressId: RemoveCustomerBillingAddressId + removeShippingAddressId: RemoveCustomerShippingAddressId + removeStore: RemoveCustomerStore + setCompanyName: SetCustomerCompanyName + setCustomField: SetCustomerCustomField + setCustomType: SetCustomerCustomType + setCustomerGroup: SetCustomerGroup + setKey: SetCustomerKey + setLocale: SetCustomerLocale + setCustomerNumber: SetCustomerNumber + setDateOfBirth: SetCustomerDateOfBirth + setDefaultBillingAddress: SetCustomerDefaultBillingAddress + setDefaultShippingAddress: SetCustomerDefaultShippingAddress + setExternalId: SetCustomerExternalId + setFirstName: SetCustomerFirstName + setLastName: SetCustomerLastName + setMiddleName: SetCustomerMiddleName + setSalutation: SetCustomerSalutation + setStores: SetCustomerStores + setTitle: SetCustomerTitle + setVatId: SetCustomerVatId +} + +interface CustomField { + name: String! +} + +""" +A key-value pair representing the field name and value of one single custom field. + +The value of this custom field consists of escaped JSON based on the FieldDefinition of the Type. + +Examples for `value`: + +* FieldType `String`: `"\"This is a string\""` +* FieldType `DateTimeType`: `"\"2001-09-11T14:00:00.000Z\""` +* FieldType `Number`: `"4"` +* FieldType `Set` with an elementType of `String`: `"[\"This is a string\", \"This is another string\"]"` +* FieldType `Reference`: `"{\"id\", \"b911b62d-353a-4388-93ee-8d488d9af962\", \"typeId\", \"product\"}"` +""" +input CustomFieldInput { + name: String! + + """ + The value of this custom field consists of escaped JSON based on the FieldDefinition of the Type. + + Examples for `value`: + + * FieldType `String`: `"\"This is a string\""` + * FieldType `DateTimeType`: `"\"2001-09-11T14:00:00.000Z\""` + * FieldType `Number`: `"4"` + * FieldType `Set` with an elementType of `String`: `"[\"This is a string\", \"This is another string\"]"` + * FieldType `Reference`: `"{\"id\", \"b911b62d-353a-4388-93ee-8d488d9af962\", \"typeId\", \"product\"}"` + """ + value: String! +} + +input CustomFieldsDraft { + typeId: String + typeKey: String + type: ResourceIdentifierInput + fields: [CustomFieldInput!] +} + +type CustomFieldsType { + typeRef: Reference! + type: TypeDefinition + + """ + This field contains non-typed data. For a typed alternative, have a look at `customFields`. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] + + """This field would contain type data""" + customFields: Type! + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +""" +A custom line item is a generic item that can be added to the cart but is not +bound to a product. You can use it for discounts (negative money), vouchers, +complex cart rules, additional services or fees. You control the lifecycle of this item. +""" +type CustomLineItem { + id: String! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + money: BaseMoney! + totalPrice: Money! + slug: String! + quantity: Long! + state: [ItemState!]! + taxCategory: TaxCategory + taxCategoryRef: Reference + taxRate: TaxRate + discountedPricePerQuantity: [DiscountedLineItemPriceForQuantity!]! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + shippingDetails: ItemShippingDetails + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input CustomLineItemDraft { + name: [LocalizedStringItemInputType!]! + money: BaseMoneyInput! + slug: String! + taxCategory: ReferenceInput + externalTaxRate: ExternalTaxRateDraft + quantity: Long + custom: CustomFieldsDraft + shippingDetails: ItemShippingDetailsDraft +} + +type CustomLineItemReturnItem implements ReturnItem { + type: String! + customLineItemId: String! + id: String! + quantity: Long! + comment: String + shipmentState: ReturnShipmentState! + paymentState: ReturnPaymentState! + lastModifiedAt: DateTime! + createdAt: DateTime! +} + +type CustomLineItemsTarget implements CartDiscountTarget { + predicate: String! + type: String! +} + +input CustomLineItemsTargetInput { + predicate: String! +} + +input CustomSuggestTokenizerInput { + text: String! + suggestTokenizer: BaseSearchKeywordInput +} + +"""DateTime is a scalar value that represents an ISO8601 formatted date.""" +scalar Date + +type DateAttribute implements Attribute { + value: Date! + name: String! +} + +type DateAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +type DateField implements CustomField { + value: Date! + name: String! +} + +""" +DateTime is a scalar value that represents an ISO8601 formatted date and time. +""" +scalar DateTime + +type DateTimeAttribute implements Attribute { + value: DateTime! + name: String! +} + +type DateTimeAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +type DateTimeField implements CustomField { + value: DateTime! + name: String! +} + +type DateTimeType implements FieldType { + name: String! +} + +type DateType implements FieldType { + name: String! +} + +type Delivery { + id: String! + createdAt: DateTime! + items: [DeliveryItem!]! + parcels: [Parcel!]! + address: Address +} + +type DeliveryItem { + id: String! + quantity: Long! +} + +input DeliveryItemDraftType { + id: String! + quantity: Long! +} + +type Dimensions { + width: Int! + height: Int! +} + +input DimensionsInput { + width: Int! + height: Int! +} + +""" +With discount codes it is possible to give specific cart discounts to an +eligible amount of users. They are defined by a string value which can be added +to a cart so that specific cart discounts can be applied to the cart. +""" +type DiscountCode implements Versioned { + code: String! + isActive: Boolean! + maxApplications: Long + maxApplicationsPerCustomer: Long + cartPredicate: String + applicationVersion: Long + validFrom: DateTime + validUntil: DateTime + groups: [String!]! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + cartDiscounts: [CartDiscount!]! + nameAllLocales: [LocalizedString!] + descriptionAllLocales: [LocalizedString!] + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + + """ + How many times this discount code was applied (only applications that were part of a successful checkout are considered) + """ + applicationCount: Long! + cartDiscountRefs: [Reference!]! + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input DiscountCodeDraft { + code: String! + name: [LocalizedStringItemInputType!] + description: [LocalizedStringItemInputType!] + cartDiscounts: [ReferenceInput!]! + isActive: Boolean = true + maxApplications: Long + maxApplicationsPerCustomer: Long + cartPredicate: String + custom: CustomFieldsDraft + validFrom: DateTime + validUntil: DateTime + groups: [String!] = [] +} + +type DiscountCodeInfo { + discountCodeRef: Reference! + state: DiscountCodeState + discountCode: DiscountCode +} + +type DiscountCodeQueryResult { + offset: Int! + count: Int! + total: Long! + results: [DiscountCode!]! +} + +enum DiscountCodeState { + """ + The discount code is active and none of the discounts were applied because the + discount application was stopped by one discount that has the StackingMode of + StopAfterThisDiscount defined + """ + ApplicationStoppedByPreviousDiscount + + """ + The discount code is not valid or it does not contain any valid cart + discounts. Validity is determined based on the validFrom and validUntil dates + """ + NotValid + + """ + maxApplications or maxApplicationsPerCustomer for discountCode has been reached. + """ + MaxApplicationReached + + """ + The discount code is active and it contains at least one active and valid + CartDiscount. The discount code cartPredicate matches the cart and at least + one of the contained active discount’s cart predicates matches the cart. + """ + MatchesCart + + """ + The discount code is active and it contains at least one active and valid + CartDiscount. But its cart predicate does not match the cart or none of the + contained active discount’s cart predicates match the cart + """ + DoesNotMatchCart + + """ + The discount code is not active or it does not contain any active cart discounts. + """ + NotActive +} + +input DiscountCodeUpdateAction { + changeCartDiscounts: ChangeDiscountCodeCartDiscounts + changeGroups: ChangeDiscountCodeGroups + changeIsActive: ChangeDiscountCodeIsActive + setCartPredicate: SetDiscountCodeCartPredicate + setCustomField: SetDiscountCodeCustomField + setCustomType: SetDiscountCodeCustomType + setDescription: SetDiscountCodeDescription + setMaxApplications: SetDiscountCodeMaxApplications + setMaxApplicationsPerCustomer: SetDiscountCodeMaxApplicationsPerCustomer + setName: SetDiscountCodeName + setValidFrom: SetDiscountCodeValidFrom + setValidFromAndUntil: SetDiscountCodeValidFromAndUntil + setValidUntil: SetDiscountCodeValidUntil +} + +type DiscountedLineItemPortion { + discount: CartDiscount + discountRef: Reference! + discountedAmount: BaseMoney! +} + +type DiscountedLineItemPrice { + value: BaseMoney! + includedDiscounts: [DiscountedLineItemPortion!]! +} + +type DiscountedLineItemPriceForQuantity { + quantity: Long! + discountedPrice: DiscountedLineItemPrice! +} + +type DiscountedProductPriceValue { + value: BaseMoney! + discountRef: Reference! + discount: ProductDiscount + + """ + Temporal. Will be renamed some time in the future. Please use 'discount'. + """ + discountRel: ProductDiscount @deprecated(reason: "Will be removed in the future. Please use 'discount'.") +} + +input DiscountedProductPriceValueInput { + value: BaseMoneyInput! + discount: ReferenceInput! +} + +type EnumAttribute implements Attribute { + key: String! + label: String! + name: String! +} + +type EnumAttributeDefinitionType implements AttributeDefinitionType { + values( + """ + The keys of the enum values to include. + + If neither `includeKeys` nor `excludeKeys` are provided, then all enum values are returned. + """ + includeKeys: [String!] + + """ + The keys of the enum values to exclude. + + If neither `includeKeys` nor `excludeKeys` are provided, then all enum values are returned. + """ + excludeKeys: [String!] + limit: Int + offset: Int + sort: [String!] + ): PlainEnumValueResult! + name: String! +} + +type EnumField implements CustomField { + key: String! + name: String! +} + +type EnumType implements FieldType { + values: [EnumValue!]! + name: String! +} + +input EnumTypeDraft { + values: [PlainEnumValueDraft!]! +} + +type EnumValue { + key: String! + label: String! +} + +type ExternalDiscountValue implements ProductDiscountValue { + type: String! +} + +input ExternalDiscountValueInput { + dummy: String +} + +input ExternalLineItemTotalPriceDraft { + price: BaseMoneyInput! + totalPrice: MoneyInput! +} + +type ExternalOAuth { + url: String! + authorizationHeader: String! +} + +input ExternalOAuthDraft { + url: String! + authorizationHeader: String! +} + +input ExternalTaxAmountDraft { + totalGross: MoneyInput! + taxRate: ExternalTaxRateDraft! +} + +input ExternalTaxRateDraft { + name: String! + amount: Float! + country: Country! + state: String + subRates: [SubRateDraft!] = [] + includedInPrice: Boolean = false +} + +""" +Field definitions describe custom fields and allow you to define some meta-information associated with the field. +""" +type FieldDefinition { + name: String! + required: Boolean! + inputHint: TextInputHint! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + labelAllLocales: [LocalizedString!]! + type: FieldType! +} + +interface FieldType { + name: String! +} + +interface Geometry { + type: String! +} + +type GiftLineItemValue implements CartDiscountValue { + type: String! + variantId: Int! + productRef: ProductReferenceIdentifier! + distributionChannelRef: ChannelReferenceIdentifier + supplyChannelRef: ChannelReferenceIdentifier +} + +input GiftLineItemValueInput { + product: ResourceIdentifierInput! + variantId: Int! + distributionChannel: ResourceIdentifierInput + supplyChannel: ResourceIdentifierInput +} + +type HighPrecisionMoney implements BaseMoney { + type: String! + currencyCode: Currency! + preciseAmount: Long! + centAmount: Long! + fractionDigits: Int! +} + +input HighPrecisionMoneyInput { + currencyCode: Currency! + preciseAmount: Long! + fractionDigits: Int! + centAmount: Long +} + +type Image { + url: String! + dimensions: Dimensions! + label: String +} + +input ImageInput { + url: String! + label: String + dimensions: DimensionsInput! +} + +input ImportOrderCustomLineItemState { + customLineItemId: String! + state: [ItemStateDraftType!]! +} + +input ImportOrderLineItemState { + lineItemId: String! + state: [ItemStateDraftType!]! +} + +type Initiator { + isPlatformClient: Boolean + user: Reference + externalUserId: String + customer: Reference + anonymousId: String + clientId: String +} + +type InStore implements CartQueryInterface & CustomerActiveCartInterface & OrderQueryInterface & CustomerQueryInterface & ShippingMethodsByCartInterface & MeFieldInterface { + """ + This field can only be used with an access token created with the password flow or with an anonymous session. + + It gives access to the data that is specific to the customer or the anonymous session linked to the access token. + """ + me: InStoreMe! + shippingMethodsByCart(id: String!): [ShippingMethod!]! + customer( + """Queries a customer with specified email token""" + emailToken: String + + """Queries a customer with specified password token""" + passwordToken: String + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Customer + customers(where: String, sort: [String!], limit: Int, offset: Int): CustomerQueryResult! + cart(id: String!): Cart + carts(where: String, sort: [String!], limit: Int, offset: Int): CartQueryResult! + customerActiveCart(customerId: String!): Cart + order( + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + orders(where: String, sort: [String!], limit: Int, offset: Int): OrderQueryResult! +} + +type InStoreMe implements MeQueryInterface { + customer: Customer + cart(id: String!): Cart + carts(where: String, sort: [String!], limit: Int, offset: Int): CartQueryResult! + activeCart: Cart + order( + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + orders(where: String, sort: [String!], limit: Int, offset: Int): OrderQueryResult! + shoppingList( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShoppingList + shoppingLists(where: String, sort: [String!], limit: Int, offset: Int): ShoppingListQueryResult! +} + +type InterfaceInteractionsRaw { + typeRef: Reference! + type: TypeDefinition + fields( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!]! +} + +type InterfaceInteractionsRawResult { + limit: Int + offset: Int + total: Int! + results: [InterfaceInteractionsRaw!]! +} + +""" +Inventory allows you to track stock quantity per SKU and optionally per supply channel +""" +type InventoryEntry implements Versioned { + sku: String! + supplyChannel: Reference + quantityOnStock: Long! + availableQuantity: Long! + restockableInDays: Int + expectedDelivery: DateTime + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input InventoryEntryDraft { + sku: String! + quantityOnStock: Long + restockableInDays: Int + expectedDelivery: DateTime + supplyChannel: ResourceIdentifierInput + custom: CustomFieldsDraft +} + +type InventoryEntryQueryResult { + offset: Int! + count: Int! + total: Long! + results: [InventoryEntry!]! +} + +input InventoryEntryUpdateAction { + addQuantity: AddInventoryEntryQuantity + changeQuantity: ChangeInventoryEntryQuantity + removeQuantity: RemoveInventoryEntryQuantity + setRestockableInDays: SetInventoryEntryRestockableInDays + setExpectedDelivery: SetInventoryEntryExpectedDelivery + setSupplyChannel: SetInventoryEntrySupplyChannel + setCustomType: SetInventoryEntryCustomType + setCustomField: SetInventoryEntryCustomField +} + +enum InventoryMode { + """ + Adding items to cart and ordering is independent of inventory. No inventory checks or modifications. + This is the default mode for a new cart. + """ + None + + """ + Creating an order will fail with an OutOfStock error if an unavailable line item exists. Line items in the cart + are only reserved for the duration of the ordering transaction. + """ + ReserveOnOrder + + """ + Orders are tracked on inventory. That means, ordering a LineItem will decrement the available quantity on the + respective InventoryEntry. Creating an order will succeed even if the line item’s available quantity is zero or + negative. But creating an order will fail with an OutOfStock error if no matching inventory entry exists for a + line item. + """ + TrackOnly +} + +type iOSUserType implements Type { + typeRef: Reference! + type: TypeDefinition! + apnsToken: StringField + myStore: ReferenceField +} + +type ItemShippingDetails { + targets: [ItemShippingTarget!]! + valid: Boolean! +} + +input ItemShippingDetailsDraft { + targets: [ShippingTargetDraft!]! +} + +input ItemShippingDetailsDraftType { + targets: [ShippingTargetDraftType!]! +} + +type ItemShippingTarget { + addressKey: String! + quantity: Long! +} + +type ItemState { + quantity: Long! + stateRef: Reference! + state: State +} + +input ItemStateDraftType { + quantity: Long! + state: ReferenceInput! +} + +"""Raw JSON value""" +scalar Json + +type KeyReference { + typeId: String! + key: String! +} + +"""A key that references a resource.""" +scalar KeyReferenceInput + +""" +A line item is a snapshot of a product variant at the time it was added to the cart. + +Since a product variant may change at any time, the ProductVariant data is copied into the field variant. +The relation to the Product is kept but the line item will not automatically update if the product variant changes. +On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. +It can be used to link to the product. If the product has been deleted, the line item remains but refers to a +non-existent product and the productSlug is left empty. + +Please also note that creating an order is impossible if the product or product +variant a line item relates to has been deleted. +""" +type LineItem { + id: String! + productId: String! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + productSlug( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + productType: ProductTypeDefinition + productTypeRef: Reference + variant: ProductVariant + price: ProductPrice! + taxedPrice: TaxedItemPrice + totalPrice: Money + quantity: Long! + state: [ItemState!]! + taxRate: TaxRate + supplyChannel: Channel + supplyChannelRef: Reference + distributionChannel: Channel + distributionChannelRef: Reference + discountedPricePerQuantity: [DiscountedLineItemPriceForQuantity!]! + lineItemMode: LineItemMode! + priceMode: LineItemPriceMode! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + shippingDetails: ItemShippingDetails + inventoryMode: ItemShippingDetails + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input LineItemDraft { + productId: String + sku: String + quantity: Long + variantId: Int + supplyChannel: ResourceIdentifierInput + distributionChannel: ResourceIdentifierInput + custom: CustomFieldsDraft + shippingDetails: ItemShippingDetailsDraft + externalTaxRate: ExternalTaxRateDraft + externalPrice: BaseMoneyInput + externalTotalPrice: ExternalLineItemTotalPriceDraft +} + +enum LineItemMode { + """ + The line item was added automatically, because a discount has added a free gift to the cart. + The quantity can not be increased, and it won’t be merged when the same product variant is added. + If the gift is removed, an entry is added to the "refusedGifts" array and the discount won’t be applied again + to the cart. The price can not be changed externally. + All other updates, such as the ones related to custom fields, can be used. + """ + GiftLineItem + + """ + The line item was added during cart creation or with the update action addLineItem. Its quantity can be + changed without restrictions. + """ + Standard +} + +enum LineItemPriceMode { + """ + The price is selected form the product variant. This is the default mode. + """ + Platform + + """ + The line item price was set externally. Cart discounts can apply to line items + with this price mode. All update actions that change the quantity of a line + item with this price mode require the externalPrice field to be given. + """ + ExternalPrice + + """The line item price with the total was set externally.""" + ExternalTotal +} + +type LineItemReturnItem implements ReturnItem { + type: String! + lineItemId: String! + id: String! + quantity: Long! + comment: String + shipmentState: ReturnShipmentState! + paymentState: ReturnPaymentState! + lastModifiedAt: DateTime! + createdAt: DateTime! +} + +type LineItemsTarget implements CartDiscountTarget { + predicate: String! + type: String! +} + +input LineItemsTargetInput { + predicate: String! +} + +"""Locale is a scalar value represented as a string language tag.""" +scalar Locale + +type LocalizableEnumAttributeDefinitionType implements AttributeDefinitionType { + values( + """ + The keys of the enum values to include. + + If neither `includeKeys` nor `excludeKeys` are provided, then all enum values are returned. + """ + includeKeys: [String!] + + """ + The keys of the enum values to exclude. + + If neither `includeKeys` nor `excludeKeys` are provided, then all enum values are returned. + """ + excludeKeys: [String!] + limit: Int + offset: Int + sort: [String!] + ): LocalizableEnumValueTypeResult! + name: String! +} + +input LocalizableEnumTypeDraft { + values: [LocalizedEnumValueDraft!]! +} + +type LocalizableEnumValueType { + key: String! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + labelAllLocales: [LocalizedString!]! +} + +type LocalizableEnumValueTypeResult { + limit: Int + offset: Int + total: Int! + results: [LocalizableEnumValueType!]! +} + +type LocalizableTextAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +type LocalizedEnumAttribute implements Attribute { + key: String! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale! + ): String + name: String! +} + +type LocalizedEnumField implements CustomField { + key: String! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale! + ): String + name: String! +} + +type LocalizedEnumType implements FieldType { + values: [LocalizedEnumValue!]! + name: String! +} + +type LocalizedEnumValue { + key: String! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + labelAllLocales: [LocalizedString!]! +} + +input LocalizedEnumValueDraft { + key: String! + label: [LocalizedStringItemInputType!]! +} + +input LocalizedEnumValueInput { + key: String! + label: [LocalizedStringItemInputType!]! +} + +type LocalizedString { + locale: Locale! + value: String! +} + +type LocalizedStringAttribute implements Attribute { + value( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale! + ): String + name: String! +} + +type LocalizedStringField implements CustomField { + value( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale! + ): String + name: String! +} + +input LocalizedStringItemInputType { + locale: Locale! + value: String! +} + +type LocalizedStringType implements FieldType { + name: String! +} + +input LocalizedText { + text: String! + locale: Locale! +} + +type Location { + country: Country! + state: String +} + +""" +The `Long` scalar type represents non-fractional signed whole numeric values. +Long can represent values between -(2^63) and 2^63 - 1. +""" +scalar Long + +"""Sunrise Product Data Set Structure""" +type mainProductType implements ProductType { + productTypeId: String! + creationDate: DateTimeAttribute + articleNumberManufacturer: StringAttribute + articleNumberMax: StringAttribute + matrixId: StringAttribute + baseId: StringAttribute + designer: EnumAttribute + madeInItaly: EnumAttribute + completeTheLook: [StringAttribute!] + commonSize: EnumAttribute + size: StringAttribute + color: LocalizedEnumAttribute + colorFreeDefinition: LocalizedStringAttribute + details: [LocalizedStringAttribute!] + style: EnumAttribute + gender: EnumAttribute + season: StringAttribute + isOnStock: BooleanAttribute + isLook: BooleanAttribute + lookProducts: [StringAttribute!] + seasonNew: StringAttribute + sapExternalId: StringAttribute +} + +type Me implements MeQueryInterface { + customer: Customer + cart(id: String!): Cart + carts(where: String, sort: [String!], limit: Int, offset: Int): CartQueryResult! + activeCart: Cart + order( + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + orders(where: String, sort: [String!], limit: Int, offset: Int): OrderQueryResult! + shoppingList( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShoppingList + shoppingLists(where: String, sort: [String!], limit: Int, offset: Int): ShoppingListQueryResult! +} + +""" +The me field gives access to the data that is specific to the customer or anonymous session linked to the access token. +""" +interface MeFieldInterface { + me: MeQueryInterface! +} + +interface MeQueryInterface { + cart(id: String!): Cart + carts(where: String, sort: [String!], limit: Int, offset: Int): CartQueryResult! + activeCart: Cart + order( + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + orders(where: String, sort: [String!], limit: Int, offset: Int): OrderQueryResult! + shoppingList( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShoppingList + shoppingLists(where: String, sort: [String!], limit: Int, offset: Int): ShoppingListQueryResult! +} + +type MessagesConfiguration { + enabled: Boolean! + deleteDaysAfterCreation: Int +} + +input MessagesConfigurationDraft { + enabled: Boolean! + deleteDaysAfterCreation: Int! +} + +type Money implements BaseMoney { + type: String! + currencyCode: Currency! + centAmount: Long! + + """ + For the `Money` it equals to the default number of fraction digits used with the currency. + """ + fractionDigits: Int! +} + +type MoneyAttribute implements Attribute { + centAmount: Long! + currencyCode: Currency! + name: String! +} + +type MoneyAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +input MoneyDraft { + currencyCode: Currency! + centAmount: Long! +} + +type MoneyField implements CustomField { + centAmount: Long! + currencyCode: Currency! + name: String! +} + +input MoneyInput { + currencyCode: Currency! + centAmount: Long! +} + +type MoneyType implements FieldType { + name: String! +} + +input MoveProductImageToPosition { + variantId: Int + sku: String + imageUrl: String! + position: Int! + staged: Boolean +} + +type MultiBuyCustomLineItemsTarget implements CartDiscountTarget { + predicate: String! + triggerQuantity: Long! + discountedQuantity: Long! + maxOccurrence: Int + selectionMode: SelectionMode! + type: String! +} + +input MultiBuyCustomLineItemsTargetInput { + predicate: String! + triggerQuantity: Long! + discountedQuantity: Long! + maxOccurrence: Int + selectionMode: SelectionMode +} + +type MultiBuyLineItemsTarget implements CartDiscountTarget { + predicate: String! + triggerQuantity: Long! + discountedQuantity: Long! + maxOccurrence: Int + selectionMode: SelectionMode! + type: String! +} + +input MultiBuyLineItemsTargetInput { + predicate: String! + triggerQuantity: Long! + discountedQuantity: Long! + maxOccurrence: Int + selectionMode: SelectionMode +} + +type Mutation { + createCustomerGroup(draft: CustomerGroupDraft!): CustomerGroup + updateCustomerGroup( + version: Long! + actions: [CustomerGroupUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): CustomerGroup + deleteCustomerGroup( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): CustomerGroup + createCategory(draft: CategoryDraft!): Category + updateCategory( + version: Long! + actions: [CategoryUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Category + deleteCategory( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Category + createProductType(draft: ProductTypeDraft!): ProductTypeDefinition + updateProductType( + version: Long! + actions: [ProductTypeUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ProductTypeDefinition + deleteProductType( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ProductTypeDefinition + createShippingMethod(draft: ShippingMethodDraft!): ShippingMethod + updateShippingMethod( + version: Long! + actions: [ShippingMethodUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShippingMethod + deleteShippingMethod( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShippingMethod + createZone(draft: CreateZone!): Zone + updateZone( + version: Long! + actions: [ZoneUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Zone + deleteZone( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Zone + createTaxCategory(draft: TaxCategoryDraft!): TaxCategory + updateTaxCategory( + version: Long! + actions: [TaxCategoryUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): TaxCategory + deleteTaxCategory( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): TaxCategory + createDiscountCode(draft: DiscountCodeDraft!): DiscountCode + updateDiscountCode(id: String!, version: Long!, actions: [DiscountCodeUpdateAction!]!): DiscountCode + deleteDiscountCode(id: String!, version: Long!): DiscountCode + createCartDiscount(draft: CartDiscountDraft!): CartDiscount + updateCartDiscount( + version: Long! + actions: [CartDiscountUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): CartDiscount + deleteCartDiscount( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): CartDiscount + createProductDiscount(draft: ProductDiscountDraft!): ProductDiscount + updateProductDiscount( + version: Long! + actions: [ProductDiscountUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ProductDiscount + deleteProductDiscount( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ProductDiscount + createProduct(draft: ProductDraft!): Product + updateProduct( + version: Long! + actions: [ProductUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Product + deleteProduct( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Product + + """ + Creates a customer. If an anonymous cart is given then the cart is assigned to + the created customer and the version number of the Cart will increase. If the + id of an anonymous session is given, all carts and orders will be assigned to + the created customer. + """ + customerSignUp( + draft: CustomerSignUpDraft! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): CustomerSignInResult! + + """ + Retrieves the authenticated customer (a customer that matches the given email/password pair). + + There may be carts and orders created before the sign in that should be + assigned to the customer account. With the `anonymousCartId`, a single + anonymous cart can be assigned. With the `anonymousId`, all orders and carts + that have this `anonymousId` set will be assigned to the customer. + If both `anonymousCartId` and `anonymousId` are given, the anonymous cart must have the `anonymousId`. + + Additionally, there might also exist one or more active customer carts from an + earlier session. On customer sign in there are several ways how to proceed + with this cart and the cart referenced by the `anonymousCartId`. + + * If the customer does not have a cart yet, the anonymous cart becomes the customer's cart. + * If the customer already has one or more carts, the content of the anonymous + cart will be copied to the customer's active cart that has been modified most recently. + + In this case the `CartState` of the anonymous cart gets changed to `Merged` + while the customer's cart remains the `Active` cart. + + If a `LineItem` in the anonymous cart matches an existing line item, or a + `CustomLineItem` matches an existing custom line item in the customer's cart, + the maximum quantity of both line items is used as the new quantity. + + `ItemShippingDetails` are copied from the item with the highest quantity. + + If `itemShippingAddresses` are different in the two carts, the resulting cart + contains the addresses of both the customer cart and the anonymous cart. + + Note, that it is not possible to merge carts that differ in their currency (set during creation of the cart). + + If a cart is is returned as part of the `CustomerSignInResult`, it has been + recalculated (it will have up-to-date prices, taxes and discounts, and invalid + line items have been removed). + """ + customerSignIn( + draft: CustomerSignInDraft! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): CustomerSignInResult! + updateCustomer( + version: Long! + actions: [CustomerUpdateAction!]! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Customer + deleteCustomer( + version: Long! + personalDataErasure: Boolean = false + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Customer + customerChangePassword( + id: String! + version: Long! + currentPassword: String! + newPassword: String! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + + """ + The following workflow can be used to reset the customer’s password: + + 1. Create a password reset token and send it embedded in a link to the customer. + 2. When the customer clicks on the link, you may optionally retrieve customer by password token. + 3. When the customer entered new password, use reset customer’s password to reset the password. + """ + customerResetPassword( + version: Long + tokenValue: String! + newPassword: String! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + + """Verifies customer's email using a token.""" + customerConfirmEmail( + version: Long + tokenValue: String! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + + """ + The token value is used to reset the password of the customer with the given + email. The token is valid only for 10 minutes. + """ + customerCreatePasswordResetToken( + email: String! + + """The validity of the created token in minutes.""" + ttlMinutes: Int + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): CustomerToken + customerCreateEmailVerificationToken( + id: String! + version: Long + + """The validity of the created token in minutes.""" + ttlMinutes: Int! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): CustomerToken! + + """ + If used with an access token for Anonymous Sessions, all orders and carts + belonging to the anonymousId will be assigned to the newly created customer. + """ + customerSignMeUp( + draft: CustomerSignMeUpDraft! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): CustomerSignInResult! + + """ + Retrieves the authenticated customer (a customer that matches the given email/password pair). + + If used with an access token for Anonymous Sessions, all orders and carts + belonging to the `anonymousId` will be assigned to the newly created customer. + + * If the customer does not have a cart yet, the anonymous cart that was + modified most recently becomes the customer's cart. + * If the customer already has a cart, the most recently modified anonymous + cart will be handled according to the `AnonymousCartSignInMode`. + + If a cart is is returned as part of the `CustomerSignInResult`, it has been + recalculated (it will have up-to-date prices, taxes and discounts, and invalid + line items have been removed). + """ + customerSignMeIn( + draft: CustomerSignMeInDraft! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): CustomerSignInResult! + updateMyCustomer( + version: Long! + actions: [MyCustomerUpdateAction!]! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + deleteMyCustomer( + version: Long! + personalDataErasure: Boolean = false + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + customerChangeMyPassword( + version: Long! + currentPassword: String! + newPassword: String! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + customerConfirmMyEmail( + tokenValue: String! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + customerResetMyPassword( + tokenValue: String! + newPassword: String! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Customer + createInventoryEntry(draft: InventoryEntryDraft!): InventoryEntry + updateInventoryEntry(id: String!, version: Long!, actions: [InventoryEntryUpdateAction!]!): InventoryEntry + deleteInventoryEntry(id: String!, version: Long!): InventoryEntry + createCart( + draft: CartDraft! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Cart + updateCart( + id: String! + version: Long! + actions: [CartUpdateAction!]! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Cart + deleteCart( + id: String! + version: Long! + personalDataErasure: Boolean = false + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Cart + replicateCart(reference: ReferenceInput!): Cart + createMyCart( + draft: MyCartDraft! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Cart + updateMyCart( + id: String! + version: Long! + actions: [MyCartUpdateAction!]! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Cart + deleteMyCart( + id: String! + version: Long! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Cart + createOrderFromCart( + draft: OrderCartCommand! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Order + updateOrder( + version: Long! + actions: [OrderUpdateAction!]! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + deleteOrder( + version: Long! + personalDataErasure: Boolean = false + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + createMyOrderFromCart( + draft: OrderMyCartCommand! + + """ + Beta feature. The mutation is only performed if the resource is part of the + store. Can be used with store-specific OAuth permissions. + """ + storeKey: KeyReferenceInput + ): Order + createShoppingList(draft: ShoppingListDraft!): ShoppingList + updateShoppingList( + version: Long! + actions: [ShoppingListUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShoppingList + deleteShoppingList( + version: Long! + personalDataErasure: Boolean = false + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShoppingList + createMyShoppingList(draft: MyShoppingListDraft!): ShoppingList + updateMyShoppingList(id: String!, version: Long!, actions: [MyShoppingListUpdateAction!]!): ShoppingList + deleteMyShoppingList(id: String!, version: Long!): ShoppingList + updateProject(version: Long!, actions: [ProjectSettingsUpdateAction!]!): ProjectProjection + createStore(draft: CreateStore!): Store @deprecated(reason: "beta feature") + updateStore( + version: Long! + actions: [StoreUpdateAction!]! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Store @deprecated(reason: "beta feature") + deleteStore( + version: Long! + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Store @deprecated(reason: "beta feature") + createApiClient(draft: CreateApiClient!): APIClientWithSecret + deleteApiClient(id: String!): APIClientWithoutSecret +} + +input MyCartDraft { + currency: Currency! + country: Country + inventoryMode: InventoryMode = None + custom: CustomFieldsDraft + customerEmail: String + shippingAddress: AddressInput + billingAddress: AddressInput + shippingMethod: ResourceIdentifierInput + taxMode: TaxMode = Platform + locale: Locale + deleteDaysAfterLastModification: Int + itemShippingAddresses: [AddressInput!] = [] + discountCodes: [String!] = [] + lineItems: [MyLineItemDraft!] = [] +} + +input MyCartUpdateAction { + addDiscountCode: AddCartDiscountCode + addItemShippingAddress: AddCartItemShippingAddress + addLineItem: AddMyCartLineItem + addPayment: AddCartPayment + addShoppingList: AddCartShoppingList + applyDeltaToLineItemShippingDetailsTargets: ApplyCartDeltaToLineItemShippingDetailsTargets + changeLineItemQuantity: ChangeCartLineItemQuantity + changeTaxMode: ChangeMyCartTaxMode + recalculate: RecalculateCart + removeDiscountCode: RemoveCartDiscountCode + removeItemShippingAddress: RemoveCartItemShippingAddress + removeLineItem: RemoveCartLineItem + removePayment: RemoveCartPayment + setBillingAddress: SetCartBillingAddress + setCountry: SetCartCountry + setCustomField: SetCartCustomField + setCustomType: SetCartCustomType + setCustomerEmail: SetCartCustomerEmail + setDeleteDaysAfterLastModification: SetCartDeleteDaysAfterLastModification + setLineItemCustomField: SetCartLineItemCustomField + setLineItemCustomType: SetCartLineItemCustomType + setLineItemShippingDetails: SetCartLineItemShippingDetails + setLocale: SetCartLocale + setShippingMethod: SetMyCartShippingMethod + setShippingAddress: SetCartShippingAddress + updateItemShippingAddress: UpdateCartItemShippingAddress +} + +input MyCustomerUpdateAction { + addAddress: AddCustomerAddress + addBillingAddressId: AddCustomerBillingAddressId + addShippingAddressId: AddCustomerShippingAddressId + changeAddress: ChangeCustomerAddress + changeEmail: ChangeCustomerEmail + removeAddress: RemoveCustomerAddress + removeBillingAddressId: RemoveCustomerBillingAddressId + removeShippingAddressId: RemoveCustomerShippingAddressId + setCompanyName: SetCustomerCompanyName + setCustomField: SetCustomerCustomField + setCustomType: SetCustomerCustomType + setLocale: SetCustomerLocale + setDateOfBirth: SetCustomerDateOfBirth + setDefaultBillingAddress: SetCustomerDefaultBillingAddress + setDefaultShippingAddress: SetCustomerDefaultShippingAddress + setFirstName: SetCustomerFirstName + setLastName: SetCustomerLastName + setMiddleName: SetCustomerMiddleName + setSalutation: SetCustomerSalutation + setTitle: SetCustomerTitle + setVatId: SetCustomerVatId +} + +input MyLineItemDraft { + productId: String + sku: String + quantity: Long + variantId: Int + supplyChannel: ResourceIdentifierInput + distributionChannel: ResourceIdentifierInput + custom: CustomFieldsDraft + shippingDetails: ItemShippingDetailsDraft +} + +input MyShoppingListDraft { + name: [LocalizedStringItemInputType!]! + description: [LocalizedStringItemInputType!] + lineItems: [ShoppingListLineItemDraft!] = [] + textLineItems: [TextLineItemDraft!] = [] + custom: CustomFieldsDraft + deleteDaysAfterLastModification: Int +} + +input MyShoppingListUpdateAction { + addLineItem: AddShoppingListLineItem + addTextLineItem: AddShoppingListTextLineItem + changeLineItemQuantity: ChangeShoppingListLineItemQuantity + changeLineItemsOrder: ChangeShoppingListLineItemsOrder + changeName: ChangeShoppingListName + changeTextLineItemName: ChangeShoppingListTextLineItemName + changeTextLineItemQuantity: ChangeShoppingListTextLineItemQuantity + changeTextLineItemsOrder: ChangeShoppingListTextLineItemsOrder + removeLineItem: RemoveShoppingListLineItem + removeTextLineItem: RemoveShoppingListTextLineItem + setCustomField: SetShoppingListCustomField + setCustomType: SetShoppingListCustomType + setDeleteDaysAfterLastModification: SetShoppingListDeleteDaysAfterLastModification + setDescription: SetShoppingListDescription + setLineItemCustomField: SetShoppingListLineItemCustomField + setLineItemCustomType: SetShoppingListLineItemCustomType + setTextLineItemCustomField: SetShoppingListTextLineItemCustomField + setTextLineItemCustomType: SetShoppingListTextLineItemCustomType + setTextLineItemDescription: SetShoppingListTextLineItemDescription +} + +type NestedAttributeDefinitionType implements AttributeDefinitionType { + typeReference: Reference! + name: String! +} + +type NumberAttribute implements Attribute { + value: BigDecimal! + name: String! +} + +type NumberAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +type NumberField implements CustomField { + value: BigDecimal! + name: String! +} + +type NumberType implements FieldType { + name: String! +} + +""" +An order can be created from a cart, usually after a checkout process has been completed. +[documentation](https://docs.commercetools.com/http-api-projects-orders.html) +""" +type Order implements Versioned { + customerId: String + customer: Customer + customerEmail: String + anonymousId: String + lineItems: [LineItem!]! + customLineItems: [CustomLineItem!]! + totalPrice: Money! + taxedPrice: TaxedPrice + shippingAddress: Address + billingAddress: Address + inventoryMode: InventoryMode! + taxMode: TaxMode! + taxRoundingMode: RoundingMode! + taxCalculationMode: TaxCalculationMode! + customerGroup: CustomerGroup + customerGroupRef: Reference + country: Country + shippingInfo: ShippingInfo + discountCodes: [DiscountCodeInfo!]! + refusedGifts: [CartDiscount!]! + refusedGiftsRefs: [Reference!]! + paymentInfo: PaymentInfo + locale: Locale + shippingRateInput: ShippingRateInput + origin: CartOrigin! + storeRef: KeyReference @deprecated(reason: "beta feature") + store: Store @deprecated(reason: "beta feature") + itemShippingAddresses: [Address!]! + completedAt: DateTime + orderNumber: String + orderState: OrderState! + stateRef: Reference + state: State + shipmentState: ShipmentState + paymentState: PaymentState + syncInfo: [SyncInfo!]! + returnInfo: [ReturnInfo!]! + lastMessageSequenceNumber: Long! + cartRef: Reference + cart: Cart + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input OrderCartCommand { + id: String! + version: Long! + paymentState: PaymentState + orderState: OrderState + state: ReferenceInput + shipmentState: ShipmentState + orderNumber: String +} + +input OrderMyCartCommand { + id: String! + version: Long! +} + +""" +Fields to access orders. Includes direct access to a single order and searching for orders. +""" +interface OrderQueryInterface { + order( + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + orders(where: String, sort: [String!], limit: Int, offset: Int): OrderQueryResult! +} + +type OrderQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Order!]! +} + +enum OrderState { + Confirmed + Cancelled + Complete + Open +} + +input OrderUpdateAction { + addDelivery: AddOrderDelivery + addItemShippingAddress: AddOrderItemShippingAddress + addParcelToDelivery: AddOrderParcelToDelivery + addPayment: AddOrderPayment + addReturnInfo: AddOrderReturnInfo + changeOrderState: ChangeOrderState + changePaymentState: ChangeOrderPaymentState + changeShipmentState: ChangeOrderShipmentState + importCustomLineItemState: ImportOrderCustomLineItemState + importLineItemState: ImportOrderLineItemState + removeDelivery: RemoveOrderDelivery + removeItemShippingAddress: RemoveOrderItemShippingAddress + removeParcelFromDelivery: RemoveOrderParcelFromDelivery + removePayment: RemoveOrderPayment + setBillingAddress: SetOrderBillingAddress + setCustomField: SetOrderCustomField + setCustomLineItemCustomField: SetOrderCustomLineItemCustomField + setCustomLineItemCustomType: SetOrderCustomLineItemCustomType + setCustomLineItemShippingDetails: SetOrderCustomLineItemShippingDetails + setCustomType: SetOrderCustomType + setCustomerEmail: SetOrderCustomerEmail + setCustomerId: SetOrderCustomerId + setDeliveryAddress: SetOrderDeliveryAddress + setDeliveryItems: SetOrderDeliveryItems + setLineItemCustomField: SetOrderLineItemCustomField + setLineItemCustomType: SetOrderLineItemCustomType + setLineItemShippingDetails: SetOrderLineItemShippingDetails + setLocale: SetOrderLocale + setOrderNumber: SetOrderNumber + setParcelItems: SetOrderParcelItems + setParcelMeasurements: SetOrderParcelMeasurements + setParcelTrackingData: SetOrderParcelTrackingData + setReturnPaymentState: SetOrderReturnPaymentState + setReturnShipmentState: SetOrderReturnShipmentState + setShippingAddress: SetOrderShippingAddress + transitionCustomLineItemState: TransitionOrderCustomLineItemState + transitionLineItemState: TransitionOrderLineItemState + transitionState: TransitionOrderState + updateItemShippingAddress: UpdateOrderItemShippingAddress + updateSyncInfo: UpdateOrderSyncInfo +} + +type Parcel { + id: String! + createdAt: DateTime! + measurements: ParcelMeasurements + trackingData: TrackingData + items: [DeliveryItem!]! +} + +type ParcelMeasurements { + heightInMillimeter: Int + lengthInMillimeter: Int + widthInMillimeter: Int + weightInGram: Int +} + +input ParcelMeasurementsDraftType { + heightInMillimeter: Int + lengthInMillimeter: Int + widthInMillimeter: Int + weightInGram: Int +} + +""" +Payments hold information about the current state of receiving and/or refunding money. +[documentation](https://docs.commercetools.com/http-api-projects-payments) +""" +type Payment implements Versioned { + key: String + customerRef: Reference + customer: Customer + anonymousId: String + interfaceId: String + amountPlanned: Money! + amountAuthorized: Money @deprecated(reason: "https://docs.commercetools.com/release-notes.html#releases-2017-09-29-payment-api-beta-changes") + authorizedUntil: DateTime @deprecated(reason: "https://docs.commercetools.com/release-notes.html#releases-2017-09-29-payment-api-beta-changes") + amountPaid: Money @deprecated(reason: "https://docs.commercetools.com/release-notes.html#releases-2017-09-29-payment-api-beta-changes") + amountRefunded: Money @deprecated(reason: "https://docs.commercetools.com/release-notes.html#releases-2017-09-29-payment-api-beta-changes") + paymentMethodInfo: PaymentMethodInfo! + paymentStatus: PaymentStatus! + transactions: [Transaction!]! + interfaceInteractionsRaw(limit: Int, offset: Int): InterfaceInteractionsRawResult! + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +type PaymentInfo { + payments: [Payment!]! + paymentRefs: [Reference!]! +} + +type PaymentMethodInfo { + paymentInterface: String + method: String + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!] +} + +type PaymentQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Payment!]! +} + +enum PaymentState { + Paid + CreditOwed + Pending + Failed + BalanceDue +} + +type PaymentStatus { + interfaceCode: String + interfaceText: String + stateRef: Reference + state: State +} + +type PlainEnumValue { + key: String! + label: String! +} + +input PlainEnumValueDraft { + key: String! + label: String! +} + +type PlainEnumValueResult { + limit: Int + offset: Int + total: Int! + results: [PlainEnumValue!]! +} + +type Point implements Geometry { + coordinates: [Float!]! + type: String! +} + +type PriceFunction { + function: String! + currencyCode: Currency! +} + +input PriceFunctionDraft { + function: String! + currencyCode: Currency! +} + +type Product implements Versioned { + id: String! + key: String + version: Long! + productTypeRef: Reference! + productType: ProductTypeDefinition + masterData: ProductCatalogData! + catalogData(id: String!): ProductCatalogData @deprecated(reason: "only 'masterData' supported") + skus: [String!]! + createdAt: DateTime! + lastModifiedAt: DateTime! + stateRef: Reference + state: State + taxCategoryRef: Reference + taxCategory: TaxCategory + createdBy: Initiator + lastModifiedBy: Initiator +} + +input ProductAttributeInput { + name: String! + value: String! +} + +type ProductCatalogData { + current: ProductData + staged: ProductData + published: Boolean! + hasStagedChanges: Boolean! +} + +type ProductData { + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + descriptionAllLocales: [LocalizedString!] + slug( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + categoryOrderHint(categoryId: String!): String + categoryOrderHints: [CategoryOrderHint!]! + categoriesRef: [Reference!]! + categories: [Category!]! + searchKeyword( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale! + ): [SearchKeyword!] + searchKeywords: [SearchKeywords!]! + metaTitle( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + metaKeywords( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + metaDescription( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + masterVariant: ProductVariant! + variants( + """Queries for products with specified SKUs""" + skus: [String!] + isOnStock: Boolean + + """ + The IDs of channels for which to check the stock of the `isOnStock`. + + Variant is returned if at least one of the channels is matching the `isOnStock` + + If the list is not provided then noChannel is checked for `isOnStock` + """ + stockChannelIds: [String!] + hasImages: Boolean + ): [ProductVariant!]! + allVariants( + """Queries for products with specified SKUs""" + skus: [String!] + isOnStock: Boolean + + """ + The IDs of channels for which to check the stock of the `isOnStock`. + + Variant is returned if at least one of the channels is matching the `isOnStock` + + If the list is not provided then noChannel is checked for `isOnStock` + """ + stockChannelIds: [String!] + hasImages: Boolean + ): [ProductVariant!]! + variant( + """Queries for a variant with specified SKU""" + sku: String + + """ + Queries for a variant with specified [key](https://dev.commercetools.com/http-api-projects-products.html#variant_key) + """ + key: String + ): ProductVariant + skus: [String!]! +} + +""" +A product price can be discounted in two ways: + +* with a relative or an absolute product discount, which will be automatically +applied to all prices in a product that match a discount predicate. + A relative discount reduces the matching price by a fraction (for example 10 % +off). An absolute discount reduces the matching price by a fixed amount (for +example 10€ off). If more than one product discount matches a price, the +discount sort order determines which one will be applied. +* with an external product discount, which can then be used to explicitly set a +discounted value on a particular product price. + +The discounted price is stored in the discounted field of the Product Price. + +Note that when a discount is created, updated or removed it can take up to 15 +minutes to update all the prices with the discounts. + +The maximum number of ProductDiscounts that can be active at the same time is **200**. +""" +type ProductDiscount implements Versioned { + predicate: String! + validFrom: DateTime + validUntil: DateTime + isActive: Boolean! + isValid: Boolean! + sortOrder: String! + key: String + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + descriptionAllLocales: [LocalizedString!] + value: ProductDiscountValue! + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +input ProductDiscountDraft { + value: ProductDiscountValueInput! + predicate: String! + sortOrder: String! + name: [LocalizedStringItemInputType!]! + description: [LocalizedStringItemInputType!] + validFrom: DateTime + validUntil: DateTime + isActive: Boolean = true + key: String +} + +type ProductDiscountQueryResult { + offset: Int! + count: Int! + total: Long! + results: [ProductDiscount!]! +} + +input ProductDiscountUpdateAction { + changeIsActive: ChangeProductDiscountIsActive + changeName: ChangeProductDiscountName + changePredicate: ChangeProductDiscountPredicate + changeSortOrder: ChangeProductDiscountSortOrder + changeValue: ChangeProductDiscountValue + setDescription: SetProductDiscountDescription + setKey: SetProductDiscountKey + setValidFrom: SetProductDiscountValidFrom + setValidFromAndUntil: SetProductDiscountValidFromAndUntil + setValidUntil: SetProductDiscountValidUntil +} + +interface ProductDiscountValue { + type: String! +} + +input ProductDiscountValueInput { + relative: RelativeDiscountValueInput + absolute: AbsoluteDiscountValueInput + external: ExternalDiscountValueInput +} + +input ProductDraft { + name: [LocalizedStringItemInputType!]! + productType: ResourceIdentifierInput! + slug: [LocalizedStringItemInputType!]! + key: String + description: [LocalizedStringItemInputType!] + categories: [ResourceIdentifierInput!] + categoryOrderHints: [CategoryOrderHintInput!] + metaTitle: [LocalizedStringItemInputType!] + metaDescription: [LocalizedStringItemInputType!] + metaKeywords: [LocalizedStringItemInputType!] + masterVariant: ProductVariantInput + variants: [ProductVariantInput!] = [] + taxCategory: ResourceIdentifierInput + state: ResourceIdentifierInput + searchKeywords: [SearchKeywordInput!] + publish: Boolean +} + +type ProductPrice { + id: String + value: BaseMoney! + country: Country + customerGroup: Reference + channel: Reference + validFrom: DateTime + validUntil: DateTime + discounted: DiscountedProductPriceValue + tiers: [ProductPriceTier!] + + """ + This field contains non-typed data. Consider using `customFields` as a typed alternative. + """ + customFieldsRaw( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [RawCustomField!] @deprecated(reason: "Please use 'custom.customFieldsRaw'") + + """This field would contain type data""" + customFields: Type @deprecated(reason: "Please use 'custom.customFields'") + custom: CustomFieldsType + + """Custom fields are returned as a list instead of an object structure.""" + customFieldList( + """ + The names of the custom fields to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the custom fields to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [CustomField!] +} + +input ProductPriceDataInput { + value: BaseMoneyInput! + country: Country + customerGroup: ReferenceInput + channel: ResourceIdentifierInput + validFrom: DateTime + validUntil: DateTime + tiers: [ProductPriceTierInput!] = [] + custom: CustomFieldsDraft +} + +type ProductPriceTier { + minimumQuantity: Int! + value: BaseMoney! +} + +input ProductPriceTierInput { + minimumQuantity: Int! + value: BaseMoneyInput! +} + +type ProductQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Product!]! +} + +type ProductReferenceIdentifier { + typeId: String! + id: String + key: String +} + +interface ProductType { + productTypeId: String! +} + +type ProductTypeDefinition implements Versioned { + key: String + name: String! + description: String! + attributeDefinitions( + """ + The names of the attribute definitions to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attribute definitions are returned. + """ + includeNames: [String!] + + """ + The names of the attribute definitions to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attribute definitions are returned. + """ + excludeNames: [String!] + limit: Int + offset: Int + sort: [String!] + ): AttributeDefinitionResult! + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +type ProductTypeDefinitionQueryResult { + offset: Int! + count: Int! + total: Long! + results: [ProductTypeDefinition!]! +} + +input ProductTypeDraft { + name: String! + description: String! + key: String + attributeDefinitions: [AttributeDefinitionDraft!] +} + +input ProductTypeUpdateAction { + setKey: setKey + changeName: changeName + changeDescription: changeDescription + removeAttributeDefinition: removeAttributeDefinition + changeLabel: changeLabel + setInputTip: setInputTip + changeIsSearchable: changeIsSearchable + changeInputHint: changeInputHint + addAttributeDefinition: addAttributeDefinition + changeAttributeOrder: changeAttributeOrder + changeAttributeOrderByName: changeAttributeOrderByName + removeEnumValues: removeEnumValues + addPlainEnumValue: addPlainEnumValue + changePlainEnumValueLabel: changePlainEnumValueLabel + changePlainEnumValueOrder: changePlainEnumValueOrder + addLocalizedEnumValue: addLocalizedEnumValue + changeLocalizedEnumValueLabel: changeLocalizedEnumValueLabel + changeLocalizedEnumValueOrder: changeLocalizedEnumValueOrder + changeAttributeName: changeAttributeName + changeEnumKey: changeEnumKey +} + +input ProductUpdateAction { + moveImageToPosition: MoveProductImageToPosition + setSearchKeywords: SetSearchKeywords + revertStagedChanges: RevertStagedChanges + revertStagedVariantChanges: RevertStagedVariantChanges + publish: PublishProduct + unpublish: UnpublishProduct + transitionState: TransitionProductState + addAsset: AddProductAsset + addExternalImage: AddProductExternalImage + addPrice: AddProductPrice + addToCategory: AddProductToCategory + addVariant: AddProductVariant + changeAssetName: ChangeProductAssetName + changeAssetOrder: ChangeProductAssetOrder + changeMasterVariant: ChangeProductMasterVariant + changeImageLabel: ChangeProductImageLabel + changeName: ChangeProductName + changePrice: ChangeProductPrice + changeSlug: ChangeProductSlug + removeAsset: RemoveProductAsset + removeFromCategory: RemoveProductFromCategory + removeImage: RemoveProductImage + removePrice: RemoveProductPrice + removeVariant: RemoveProductVariant + setAssetCustomField: SetProductAssetCustomField + setAssetCustomType: SetProductAssetCustomType + setAssetDescription: SetProductAssetDescription + setAssetKey: SetProductAssetKey + setAssetSources: SetProductAssetSources + setAssetTags: SetProductAssetTags + setCategoryOrderHint: SetProductCategoryOrderHint + setDiscountedPrice: SetProductDiscountedPrice + setAttribute: SetProductAttribute + setAttributeInAllVariants: SetProductAttributeInAllVariants + setDescription: SetProductDescription + setImageLabel: SetProductImageLabel + setKey: SetProductKey + setMetaAttributes: SetProductMetaAttributes + setMetaDescription: SetProductMetaDescription + setMetaKeywords: SetProductMetaKeywords + setMetaTitle: SetProductMetaTitle + setProductPriceCustomField: SetProductPriceCustomField + setProductPriceCustomType: SetProductPriceCustomType + setPrices: SetProductPrices + setSku: SetProductSku + setTaxCategory: SetProductTaxCategory + setProductVariantKey: SetProductVariantKey +} + +type ProductVariant { + id: Int! + key: String + sku: String + prices: [ProductPrice!] + + """Returns a single price based on the price selection rules.""" + price(currency: Currency!, country: Country, customerGroupId: String, channelId: String, date: DateTime): ProductPrice + images: [Image!]! + assets: [Asset!]! + availability: ProductVariantAvailabilityWithChannels + + """ + This field contains non-typed data. Consider using `attributes` as a typed alternative. + """ + attributesRaw( + """ + The names of the attributes to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the attributes to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [RawProductAttribute!]! + + """Product attributes""" + attributes: ProductType! + + """ + Product attributes are returned as a list instead of an object structure. + """ + attributeList( + """ + The names of the attributes to include. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + includeNames: [String!] + + """ + The names of the attributes to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all attributes are returned. + """ + excludeNames: [String!] + ): [Attribute!]! +} + +"""Product variant availabilities""" +type ProductVariantAvailabilitiesResult { + limit: Int + offset: Int + total: Int! + results: [ProductVariantAvailabilityWithChannel!]! +} + +"""Product variant availability""" +type ProductVariantAvailability { + isOnStock: Boolean! + restockableInDays: Int + availableQuantity: Long +} + +type ProductVariantAvailabilityWithChannel { + channelRef: Reference! + channel: Channel + availability: ProductVariantAvailability! +} + +type ProductVariantAvailabilityWithChannels { + noChannel: ProductVariantAvailability + channels( + """ + The IDs of channels to include. + + If neither `includeChannelIds` nor `excludeChannelIds` are provided, then all channels are returned. + """ + includeChannelIds: [String!] + + """ + The IDs of channels to exclude. + + If neither `includeChannelIds` nor `excludeChannelIds` are provided, then all channels are returned. + """ + excludeChannelIds: [String!] + limit: Int + offset: Int + ): ProductVariantAvailabilitiesResult! +} + +input ProductVariantInput { + sku: String + key: String + prices: [ProductPriceDataInput!] = [] + images: [ImageInput!] = [] + attributes: [ProductAttributeInput!] = [] + assets: [AssetDraftInput!] = [] +} + +"""Project contains information about project.""" +type ProjectProjection { + key: String! + name: String! + languages: [Locale!]! + createdAt: DateTime! + trialUntil: YearMonth + version: Long! + externalOAuth: ExternalOAuth + messages: MessagesConfiguration! + countries: [Country!]! + currencies: [Currency!]! + shippingRateInputType: ShippingRateInputType +} + +input ProjectSettingsUpdateAction { + changeCountries: ChangeProjectSettingsCountries + changeCurrencies: ChangeProjectSettingsCurrencies + changeLanguages: ChangeProjectSettingsLanguages + changeMessagesConfiguration: ChangeProjectSettingsMessagesConfiguration + changeMessagesEnabled: ChangeProjectSettingsMessagesEnabled + changeName: ChangeProjectSettingsName + setExternalOAuth: SetProjectSettingsExternalOAuth + setShippingRateInputType: SetProjectSettingsShippingRateInputType +} + +input PublishProduct { + scope: PublishScope +} + +enum PublishScope { + """Publishes the complete staged projection""" + All + + """Publishes only prices on the staged projection""" + Prices +} + +type Query implements CartQueryInterface & CustomerActiveCartInterface & OrderQueryInterface & CustomerQueryInterface & ShoppingListQueryInterface & ShippingMethodsByCartInterface & MeFieldInterface { + """ + This field can only be used with an access token created with the password flow or with an anonymous session. + + It gives access to the data that is specific to the customer or the anonymous session linked to the access token. + """ + me: Me! + + """ + This field gives access to the resources (such as carts) that are inside the given store. + """ + inStore(key: KeyReferenceInput!): InStore! @deprecated(reason: "beta feature") + + """ + This field gives access to the resources (such as carts) that are inside one of the given stores. + """ + inStores(keys: [KeyReferenceInput!]!): InStore! @deprecated(reason: "beta feature") + customerGroup( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): CustomerGroup + customerGroups(where: String, sort: [String!], limit: Int, offset: Int): CustomerGroupQueryResult! + category( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Category + categories(where: String, sort: [String!], limit: Int, offset: Int): CategoryQueryResult! + + """ + Autocomplete the categories based on category fields like name, description, etc. + """ + categoryAutocomplete( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale! + + """Incomplete user input.""" + text: String! + limit: Int = 10 + offset: Int = 0 + + """ + Filters to apply during the search and autocomplete - supported fields are: + * `id` + * `slug` + * `externalId` + * `key` + * `ancestors` + * `parent.id` + * `level` + * `createdAt` + * `modifiedAt` + * `name.{language}:missing` + * `externalId:missing` + * `description.{language}:missing` + * `childCount` + * `productCount` + * `productTypeNames` + """ + filters: [SearchFilter!] + + """Enables experimental features.""" + experimental: Boolean = false + ): CategorySearchResult! + + """Search the categories using full-text search, filtering and sorting""" + categorySearch( + """Full-text search input.""" + fulltext: LocalizedText + limit: Int = 10 + offset: Int = 0 + + """Filters to apply before the results of facets""" + queryFilters: [SearchFilter!] + + """ + Filters to apply during the search and autocomplete - supported fields are: + * `id` + * `slug` + * `externalId` + * `key` + * `ancestors` + * `parent.id` + * `level` + * `createdAt` + * `modifiedAt` + * `name.{language}:missing` + * `externalId:missing` + * `description.{language}:missing` + * `childCount` + * `productCount` + * `productTypeNames` + """ + filters: [SearchFilter!] + + """ + Sort result - supported fields are: + + * `id` + * `name` + * `createdAt` + * `modifiedAt` + + by default sorted by relevance (a score of matches against the search term in descending order) + """ + sorts: [SearchSort!] + + """Enables experimental features.""" + experimental: Boolean = false + ): CategorySearchResult! + channel( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Channel + channels(where: String, sort: [String!], limit: Int, offset: Int): ChannelQueryResult! + productType( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ProductTypeDefinition + productTypes(where: String, sort: [String!], limit: Int, offset: Int): ProductTypeDefinitionQueryResult! + typeDefinition( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): TypeDefinition + typeDefinitions(where: String, sort: [String!], limit: Int, offset: Int): TypeDefinitionQueryResult! + shippingMethod( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShippingMethod + shippingMethods(where: String, sort: [String!], limit: Int, offset: Int): ShippingMethodQueryResult! + shippingMethodsByCart(id: String!): [ShippingMethod!]! + shippingMethodsByLocation(country: Country!, state: String, currency: Currency): [ShippingMethod!]! + zone( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Zone + zones(where: String, sort: [String!], limit: Int, offset: Int): ZoneQueryResult! + taxCategory( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): TaxCategory + taxCategories(where: String, sort: [String!], limit: Int, offset: Int): TaxCategoryQueryResult! + discountCode(id: String!): DiscountCode + discountCodes(where: String, sort: [String!], limit: Int, offset: Int): DiscountCodeQueryResult! + cartDiscount( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): CartDiscount + cartDiscounts(where: String, sort: [String!], limit: Int, offset: Int): CartDiscountQueryResult! + productDiscount(id: String!): ProductDiscount + productDiscounts(where: String, sort: [String!], limit: Int, offset: Int): ProductDiscountQueryResult! + product( + """Queries for a product with specified SKU""" + sku: String + + """ + Queries for a product with specified [product variant key](https://dev.commercetools.com/http-api-projects-products.html#variant_key) + """ + variantKey: String + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Product + products( + where: String + sort: [String!] + limit: Int + offset: Int + + """Queries for products with specified SKUs""" + skus: [String!] + ): ProductQueryResult! + state( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): State + states(where: String, sort: [String!], limit: Int, offset: Int): StateQueryResult! + customer( + """Queries a customer with specified email token""" + emailToken: String + + """Queries a customer with specified password token""" + passwordToken: String + + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Customer + customers(where: String, sort: [String!], limit: Int, offset: Int): CustomerQueryResult! + inventoryEntry(id: String!): InventoryEntry + inventoryEntries(where: String, sort: [String!], limit: Int, offset: Int): InventoryEntryQueryResult! + cart(id: String!): Cart + carts(where: String, sort: [String!], limit: Int, offset: Int): CartQueryResult! + customerActiveCart(customerId: String!): Cart + order( + """Queries with specified ID""" + id: String + orderNumber: String + ): Order + orders(where: String, sort: [String!], limit: Int, offset: Int): OrderQueryResult! + shoppingList( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShoppingList + shoppingLists(where: String, sort: [String!], limit: Int, offset: Int): ShoppingListQueryResult! + payment( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Payment + payments(where: String, sort: [String!], limit: Int, offset: Int): PaymentQueryResult! + project: ProjectProjection! + store( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): Store @deprecated(reason: "beta feature") + stores(where: String, sort: [String!], limit: Int, offset: Int): StoreQueryResult! @deprecated(reason: "beta feature") + apiClient(id: String!): APIClientWithoutSecret + apiClients(where: String, sort: [String!], limit: Int, offset: Int): APIClientWithoutSecretQueryResult! +} + +type RawCustomField { + name: String! + value: Json! +} + +type RawProductAttribute { + name: String! + value: Json! + attributeDefinition: AttributeDefinition +} + +input RecalculateCart { + updateProductData: Boolean = false +} + +type Reference { + typeId: String! + id: String! +} + +type ReferenceAttribute implements Attribute { + typeId: String! + id: String! + name: String! +} + +type ReferenceAttributeDefinitionType implements AttributeDefinitionType { + referenceTypeId: String! + name: String! +} + +type ReferenceField implements CustomField { + typeId: String! + id: String! + name: String! +} + +input ReferenceInput { + typeId: String! + id: String! +} + +type ReferenceType implements FieldType { + referenceTypeId: String! + name: String! +} + +input ReferenceTypeDefinitionDraft { + referenceTypeId: String! +} + +type RelativeDiscountValue implements CartDiscountValue & ProductDiscountValue { + permyriad: Int! + type: String! +} + +input RelativeDiscountValueInput { + permyriad: Int! +} + +input removeAttributeDefinition { + name: String! +} + +input RemoveCartCustomLineItem { + customLineItemId: String! +} + +input RemoveCartDiscountCode { + discountCode: ReferenceInput! +} + +input RemoveCartItemShippingAddress { + addressKey: String! +} + +input RemoveCartLineItem { + lineItemId: String! + quantity: Long + externalPrice: BaseMoneyInput + externalTotalPrice: ExternalLineItemTotalPriceDraft + shippingDetailsToRemove: ItemShippingDetailsDraft +} + +input RemoveCartPayment { + payment: ResourceIdentifierInput! +} + +input RemoveCategoryAsset { + assetKey: String + assetId: String +} + +input RemoveCustomerAddress { + addressId: String! +} + +input RemoveCustomerBillingAddressId { + addressId: String! +} + +input RemoveCustomerShippingAddressId { + addressId: String! +} + +input RemoveCustomerStore { + store: ResourceIdentifierInput! +} + +input removeEnumValues { + attributeName: String! + keys: [String!]! +} + +input RemoveInventoryEntryQuantity { + quantity: Long! +} + +input RemoveOrderDelivery { + deliveryId: String! +} + +input RemoveOrderItemShippingAddress { + addressKey: String! +} + +input RemoveOrderParcelFromDelivery { + parcelId: String! +} + +input RemoveOrderPayment { + payment: ResourceIdentifierInput! +} + +input RemoveProductAsset { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + assetKey: String + assetId: String +} + +input RemoveProductFromCategory { + category: ResourceIdentifierInput! + staged: Boolean = true +} + +input RemoveProductImage { + variantId: Int + sku: String + imageUrl: String! + staged: Boolean = true +} + +input RemoveProductPrice { + priceId: String + variantId: Int + sku: String + price: ProductPriceDataInput + catalog: ReferenceInput + staged: Boolean = true +} + +input RemoveProductVariant { + id: Int + sku: String + staged: Boolean = true +} + +input RemoveShippingMethodShippingRate { + zone: ResourceIdentifierInput! + shippingRate: ShippingRateDraft! +} + +input RemoveShippingMethodZone { + zone: ResourceIdentifierInput! +} + +input RemoveShoppingListLineItem { + lineItemId: String! + quantity: Int +} + +input RemoveShoppingListTextLineItem { + textLineItemId: String! + quantity: Int +} + +input RemoveZoneLocation { + location: ZoneLocation! +} + +type reservationOrderType implements Type { + typeRef: Reference! + type: TypeDefinition! + isReservation: BooleanField +} + +input ResourceIdentifierInput { + typeId: String + id: String + key: String +} + +"""Stores information about returns connected to this order.""" +type ReturnInfo { + items: [ReturnItem!]! + returnTrackingId: String + returnDate: DateTime +} + +interface ReturnItem { + type: String! + id: String! + quantity: Long! + comment: String + shipmentState: ReturnShipmentState! + paymentState: ReturnPaymentState! + lastModifiedAt: DateTime! + createdAt: DateTime! +} + +input ReturnItemDraftType { + quantity: Long! + lineItemId: String + customLineItemId: String + comment: String + shipmentState: ReturnShipmentState! +} + +enum ReturnPaymentState { + NotRefunded + Refunded + Initial + NonRefundable +} + +enum ReturnShipmentState { + Unusable + BackInStock + Returned + Advised +} + +input RevertStagedChanges { + dummy: String +} + +input RevertStagedVariantChanges { + variantId: Int! +} + +enum RoundingMode { + """ + [Round half down](https://en.wikipedia.org/wiki/Rounding#Round_half_down). + Rounding mode used by, e.g., [Avalara Sales TaxII](https://help.avalara.com/kb/001/How_does_Rounding_with_SalesTaxII_work%3F) + """ + HalfDown + + """[Round half up](https://en.wikipedia.org/wiki/Rounding#Round_half_up)""" + HalfUp + + """ + [Round half to even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even). + Default rounding mode as used in IEEE 754 computing functions and operators. + """ + HalfEven +} + +type ScoreShippingRateInput implements ShippingRateInput { + score: Int! + type: String! +} + +input ScoreShippingRateInputDraft { + score: Int! +} + +""" +Search filter. It is represented as a string and has th same format as in REST API: "field:filter_criteria" +""" +scalar SearchFilter + +type SearchKeyword { + text: String! +} + +input SearchKeywordInput { + locale: Locale! + keywords: [CustomSuggestTokenizerInput!]! +} + +type SearchKeywords { + locale: Locale! + searchKeywords: [SearchKeyword!]! +} + +"""Search sort""" +scalar SearchSort + +""" +In order to decide which of the matching items will actually be discounted +""" +enum SelectionMode { + MostExpensive + Cheapest +} + +type SetAttributeDefinitionType implements AttributeDefinitionType { + elementType: AttributeDefinitionType! + name: String! +} + +input SetCartAnonymousId { + anonymousId: String +} + +input SetCartBillingAddress { + address: AddressInput +} + +input SetCartCountry { + country: Country +} + +input SetCartCustomerEmail { + email: String +} + +input SetCartCustomerGroup { + customerGroup: ResourceIdentifierInput +} + +input SetCartCustomerId { + customerId: String +} + +input SetCartCustomField { + name: String! + value: String +} + +input SetCartCustomLineItemCustomField { + customLineItemId: String! + name: String! + value: String +} + +input SetCartCustomLineItemCustomType { + customLineItemId: String! + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetCartCustomLineItemShippingDetails { + customLineItemId: String! + shippingDetails: ItemShippingDetailsDraft +} + +input SetCartCustomLineItemTaxAmount { + customLineItemId: String! + externalTaxAmount: ExternalTaxAmountDraft +} + +input SetCartCustomLineItemTaxRate { + customLineItemId: String! + externalTaxRate: ExternalTaxRateDraft +} + +input SetCartCustomShippingMethod { + shippingMethodName: String! + shippingRate: ShippingRateDraft! + taxCategory: ResourceIdentifierInput + externalTaxRate: ExternalTaxRateDraft +} + +input SetCartCustomType { + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetCartDeleteDaysAfterLastModification { + deleteDaysAfterLastModification: Int +} + +input SetCartDiscountCustomField { + name: String! + value: String +} + +input SetCartDiscountCustomType { + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetCartDiscountDescription { + description: [LocalizedStringItemInputType!] +} + +input SetCartDiscountKey { + key: String +} + +input SetCartDiscountValidFrom { + validFrom: DateTime +} + +input SetCartDiscountValidFromAndUntil { + validFrom: DateTime + validUntil: DateTime +} + +input SetCartDiscountValidUntil { + validUntil: DateTime +} + +input SetCartLineItemCustomField { + lineItemId: String! + name: String! + value: String +} + +input SetCartLineItemCustomType { + lineItemId: String! + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetCartLineItemPrice { + lineItemId: String! + externalPrice: BaseMoneyInput +} + +input SetCartLineItemShippingDetails { + lineItemId: String! + shippingDetails: ItemShippingDetailsDraft +} + +input SetCartLineItemTaxAmount { + lineItemId: String! + externalTaxAmount: ExternalTaxAmountDraft +} + +input SetCartLineItemTaxRate { + lineItemId: String! + externalTaxRate: ExternalTaxRateDraft +} + +input SetCartLineItemTotalPrice { + lineItemId: String! + externalTotalPrice: ExternalLineItemTotalPriceDraft +} + +input SetCartLocale { + locale: Locale +} + +input SetCartShippingAddress { + address: AddressInput +} + +input SetCartShippingMethod { + shippingMethod: ResourceIdentifierInput + externalTaxRate: ExternalTaxRateDraft +} + +input SetCartShippingMethodTaxAmount { + externalTaxAmount: ExternalTaxAmountDraft +} + +input SetCartShippingMethodTaxRate { + externalTaxRate: ExternalTaxRateDraft +} + +input SetCartShippingRateInput { + shippingRateInput: ShippingRateInputDraft +} + +input SetCartTotalTax { + externalTotalGross: MoneyInput + externalTaxPortions: [TaxPortionDraft!] = [] +} + +input SetCategoryAssetCustomField { + value: String + name: String! + assetKey: String + assetId: String +} + +input SetCategoryAssetCustomType { + typeId: String + typeKey: String + type: ResourceIdentifierInput + fields: [CustomFieldInput!] + assetKey: String + assetId: String +} + +input SetCategoryAssetDescription { + description: [LocalizedStringItemInputType!] + assetKey: String + assetId: String +} + +input SetCategoryAssetKey { + assetKey: String + assetId: String! +} + +input SetCategoryAssetSources { + sources: [AssetSourceInput!] = [] + assetKey: String + assetId: String +} + +input SetCategoryAssetTags { + tags: [String!] = [] + assetKey: String + assetId: String +} + +input SetCategoryCustomField { + name: String! + value: String +} + +input SetCategoryCustomType { + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetCategoryDescription { + description: [LocalizedStringItemInputType!] +} + +input SetCategoryExternalId { + externalId: String +} + +input SetCategoryKey { + key: String +} + +input SetCategoryMetaDescription { + metaDescription: [LocalizedStringItemInputType!] +} + +input SetCategoryMetaKeywords { + metaKeywords: [LocalizedStringItemInputType!] +} + +input SetCategoryMetaTitle { + metaTitle: [LocalizedStringItemInputType!] +} + +input SetCustomerCompanyName { + companyName: String +} + +input SetCustomerCustomField { + name: String! + value: String +} + +input SetCustomerCustomType { + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetCustomerDateOfBirth { + dateOfBirth: Date +} + +input SetCustomerDefaultBillingAddress { + addressId: String +} + +input SetCustomerDefaultShippingAddress { + addressId: String +} + +input SetCustomerExternalId { + externalId: String +} + +input SetCustomerFirstName { + firstName: String +} + +input SetCustomerGroup { + customerGroup: ResourceIdentifierInput +} + +input SetCustomerGroupCustomField { + name: String! + value: String +} + +input SetCustomerGroupCustomType { + typeId: String + typeKey: String + type: ResourceIdentifierInput + fields: [CustomFieldInput!] +} + +input SetCustomerGroupKey { + key: String +} + +input SetCustomerKey { + key: String +} + +input SetCustomerLastName { + lastName: String +} + +input SetCustomerLocale { + locale: Locale +} + +input SetCustomerMiddleName { + middleName: String +} + +input SetCustomerNumber { + customerNumber: String +} + +input SetCustomerSalutation { + salutation: String +} + +input SetCustomerStores { + stores: [ResourceIdentifierInput!]! +} + +input SetCustomerTitle { + title: String +} + +input SetCustomerVatId { + vatId: String +} + +input SetDiscountCodeCartPredicate { + cartPredicate: String +} + +input SetDiscountCodeCustomField { + name: String! + value: String +} + +input SetDiscountCodeCustomType { + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetDiscountCodeDescription { + description: [LocalizedStringItemInputType!] +} + +input SetDiscountCodeMaxApplications { + maxApplications: Long +} + +input SetDiscountCodeMaxApplicationsPerCustomer { + maxApplicationsPerCustomer: Long +} + +input SetDiscountCodeName { + name: [LocalizedStringItemInputType!] +} + +input SetDiscountCodeValidFrom { + validFrom: DateTime +} + +input SetDiscountCodeValidFromAndUntil { + validFrom: DateTime + validUntil: DateTime +} + +input SetDiscountCodeValidUntil { + validUntil: DateTime +} + +input setInputTip { + attributeName: String! + inputTip: [LocalizedStringItemInputType!] +} + +input SetInventoryEntryCustomField { + name: String! + value: String +} + +input SetInventoryEntryCustomType { + typeId: String + typeKey: String + type: ResourceIdentifierInput + fields: [CustomFieldInput!] +} + +input SetInventoryEntryExpectedDelivery { + expectedDelivery: DateTime +} + +input SetInventoryEntryRestockableInDays { + restockableInDays: Int +} + +input SetInventoryEntrySupplyChannel { + supplyChannel: ResourceIdentifierInput +} + +input setKey { + key: String +} + +input SetMyCartShippingMethod { + shippingMethod: ResourceIdentifierInput +} + +input SetOrderBillingAddress { + address: AddressInput +} + +input SetOrderCustomerEmail { + email: String +} + +input SetOrderCustomerId { + customerId: String +} + +input SetOrderCustomField { + name: String! + value: String +} + +input SetOrderCustomLineItemCustomField { + customLineItemId: String! + name: String! + value: String +} + +input SetOrderCustomLineItemCustomType { + customLineItemId: String! + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetOrderCustomLineItemShippingDetails { + customLineItemId: String! + shippingDetails: ItemShippingDetailsDraftType +} + +input SetOrderCustomType { + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetOrderDeliveryAddress { + deliveryId: String! + address: AddressInput +} + +input SetOrderDeliveryItems { + deliveryId: String! + items: [DeliveryItemDraftType!]! +} + +input SetOrderLineItemCustomField { + lineItemId: String! + name: String! + value: String +} + +input SetOrderLineItemCustomType { + lineItemId: String! + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetOrderLineItemShippingDetails { + lineItemId: String! + shippingDetails: ItemShippingDetailsDraftType +} + +input SetOrderLocale { + locale: Locale +} + +input SetOrderNumber { + orderNumber: String +} + +input SetOrderParcelItems { + parcelId: String! + items: [DeliveryItemDraftType!]! +} + +input SetOrderParcelMeasurements { + parcelId: String! + measurements: ParcelMeasurementsDraftType +} + +input SetOrderParcelTrackingData { + parcelId: String! + trackingData: TrackingDataDraftType +} + +input SetOrderReturnPaymentState { + returnItemId: String! + paymentState: ReturnPaymentState! +} + +input SetOrderReturnShipmentState { + returnItemId: String! + shipmentState: ReturnShipmentState! +} + +input SetOrderShippingAddress { + address: AddressInput +} + +input SetProductAssetCustomField { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + value: String + name: String! + assetKey: String + assetId: String +} + +input SetProductAssetCustomType { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + typeId: String + typeKey: String + type: ResourceIdentifierInput + fields: [CustomFieldInput!] + assetKey: String + assetId: String +} + +input SetProductAssetDescription { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + description: [LocalizedStringItemInputType!] + assetKey: String + assetId: String +} + +input SetProductAssetKey { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + assetKey: String + assetId: String! +} + +input SetProductAssetSources { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + sources: [AssetSourceInput!] = [] + assetKey: String + assetId: String +} + +input SetProductAssetTags { + variantId: Int + sku: String + catalog: ReferenceInput + staged: Boolean = true + tags: [String!] = [] + assetKey: String + assetId: String +} + +input SetProductAttribute { + variantId: Int + sku: String + name: String! + value: String + staged: Boolean = true +} + +input SetProductAttributeInAllVariants { + name: String! + value: String + staged: Boolean = true +} + +input SetProductCategoryOrderHint { + categoryId: String! + orderHint: String + staged: Boolean = true +} + +input SetProductDescription { + description: [LocalizedStringItemInputType!] + staged: Boolean = true +} + +input SetProductDiscountDescription { + description: [LocalizedStringItemInputType!] +} + +input SetProductDiscountedPrice { + priceId: String! + discounted: DiscountedProductPriceValueInput + catalog: ReferenceInput + staged: Boolean = true +} + +input SetProductDiscountKey { + key: String +} + +input SetProductDiscountValidFrom { + validFrom: DateTime +} + +input SetProductDiscountValidFromAndUntil { + validFrom: DateTime + validUntil: DateTime +} + +input SetProductDiscountValidUntil { + validUntil: DateTime +} + +input SetProductImageLabel { + variantId: Int + sku: String + imageUrl: String! + label: String + staged: Boolean = true +} + +input SetProductKey { + key: String +} + +input SetProductMetaAttributes { + metaDescription: [LocalizedStringItemInputType!] + metaKeywords: [LocalizedStringItemInputType!] + metaTitle: [LocalizedStringItemInputType!] + staged: Boolean = true +} + +input SetProductMetaDescription { + metaDescription: [LocalizedStringItemInputType!] + staged: Boolean = true +} + +input SetProductMetaKeywords { + metaKeywords: [LocalizedStringItemInputType!] + staged: Boolean = true +} + +input SetProductMetaTitle { + metaTitle: [LocalizedStringItemInputType!] + staged: Boolean = true +} + +input SetProductPriceCustomField { + priceId: String! + catalog: ReferenceInput + staged: Boolean = true + name: String! + value: String +} + +input SetProductPriceCustomType { + priceId: String! + catalog: ReferenceInput + staged: Boolean = true + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetProductPrices { + variantId: Int + sku: String + prices: [ProductPriceDataInput!]! + catalog: ReferenceInput + staged: Boolean = true +} + +input SetProductSku { + variantId: Int! + sku: String + staged: Boolean = true +} + +input SetProductTaxCategory { + taxCategory: ResourceIdentifierInput +} + +input SetProductVariantKey { + variantId: Int + sku: String + key: String + staged: Boolean = true +} + +input SetProjectSettingsExternalOAuth { + externalOAuth: ExternalOAuthDraft +} + +input SetProjectSettingsShippingRateInputType { + shippingRateInputType: ShippingRateInputTypeInput +} + +input SetSearchKeywords { + searchKeywords: [SearchKeywordInput!]! + staged: Boolean +} + +input SetShippingMethodDescription { + description: String +} + +input SetShippingMethodKey { + key: String +} + +input SetShippingMethodPredicate { + predicate: String +} + +input SetShoppingListAnonymousId { + anonymousId: String +} + +input SetShoppingListCustomer { + customer: ResourceIdentifierInput +} + +input SetShoppingListCustomField { + name: String! + value: String +} + +input SetShoppingListCustomType { + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetShoppingListDeleteDaysAfterLastModification { + deleteDaysAfterLastModification: Int +} + +input SetShoppingListDescription { + description: [LocalizedStringItemInputType!] +} + +input SetShoppingListKey { + key: String +} + +input SetShoppingListLineItemCustomField { + lineItemId: String! + name: String! + value: String +} + +input SetShoppingListLineItemCustomType { + lineItemId: String! + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetShoppingListSlug { + slug: [LocalizedStringItemInputType!] +} + +input SetShoppingListTextLineItemCustomField { + textLineItemId: String! + name: String! + value: String +} + +input SetShoppingListTextLineItemCustomType { + textLineItemId: String! + fields: [CustomFieldInput!] + type: ResourceIdentifierInput + typeKey: String + typeId: String +} + +input SetShoppingListTextLineItemDescription { + textLineItemId: String! + description: [LocalizedStringItemInputType!] +} + +input SetStoreLanguages { + languages: [Locale!] +} + +input SetStoreName { + name: [LocalizedStringItemInputType!] +} + +input SetTaxCategoryKey { + key: String +} + +type SetType implements FieldType { + elementType: FieldType! + name: String! +} + +input SetZoneDescription { + description: String +} + +input SetZoneKey { + key: String +} + +enum ShipmentState { + Delayed + Backorder + Partial + Pending + Ready + Shipped +} + +type ShippingInfo { + shippingMethodName: String! + price: Money! + shippingRate: ShippingRate! + taxRate: TaxRate + taxCategory: Reference + deliveries: [Delivery!]! + discountedPrice: DiscountedLineItemPrice + taxedPrice: TaxedItemPrice + shippingMethodState: ShippingMethodState! + shippingMethod: ShippingMethod + shippingMethodRef: Reference +} + +type ShippingMethod implements Versioned { + id: String! + version: Long! + name: String! + description: String + zoneRates: [ZoneRate!]! + isDefault: Boolean! + predicate: String + createdAt: DateTime! + lastModifiedAt: DateTime! + key: String + lastModifiedBy: Initiator + createdBy: Initiator + taxCategoryRef: Reference + taxCategory: TaxCategory +} + +input ShippingMethodDraft { + name: String! + description: String + taxCategory: ResourceIdentifierInput! + zoneRates: [ZoneRateDraft!] = [] + isDefault: Boolean! + predicate: String + key: String +} + +type ShippingMethodQueryResult { + offset: Int! + count: Int! + total: Long! + results: [ShippingMethod!]! +} + +"""A field to retrieve available shipping methods for a cart.""" +interface ShippingMethodsByCartInterface { + shippingMethodsByCart(id: String!): [ShippingMethod!]! +} + +enum ShippingMethodState { + """ + Either there is no predicate defined for the ShippingMethod or the given predicate matches the cart + """ + MatchesCart + + """ + The ShippingMethod predicate does not match the cart. Ordering this cart will + fail with error ShippingMethodDoesNotMatchCart + """ + DoesNotMatchCart +} + +input ShippingMethodUpdateAction { + addShippingRate: AddShippingMethodShippingRate + addZone: AddShippingMethodZone + changeIsDefault: ChangeShippingMethodIsDefault + changeName: ChangeShippingMethodName + changeTaxCategory: ChangeShippingMethodTaxCategory + removeShippingRate: RemoveShippingMethodShippingRate + removeZone: RemoveShippingMethodZone + setDescription: SetShippingMethodDescription + setKey: SetShippingMethodKey + setPredicate: SetShippingMethodPredicate +} + +"""Shipping Rate""" +type ShippingRate { + price: Money! + freeAbove: Money + isMatching: Boolean + tiers: [ShippingRatePriceTier!]! +} + +type ShippingRateCartClassificationPriceTier implements ShippingRatePriceTier { + value: String! + price: Money! + isMatching: Boolean + type: String! +} + +type ShippingRateCartScorePriceTier implements ShippingRatePriceTier { + score: Int! + price: Money + priceFunction: PriceFunction + isMatching: Boolean + type: String! +} + +type ShippingRateCartValuePriceTier implements ShippingRatePriceTier { + minimumCentAmount: Int! + price: Money! + isMatching: Boolean + type: String! +} + +input ShippingRateDraft { + price: MoneyDraft! + freeAbove: MoneyDraft + tiers: [ShippingRatePriceTierDraft!] = [] +} + +interface ShippingRateInput { + type: String! +} + +input ShippingRateInputDraft { + Classification: ClassificationShippingRateInputDraft + Score: ScoreShippingRateInputDraft +} + +type ShippingRateInputLocalizedEnumValue { + key: String! + label( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + labelAllLocales: [LocalizedString!]! +} + +interface ShippingRateInputType { + type: String! +} + +input ShippingRateInputTypeInput { + CartValue: CartValueInput + CartClassification: CartClassificationInput + CartScore: CartScoreInput +} + +interface ShippingRatePriceTier { + type: String! +} + +input ShippingRatePriceTierCartClassificationDraft { + value: String! + price: MoneyDraft! +} + +input ShippingRatePriceTierCartScoreDraft { + score: Int! + price: MoneyDraft + priceFunction: PriceFunctionDraft +} + +input ShippingRatePriceTierCartValueDraft { + minimumCentAmount: Int! + price: MoneyDraft! +} + +input ShippingRatePriceTierDraft { + CartValue: ShippingRatePriceTierCartValueDraft + CartClassification: ShippingRatePriceTierCartClassificationDraft + CartScore: ShippingRatePriceTierCartScoreDraft +} + +type ShippingTarget implements CartDiscountTarget { + type: String! +} + +input ShippingTargetDraft { + addressKey: String! + quantity: Long! +} + +input ShippingTargetDraftType { + addressKey: String! + quantity: Long! +} + +input ShippingTargetInput { + dummy: String +} + +type ShoppingList implements Versioned { + key: String + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + descriptionAllLocales: [LocalizedString!] + slug( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + slugAllLocales: [LocalizedString!] + customerRef: Reference + customer: Customer + anonymousId: String + lineItems: [ShoppingListLineItem!]! + textLineItems: [TextLineItem!]! + custom: CustomFieldsType + deleteDaysAfterLastModification: Int + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +input ShoppingListDraft { + name: [LocalizedStringItemInputType!]! + description: [LocalizedStringItemInputType!] + lineItems: [ShoppingListLineItemDraft!] = [] + textLineItems: [TextLineItemDraft!] = [] + custom: CustomFieldsDraft + deleteDaysAfterLastModification: Int + key: String + customer: ResourceIdentifierInput + slug: [LocalizedStringItemInputType!] + anonymousId: String +} + +type ShoppingListLineItem { + id: String! + productId: String! + variantId: Int + productTypeRef: Reference! + productType: ProductTypeDefinition! + quantity: Int! + addedAt: DateTime! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + deactivatedAt: DateTime + custom: CustomFieldsType + productSlug( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + variant: ProductVariant +} + +input ShoppingListLineItemDraft { + productId: String + sku: String + variantId: Int + quantity: Int = 1 + custom: CustomFieldsDraft + addedAt: DateTime +} + +""" +Fields to access shopping lists. Includes direct access to a single list and searching for shopping lists. +""" +interface ShoppingListQueryInterface { + shoppingList( + """Queries with specified ID""" + id: String + + """Queries with specified key""" + key: String + ): ShoppingList + shoppingLists(where: String, sort: [String!], limit: Int, offset: Int): ShoppingListQueryResult! +} + +type ShoppingListQueryResult { + offset: Int! + count: Int! + total: Long! + results: [ShoppingList!]! +} + +input ShoppingListUpdateAction { + addLineItem: AddShoppingListLineItem + addTextLineItem: AddShoppingListTextLineItem + changeLineItemQuantity: ChangeShoppingListLineItemQuantity + changeLineItemsOrder: ChangeShoppingListLineItemsOrder + changeName: ChangeShoppingListName + changeTextLineItemName: ChangeShoppingListTextLineItemName + changeTextLineItemQuantity: ChangeShoppingListTextLineItemQuantity + changeTextLineItemsOrder: ChangeShoppingListTextLineItemsOrder + removeLineItem: RemoveShoppingListLineItem + removeTextLineItem: RemoveShoppingListTextLineItem + setAnonymousId: SetShoppingListAnonymousId + setCustomField: SetShoppingListCustomField + setCustomType: SetShoppingListCustomType + setCustomer: SetShoppingListCustomer + setDeleteDaysAfterLastModification: SetShoppingListDeleteDaysAfterLastModification + setDescription: SetShoppingListDescription + setKey: SetShoppingListKey + setLineItemCustomField: SetShoppingListLineItemCustomField + setLineItemCustomType: SetShoppingListLineItemCustomType + setSlug: SetShoppingListSlug + setTextLineItemCustomField: SetShoppingListTextLineItemCustomField + setTextLineItemCustomType: SetShoppingListTextLineItemCustomType + setTextLineItemDescription: SetShoppingListTextLineItemDescription +} + +input SimpleAttributeTypeDraft { + dummy: String +} + +"""Describes how this discount interacts with other discounts""" +enum StackingMode { + """Don’t apply any more matching discounts after this one.""" + StopAfterThisDiscount + + """ + Default. Continue applying other matching discounts after applying this one. + """ + Stacking +} + +"""[State](http://dev.commercetools.com/http-api-projects-states.html)""" +type State implements Versioned { + id: String! + version: Long! + key: String + type: StateType! + roles: [StateRole!]! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!] + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + descriptionAllLocales: [LocalizedString!] + builtIn: Boolean! + transitionsRef: [Reference!] + transitions: [State!] + initial: Boolean! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +type StateQueryResult { + offset: Int! + count: Int! + total: Long! + results: [State!]! +} + +enum StateRole { + Return + ReviewIncludedInStatistics +} + +enum StateType { + OrderState + ProductState + ReviewState + PaymentState + LineItemState +} + +"""[BETA] Stores allow defining different contexts for a project.""" +type Store implements Versioned { + id: String! + version: Long! + key: String! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!] + languages: [Locale!] + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +type StoreQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Store!]! +} + +input StoreUpdateAction { + setLanguages: SetStoreLanguages + setName: SetStoreName +} + +type StringAttribute implements Attribute { + value: String! + name: String! +} + +type StringField implements CustomField { + value: String! + name: String! +} + +type StringType implements FieldType { + name: String! +} + +type SubRate { + name: String! + amount: Float! +} + +input SubRateDraft { + name: String! + amount: Float! +} + +""" +Stores information about order synchronization activities (like export or import). +""" +type SyncInfo { + channelRef: Reference! + channel: Channel + externalId: String + syncedAt: DateTime! +} + +enum TaxCalculationMode { + """ + This calculation mode calculates the taxes on the unit price before multiplying with the quantity. + E.g. `($1.08 * 1.19 = $1.2852 -> $1.29 rounded) * 3 = $3.87` + """ + UnitPriceLevel + + """ + Default. This calculation mode calculates the taxes after the unit price is multiplied with the quantity. + E.g. `($1.08 * 3 = $3.24) * 1.19 = $3.8556 -> $3.86 rounded` + """ + LineItemLevel +} + +""" +Tax Categories define how products are to be taxed in different countries. +""" +type TaxCategory implements Versioned { + name: String! + description: String + rates: [TaxRate!]! + key: String + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +input TaxCategoryAddTaxRate { + taxRate: TaxRateDraft! +} + +input TaxCategoryChangeName { + name: String! +} + +input TaxCategoryDraft { + name: String! + description: String + rates: [TaxRateDraft!] + key: String +} + +type TaxCategoryQueryResult { + offset: Int! + count: Int! + total: Long! + results: [TaxCategory!]! +} + +input TaxCategoryRemoveTaxRate { + taxRateId: String! +} + +input TaxCategoryReplaceTaxRate { + taxRateId: String! + taxRate: TaxRateDraft! +} + +input TaxCategorySetDescription { + description: String +} + +input TaxCategoryUpdateAction { + changeName: TaxCategoryChangeName + setDescription: TaxCategorySetDescription + addTaxRate: TaxCategoryAddTaxRate + replaceTaxRate: TaxCategoryReplaceTaxRate + removeTaxRate: TaxCategoryRemoveTaxRate + setKey: SetTaxCategoryKey +} + +type TaxedItemPrice { + totalNet: Money! + totalGross: Money! +} + +type TaxedPrice { + totalNet: Money! + totalGross: Money! + taxPortions: [TaxPortion!]! +} + +enum TaxMode { + """No taxes are added to the cart.""" + Disabled + + """ + The tax amounts and the tax rates as well as the tax portions are set externally per ExternalTaxAmountDraft. + A cart with this tax mode can only be ordered if the cart itself and all line items, all custom line items and + the shipping method have an external tax amount and rate set + """ + ExternalAmount + + """ + The tax rates are set externally per ExternalTaxRateDraft. A cart with this tax mode can only be ordered if all + line items, all custom line items and the shipping method have an external tax rate set. The totalNet and + totalGross as well as the taxPortions fields are calculated by the platform according to the taxRoundingMode. + """ + External + + """ + The tax rates are selected by the platform from the TaxCategories based on the cart shipping address. + The totalNet and totalGross as well as the taxPortions fields are calculated by the platform according to the + taxRoundingMode. + """ + Platform +} + +""" +Represents the portions that sum up to the totalGross field of a TaxedPrice. The portions are calculated +from the TaxRates. If a tax rate has SubRates, they are used and can be identified by name. Tax portions +from line items that have the same rate and name will be accumulated to the same tax portion. +""" +type TaxPortion { + rate: Float! + amount: Money! + name: String +} + +input TaxPortionDraft { + name: String + rate: Float! + amount: MoneyInput! +} + +type TaxRate { + name: String! + amount: Float! + includedInPrice: Boolean! + country: Country! + state: String + id: String + subRates: [SubRate!]! +} + +input TaxRateDraft { + name: String! + amount: Float + includedInPrice: Boolean! + country: Country! + state: String + subRates: [SubRateDraft!] = [] +} + +type TextAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +""" +UI hint telling what kind of edit control should be displayed for a text attribute. +""" +enum TextInputHint { + MultiLine + SingleLine +} + +type TextLineItem { + id: String! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + descriptionAllLocales: [LocalizedString!] + quantity: Int! + custom: CustomFieldsType + addedAt: DateTime! +} + +input TextLineItemDraft { + name: [LocalizedStringItemInputType!]! + description: [LocalizedStringItemInputType!] + quantity: Int = 1 + custom: CustomFieldsDraft + addedAt: DateTime +} + +"""Time is a scalar value that represents an ISO8601 formatted time.""" +scalar Time + +type TimeAttribute implements Attribute { + value: Time! + name: String! +} + +type TimeAttributeDefinitionType implements AttributeDefinitionType { + name: String! +} + +type TimeField implements CustomField { + value: Time! + name: String! +} + +type TimeType implements FieldType { + name: String! +} + +type TrackingData { + trackingId: String + carrier: String + provider: String + providerTransaction: String + isReturn: Boolean! +} + +input TrackingDataDraftType { + trackingId: String + carrier: String + provider: String + providerTransaction: String + isReturn: Boolean = false +} + +type Transaction { + id: String! + timestamp: DateTime + type: TransactionType + amount: Money! + interactionId: String + state: TransactionState! +} + +enum TransactionState { + Failure + Success + Pending + Initial +} + +enum TransactionType { + Chargeback + Refund + Charge + CancelAuthorization + Authorization +} + +input TransitionOrderCustomLineItemState { + customLineItemId: String! + quantity: Long! + fromState: ResourceIdentifierInput! + toState: ResourceIdentifierInput! + actualTransitionDate: DateTime +} + +input TransitionOrderLineItemState { + lineItemId: String! + quantity: Long! + fromState: ResourceIdentifierInput! + toState: ResourceIdentifierInput! + actualTransitionDate: DateTime +} + +input TransitionOrderState { + state: ResourceIdentifierInput! + force: Boolean = false +} + +input TransitionProductState { + state: ReferenceInput! + force: Boolean +} + +interface Type { + typeRef: Reference! + type: TypeDefinition +} + +""" +Types define the structure of custom fields which can be attached to different entities throughout the platform. +""" +type TypeDefinition implements Versioned { + key: String! + name( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + description( + """ + String is defined for different locales. This argument specifies the desired locale. + """ + locale: Locale + + """ + List of languages the client is able to understand, and which locale variant is preferred. + """ + acceptLanguage: [Locale!] + ): String + nameAllLocales: [LocalizedString!]! + descriptionAllLocales: [LocalizedString!] + resourceTypeIds: [String!]! + fieldDefinitions( + """ + The names of the custom field definitions to include. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + includeNames: [String!] + + """ + The names of the custom field definitions to exclude. + + If neither `includeNames` nor `excludeNames` are provided, then all custom fields are returned. + """ + excludeNames: [String!] + ): [FieldDefinition!]! + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +type TypeDefinitionQueryResult { + offset: Int! + count: Int! + total: Long! + results: [TypeDefinition!]! +} + +input UnpublishProduct { + dummy: String +} + +input UpdateCartItemShippingAddress { + address: AddressInput! +} + +input UpdateOrderItemShippingAddress { + address: AddressInput! +} + +input UpdateOrderSyncInfo { + channel: ResourceIdentifierInput! + syncedAt: DateTime + externalId: String +} + +""" +Versioned object have an ID and version and modification. Every update of this object changes it's version. +""" +interface Versioned { + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +input WhitespaceSuggestTokenizerInput { + dummy: String +} + +""" +YearMonth is a scalar value that represents an ISO8601 formatted year and month. +""" +scalar YearMonth + +"""Zones allow defining ShippingRates for specific Locations.""" +type Zone implements Versioned { + name: String! + key: String + description: String + locations: [Location!]! + id: String! + version: Long! + createdAt: DateTime! + lastModifiedAt: DateTime! + createdBy: Initiator + lastModifiedBy: Initiator +} + +input ZoneLocation { + country: Country! + state: String +} + +type ZoneQueryResult { + offset: Int! + count: Int! + total: Long! + results: [Zone!]! +} + +type ZoneRate { + shippingRates: [ShippingRate!]! + zoneRef: Reference + zone: Zone +} + +input ZoneRateDraft { + zone: ResourceIdentifierInput! + shippingRates: [ShippingRateDraft!] = [] +} + +input ZoneUpdateAction { + addLocation: AddZoneLocation + changeName: ChangeZoneName + removeLocation: RemoveZoneLocation + setDescription: SetZoneDescription + setKey: SetZoneKey +} diff --git a/packages/commercetools/api-client/src/api-extractor-data.ts b/packages/commercetools/api-client/src/api-extractor-data.ts new file mode 100644 index 0000000000..fb829ad5a2 --- /dev/null +++ b/packages/commercetools/api-client/src/api-extractor-data.ts @@ -0,0 +1,15 @@ +/** + * `api-client` for commercetools integration for Vue Storefront 2. + * + * @remarks + * The `@vue-storefront/commercetools-api` library includes everything needed to fetch data from the + * commercetools eCommerce platform. This includes API client configuration, API endpoints, and + * GraphQL types and fragments. + * + * @packageDocumentation + */ + +import * as apiMethods from './api'; + +export * from './index'; +export { apiMethods }; diff --git a/packages/commercetools/api-client/src/api/addToCart/index.ts b/packages/commercetools/api-client/src/api/addToCart/index.ts new file mode 100644 index 0000000000..8c24de9343 --- /dev/null +++ b/packages/commercetools/api-client/src/api/addToCart/index.ts @@ -0,0 +1,30 @@ +import { Context, CustomQuery } from '@vue-storefront/core'; +import updateCart from './../updateCart'; +import { CartDetails, CartResponse } from './../../types/Api'; +import { ProductVariant } from './../../types/GraphQL'; +import { createAddLineItemAction } from './../../helpers/cart/actions'; + +const addToCart = async ( + context: Context, + { id, version }: CartDetails, + params: { + product: ProductVariant; + quantity: number; + supplyChannel?: string; + distributionChannel?: string; + }, + customQuery?: CustomQuery +): Promise => { + + return await updateCart( + context, + { + id, + version, + actions: [createAddLineItemAction(params)] + }, + customQuery + ); +}; + +export default addToCart; diff --git a/packages/commercetools/api-client/src/api/applyCartCoupon/index.ts b/packages/commercetools/api-client/src/api/applyCartCoupon/index.ts new file mode 100644 index 0000000000..76b300fb81 --- /dev/null +++ b/packages/commercetools/api-client/src/api/applyCartCoupon/index.ts @@ -0,0 +1,19 @@ +import { CustomQuery } from '@vue-storefront/core'; +import updateCart from '../updateCart'; +import { CartDetails, CartResponse } from '../../types/Api'; +import { addDiscountCodeAction } from '../../helpers/cart/actions'; + +const applyCartCoupon = async ( + settings, + { id, version }: CartDetails, + discountCode: string, + customQuery?: CustomQuery +): Promise => { + return await updateCart(settings, { + id, + version, + actions: [addDiscountCodeAction(discountCode)] + }, customQuery); +}; + +export default applyCartCoupon; diff --git a/packages/commercetools/api-client/src/api/createCart/defaultMutation.ts b/packages/commercetools/api-client/src/api/createCart/defaultMutation.ts new file mode 100644 index 0000000000..3ee3847f1b --- /dev/null +++ b/packages/commercetools/api-client/src/api/createCart/defaultMutation.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { CartFragment } from './../../fragments'; + +export default gql` + ${CartFragment} + + mutation createCart($draft: MyCartDraft!, $locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!, $storeKey: KeyReferenceInput) { + cart: createMyCart(draft: $draft, storeKey: $storeKey) { + ...DefaultCart + } + } +`; diff --git a/packages/commercetools/api-client/src/api/createCart/index.ts b/packages/commercetools/api-client/src/api/createCart/index.ts new file mode 100644 index 0000000000..1f8c2fc63b --- /dev/null +++ b/packages/commercetools/api-client/src/api/createCart/index.ts @@ -0,0 +1,36 @@ +import defaultMutation from './defaultMutation'; +import { CartData } from './../../types/Api'; +import gql from 'graphql-tag'; +import { CustomQuery } from '@vue-storefront/core'; +import { getStoreKey } from '../../helpers/utils'; + +const createCart = async (context, cartDraft: CartData = {}, customQuery?: CustomQuery) => { + const { locale, acceptLanguage, currency, country, store, inventoryMode } = context.config; + + const defaultVariables = { + acceptLanguage, + locale, + currency, + draft: { + currency, + country, + inventoryMode, + ...cartDraft + }, + ...getStoreKey(store) + }; + + const { createCart: createCartGql } = context.extendQuery( + customQuery, { createCart: { query: defaultMutation, variables: defaultVariables } } + ); + + const request = await context.client.mutate({ + mutation: gql`${createCartGql.query}`, + variables: createCartGql.variables, + fetchPolicy: 'no-cache' + }); + + return request; +}; + +export default createCart; diff --git a/packages/commercetools/api-client/src/api/createMyOrderFromCart/defaultMutation.ts b/packages/commercetools/api-client/src/api/createMyOrderFromCart/defaultMutation.ts new file mode 100644 index 0000000000..ebaed06d73 --- /dev/null +++ b/packages/commercetools/api-client/src/api/createMyOrderFromCart/defaultMutation.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { OrderFragment } from '../../fragments'; + +export default gql` + ${OrderFragment} + + mutation createMyOrderFromCart($draft: OrderMyCartCommand!, $locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!, $storeKey: KeyReferenceInput) { + order: createMyOrderFromCart(draft: $draft, storeKey: $storeKey) { + ...DefaultOrder + } + } +`; diff --git a/packages/commercetools/api-client/src/api/createMyOrderFromCart/index.ts b/packages/commercetools/api-client/src/api/createMyOrderFromCart/index.ts new file mode 100644 index 0000000000..636651e96d --- /dev/null +++ b/packages/commercetools/api-client/src/api/createMyOrderFromCart/index.ts @@ -0,0 +1,30 @@ +import { OrderMyCartCommand } from '../../types/GraphQL'; +import defaultMutation from './defaultMutation'; +import { OrderMutationResponse } from '../../types/Api'; +import gql from 'graphql-tag'; +import { CustomQuery } from '@vue-storefront/core'; +import { getStoreKey } from '../../helpers/utils'; + +const createMyOrderFromCart = async (context, draft: OrderMyCartCommand, customQuery?: CustomQuery): Promise => { + const { locale, acceptLanguage, currency, store } = context.config; + + const defaultVariables = { + locale, + acceptLanguage, + currency, + draft, + ...getStoreKey(store) + }; + + const { createMyOrderFromCart } = context.extendQuery( + customQuery, { createMyOrderFromCart: { query: defaultMutation, variables: defaultVariables } } + ); + + return await context.client.mutate({ + mutation: gql`${createMyOrderFromCart.query}`, + variables: createMyOrderFromCart.variables, + fetchPolicy: 'no-cache' + }); +}; + +export default createMyOrderFromCart; diff --git a/packages/commercetools/api-client/src/api/customerChangeMyPassword/defaultMutation.ts b/packages/commercetools/api-client/src/api/customerChangeMyPassword/defaultMutation.ts new file mode 100644 index 0000000000..69957b050f --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerChangeMyPassword/defaultMutation.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { CustomerFragment } from '../../fragments'; + +export default gql` + ${CustomerFragment} + + mutation customerChangeMyPassword($version: Long!, $currentPassword: String!, $newPassword: String!, $storeKey: KeyReferenceInput) { + user: customerChangeMyPassword(version: $version, currentPassword: $currentPassword, newPassword: $newPassword, storeKey: $storeKey) { + ...DefaultCustomer + } + } +`; diff --git a/packages/commercetools/api-client/src/api/customerChangeMyPassword/index.ts b/packages/commercetools/api-client/src/api/customerChangeMyPassword/index.ts new file mode 100644 index 0000000000..ce2625e662 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerChangeMyPassword/index.ts @@ -0,0 +1,16 @@ +import CustomerChangeMyPassword from './defaultMutation'; +import { ChangeMyPasswordResponse } from '../../types/Api'; + +const customerChangeMyPassword = async ({ client }, version: any, currentPassword: string, newPassword: string): Promise => { + return await client.mutate({ + mutation: CustomerChangeMyPassword, + variables: { + version, + currentPassword, + newPassword + }, + fetchPolicy: 'no-cache' + }) as ChangeMyPasswordResponse; +}; + +export default customerChangeMyPassword; diff --git a/packages/commercetools/api-client/src/api/customerCreatePasswordResetToken/defaultMutation.ts b/packages/commercetools/api-client/src/api/customerCreatePasswordResetToken/defaultMutation.ts new file mode 100644 index 0000000000..c6e8c95b12 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerCreatePasswordResetToken/defaultMutation.ts @@ -0,0 +1,43 @@ +import gql from 'graphql-tag'; + +export default gql` + mutation customerCreatePasswordResetToken($email: String!, $storeKey: KeyReferenceInput) { + customerCreatePasswordResetToken(email: $email, storeKey: $storeKey) { + customerId + expiresAt + value + id + version + createdAt + lastModifiedAt + createdBy { + isPlatformClient + externalUserId + anonymousId + clientId + customerRef { + typeId + id + } + userRef { + typeId + id + } + } + lastModifiedBy { + isPlatformClient + externalUserId + anonymousId + clientId + customerRef { + typeId + id + } + userRef { + typeId + id + } + } + } + } +`; diff --git a/packages/commercetools/api-client/src/api/customerCreatePasswordResetToken/index.ts b/packages/commercetools/api-client/src/api/customerCreatePasswordResetToken/index.ts new file mode 100644 index 0000000000..c192420b68 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerCreatePasswordResetToken/index.ts @@ -0,0 +1,32 @@ +import defaultQuery from './defaultMutation'; +import { CreatePasswordResetTokenResponse } from 'src/types/Api'; +import { Context, CustomQuery, Logger } from '@vue-storefront/core'; +import gql from 'graphql-tag'; + +const customerCreatePasswordResetToken = async (context: Context, email: string, customQuery?: CustomQuery): Promise => { + const { locale, acceptLanguage } = context.config; + const defaultVariables = email + ? { + locale, + acceptLanguage, + email + } + : { acceptLanguage }; + + const { customerCreatePasswordResetToken } = context.extendQuery( + customQuery, { customerCreatePasswordResetToken: { query: defaultQuery, variables: defaultVariables } } + ); + + try { + return await context.client.mutate({ + mutation: gql`${customerCreatePasswordResetToken.query}`, + variables: customerCreatePasswordResetToken.variables, + fetchPolicy: 'no-cache' + }) as CreatePasswordResetTokenResponse; + } catch (error) { + Logger.error(`Cannot create password reset token. Error: ${error}`); + throw error; + } +}; + +export default customerCreatePasswordResetToken; diff --git a/packages/commercetools/api-client/src/api/customerResetPassword/defaultMutation.ts b/packages/commercetools/api-client/src/api/customerResetPassword/defaultMutation.ts new file mode 100644 index 0000000000..78cd9d4b4a --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerResetPassword/defaultMutation.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { CustomerFragment } from '../../fragments'; + +export default gql` + ${CustomerFragment} + + mutation customerResetPassword($tokenValue: String!, $newPassword: String!, $storeKey: KeyReferenceInput) { + customerResetPassword(tokenValue: $tokenValue, newPassword: $newPassword, storeKey: $storeKey) { + ...DefaultCustomer + } + } +`; diff --git a/packages/commercetools/api-client/src/api/customerResetPassword/index.ts b/packages/commercetools/api-client/src/api/customerResetPassword/index.ts new file mode 100644 index 0000000000..ba918724d4 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerResetPassword/index.ts @@ -0,0 +1,33 @@ +import defaultQuery from './defaultMutation'; +import { ResetPasswordResponse } from 'src/types/Api'; +import { CustomQuery, Logger } from '@vue-storefront/core'; +import gql from 'graphql-tag'; + +const customerResetPassword = async (context, tokenValue: string, newPassword: string, customQuery?: CustomQuery): Promise => { + const { locale, acceptLanguage } = context.config; + const defaultVariables = tokenValue && newPassword + ? { + locale, + acceptLanguage, + tokenValue, + newPassword + } + : { acceptLanguage }; + + const { customerResetPassword } = context.extendQuery( + customQuery, { customerResetPassword: { query: defaultQuery, variables: defaultVariables } } + ); + + try { + return await context.client.mutate({ + mutation: gql`${customerResetPassword.query}`, + variables: customerResetPassword.variables, + fetchPolicy: 'no-cache' + }) as ResetPasswordResponse; + } catch (error) { + Logger.error(`Cannot set new password after reset. Error: ${error}`); + throw error; + } +}; + +export default customerResetPassword; diff --git a/packages/commercetools/api-client/src/api/customerSignMeIn/defaultMutation.ts b/packages/commercetools/api-client/src/api/customerSignMeIn/defaultMutation.ts new file mode 100644 index 0000000000..aa1b3fc8e9 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerSignMeIn/defaultMutation.ts @@ -0,0 +1,18 @@ +import gql from 'graphql-tag'; +import { CustomerFragment, CartFragment } from '../../fragments'; + +export default gql` + ${CustomerFragment} + ${CartFragment} + + mutation customerSignMeIn($draft: CustomerSignMeInDraft!, $locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!, $storeKey: KeyReferenceInput) { + user: customerSignMeIn(draft: $draft, storeKey: $storeKey) { + customer { + ...DefaultCustomer + } + cart { + ...DefaultCart + } + } + } +`; diff --git a/packages/commercetools/api-client/src/api/customerSignMeIn/index.ts b/packages/commercetools/api-client/src/api/customerSignMeIn/index.ts new file mode 100644 index 0000000000..44ddd36d73 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerSignMeIn/index.ts @@ -0,0 +1,22 @@ +import { CustomerSignMeInDraft } from '../../types/GraphQL'; +import CustomerSignMeInMutation from './defaultMutation'; +import { SignInResponse } from './../../types/Api'; + +const customerSignMeIn = async (context, draft: CustomerSignMeInDraft): Promise => { + const { locale, acceptLanguage, currency } = context.config; + + const loginResponse = await context.client.mutate({ + mutation: CustomerSignMeInMutation, + variables: { + draft, + locale, + acceptLanguage, + currency + }, + fetchPolicy: 'no-cache' + }) as SignInResponse; + + return loginResponse; +}; + +export default customerSignMeIn; diff --git a/packages/commercetools/api-client/src/api/customerSignMeUp/defaultMutation.ts b/packages/commercetools/api-client/src/api/customerSignMeUp/defaultMutation.ts new file mode 100644 index 0000000000..af852c90b3 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerSignMeUp/defaultMutation.ts @@ -0,0 +1,18 @@ +import gql from 'graphql-tag'; +import { CustomerFragment, CartFragment } from './../../fragments'; + +export default gql` + ${CustomerFragment} + ${CartFragment} + + mutation customerSignMeUp($draft: CustomerSignMeUpDraft!, $locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!, $storeKey: KeyReferenceInput) { + user: customerSignMeUp(draft: $draft, storeKey: $storeKey) { + customer { + ...DefaultCustomer + } + cart { + ...DefaultCart + } + } + } +`; diff --git a/packages/commercetools/api-client/src/api/customerSignMeUp/index.ts b/packages/commercetools/api-client/src/api/customerSignMeUp/index.ts new file mode 100644 index 0000000000..77e5f034b8 --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerSignMeUp/index.ts @@ -0,0 +1,20 @@ +import { CustomerSignMeUpDraft } from '../../types/GraphQL'; +import CustomerSignMeUpMutation from './defaultMutation'; +import { SignInResponse } from '../../types/Api'; + +const customerSignMeUp = async (context, draft: CustomerSignMeUpDraft): Promise => { + const { locale, acceptLanguage, currency } = context.config; + + return await context.client.mutate({ + mutation: CustomerSignMeUpMutation, + variables: { + draft, + locale, + acceptLanguage, + currency + }, + fetchPolicy: 'no-cache' + }) as SignInResponse; +}; + +export default customerSignMeUp; diff --git a/packages/commercetools/api-client/src/api/customerSignOut/index.ts b/packages/commercetools/api-client/src/api/customerSignOut/index.ts new file mode 100644 index 0000000000..1bfa02657b --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerSignOut/index.ts @@ -0,0 +1,11 @@ +const customerSignOut = async ({ config, client }) => { + if (config.auth.onTokenRemove) { + config.auth.onTokenRemove(); + } + + if (client.tokenProvider) { + client.tokenProvider.invalidateTokenInfo(); + } +}; + +export default customerSignOut; diff --git a/packages/commercetools/api-client/src/api/customerUpdateMe/defaultMutation.ts b/packages/commercetools/api-client/src/api/customerUpdateMe/defaultMutation.ts new file mode 100644 index 0000000000..0c252a075b --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerUpdateMe/defaultMutation.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { CustomerFragment } from '../../fragments'; + +export default gql` + ${CustomerFragment} + + mutation customerUpdateMe($version: Long!, $actions: [MyCustomerUpdateAction!]!, $storeKey: KeyReferenceInput) { + user: updateMyCustomer(version: $version, actions: $actions, storeKey: $storeKey) { + ...DefaultCustomer + } + } +`; diff --git a/packages/commercetools/api-client/src/api/customerUpdateMe/index.ts b/packages/commercetools/api-client/src/api/customerUpdateMe/index.ts new file mode 100644 index 0000000000..aeba173fad --- /dev/null +++ b/packages/commercetools/api-client/src/api/customerUpdateMe/index.ts @@ -0,0 +1,21 @@ +import { changeCustomerEmailAction, setCustomerFirstNameAction, setCustomerLastNameAction } from '../../helpers/customer'; +import CustomerUpdateMeMutation from './defaultMutation'; + +const customerUpdateMe = async ({ client }, currentUser, updatedUserData) => { + const updateResponse = await client.mutate({ + mutation: CustomerUpdateMeMutation, + variables: { + version: currentUser.version, + actions: [ + changeCustomerEmailAction(updatedUserData.email), + setCustomerFirstNameAction(updatedUserData.firstName), + setCustomerLastNameAction(updatedUserData.lastName) + ] + }, + fetchPolicy: 'no-cache' + }); + + return updateResponse.data; +}; + +export default customerUpdateMe; diff --git a/packages/commercetools/api-client/src/api/deleteCart/defaultMutation.ts b/packages/commercetools/api-client/src/api/deleteCart/defaultMutation.ts new file mode 100644 index 0000000000..35c12d94cd --- /dev/null +++ b/packages/commercetools/api-client/src/api/deleteCart/defaultMutation.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { CartFragment } from './../../fragments'; + +export default gql` + ${CartFragment} + + mutation deleteCart($id: String!, $version: Long!, $locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!) { + cart: deleteMyCart(id: $id, version: $version) { + ...DefaultCart + } + } +`; diff --git a/packages/commercetools/api-client/src/api/deleteCart/index.ts b/packages/commercetools/api-client/src/api/deleteCart/index.ts new file mode 100644 index 0000000000..d0ec665018 --- /dev/null +++ b/packages/commercetools/api-client/src/api/deleteCart/index.ts @@ -0,0 +1,28 @@ +import defaultMutation from './defaultMutation'; +import { CartDetails } from './../../types/Api'; +import gql from 'graphql-tag'; +import { CustomQuery } from '@vue-storefront/core'; + +const deleteCart = async (context, { id, version }: CartDetails, customQuery?: CustomQuery) => { + const { locale, acceptLanguage, currency } = context.config; + + const defaultVariables = { + id, + version, + acceptLanguage, + locale, + currency + }; + + const { deleteCart: deleteCartGql } = context.extendQuery( + customQuery, { deleteCart: { query: defaultMutation, variables: defaultVariables } } + ); + + return await context.client.mutate({ + mutation: gql`${deleteCartGql.query}`, + variables: deleteCartGql.variables, + fetchPolicy: 'no-cache' + }); +}; + +export default deleteCart; diff --git a/packages/commercetools/api-client/src/api/getCart/defaultQuery.ts b/packages/commercetools/api-client/src/api/getCart/defaultQuery.ts new file mode 100644 index 0000000000..9b866a4670 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getCart/defaultQuery.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { CartFragment } from './../../fragments'; + +export default gql` + ${CartFragment} + + query getCart($cartId: String!, $locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!) { + cart(id: $cartId) { + ...DefaultCart + } + } +`; diff --git a/packages/commercetools/api-client/src/api/getCart/index.ts b/packages/commercetools/api-client/src/api/getCart/index.ts new file mode 100644 index 0000000000..4b7e95b09f --- /dev/null +++ b/packages/commercetools/api-client/src/api/getCart/index.ts @@ -0,0 +1,14 @@ +import { CartQueryResponse } from '../../types/Api'; +import defaultQuery from './defaultQuery'; + +const getCart = async ({ config, client }, cartId: string): Promise => { + const { locale, acceptLanguage, currency } = config; + + return await client.query({ + query: defaultQuery, + variables: { cartId, locale, acceptLanguage, currency }, + fetchPolicy: 'no-cache' + }); +}; + +export default getCart; diff --git a/packages/commercetools/api-client/src/api/getCategory/defaultQuery.ts b/packages/commercetools/api-client/src/api/getCategory/defaultQuery.ts new file mode 100644 index 0000000000..36c9492c37 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getCategory/defaultQuery.ts @@ -0,0 +1,53 @@ +import gql from 'graphql-tag'; + +export default gql` + fragment Children on Category { + id + slug(acceptLanguage: $acceptLanguage) + name(acceptLanguage: $acceptLanguage) + childCount + } + + fragment DefaultCategory on Category { + id + slug(acceptLanguage: $acceptLanguage) + name(acceptLanguage: $acceptLanguage) + childCount + children { + ...Children + children { + ...Children + children { + ...Children + } + } + } + } + + query categories($where: String, $sort: [String!], $limit: Int, $offset: Int, $acceptLanguage: [Locale!]) { + categories(where: $where, sort: $sort, limit: $limit, offset: $offset) { + offset + count + total + results { + id + slug(acceptLanguage: $acceptLanguage) + name(acceptLanguage: $acceptLanguage) + description(acceptLanguage: $acceptLanguage) + childCount + parent { + ...DefaultCategory + parent { + ...DefaultCategory + parent { + ...DefaultCategory + } + } + } + children { + ...DefaultCategory + } + } + } + } +`; diff --git a/packages/commercetools/api-client/src/api/getCategory/index.ts b/packages/commercetools/api-client/src/api/getCategory/index.ts new file mode 100644 index 0000000000..a3d2711b3c --- /dev/null +++ b/packages/commercetools/api-client/src/api/getCategory/index.ts @@ -0,0 +1,34 @@ +import gql from 'graphql-tag'; +import defaultQuery from './defaultQuery'; +import { CategoryQueryResult } from '../../types/GraphQL'; +import { buildCategoryWhere } from '../../helpers/search'; +import ApolloClient from 'apollo-client'; +import { CustomQuery } from '@vue-storefront/core'; + +export interface CategoryData { + categories: CategoryQueryResult; +} + +const getCategory = async (context, params, customQuery?: CustomQuery) => { + const { acceptLanguage } = context.config; + const defaultVariables = params ? { + where: buildCategoryWhere(context.config, params), + limit: params.limit, + offset: params.offset, + acceptLanguage + } : { acceptLanguage }; + + const { categories } = context.extendQuery(customQuery, + { categories: { query: defaultQuery, variables: defaultVariables } } + ); + + const request = await (context.client as ApolloClient).query({ + query: gql`${categories.query}`, + variables: categories.variables, + fetchPolicy: 'no-cache' + }); + + return request; +}; + +export default getCategory; diff --git a/packages/commercetools/api-client/src/api/getInventory/defaultQuery.ts b/packages/commercetools/api-client/src/api/getInventory/defaultQuery.ts new file mode 100644 index 0000000000..ad9e2719f4 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getInventory/defaultQuery.ts @@ -0,0 +1,24 @@ +import gql from 'graphql-tag'; +import { InventoryEntriesQueryResultFragment } from './../../fragments'; + +const inventoryEntriesData = gql` + ${InventoryEntriesQueryResultFragment} + + query inventoryEntries( + $where: String + $sort: [String!] + $limit: Int + $offset: Int + ) { + inventoryEntries( + where: $where + sort: $sort + limit: $limit + offset: $offset + ) { + ...InventoryEntriesQueryResultFragment + } + } +`; + +export { inventoryEntriesData }; diff --git a/packages/commercetools/api-client/src/api/getInventory/index.ts b/packages/commercetools/api-client/src/api/getInventory/index.ts new file mode 100644 index 0000000000..bb0b9ff4f7 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getInventory/index.ts @@ -0,0 +1,33 @@ +import { Context, CustomQuery } from '@vue-storefront/core'; +import ApolloClient from 'apollo-client'; +import gql from 'graphql-tag'; + +import { ApiResponseWrapper } from '../../types/Api'; +import { InventoryEntryQueryResult } from '../../types/GraphQL'; +import { inventoryEntriesData } from './defaultQuery'; +import { buildInventoryEntriesWhere } from '../../helpers/search'; + +export interface GetInventoryParams { + sku: string; + customQuery: CustomQuery; +} + +export default async function getInventory(context: Context, params?: GetInventoryParams): Promise { + const variables = { + where: buildInventoryEntriesWhere(context.config, params) + }; + + const { customQuery } = Object(params); + + const { getInventoryEntriesData } = context.extendQuery(customQuery, { + getInventoryEntriesData: { query: inventoryEntriesData, variables } + }); + + const response = await (context.client as ApolloClient).query>({ + query: gql`${getInventoryEntriesData.query}`, + variables: getInventoryEntriesData.variables, + fetchPolicy: 'no-cache' + }); + + return response.data.inventory; +} diff --git a/packages/commercetools/api-client/src/api/getMe/defaultQuery.ts b/packages/commercetools/api-client/src/api/getMe/defaultQuery.ts new file mode 100644 index 0000000000..584a76d46b --- /dev/null +++ b/packages/commercetools/api-client/src/api/getMe/defaultQuery.ts @@ -0,0 +1,32 @@ +import gql from 'graphql-tag'; +import { CartFragment, CustomerFragment } from './../../fragments'; + +const basicProfile = gql` + ${CartFragment} + + query getBasicProfile($locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!) { + me { + activeCart { + ...DefaultCart + } + } + } +`; + +const fullProfile = gql` + ${CartFragment} + ${CustomerFragment} + + query getFullProfile($locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!) { + me { + activeCart { + ...DefaultCart + } + customer { + ...DefaultCustomer + } + } + } +`; + +export { basicProfile, fullProfile }; diff --git a/packages/commercetools/api-client/src/api/getMe/index.ts b/packages/commercetools/api-client/src/api/getMe/index.ts new file mode 100644 index 0000000000..8c40e128e8 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getMe/index.ts @@ -0,0 +1,41 @@ +import { basicProfile, fullProfile } from './defaultQuery'; +import gql from 'graphql-tag'; +import ApolloClient from 'apollo-client'; +import { CustomQuery } from '@vue-storefront/core'; + +export interface GetMeParams { + customer?: boolean; +} + +export interface OrdersData { + // TODO: When https://github.com/DivanteLtd/vue-storefront/issues/4900 is finished, please change "me: any" to "me: Pick" + me: any; +} + +const getMe = async (context, params: GetMeParams = {}, customQuery?: CustomQuery) => { + const { locale, acceptLanguage, currency } = context.config; + const { customer }: GetMeParams = params; + + const defaultVariables = { + locale, + acceptLanguage, + currency + }; + + const { getBasicProfile, getFullProfile } = context.extendQuery(customQuery, { + getBasicProfile: { query: basicProfile, variables: defaultVariables }, + getFullProfile: { query: fullProfile, variables: defaultVariables } + }); + + const profile = customer ? getFullProfile : getBasicProfile; + + const request = await (context.client as ApolloClient).query({ + query: gql`${profile.query}`, + variables: profile.variables, + fetchPolicy: 'no-cache' + }); + + return request; +}; + +export default getMe; diff --git a/packages/commercetools/api-client/src/api/getOrders/defaultQuery.ts b/packages/commercetools/api-client/src/api/getOrders/defaultQuery.ts new file mode 100644 index 0000000000..8b9060b4de --- /dev/null +++ b/packages/commercetools/api-client/src/api/getOrders/defaultQuery.ts @@ -0,0 +1,19 @@ +import gql from 'graphql-tag'; +import { OrderFragment } from '../../fragments'; + +export default gql` + ${OrderFragment} + + query getMyOrders($where: String, $sort: [String!], $limit: Int, $offset: Int, $locale: Locale!, $acceptLanguage: [Locale!], $currency: Currency!) { + me { + orders(where: $where, sort: $sort, limit: $limit, offset: $offset) { + results { + ...DefaultOrder + } + total + offset + count + } + } + } +`; diff --git a/packages/commercetools/api-client/src/api/getOrders/index.ts b/packages/commercetools/api-client/src/api/getOrders/index.ts new file mode 100644 index 0000000000..3b4b10bf37 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getOrders/index.ts @@ -0,0 +1,37 @@ +import { MeQueryInterface } from '../../index'; +import defaultQuery from './defaultQuery'; +import { buildOrderWhere } from '../../helpers/search'; +import gql from 'graphql-tag'; +import ApolloClient from 'apollo-client'; +import { CustomQuery } from '@vue-storefront/core'; + +interface OrdersData { + me: Pick; +} + +const getOrders = async (context, params, customQuery?: CustomQuery) => { + const { locale, acceptLanguage, currency } = context.config; + + const defaultVariables = { + where: buildOrderWhere(params), + sort: params.sort, + limit: params.limit, + offset: params.offset, + acceptLanguage, + locale, + currency + }; + + const { getMyOrders } = context.extendQuery( + customQuery, { getMyOrders: { query: defaultQuery, variables: defaultVariables } } + ); + + const request = await (context.client as ApolloClient).query({ + query: gql`${getMyOrders.query}`, + variables: getMyOrders.variables, + fetchPolicy: 'no-cache' + }); + return request; +}; + +export default getOrders; diff --git a/packages/commercetools/api-client/src/api/getProduct/defaultQuery.ts b/packages/commercetools/api-client/src/api/getProduct/defaultQuery.ts new file mode 100644 index 0000000000..7985562158 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getProduct/defaultQuery.ts @@ -0,0 +1,131 @@ +import gql from 'graphql-tag'; +import { ChannelFragment, ProductPriceFragment } from './../../fragments'; + +export default gql` + ${ProductPriceFragment} + ${ChannelFragment} + + fragment Images on ProductVariant { + images { + url + label + } + } + + fragment Price on ProductVariant { + price(currency: $currency, country: $country, channelId: $channelId) { + ...DefaultProductPrice + } + } + + fragment Attributes on ProductVariant { + attributesRaw { + name + value + attributeDefinition { + type { + name + } + label(locale: $locale) + } + } + } + + fragment Availability on ProductVariant { + availability { + noChannel { + isOnStock + restockableInDays + availableQuantity + } + channels( + includeChannelIds: $includeChannelIds + excludeChannelIds: $excludeChannelIds + limit: $channelLimit + offset: $channelOffset + ) { + limit + offset + total + results { + channelRef { + id + } + availability { + isOnStock + restockableInDays + availableQuantity + } + channel { + ...ChannelFragment + } + } + } + } + } + + fragment DefaultVariant on ProductVariant { + id + sku + ...Images + ...Price + ...Attributes + ...Availability + } + + query products( + $where: String + $sort: [String!] + $limit: Int + $offset: Int + $skus: [String!] + $locale: Locale! + $acceptLanguage: [Locale!] + $currency: Currency! + $country: Country! + $channelId: String + $includeChannelIds: [String!] + $excludeChannelIds: [String!] + $channelLimit: Int + $channelOffset: Int + ) { + products( + where: $where + sort: $sort + limit: $limit + offset: $offset + skus: $skus + ) { + offset + count + total + results { + id + reviewRatingStatistics { + averageRating, + ratingsDistribution, + count + } + masterData { + current { + slug(acceptLanguage: $acceptLanguage) + name(acceptLanguage: $acceptLanguage) + metaTitle(acceptLanguage: $acceptLanguage) + metaKeywords(acceptLanguage: $acceptLanguage) + metaDescription(acceptLanguage: $acceptLanguage) + description(acceptLanguage: $acceptLanguage) + categoriesRef { + id + } + allVariants { + ...DefaultVariant + } + masterVariant { + ...DefaultVariant + } + } + } + } + } + } +`; diff --git a/packages/commercetools/api-client/src/api/getProduct/index.ts b/packages/commercetools/api-client/src/api/getProduct/index.ts new file mode 100644 index 0000000000..a5d6d986f3 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getProduct/index.ts @@ -0,0 +1,46 @@ +import gql from 'graphql-tag'; +import { ProductQueryResult } from '../../types/GraphQL'; +import defaultQuery from './defaultQuery'; +import { buildProductWhere } from '../../helpers/search'; +import ApolloClient from 'apollo-client'; +import { CustomQuery, Context } from '@vue-storefront/core'; + +export interface ProductData { + products: ProductQueryResult; +} + +const getProduct = async (context: Context, params, customQuery?: CustomQuery) => { + const { locale, acceptLanguage, currency, country } = context.config; + + const defaultVariables = { + where: buildProductWhere(context.config, params), + skus: params.skus, + limit: params.limit, + offset: params.offset, + channelId: params.channelId, + locale, + acceptLanguage, + currency, + country + }; + + const { products } = context.extendQuery( + customQuery, { products: { query: defaultQuery, variables: defaultVariables } } + ); + + try { + const request = await (context.client as ApolloClient).query({ + query: gql`${products.query}`, + variables: products.variables, + // temporary, seems like bug in apollo: + // @link: https://github.com/apollographql/apollo-client/issues/3234 + fetchPolicy: 'no-cache' + }); + return request; + } catch (error) { + throw error.graphQLErrors?.[0] || error.networkError?.result || error; + } + +}; + +export default getProduct; diff --git a/packages/commercetools/api-client/src/api/getShippingMethods/defaultQuery.ts b/packages/commercetools/api-client/src/api/getShippingMethods/defaultQuery.ts new file mode 100644 index 0000000000..58a17071af --- /dev/null +++ b/packages/commercetools/api-client/src/api/getShippingMethods/defaultQuery.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; +import { ShippingMethodFragment } from '../../fragments'; + +export default gql` + ${ShippingMethodFragment} + + query shippingMethods($acceptLanguage: [Locale!], $cartId: String!) { + shippingMethods: shippingMethodsByCart(id: $cartId) { + ...DefaultShippingMethod + } + } +`; diff --git a/packages/commercetools/api-client/src/api/getShippingMethods/index.ts b/packages/commercetools/api-client/src/api/getShippingMethods/index.ts new file mode 100644 index 0000000000..601191309c --- /dev/null +++ b/packages/commercetools/api-client/src/api/getShippingMethods/index.ts @@ -0,0 +1,28 @@ +import defaultQuery from './defaultQuery'; +import { ShippingMethod } from '../../types/GraphQL'; +import gql from 'graphql-tag'; +import ApolloClient from 'apollo-client'; +import { CustomQuery } from '@vue-storefront/core'; + +export interface ShippingMethodData { + shippingMethods: ShippingMethod[]; +} + +const getShippingMethods = async (context, cartId?: string, customQuery?: CustomQuery) => { + const { acceptLanguage } = context.config; + const defaultVariables = { + acceptLanguage, cartId + }; + + const { shippingMethods } = context.extendQuery( + customQuery, { shippingMethods: { query: defaultQuery, variables: defaultVariables } } + ); + + return await (context.client as ApolloClient).query({ + query: gql`${shippingMethods.query}`, + variables: shippingMethods.variables, + fetchPolicy: 'no-cache' + }); +}; + +export default getShippingMethods; diff --git a/packages/commercetools/api-client/src/api/getStores/defaultQuery.ts b/packages/commercetools/api-client/src/api/getStores/defaultQuery.ts new file mode 100644 index 0000000000..6cdd811e84 --- /dev/null +++ b/packages/commercetools/api-client/src/api/getStores/defaultQuery.ts @@ -0,0 +1,25 @@ +import gql from 'graphql-tag'; +import { StoreQueryResultFragment } from './../../fragments'; + +const storesData = gql` + ${StoreQueryResultFragment} + + query stores( + $where: String + $sort: [String!] + $limit: Int + $offset: Int + $locale: Locale! + ) { + stores( + where: $where + sort: $sort + limit: $limit + offset: $offset + ) { + ...StoreQueryResultFragment + } + } +`; + +export { storesData }; diff --git a/packages/commercetools/api-client/src/api/getStores/index.ts b/packages/commercetools/api-client/src/api/getStores/index.ts new file mode 100644 index 0000000000..25c012385c --- /dev/null +++ b/packages/commercetools/api-client/src/api/getStores/index.ts @@ -0,0 +1,28 @@ +import { Context, CustomQuery } from '@vue-storefront/core'; +import ApolloClient from 'apollo-client'; +import gql from 'graphql-tag'; + +import { ApiResponseWrapper } from '../../types/Api'; +import { StoreQueryResult } from '../../types/GraphQL'; +import { storesData } from './defaultQuery'; + +export interface GetStoresParams { + customQuery: CustomQuery; +} + +export default async function getStores(context: Context, params?: GetStoresParams): Promise { + const variables = { locale: context.config.locale }; + const { customQuery } = Object(params); + + const { getStoresData } = context.extendQuery(customQuery, { + getStoresData: { query: storesData, variables } + }); + + const response = await (context.client as ApolloClient).query>({ + query: gql`${getStoresData.query}`, + variables: getStoresData.variables, + fetchPolicy: 'no-cache' + }); + + return response.data.stores; +} diff --git a/packages/commercetools/api-client/src/api/index.ts b/packages/commercetools/api-client/src/api/index.ts new file mode 100644 index 0000000000..4197c828bc --- /dev/null +++ b/packages/commercetools/api-client/src/api/index.ts @@ -0,0 +1,25 @@ +export { default as addToCart } from './addToCart'; +export { default as applyCartCoupon } from './applyCartCoupon'; +export { default as createCart } from './createCart'; +export { default as createMyOrderFromCart } from './createMyOrderFromCart'; +export { default as customerChangeMyPassword } from './customerChangeMyPassword'; +export { default as customerSignMeIn } from './customerSignMeIn'; +export { default as customerSignMeUp } from './customerSignMeUp'; +export { default as customerSignOut } from './customerSignOut'; +export { default as customerUpdateMe } from './customerUpdateMe'; +export { default as deleteCart } from './deleteCart'; +export { default as getCart } from './getCart'; +export { default as getCategory } from './getCategory'; +export { default as getMe } from './getMe'; +export { default as getOrders } from './getOrders'; +export { default as getProduct } from './getProduct'; +export { default as getShippingMethods } from './getShippingMethods'; +export { default as isGuest } from './isGuest'; +export { default as removeCartCoupon } from './removeCartCoupon'; +export { default as removeFromCart } from './removeFromCart'; +export { default as updateCart } from './updateCart'; +export { default as updateCartQuantity } from './updateCartQuantity'; +export { default as updateShippingDetails } from './updateShippingDetails'; +export { default as customerResetPassword } from './customerResetPassword'; +export { default as customerCreatePasswordResetToken } from './customerCreatePasswordResetToken'; +export { default as getStores } from './getStores'; diff --git a/packages/commercetools/api-client/src/api/isGuest/index.ts b/packages/commercetools/api-client/src/api/isGuest/index.ts new file mode 100644 index 0000000000..08e6c8c8e2 --- /dev/null +++ b/packages/commercetools/api-client/src/api/isGuest/index.ts @@ -0,0 +1,18 @@ +import { isAnonymousSession, isUserSession } from '../../helpers/utils'; + +const isGuest = (context) => { + const { client, config } = context; + + if (config.handleIsGuest) { + return config.handleIsGuest(context); + } + + if (client.tokenProvider || context.isProxy) { + const token = config.auth.onTokenRead(); + return !isAnonymousSession(token) && !isUserSession(token); + } + + return false; +}; + +export default isGuest; diff --git a/packages/commercetools/api-client/src/api/removeCartCoupon/index.ts b/packages/commercetools/api-client/src/api/removeCartCoupon/index.ts new file mode 100644 index 0000000000..9a85791eca --- /dev/null +++ b/packages/commercetools/api-client/src/api/removeCartCoupon/index.ts @@ -0,0 +1,20 @@ +import { CustomQuery } from '@vue-storefront/core'; +import updateCart from '../updateCart'; +import { CartDetails, CartResponse } from '../../types/Api'; +import { ReferenceInput } from '../../types/GraphQL'; +import { removeDiscountCodeAction } from '../../helpers/cart/actions'; + +const removeCartCoupon = async ( + context, + { id, version }: CartDetails, + discountCode: ReferenceInput, + customQuery?: CustomQuery +): Promise => { + return await updateCart(context, { + id, + version, + actions: [removeDiscountCodeAction(discountCode)] + }, customQuery); +}; + +export default removeCartCoupon; diff --git a/packages/commercetools/api-client/src/api/removeFromCart/index.ts b/packages/commercetools/api-client/src/api/removeFromCart/index.ts new file mode 100644 index 0000000000..d58e3c90c4 --- /dev/null +++ b/packages/commercetools/api-client/src/api/removeFromCart/index.ts @@ -0,0 +1,24 @@ +import { CustomQuery } from '@vue-storefront/core'; +import updateCart from './../updateCart'; +import { CartDetails, CartResponse } from './../../types/Api'; +import { LineItem } from './../../types/GraphQL'; +import { createRemoveLineItemAction } from './../../helpers/cart/actions'; + +const removeFromCart = async ( + context, + { id, version }: CartDetails, + product: LineItem, + customQuery?: CustomQuery +): Promise => { + return await updateCart( + context, + { + id, + version, + actions: [createRemoveLineItemAction(product)] + }, + customQuery + ); +}; + +export default removeFromCart; diff --git a/packages/commercetools/api-client/src/api/updateCart/defaultMutation.ts b/packages/commercetools/api-client/src/api/updateCart/defaultMutation.ts new file mode 100644 index 0000000000..25d07b23c3 --- /dev/null +++ b/packages/commercetools/api-client/src/api/updateCart/defaultMutation.ts @@ -0,0 +1,20 @@ +import gql from 'graphql-tag'; +import { CartFragment } from './../../fragments'; + +export default gql` + ${CartFragment} + + mutation updateCart( + $id: String!, + $version: Long!, + $actions: [MyCartUpdateAction!]!, + $locale: Locale!, + $acceptLanguage: [Locale!], + $currency: Currency!, + $storeKey: KeyReferenceInput + ) { + cart: updateMyCart(id: $id, version: $version, actions: $actions, storeKey: $storeKey) { + ...DefaultCart + } + } +`; diff --git a/packages/commercetools/api-client/src/api/updateCart/index.ts b/packages/commercetools/api-client/src/api/updateCart/index.ts new file mode 100644 index 0000000000..65b3624b02 --- /dev/null +++ b/packages/commercetools/api-client/src/api/updateCart/index.ts @@ -0,0 +1,56 @@ +import gql from 'graphql-tag'; +import { Logger, CustomQuery } from '@vue-storefront/core'; +import defaultQuery from './defaultMutation'; +import { CartUpdateAction, MyCartUpdateAction } from '../../types/GraphQL'; +import { getStoreKey } from '../../helpers/utils'; + +const VERSION_MISMATCH_CODE = 'ConcurrentModification'; + +export interface UpdateCartParams { + id: string; + version: number; + actions: CartUpdateAction[] | MyCartUpdateAction[]; + versionFallback?: boolean; +} + +const updateCart = async (context, params: UpdateCartParams, customQuery?: CustomQuery) => { + const { locale, acceptLanguage, currency, store } = context.config; + + const userVariables = params ? { + locale, + currency, + ...params + } : {}; + + const defaultVariables = { + ...userVariables, + acceptLanguage, + ...getStoreKey(store) + }; + + const { updateCart: updateCartGql } = context.extendQuery( + customQuery, { updateCart: { query: defaultQuery, variables: defaultVariables } } + ); + + try { + return await context.client.mutate({ + mutation: gql`${updateCartGql.query}`, + variables: updateCartGql.variables, + fetchPolicy: 'no-cache' + }); + } catch (error) { + const canRetry = params.versionFallback ?? true; + const causedByMismatch = error.graphQLErrors?.[0]?.code?.includes(VERSION_MISMATCH_CODE); + const currentVersion = error.graphQLErrors?.[0]?.currentVersion; + + if (!causedByMismatch || !canRetry || !currentVersion) { + throw error; + } + + Logger.debug('Cart version mismatch. Retrying with current version.'); + + return updateCart(context, { ...params, version: currentVersion }, customQuery); + } +}; + +export default updateCart; diff --git a/packages/commercetools/api-client/src/api/updateCartQuantity/index.ts b/packages/commercetools/api-client/src/api/updateCartQuantity/index.ts new file mode 100644 index 0000000000..4002f8e0e8 --- /dev/null +++ b/packages/commercetools/api-client/src/api/updateCartQuantity/index.ts @@ -0,0 +1,24 @@ +import { CustomQuery } from '@vue-storefront/core'; +import updateCart from '../updateCart'; +import { CartDetails, CartResponse } from '../../types/Api'; +import { LineItem } from '../../types/GraphQL'; +import { createChangeLineItemQuantityAction } from '../../helpers/cart/actions'; + +const updateCartQuantity = async ( + context, + { id, version }: CartDetails, + product: LineItem, + customQuery?: CustomQuery +): Promise => { + return await updateCart( + context, + { + id, + version, + actions: [createChangeLineItemQuantityAction(product)] + }, + customQuery + ); +}; + +export default updateCartQuantity; diff --git a/packages/commercetools/api-client/src/api/updateShippingDetails/index.ts b/packages/commercetools/api-client/src/api/updateShippingDetails/index.ts new file mode 100644 index 0000000000..06deb62a51 --- /dev/null +++ b/packages/commercetools/api-client/src/api/updateShippingDetails/index.ts @@ -0,0 +1,17 @@ +import { CustomQuery } from '@vue-storefront/core'; +import updateCart from '../updateCart'; +import { CartResponse } from '../../types/Api'; +import { Cart, Address } from '../../types/GraphQL'; +import { setShippingAddressAction } from '../../helpers/cart/actions'; + +const updateShippingDetails = async (context, cart: Cart, shippingDetails: Address, customQuery?: CustomQuery): Promise => { + const cartResponse = await updateCart(context, { + id: cart.id, + version: cart.version, + actions: [setShippingAddressAction(shippingDetails)] + }, customQuery); + + return cartResponse; +}; + +export default updateShippingDetails; diff --git a/packages/commercetools/api-client/src/fragments/address.ts b/packages/commercetools/api-client/src/fragments/address.ts new file mode 100644 index 0000000000..86dffd1697 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/address.ts @@ -0,0 +1,19 @@ +export const AddressFragment = ` + fragment DefaultAddress on Address { + id + title + firstName + lastName + streetName + streetNumber + postalCode + city + country + state + region + company + apartment + phone + mobile + } +`; diff --git a/packages/commercetools/api-client/src/fragments/cart.ts b/packages/commercetools/api-client/src/fragments/cart.ts new file mode 100644 index 0000000000..ff68c9aa59 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/cart.ts @@ -0,0 +1,81 @@ +import {AddressFragment} from './address'; +import {CustomerFragment} from './customer'; +import {LineItemFragment} from './line-item'; +import {ShippingMethodFragment} from './shipping-method'; + +export const CartFragment = ` + ${AddressFragment} + ${CustomerFragment} + ${LineItemFragment} + ${ShippingMethodFragment} + + fragment DefaultCart on Cart { + id + customerId + customerEmail + lineItems { + ...DefaultLineItem + } + totalPrice { + centAmount + } + shippingAddress { + ...DefaultAddress + } + billingAddress { + ...DefaultAddress + } + customer { + ...DefaultCustomer + } + totalPrice { + centAmount + } + taxedPrice { + totalNet { + centAmount + } + totalGross { + centAmount + } + } + paymentInfo { + payments { + id + } + } + shippingInfo { + price { + centAmount + } + shippingMethod { + ...DefaultShippingMethod + } + } + discountCodes { + discountCode { + id + code + isActive + validFrom + validUntil + name(acceptLanguage: $acceptLanguage) + } + } + refusedGifts { + isActive + validFrom + validUntil + name(acceptLanguage: $acceptLanguage) + } + custom { + customFieldsRaw { + name + value + } + } + cartState + version + inventoryMode + } +`; diff --git a/packages/commercetools/api-client/src/fragments/channel.ts b/packages/commercetools/api-client/src/fragments/channel.ts new file mode 100644 index 0000000000..7de0773421 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/channel.ts @@ -0,0 +1,25 @@ +import { LocalizedStringFragment } from './localized-string'; +import { AddressFragment } from './address'; + +export const ChannelFragment = ` + ${LocalizedStringFragment} + ${AddressFragment} + + fragment ChannelFragment on Channel { + id + version + key + roles + name(locale: $locale) + description(locale: $locale) + nameAllLocales { + ...LocalizedStringFragment + } + descriptionAllLocales { + ...LocalizedStringFragment + } + address { + ...DefaultAddress + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/customer.ts b/packages/commercetools/api-client/src/fragments/customer.ts new file mode 100644 index 0000000000..10b1554668 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/customer.ts @@ -0,0 +1,24 @@ +import { AddressFragment } from './address'; + +// TODO: Remove all address information and update PRO packages to use customQueries when this is implemented: https://github.com/DivanteLtd/vue-storefront/issues/5049 +export const CustomerFragment = ` + ${AddressFragment} + + fragment DefaultCustomer on Customer { + version + firstName + lastName + email + addresses { + id + } + shippingAddresses { + ...DefaultAddress + } + billingAddresses { + ...DefaultAddress + } + defaultBillingAddressId + defaultShippingAddressId + } +`; diff --git a/packages/commercetools/api-client/src/fragments/index.ts b/packages/commercetools/api-client/src/fragments/index.ts new file mode 100644 index 0000000000..d690d0a7a9 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/index.ts @@ -0,0 +1,14 @@ +export * from './address'; +export * from './cart'; +export * from './channel'; +export * from './customer'; +export * from './inventory'; +export * from './inventory-entries-query-result'; +export * from './line-item'; +export * from './localized-string'; +export * from './order'; +export * from './product-price'; +export * from './reference'; +export * from './shipping-method'; +export * from './store'; +export * from './store-query-result'; diff --git a/packages/commercetools/api-client/src/fragments/initiator.ts b/packages/commercetools/api-client/src/fragments/initiator.ts new file mode 100644 index 0000000000..d74db7a4d0 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/initiator.ts @@ -0,0 +1,18 @@ +import { ReferenceFragment } from './reference'; + +export const InitiatorFragment = ` + ${ReferenceFragment} + + fragment InitiatorFragment on Initiator { + isPlatformClient + externalUserId + anonymousId + clientId + customerRef { + ...ReferenceFragment + } + userRef { + ...ReferenceFragment + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/inventory-entries-query-result.ts b/packages/commercetools/api-client/src/fragments/inventory-entries-query-result.ts new file mode 100644 index 0000000000..23c4514411 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/inventory-entries-query-result.ts @@ -0,0 +1,14 @@ +import { InventoryEntryFragment } from './inventory'; + +export const InventoryEntriesQueryResultFragment = ` + ${InventoryEntryFragment} + + fragment InventoryEntriesQueryResultFragment on InventoryEntryQueryResult { + offset + count + total + results { + ...InventoryEntryFragment + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/inventory.ts b/packages/commercetools/api-client/src/fragments/inventory.ts new file mode 100644 index 0000000000..925bed7912 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/inventory.ts @@ -0,0 +1,26 @@ +import { InitiatorFragment } from './initiator'; + +export const InventoryEntryFragment = ` + ${InitiatorFragment} + + fragment InventoryEntryFragment on InventoryEntry { + id + sku + version + createdAt + lastModifiedAt + quantityOnStock + availableQuantity + restockableInDays + expectedDelivery + supplyChannels { + id + } + createdBy { + ...InitiatorFragment + } + lastModifiedBy { + ...InitiatorFragment + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/line-item.ts b/packages/commercetools/api-client/src/fragments/line-item.ts new file mode 100644 index 0000000000..552f845d04 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/line-item.ts @@ -0,0 +1,67 @@ +import { ProductPriceFragment } from './product-price'; + +export const LineItemFragment = ` + ${ProductPriceFragment} + + fragment DefaultLineItem on LineItem { + id + productId + name(acceptLanguage: $acceptLanguage) + productSlug(acceptLanguage: $acceptLanguage) + quantity + discountedPricePerQuantity { + quantity + discountedPrice { + value { + centAmount + } + includedDiscounts { + discount { + name(acceptLanguage: $acceptLanguage) + isActive + } + } + } + } + variant { + id + sku + price(currency: $currency) { + tiers { + value { + centAmount + } + } + value { + centAmount + } + discounted { + value { + centAmount + } + discount { + isActive + name(acceptLanguage: $acceptLanguage) + } + } + } + images { + url + label + } + attributesRaw { + name + value + attributeDefinition { + type { + name + } + label(locale: $locale) + } + } + } + price { + ...DefaultProductPrice + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/localized-string.ts b/packages/commercetools/api-client/src/fragments/localized-string.ts new file mode 100644 index 0000000000..53937fc9e0 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/localized-string.ts @@ -0,0 +1,6 @@ +export const LocalizedStringFragment = ` + fragment LocalizedStringFragment on LocalizedString { + value + locale + } +`; diff --git a/packages/commercetools/api-client/src/fragments/order.ts b/packages/commercetools/api-client/src/fragments/order.ts new file mode 100644 index 0000000000..e17af6181b --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/order.ts @@ -0,0 +1,34 @@ +import {AddressFragment} from './address'; +import {LineItemFragment} from './line-item'; + +export const OrderFragment = ` + ${AddressFragment} + ${LineItemFragment} + + fragment DefaultOrder on Order { + lineItems { + ...DefaultLineItem + } + totalPrice { + centAmount + } + orderState + id + orderNumber + version + createdAt + customerEmail + shipmentState + paymentState + shippingAddress { + ...DefaultAddress + } + billingAddress { + ...DefaultAddress + } + cart { + id + version + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/product-price.ts b/packages/commercetools/api-client/src/fragments/product-price.ts new file mode 100644 index 0000000000..6db6519b53 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/product-price.ts @@ -0,0 +1,24 @@ +export const ProductPriceFragment = ` + fragment DefaultProductPrice on ProductPrice { + discounted { + value { + type + currencyCode + centAmount + fractionDigits + } + discount { + validFrom + validUntil + isActive + name(acceptLanguage: $acceptLanguage) + } + } + value { + type + currencyCode + centAmount + fractionDigits + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/reference.ts b/packages/commercetools/api-client/src/fragments/reference.ts new file mode 100644 index 0000000000..f84671029c --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/reference.ts @@ -0,0 +1,6 @@ +export const ReferenceFragment = ` + fragment ReferenceFragment on Reference { + typeId + id + } +`; diff --git a/packages/commercetools/api-client/src/fragments/shipping-method.ts b/packages/commercetools/api-client/src/fragments/shipping-method.ts new file mode 100644 index 0000000000..7e0aa6e5b1 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/shipping-method.ts @@ -0,0 +1,25 @@ +export const ShippingMethodFragment = ` + fragment DefaultShippingMethod on ShippingMethod { + id + version + name + isDefault + localizedDescription(acceptLanguage: $acceptLanguage) + zoneRates { + zone { + id + name + } + shippingRates { + freeAbove { + type + centAmount + } + isMatching + price { + centAmount + } + } + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/store-query-result.ts b/packages/commercetools/api-client/src/fragments/store-query-result.ts new file mode 100644 index 0000000000..de8d209ed0 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/store-query-result.ts @@ -0,0 +1,14 @@ +import { StoreFragment } from './store'; + +export const StoreQueryResultFragment = ` + ${StoreFragment} + + fragment StoreQueryResultFragment on StoreQueryResult { + offset + count + total + results { + ...StoreFragment + } + } +`; diff --git a/packages/commercetools/api-client/src/fragments/store.ts b/packages/commercetools/api-client/src/fragments/store.ts new file mode 100644 index 0000000000..7ee9bf6e71 --- /dev/null +++ b/packages/commercetools/api-client/src/fragments/store.ts @@ -0,0 +1,34 @@ +import { LocalizedStringFragment } from './localized-string'; +import { ChannelFragment } from './channel'; +import { InitiatorFragment } from './initiator'; + +export const StoreFragment = ` + ${LocalizedStringFragment} + ${ChannelFragment} + ${InitiatorFragment} + + fragment StoreFragment on Store { + id + version + key + languages + createdAt + lastModifiedAt + name (locale: $locale) + nameAllLocales { + ...LocalizedStringFragment + } + distributionChannels { + ...ChannelFragment + } + supplyChannels { + ...ChannelFragment + } + createdBy { + ...InitiatorFragment + } + lastModifiedBy { + ...InitiatorFragment + } + } +`; diff --git a/packages/commercetools/api-client/src/helpers/apiClient/defaultSettings.ts b/packages/commercetools/api-client/src/helpers/apiClient/defaultSettings.ts new file mode 100644 index 0000000000..180050958b --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/apiClient/defaultSettings.ts @@ -0,0 +1,27 @@ +import { + VSF_LOCALE_COOKIE, + VSF_CURRENCY_COOKIE, + VSF_COUNTRY_COOKIE, + VSF_STORE_COOKIE +} from '@vue-storefront/core'; + +export const defaultSettings = { + locale: 'en', + currency: 'USD', + country: 'US', + acceptLanguage: ['en'], + auth: { + onTokenChange: () => {}, + onTokenRead: () => '', + onTokenRemove: () => {} + }, + storeService: { + changeCurrentStore: () => {} + }, + cookies: { + currencyCookieName: VSF_CURRENCY_COOKIE, + countryCookieName: VSF_COUNTRY_COOKIE, + localeCookieName: VSF_LOCALE_COOKIE, + storeCookieName: VSF_STORE_COOKIE + } +}; diff --git a/packages/commercetools/api-client/src/helpers/cart/actions.ts b/packages/commercetools/api-client/src/helpers/cart/actions.ts new file mode 100644 index 0000000000..f840cc91d5 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/cart/actions.ts @@ -0,0 +1,133 @@ +import { Logger } from '@vue-storefront/core'; +import { ProductVariant, Address, LineItem, ReferenceInput, ResourceIdentifierInput, AddressInput } from './../../types/GraphQL'; + +const hasContactInfo = details => Object.keys(details.contactInfo || {}).some(c => ['phone', 'email', 'mobile', 'fax'].includes(c)); + +export const createAddLineItemAction = (params: { + product: ProductVariant; + quantity: number; + supplyChannel?: string; + distributionChannel?: string; +}) => { + return { + addLineItem: { + variantId: params.product.id, + quantity: params.quantity, + sku: params.product.sku, + ...(params.supplyChannel && { supplyChannel: { id: params.supplyChannel }}), + ...(params.distributionChannel && { distributionChannel: { id: params.distributionChannel }}) + } + }; +}; + +export const createRemoveLineItemAction = (product: LineItem) => ({ + removeLineItem: { + lineItemId: product.id, + quantity: product.quantity + } +}); + +export const createChangeLineItemQuantityAction = (product: LineItem) => ({ + changeLineItemQuantity: { + lineItemId: product.id, + quantity: product.quantity + } +}); + +export const setShippingAddressAction = (shippingDetails: Address): { setShippingAddress: { address: AddressInput } } => { + if (hasContactInfo(shippingDetails)) { + Logger.warn('Using `contactInfo` on Address is being deprecated in the CT API, use `email` `phone` `mobile` and `fax` fields directly.'); + } + return { + setShippingAddress: { + address: { + title: shippingDetails.title, + salutation: shippingDetails.salutation, + firstName: shippingDetails.firstName, + lastName: shippingDetails.lastName, + streetName: shippingDetails.streetName, + streetNumber: shippingDetails.streetNumber, + additionalStreetInfo: shippingDetails.additionalStreetInfo, + postalCode: shippingDetails.postalCode, + city: shippingDetails.city, + region: shippingDetails.region, + state: shippingDetails.state, + country: shippingDetails.country, + company: shippingDetails.company, + department: shippingDetails.department, + building: shippingDetails.building, + apartment: shippingDetails.apartment, + pOBox: shippingDetails.pOBox, + phone: shippingDetails.phone || shippingDetails.contactInfo?.phone, + mobile: shippingDetails.mobile || shippingDetails.contactInfo?.mobile, + email: shippingDetails.email || shippingDetails.contactInfo?.email, + fax: shippingDetails.fax || shippingDetails.contactInfo?.fax, + additionalAddressInfo: shippingDetails.additionalAddressInfo + } + } + }; +}; + +export const setShippingMethodAction = (shippingMethodId?: string) => ({ + setShippingMethod: { + shippingMethod: shippingMethodId ? { id: shippingMethodId } : null + } +}); + +export const addPayment = (payment: ResourceIdentifierInput) => ({ + addPayment: { payment } +}); + +export const setBillingAddressAction = (billingDetails: Address): { setBillingAddress: { address: AddressInput } } => { + if (hasContactInfo(billingDetails)) { + Logger.warn('Using `contactInfo` on Address is being deprecated in the CT API, use `email` `phone` `mobile` and `fax` fields directly.'); + } + return { + setBillingAddress: { + address: { + title: billingDetails.title, + salutation: billingDetails.salutation, + firstName: billingDetails.firstName, + lastName: billingDetails.lastName, + streetName: billingDetails.streetName, + streetNumber: billingDetails.streetNumber, + additionalStreetInfo: billingDetails.additionalStreetInfo, + postalCode: billingDetails.postalCode, + city: billingDetails.city, + region: billingDetails.region, + state: billingDetails.state, + country: billingDetails.country, + company: billingDetails.company, + department: billingDetails.department, + building: billingDetails.building, + apartment: billingDetails.apartment, + pOBox: billingDetails.pOBox, + phone: billingDetails.phone || billingDetails.contactInfo?.phone, + mobile: billingDetails.mobile || billingDetails.contactInfo?.mobile, + email: billingDetails.email || billingDetails.contactInfo?.email, + fax: billingDetails.fax || billingDetails.contactInfo?.fax, + additionalAddressInfo: billingDetails.additionalAddressInfo + } + } + }; +}; + +export const addPaymentAction = (paymentMethodId: string) => ({ + addPayment: { + payment: { + id: paymentMethodId + } + } +}); + +export const addDiscountCodeAction = (code: string) => ({ + addDiscountCode: { code } +}); + +export const removeDiscountCodeAction = (discountCode: ReferenceInput) => ({ + removeDiscountCode: { discountCode } +}); + +export const setCustomerEmail = (email: string) => ({ + setCustomerEmail: { email } +}); diff --git a/packages/commercetools/api-client/src/helpers/commercetoolsLink/authHelpers.ts b/packages/commercetools/api-client/src/helpers/commercetoolsLink/authHelpers.ts new file mode 100644 index 0000000000..743834e000 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/commercetoolsLink/authHelpers.ts @@ -0,0 +1,41 @@ +import SdkAuth, { TokenProvider } from '@commercetools/sdk-auth'; +import { Logger } from '@vue-storefront/core'; +import { Config, ApiConfig } from '../../types/setup'; +import { isAnonymousSession, isUserSession, getAccessToken } from '../utils'; +import fetch from 'isomorphic-fetch'; + +export const createAuthClient = (config: ApiConfig): SdkAuth => { + return new SdkAuth({ + host: config.authHost, + projectKey: config.projectKey, + disableRefreshToken: false, + credentials: { + clientId: config.clientId, + clientSecret: config.clientSecret + }, + scopes: config.scopes, + fetch + }); +}; + +export const createTokenProvider = (settings: Config, { + sdkAuth, + currentToken +}) => { + return new TokenProvider({ + sdkAuth, + fetchTokenInfo: (sdkAuthInstance) => sdkAuthInstance.clientCredentialsFlow(), + onTokenInfoChanged: (tokenInfo) => { + Logger.debug('TokenProvider.onTokenInfoChanged', getAccessToken(tokenInfo)); + settings.auth.onTokenChange(tokenInfo); + }, + onTokenInfoRefreshed: (tokenInfo) => { + Logger.debug('TokenProvider.onTokenInfoRefreshed', getAccessToken(tokenInfo)); + } + }, currentToken); +}; + +export { + isAnonymousSession, + isUserSession +}; diff --git a/packages/commercetools/api-client/src/helpers/commercetoolsLink/createCommerceToolsConnection.ts b/packages/commercetools/api-client/src/helpers/commercetoolsLink/createCommerceToolsConnection.ts new file mode 100644 index 0000000000..7205beaaa5 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/commercetoolsLink/createCommerceToolsConnection.ts @@ -0,0 +1,61 @@ +import { Config } from '../../types/setup'; +import { Logger } from '@vue-storefront/core'; +import { getAccessToken } from '../utils'; +import { createHttpLink } from 'apollo-link-http'; +import { createErrorHandler } from './graphqlError'; +import { setContext } from 'apollo-link-context'; +import { handleAfterAuth, handleBeforeAuth, handleRetry } from './linkHandlers'; +import { ApolloLink } from 'apollo-link'; +import { asyncMap } from '@apollo/client/utilities'; +import { RetryLink } from 'apollo-link-retry'; +import { createAuthClient, createTokenProvider } from './authHelpers'; + +export const createCommerceToolsConnection = (settings: Config): any => { + let currentToken: any = settings.auth.onTokenRead(); + Logger.debug('createCommerceToolsConnection', getAccessToken(currentToken)); + + const sdkAuth = createAuthClient(settings.api); + const tokenProvider = createTokenProvider(settings, { sdkAuth, currentToken }); + const httpLink = createHttpLink({ uri: settings.api.uri, fetch }); + const onErrorLink = createErrorHandler(); + + const authLinkBefore = setContext(async (apolloReq, { headers }) => { + Logger.debug('Apollo authLinkBefore', apolloReq.operationName); + currentToken = await handleBeforeAuth({ sdkAuth, tokenProvider, apolloReq, currentToken }); + Logger.debug('Apollo authLinkBefore, finished, generated token: ', getAccessToken(currentToken)); + + return { + headers: { + ...headers, + authorization: `Bearer ${currentToken.access_token}` + } + }; + }); + + const authLinkAfter = new ApolloLink((apolloReq, forward): any => { + return asyncMap(forward(apolloReq) as any, async (response: any) => { + Logger.debug('Apollo authLinkAfter', apolloReq.operationName); + currentToken = await handleAfterAuth({ sdkAuth, tokenProvider, apolloReq, currentToken, response }); + + const errors = (response.errors || []).filter(({ message }) => + !message.includes('Resource Owner Password Credentials Grant') && + !message.includes('This endpoint requires an access token issued either') + ); + + return { ...response, errors }; + }); + }); + + const errorRetry = new RetryLink({ + attempts: handleRetry({ tokenProvider }), + delay: () => 0 + }); + + const apolloLink = ApolloLink.from([onErrorLink, errorRetry, authLinkBefore, authLinkAfter.concat(httpLink)]); + + return { + apolloLink, + sdkAuth, + tokenProvider + }; +}; diff --git a/packages/commercetools/api-client/src/helpers/commercetoolsLink/graphqlError.ts b/packages/commercetools/api-client/src/helpers/commercetoolsLink/graphqlError.ts new file mode 100644 index 0000000000..383db6a7aa --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/commercetoolsLink/graphqlError.ts @@ -0,0 +1,25 @@ +import { onError } from 'apollo-link-error'; +import { Logger } from '@vue-storefront/core'; + +export const createErrorHandler = () => { + return onError(({ graphQLErrors, networkError }) => { + if (graphQLErrors) { + graphQLErrors.map(({ message, locations, path }) => { + if (!message.includes('Resource Owner Password Credentials Grant')) { + if (!locations) { + Logger.error(`[GraphQL error]: Message: ${message}, Path: ${path}`); + return; + } + + const parsedLocations = locations.map(({ column, line }) => `[column: ${column}, line: ${line}]`); + + Logger.error(`[GraphQL error]: Message: ${message}, Location: ${parsedLocations.join(', ')}, Path: ${path}`); + } + }); + } + + if (networkError) { + Logger.error(`[Network error]: ${networkError}`); + } + }); +}; diff --git a/packages/commercetools/api-client/src/helpers/commercetoolsLink/linkHandlers.ts b/packages/commercetools/api-client/src/helpers/commercetoolsLink/linkHandlers.ts new file mode 100644 index 0000000000..24ce19b397 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/commercetoolsLink/linkHandlers.ts @@ -0,0 +1,54 @@ +import { Logger } from '@vue-storefront/core'; +import { isAnonymousSession, isUserSession, getAccessToken } from '../utils'; +import { isAnonymousOperation, isUserOperation } from './restrictedOperations'; + +export const handleBeforeAuth = async ({ sdkAuth, tokenProvider, apolloReq, currentToken }) => { + const isAnonymous = isAnonymousSession(currentToken); + const isUser = isUserSession(currentToken); + const isGuest = !isAnonymous && !isUser; + + if (isGuest && isAnonymousOperation(apolloReq.operationName)) { + Logger.debug('Apollo authLinkBefore, anonymousFlow', apolloReq.operationName); + + const token = await sdkAuth.anonymousFlow(); + tokenProvider.setTokenInfo(token); + Logger.debug('Apollo authLinkBefore, anonymousFlow, generated token: ', getAccessToken(token)); + + return token; + } + + return tokenProvider.getTokenInfo(); +}; + +export const handleAfterAuth = async ({ sdkAuth, tokenProvider, apolloReq, currentToken, response }) => { + if (!isUserSession(currentToken) && isUserOperation(apolloReq.operationName)) { + const { email, password } = apolloReq.variables.draft; + Logger.debug('Apollo authLinkAfter, customerPasswordFlow', apolloReq.operationName); + + if (!response.errors?.length) { + const token = await sdkAuth.customerPasswordFlow({ username: email, password }); + tokenProvider.setTokenInfo(token); + Logger.debug('Apollo authLinkAfter, customerPasswordFlow, generated token: ', getAccessToken(token)); + + return token; + } + + return currentToken; + } + + return currentToken; +}; + +export const handleRetry = ({ tokenProvider }) => (count, operation, error) => { + if (count > 3) { + return false; + } + + if (error?.result?.message === 'invalid_token') { + Logger.debug(`Apollo retry-link, the operation (${operation.operationName}) sent with wrong token, creating a new one... (attempt: ${count})`); + tokenProvider.invalidateTokenInfo(); + return true; + } + + return false; +}; diff --git a/packages/commercetools/api-client/src/helpers/commercetoolsLink/restrictedOperations.ts b/packages/commercetools/api-client/src/helpers/commercetoolsLink/restrictedOperations.ts new file mode 100644 index 0000000000..f3623e64b3 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/commercetoolsLink/restrictedOperations.ts @@ -0,0 +1,14 @@ +/* istanbul ignore file */ +const restrictedOperations = { + anonymous: [ + 'createCart', + 'createMyShoppingList' + ], + user: [ + 'customerSignMeIn', + 'customerSignMeUp' + ] +}; + +export const isAnonymousOperation = (operationName) => restrictedOperations.anonymous.includes(operationName); +export const isUserOperation = (operationName) => restrictedOperations.user.includes(operationName); diff --git a/packages/commercetools/api-client/src/helpers/customer/index.ts b/packages/commercetools/api-client/src/helpers/customer/index.ts new file mode 100644 index 0000000000..a7ff9b2f17 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/customer/index.ts @@ -0,0 +1,11 @@ +export const changeCustomerEmailAction = (email: string) => ({ + changeEmail: { email } +}); + +export const setCustomerFirstNameAction = (firstName: string) => ({ + setFirstName: { firstName } +}); + +export const setCustomerLastNameAction = (lastName: string) => ({ + setLastName: { lastName } +}); diff --git a/packages/commercetools/api-client/src/helpers/search/index.ts b/packages/commercetools/api-client/src/helpers/search/index.ts new file mode 100644 index 0000000000..63ec298859 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/search/index.ts @@ -0,0 +1,125 @@ +import { GetInventoryParams } from 'src/api/getInventory'; +import { + CategoryWhereSearch, + ProductWhereSearch, + OrderWhereSearch, + Filter, + AttributeType +} from './../../types/Api'; +import { Config } from './../../types/setup'; + +const mapFilterToPredicate = (settings: Config, filter: Filter) => { + const { locale, currency } = settings; + + let valuePredicate: string; + switch (filter.type) { + case AttributeType.STRING: + valuePredicate = `value = "${filter.value}"`; + break; + case AttributeType.DATE: + case AttributeType.DATETIME: + case AttributeType.TIME: + valuePredicate = Array.isArray(filter.value) ? `value >= "${filter.value[0]}" and value <= "${filter.value[1]}"` : `value = "${filter.value}"`; + break; + case AttributeType.NUMBER: + valuePredicate = Array.isArray(filter.value) ? `value >= ${filter.value[0]} and value <= ${filter.value[1]}` : `value = ${filter.value}`; + break; + case AttributeType.ENUM: + case AttributeType.LOCALIZED_ENUM: + valuePredicate = `value(key = "${filter.value}")`; + break; + case AttributeType.LOCALIZED_STRING: + valuePredicate = `value(${locale.toLowerCase()} = "${filter.value}")`; + break; + case AttributeType.MONEY: + valuePredicate = Array.isArray(filter.value) + ? `value(centAmount >= ${(filter.value[0] as number) * 100} and centAmount <= ${(filter.value[1] as number) * 100} and currencyCode = "${currency}")` + : `value(centAmount = ${filter.value} and currencyCode = "${currency}")`; + break; + case AttributeType.BOOLEAN: + valuePredicate = `value = ${filter.value}`; + break; + } + + return `masterData(current(masterVariant(attributes(name = "${filter.name}" and ${valuePredicate}))))`; +}; + +const buildInventoryEntriesWhere = (settings: Config, params: GetInventoryParams) => { + // something like json-to-graphql-query would be better here + if (params.sku) return `sku="${params.sku}"`; +}; + +const buildProductWhere = (settings: Config, search: ProductWhereSearch) => { + const { acceptLanguage } = settings; + + const predicates: string[] = []; + + if (search?.catId) { + const catIds = (Array.isArray(search.catId) ? search.catId : [search.catId]).join('","'); + predicates.push(`masterData(current(categories(id in ("${catIds}"))))`); + } + + if (search?.slug) { + const predicate = acceptLanguage.map(locale => `${locale}="${search.slug}"`).join(' or '); + predicates.push(`masterData(current(slug(${predicate})))`); + } + + if (search?.id) { + predicates.push(`id="${search.id}"`); + } + + if (search?.filters) { + const filterPredicates = search.filters.map((f) => mapFilterToPredicate(settings, f)).join(' or '); + if (filterPredicates) { + predicates.push(filterPredicates); + } + } + + if (search?.key) { + predicates.push(`key="${search.key}"`); + } + + if (search?.ids) { + predicates.push(`id in ("${search.ids.join('","')}")`); + } + + return predicates.join(' and '); +}; + +const buildCategoryWhere = (settings: Config, search: CategoryWhereSearch) => { + const { acceptLanguage } = settings; + + if (search?.catId) { + return `id="${search.catId}"`; + } + + if (search?.slug) { + const predicate = acceptLanguage.map(locale => `${locale}="${search.slug}"`).join(' or '); + return `slug(${predicate})`; + } + + if (search?.key) { + return `key="${search.key}"`; + } + + return undefined; +}; + +const buildOrderWhere = (search: OrderWhereSearch): string => { + if (search?.id) { + return `id="${search.id}"`; + } + + if (search?.orderNumber) { + return `orderNumber="${search.orderNumber}"`; + } + + return null; +}; + +export { + buildProductWhere, + buildCategoryWhere, + buildOrderWhere, + buildInventoryEntriesWhere +}; diff --git a/packages/commercetools/api-client/src/helpers/utils.ts b/packages/commercetools/api-client/src/helpers/utils.ts new file mode 100644 index 0000000000..9c7d4c3cd2 --- /dev/null +++ b/packages/commercetools/api-client/src/helpers/utils.ts @@ -0,0 +1,6 @@ +export const isAnonymousSession = (token) => token?.scope?.includes('anonymous_id'); +export const isUserSession = (token) => token?.scope?.includes('customer_id'); +export const getAccessToken = (token) => token ? token.access_token : null; +export const getStoreKey = (store: string | undefined) => store + ? ({ storeKey: store?.split('/')[0] }) + : {}; diff --git a/packages/commercetools/api-client/src/index.server.ts b/packages/commercetools/api-client/src/index.server.ts new file mode 100644 index 0000000000..5af3fe3b7d --- /dev/null +++ b/packages/commercetools/api-client/src/index.server.ts @@ -0,0 +1,146 @@ +/* istanbul ignore file */ +import ApolloClient from 'apollo-client'; +import { InMemoryCache } from 'apollo-cache-inmemory'; +import * as api from './api'; +import { Config, ClientInstance } from './types/setup'; +import { createCommerceToolsConnection } from './helpers/commercetoolsLink/createCommerceToolsConnection'; +import { defaultSettings } from './helpers/apiClient/defaultSettings'; +import { apiClientFactory, ApiClientExtension } from '@vue-storefront/core'; + +const onCreate = (settings: Config): { config: Config; client: ClientInstance } => { + const languageMap = settings.languageMap || {}; + const acceptLanguage = settings.acceptLanguage || defaultSettings.acceptLanguage; + const locale = settings.locale || defaultSettings.locale; + + const config = { + ...defaultSettings, + ...settings, + languageMap, + acceptLanguage: languageMap[locale] || acceptLanguage, + auth: settings.auth || defaultSettings.auth + } as any as Config; + + if (settings.client) { + return { client: settings.client, config }; + } + + if (settings.customOptions && settings.customOptions.link) { + return { + client: new ApolloClient({ + cache: new InMemoryCache(), + ...settings.customOptions + }), + config + }; + } + + const { apolloLink, sdkAuth, tokenProvider } = createCommerceToolsConnection(config); + + const client = new ApolloClient({ + link: apolloLink, + cache: new InMemoryCache(), + ...settings.customOptions + }); + (client as ClientInstance).sdkAuth = sdkAuth; + (client as ClientInstance).tokenProvider = tokenProvider; + + return { + config, + client + }; +}; + +const parseToken = (rawToken) => { + try { + return JSON.parse(rawToken); + } catch (e) { + return null; + } +}; + +const getI18nConfig = (req, configuration) => { + const cookieSettings = configuration.cookies || defaultSettings.cookies; + const { currencyCookieName, countryCookieName, localeCookieName, storeCookieName } = cookieSettings; + const locale = req.cookies[localeCookieName] || configuration.locale || defaultSettings.locale; + const currency = req.cookies[currencyCookieName] || configuration.currency || defaultSettings.currency; + const country = req.cookies[countryCookieName] || configuration.country || defaultSettings.country; + const store = req.cookies[storeCookieName] || configuration.store; + + return { currency, country, locale, store }; +}; + +const tokenExtension: ApiClientExtension = { + name: 'tokenExtension', + hooks: (req, res) => { + const rawCurrentToken = req.cookies['vsf-commercetools-token']; + const currentToken = parseToken(rawCurrentToken); + + return { + beforeCreate: ({ configuration }) => ({ + ...configuration, + ...getI18nConfig(req, configuration), + auth: { + onTokenChange: (newToken) => { + if (!currentToken || currentToken.access_token !== newToken.access_token) { + res.cookie( + 'vsf-commercetools-token', + JSON.stringify(newToken), + newToken?.expires_at ? { expires: new Date(newToken.expires_at) } : {} + ); + } + }, + + onTokenRead: () => { + res.cookie( + 'vsf-commercetools-token', + rawCurrentToken, + currentToken?.expires_at ? { expires: new Date(currentToken.expires_at) } : {} + ); + return currentToken; + }, + + onTokenRemove: () => { + delete req.cookies['vsf-commercetools-token']; + } + } + }) + }; + } +}; + +const logMailExtension: ApiClientExtension = { + name: 'logMailExtension', + extendApiMethods: { + customerCreatePasswordResetToken: async (context, email) => { + const response = await api.customerCreatePasswordResetToken(context, email); + const token = response?.data?.customerCreatePasswordResetToken?.value; + + const emailObject = { + to: email, + from: 'password-recovery@vue-storefront.io', + subject: `Password recovery for ${email}`, + html: `Reset your password by clicking this link` + }; + + console.log(JSON.stringify(emailObject)); + return response; + } + } +}; + +const extensions = [tokenExtension]; + +if (process.env.NODE_ENV === 'development') { + extensions.push(logMailExtension); +} + +const { createApiClient } = apiClientFactory({ + onCreate, + api, + extensions +}); + +export { + createApiClient, + api +}; diff --git a/packages/commercetools/api-client/src/index.ts b/packages/commercetools/api-client/src/index.ts new file mode 100644 index 0000000000..b97523d7d5 --- /dev/null +++ b/packages/commercetools/api-client/src/index.ts @@ -0,0 +1,9 @@ +import * as cartActions from './helpers/cart/actions'; + +export * from './fragments'; +export * from './types/Api'; +export * from './types/GraphQL'; +export * from './types/setup'; +export { isAnonymousSession, isUserSession } from './helpers/utils'; +export { createErrorHandler } from './helpers/commercetoolsLink/graphqlError'; +export { cartActions }; diff --git a/packages/commercetools/api-client/src/types/Api.ts b/packages/commercetools/api-client/src/types/Api.ts new file mode 100644 index 0000000000..4cc2b015ad --- /dev/null +++ b/packages/commercetools/api-client/src/types/Api.ts @@ -0,0 +1,143 @@ +import { ApolloQueryResult } from 'apollo-client'; +import { FetchResult } from 'apollo-link'; +import { ApiClientMethods } from '@vue-storefront/core'; +import { Token, CustomerCredentials } from './setup'; +import { UpdateCartParams } from '../api/updateCart'; +import { GetMeParams } from '../api/getMe'; +import { ShippingMethodData } from '../api/getShippingMethods'; +import { GetStoresParams } from '../api/getStores'; +import { + Cart, + Order, + ShippingMethod, + CustomerSignInResult, + Customer, + CartDraft, + ProductVariant, + OrderMyCartCommand, + CustomerSignMeInDraft, + CustomerSignMeUpDraft, + ReferenceInput, + Address, + LineItem, + CategoryQueryResult, + ProductQueryResult, + Me, + CartQueryInterface, + CustomerPasswordToken, + StoreQueryResult +} from './GraphQL'; + +export interface BaseSearch { + limit?: number; + offset?: number; + sort?: string[]; +} + +export enum AttributeType { + STRING = 'StringAttribute', + DATE = 'DateAttribute', + DATETIME = 'DateTimeAttribute', + TIME = 'TimeAttribute', + NUMBER = 'NumberAttribute', + ENUM = 'EnumAttribute', + LOCALIZED_ENUM = 'LocalizedEnumAttribute', + LOCALIZED_STRING = 'LocalizedStringAttribute', + MONEY = 'MoneyAttribute', + BOOLEAN = 'BooleanAttribute' +} + +export interface Filter { + type: AttributeType; + name: string; + value: any; +} + +export interface ProductWhereSearch extends BaseSearch { + catId?: string | string[]; + skus?: string[]; + slug?: string; + id?: string; + ids?: string[]; + key?: string; + filters?: Filter[]; +} + +export interface FilterOption { + label: string; + value: string | number | boolean | [number, number] | [string, string]; + selected: boolean; +} + +export interface CategoryWhereSearch extends BaseSearch { + catId?: string; + key?: string; + slug?: string; +} + +export interface OrderWhereSearch extends BaseSearch { + id?: string; + orderNumber?: string; +} + +export interface FlowOptions { + currentToken?: Token; + customerCredentials?: CustomerCredentials; + requireUserSession?: boolean; +} + +export interface CartData extends Omit { + currency?: string; +} + +export type ApiResponseWrapper = Record; + +export type QueryResponse = ApolloQueryResult>; +export type MutationResponse = FetchResult>; +export type CartQueryResponse = QueryResponse<'cart', Cart>; +export type OrderQueryResponse = QueryResponse<'order', Order>; +export type CartMutationResponse = MutationResponse<'cart', Cart>; +export type CartResponse = CartQueryResponse | CartMutationResponse; +export type OrderMutationResponse = MutationResponse<'order', Order>; +export type OrderResponse = OrderQueryResponse | OrderMutationResponse; +export type ShippingMethodsResponse = QueryResponse<'shippingMethods', ShippingMethod>; +export type SignInResponse = QueryResponse<'user', CustomerSignInResult>; +export type ChangeMyPasswordResponse = QueryResponse<'user', Customer>; +export type CartDetails = Pick; +export type CreatePasswordResetTokenResponse = QueryResponse<'customerCreatePasswordResetToken', CustomerPasswordToken>; +export type ResetPasswordResponse = QueryResponse<'customerResetPassword', Customer>; + +interface ApiMethods { + addToCart ({ id, version }: CartDetails, params: { + product: ProductVariant; + quantity: number; + supplyChannel?: string; + distributionChannel?: string; + }): Promise; + applyCartCoupon ({ id, version }: CartDetails, discountCode: string): Promise; + createCart (cartDraft?: CartData): Promise<{ data: CartQueryInterface }>; + createMyOrderFromCart (draft: OrderMyCartCommand): Promise; + customerChangeMyPassword (version: any, currentPassword: string, newPassword: string): Promise; + customerSignMeIn (draft: CustomerSignMeInDraft): Promise; + customerSignMeUp (draft: CustomerSignMeUpDraft): Promise; + customerSignOut (): Promise; + customerUpdateMe (currentUser, updatedUserData): Promise; + customerResetPassword (tokenValue: string, newPassword: string): Promise; + customerCreatePasswordResetToken (email: string): Promise; + deleteCart ({ id, version }: CartDetails): Promise; + getCart (cartId: string): Promise; + getCategory (params): Promise>; + getMe (params?: GetMeParams): Promise<{ data: { me: Me } }>; + getOrders (params): Promise<{ data: { me: Me } }>; + getProduct (params): Promise>; + getShippingMethods (cartId?: string): Promise; + removeCartCoupon ({ id, version }: CartDetails, discountCode: ReferenceInput): Promise; + removeFromCart ({ id, version }: CartDetails, product: LineItem): Promise; + updateCart (params: UpdateCartParams): Promise; + updateCartQuantity ({ id, version }: CartDetails, product: LineItem): Promise; + updateShippingDetails (cart: Cart, shippingDetails: Address): Promise; + isGuest: () => boolean; + getStores(params: GetStoresParams): Promise; +} + +export type CommercetoolsMethods = ApiClientMethods diff --git a/packages/commercetools/api-client/src/types/GraphQL.ts b/packages/commercetools/api-client/src/types/GraphQL.ts new file mode 100644 index 0000000000..606bd889e4 --- /dev/null +++ b/packages/commercetools/api-client/src/types/GraphQL.ts @@ -0,0 +1,7311 @@ +/* istanbul ignore file */ + +export type Maybe = T | null; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; + /** The `Long` scalar type represents non-fractional signed whole numeric values. + * Long can represent values between -(2^63) and 2^63 - 1. + */ + Long: any; + /** DateTime is a scalar value that represents an ISO8601 formatted date and time. */ + DateTime: any; + /** [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) country code. */ + Country: any; + /** Locale is a scalar value represented as a string language tag. */ + Locale: any; + /** DateTime is a scalar value that represents an ISO8601 formatted date. */ + Date: any; + /** Raw JSON value */ + Json: any; + /** Represents a currency. Currencies are identified by their [ISO + * 4217](http://www.iso.org/iso/home/standards/currency_codes.htm) currency codes. + */ + Currency: any; + /** A key that references a resource. */ + KeyReferenceInput: any; + /** Search filter. It is represented as a string and has th same format as in REST API: "field:filter_criteria" */ + SearchFilter: any; + /** Search sort */ + SearchSort: any; + /** YearMonth is a scalar value that represents an ISO8601 formatted year and month. */ + YearMonth: any; + /** The `BigDecimal` scalar type represents signed fractional values with arbitrary precision. */ + BigDecimal: any; + /** Time is a scalar value that represents an ISO8601 formatted time. */ + Time: any; +}; + +export type AbsoluteDiscountValue = CartDiscountValue & + ProductDiscountValue & { + __typename?: "AbsoluteDiscountValue"; + money: Array; + type: Scalars["String"]; + }; + +export type AbsoluteDiscountValueInput = { + money: Array; +}; + +/** A field to access the active cart. */ +export type ActiveCartInterface = { + activeCart?: Maybe; +}; + +export type AddAttributeDefinition = { + attributeDefinition: AttributeDefinitionDraft; +}; + +export type AddCartCustomLineItem = { + shippingDetails?: Maybe; + custom?: Maybe; + quantity?: Maybe; + externalTaxRate?: Maybe; + taxCategory?: Maybe; + slug: Scalars["String"]; + money: BaseMoneyInput; + name: Array; +}; + +export type AddCartDiscountCode = { + code: Scalars["String"]; + validateDuplicates?: Maybe; +}; + +export type AddCartItemShippingAddress = { + address: AddressInput; +}; + +export type AddCartLineItem = { + shippingDetails?: Maybe; + externalTotalPrice?: Maybe; + externalPrice?: Maybe; + externalTaxRate?: Maybe; + custom?: Maybe; + catalog?: Maybe; + distributionChannel?: Maybe; + supplyChannel?: Maybe; + variantId?: Maybe; + quantity?: Maybe; + sku?: Maybe; + productId?: Maybe; +}; + +export type AddCartPayment = { + payment: ResourceIdentifierInput; +}; + +export type AddCartShoppingList = { + shoppingList: ResourceIdentifierInput; + supplyChannel?: Maybe; + distributionChannel?: Maybe; +}; + +export type AddCategoryAsset = { + position?: Maybe; + asset: AssetDraftInput; +}; + +export type AddCustomerAddress = { + address: AddressInput; +}; + +export type AddCustomerBillingAddressId = { + addressId: Scalars["String"]; +}; + +export type AddCustomerShippingAddressId = { + addressId: Scalars["String"]; +}; + +export type AddCustomerStore = { + store: ResourceIdentifierInput; +}; + +export type AddInventoryEntryQuantity = { + quantity: Scalars["Long"]; +}; + +export type AddLocalizedEnumValue = { + attributeName: Scalars["String"]; + value: LocalizedEnumValueDraft; +}; + +export type AddMyCartLineItem = { + shippingDetails?: Maybe; + custom?: Maybe; + catalog?: Maybe; + distributionChannel?: Maybe; + supplyChannel?: Maybe; + variantId?: Maybe; + quantity?: Maybe; + sku?: Maybe; + productId?: Maybe; +}; + +export type AddOrderDelivery = { + items?: Maybe>; + parcels?: Maybe>; + address?: Maybe; +}; + +export type AddOrderItemShippingAddress = { + address: AddressInput; +}; + +export type AddOrderParcelToDelivery = { + deliveryId: Scalars["String"]; + measurements?: Maybe; + trackingData?: Maybe; + items?: Maybe>; +}; + +export type AddOrderPayment = { + payment: ResourceIdentifierInput; +}; + +export type AddOrderReturnInfo = { + items: Array; + returnDate?: Maybe; + returnTrackingId?: Maybe; +}; + +export type AddPlainEnumValue = { + attributeName: Scalars["String"]; + value: PlainEnumValueDraft; +}; + +export type AddProductAsset = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + position?: Maybe; + asset: AssetDraftInput; +}; + +export type AddProductExternalImage = { + variantId?: Maybe; + sku?: Maybe; + image: ImageInput; + staged?: Maybe; +}; + +export type AddProductPrice = { + variantId?: Maybe; + sku?: Maybe; + price: ProductPriceDataInput; + catalog?: Maybe; + staged?: Maybe; +}; + +export type AddProductToCategory = { + category: ResourceIdentifierInput; + orderHint?: Maybe; + staged?: Maybe; +}; + +export type AddProductVariant = { + assets?: Maybe>; + attributes?: Maybe>; + images?: Maybe>; + prices?: Maybe>; + key?: Maybe; + sku?: Maybe; + staged?: Maybe; +}; + +/** An address represents a postal address. */ +export type Address = { + __typename?: "Address"; + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + contactInfo: AddressContactInfo; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; + phone?: Maybe; + mobile?: Maybe; + email?: Maybe; + fax?: Maybe; +}; + +export type AddressContactInfo = { + __typename?: "AddressContactInfo"; + phone?: Maybe; + mobile?: Maybe; + email?: Maybe; + fax?: Maybe; +}; + +export type AddressInput = { + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + phone?: Maybe; + mobile?: Maybe; + email?: Maybe; + fax?: Maybe; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; +}; + +export type AddShippingMethodShippingRate = { + zone: ResourceIdentifierInput; + shippingRate: ShippingRateDraft; +}; + +export type AddShippingMethodZone = { + zone: ResourceIdentifierInput; +}; + +export type AddShoppingListLineItem = { + addedAt?: Maybe; + custom?: Maybe; + quantity?: Maybe; + variantId?: Maybe; + sku?: Maybe; + productId?: Maybe; +}; + +export type AddShoppingListTextLineItem = { + addedAt?: Maybe; + custom?: Maybe; + quantity?: Maybe; + description?: Maybe>; + name: Array; +}; + +export type AddZoneLocation = { + location: ZoneLocation; +}; + +export enum AnonymousCartSignInMode { + /** The anonymous cart is used as new active customer cart. No `LineItem`s get merged. */ + UseAsNewActiveCustomerCart = "UseAsNewActiveCustomerCart", + /** `LineItem`s of the anonymous cart will be copied to the customer’s active cart that has been modified most recently. + * + * The `CartState` of the anonymous cart gets changed to `Merged` while the + * `CartState` of the customer’s cart remains `Active`. + * + * `CustomLineItems` and `CustomFields` of the anonymous cart will not be copied to the customers cart. + * + * If a `LineItem` in the anonymous cart matches an existing line item in the + * customer’s cart (same product ID and variant ID), the maximum quantity of both + * LineItems is used as the new quantity. In that case `CustomFields` on the + * `LineItem` of the anonymous cart will not be in the resulting `LineItem`. + */ + MergeWithExistingCustomerCart = "MergeWithExistingCustomerCart" +} + +/** API Clients can be used to obtain OAuth 2 access tokens */ +export type ApiClientWithoutSecret = { + __typename?: "APIClientWithoutSecret"; + id: Scalars["String"]; + name: Scalars["String"]; + scope: Scalars["String"]; + createdAt?: Maybe; + lastUsedAt?: Maybe; +}; + +export type ApiClientWithoutSecretQueryResult = { + __typename?: "APIClientWithoutSecretQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +/** API Clients can be used to obtain OAuth 2 access tokens. The secret is only + * shown once in the response of creating the API Client. + */ +export type ApiClientWithSecret = { + __typename?: "APIClientWithSecret"; + id: Scalars["String"]; + name: Scalars["String"]; + scope: Scalars["String"]; + createdAt?: Maybe; + lastUsedAt?: Maybe; + secret: Scalars["String"]; +}; + +export type ApplyCartDeltaToCustomLineItemShippingDetailsTargets = { + customLineItemId: Scalars["String"]; + targetsDelta: Array; +}; + +export type ApplyCartDeltaToLineItemShippingDetailsTargets = { + lineItemId: Scalars["String"]; + targetsDelta: Array; +}; + +export type Asset = { + __typename?: "Asset"; + id: Scalars["String"]; + key?: Maybe; + sources: Array; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + tags: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type AssetNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AssetDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AssetCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type AssetCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type AssetDimensions = { + __typename?: "AssetDimensions"; + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +export type AssetDimensionsInput = { + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +export type AssetDraftInput = { + key?: Maybe; + name: Array; + description?: Maybe>; + custom?: Maybe; + sources?: Maybe>; + tags?: Maybe>; + type?: Maybe; +}; + +export type AssetSource = { + __typename?: "AssetSource"; + uri: Scalars["String"]; + key?: Maybe; + dimensions?: Maybe; + contentType?: Maybe; +}; + +export type AssetSourceInput = { + uri: Scalars["String"]; + key?: Maybe; + dimensions?: Maybe; + contentType?: Maybe; +}; + +export type Attribute = { + name: Scalars["String"]; +}; + +export enum AttributeConstraint { + /** No constraints are applied to the attribute */ + None = "None", + /** Attribute value should be different in each variant */ + Unique = "Unique", + /** A set of attributes, that have this constraint, should have different combinations in each variant */ + CombinationUnique = "CombinationUnique", + /** Attribute value should be the same in all variants */ + SameForAll = "SameForAll" +} + +export type AttributeDefinition = { + __typename?: "AttributeDefinition"; + type: AttributeDefinitionType; + name: Scalars["String"]; + label?: Maybe; + isRequired: Scalars["Boolean"]; + attributeConstraint: AttributeConstraint; + inputTip?: Maybe; + inputHint: TextInputHint; + isSearchable: Scalars["Boolean"]; + labelAllLocales: Array; + inputTipAllLocales?: Maybe>; +}; + +export type AttributeDefinitionLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AttributeDefinitionInputTipArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AttributeDefinitionDraft = { + type: AttributeTypeDraft; + name: Scalars["String"]; + label: Array; + isRequired: Scalars["Boolean"]; + attributeConstraint?: Maybe; + inputTip?: Maybe>; + inputHint?: Maybe; + isSearchable: Scalars["Boolean"]; +}; + +export type AttributeDefinitionResult = { + __typename?: "AttributeDefinitionResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +/** (https://dev.commercetools.com/http-api-projects-productTypes.html#attributetype)[https://dev.commercetools.com/http-api-projects-productTypes.html#attributetype] */ +export type AttributeDefinitionType = { + name: Scalars["String"]; +}; + +export type AttributeSetElementTypeDraft = { + text?: Maybe; + number?: Maybe; + money?: Maybe; + date?: Maybe; + time?: Maybe; + datetime?: Maybe; + boolean?: Maybe; + reference?: Maybe; + enum?: Maybe; + lenum?: Maybe; + ltext?: Maybe; +}; + +export type AttributeSetTypeDraft = { + elementType: AttributeSetElementTypeDraft; +}; + +export type AttributeTypeDraft = { + set?: Maybe; + text?: Maybe; + number?: Maybe; + money?: Maybe; + date?: Maybe; + time?: Maybe; + datetime?: Maybe; + boolean?: Maybe; + reference?: Maybe; + enum?: Maybe; + lenum?: Maybe; + ltext?: Maybe; +}; + +export type BaseMoney = { + type: Scalars["String"]; + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; + fractionDigits: Scalars["Int"]; +}; + +export type BaseMoneyInput = { + centPrecision?: Maybe; + highPrecision?: Maybe; +}; + +export type BaseSearchKeywordInput = { + whitespace?: Maybe; + custom?: Maybe; +}; + +export type BooleanAttribute = Attribute & { + __typename?: "BooleanAttribute"; + value: Scalars["Boolean"]; + name: Scalars["String"]; +}; + +export type BooleanAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "BooleanAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type BooleanField = CustomField & { + __typename?: "BooleanField"; + value: Scalars["Boolean"]; + name: Scalars["String"]; +}; + +export type BooleanType = FieldType & { + __typename?: "BooleanType"; + name: Scalars["String"]; +}; + +/** A shopping cart holds product variants and can be ordered. Each cart either + * belongs to a registered customer or is an anonymous cart. + */ +export type Cart = Versioned & { + __typename?: "Cart"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + cartState: CartState; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A shopping cart holds product variants and can be ordered. Each cart either + * belongs to a registered customer or is an anonymous cart. + */ +export type CartCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A shopping cart holds product variants and can be ordered. Each cart either + * belongs to a registered customer or is an anonymous cart. + */ +export type CartCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CartClassificationInput = { + values: Array; +}; + +export type CartClassificationType = ShippingRateInputType & { + __typename?: "CartClassificationType"; + values: Array; + type: Scalars["String"]; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscount = Versioned & { + __typename?: "CartDiscount"; + cartPredicate: Scalars["String"]; + validFrom?: Maybe; + validUntil?: Maybe; + stackingMode: StackingMode; + isActive: Scalars["Boolean"]; + requiresDiscountCode: Scalars["Boolean"]; + sortOrder: Scalars["String"]; + key?: Maybe; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + value: CartDiscountValue; + target?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CartDiscountDraft = { + value: CartDiscountValueInput; + cartPredicate: Scalars["String"]; + target?: Maybe; + sortOrder: Scalars["String"]; + name: Array; + description?: Maybe>; + validFrom?: Maybe; + validUntil?: Maybe; + stackingMode?: Maybe; + requiresDiscountCode?: Maybe; + isActive?: Maybe; + custom?: Maybe; + key?: Maybe; +}; + +export type CartDiscountQueryResult = { + __typename?: "CartDiscountQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CartDiscountTarget = { + type: Scalars["String"]; +}; + +export type CartDiscountTargetInput = { + lineItems?: Maybe; + customLineItems?: Maybe; + shipping?: Maybe; + multiBuyLineItems?: Maybe; + multiBuyCustomLineItems?: Maybe; +}; + +export type CartDiscountUpdateAction = { + changeCartPredicate?: Maybe; + changeIsActive?: Maybe; + changeName?: Maybe; + changeRequiresDiscountCode?: Maybe; + changeSortOrder?: Maybe; + changeStackingMode?: Maybe; + changeTarget?: Maybe; + changeValue?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setValidFrom?: Maybe; + setValidFromAndUntil?: Maybe; + setValidUntil?: Maybe; +}; + +export type CartDiscountValue = { + type: Scalars["String"]; +}; + +export type CartDiscountValueInput = { + relative?: Maybe; + absolute?: Maybe; + giftLineItem?: Maybe; +}; + +export type CartDraft = { + currency: Scalars["Currency"]; + country?: Maybe; + inventoryMode?: Maybe; + custom?: Maybe; + customerEmail?: Maybe; + shippingAddress?: Maybe; + billingAddress?: Maybe; + shippingMethod?: Maybe; + taxMode?: Maybe; + locale?: Maybe; + deleteDaysAfterLastModification?: Maybe; + itemShippingAddresses?: Maybe>; + discountCodes?: Maybe>; + lineItems?: Maybe>; + customLineItems?: Maybe>; + customerId?: Maybe; + externalTaxRateForShippingMethod?: Maybe; + anonymousId?: Maybe; + taxRoundingMode?: Maybe; + taxCalculationMode?: Maybe; + customerGroup?: Maybe; + shippingRateInput?: Maybe; + origin?: Maybe; + store?: Maybe; +}; + +export enum CartOrigin { + /** The cart was created by the merchant on behalf of the customer */ + Merchant = "Merchant", + /** The cart was created by the customer. This is the default value */ + Customer = "Customer" +} + +/** Fields to access carts. Includes direct access to a single cart and searching for carts. */ +export type CartQueryInterface = { + cart?: Maybe; + carts: CartQueryResult; +}; + +/** Fields to access carts. Includes direct access to a single cart and searching for carts. */ +export type CartQueryInterfaceCartArgs = { + id: Scalars["String"]; +}; + +/** Fields to access carts. Includes direct access to a single cart and searching for carts. */ +export type CartQueryInterfaceCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type CartQueryResult = { + __typename?: "CartQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CartScoreInput = { + dummy?: Maybe; +}; + +export type CartScoreType = ShippingRateInputType & { + __typename?: "CartScoreType"; + type: Scalars["String"]; +}; + +export enum CartState { + /** The cart was ordered. No further operations on the cart are allowed. */ + Ordered = "Ordered", + /** Anonymous cart whose content was merged into a customers cart on signin. No further operations on the cart are allowed. */ + Merged = "Merged", + /** The cart can be updated and ordered. It is the default state. */ + Active = "Active" +} + +export type CartUpdateAction = { + addCustomLineItem?: Maybe; + addDiscountCode?: Maybe; + addItemShippingAddress?: Maybe; + addLineItem?: Maybe; + addPayment?: Maybe; + addShoppingList?: Maybe; + applyDeltaToCustomLineItemShippingDetailsTargets?: Maybe< + ApplyCartDeltaToCustomLineItemShippingDetailsTargets + >; + applyDeltaToLineItemShippingDetailsTargets?: Maybe< + ApplyCartDeltaToLineItemShippingDetailsTargets + >; + changeCustomLineItemMoney?: Maybe; + changeCustomLineItemQuantity?: Maybe; + changeLineItemQuantity?: Maybe; + changeTaxCalculationMode?: Maybe; + changeTaxMode?: Maybe; + changeTaxRoundingMode?: Maybe; + recalculate?: Maybe; + removeCustomLineItem?: Maybe; + removeDiscountCode?: Maybe; + removeItemShippingAddress?: Maybe; + removeLineItem?: Maybe; + removePayment?: Maybe; + setAnonymousId?: Maybe; + setBillingAddress?: Maybe; + setCartTotalTax?: Maybe; + setCountry?: Maybe; + setCustomField?: Maybe; + setCustomLineItemCustomField?: Maybe; + setCustomLineItemCustomType?: Maybe; + setCustomLineItemShippingDetails?: Maybe< + SetCartCustomLineItemShippingDetails + >; + setCustomLineItemTaxAmount?: Maybe; + setCustomLineItemTaxRate?: Maybe; + setCustomShippingMethod?: Maybe; + setCustomType?: Maybe; + setCustomerEmail?: Maybe; + setCustomerGroup?: Maybe; + setCustomerId?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetCartDeleteDaysAfterLastModification + >; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setLineItemPrice?: Maybe; + setLineItemShippingDetails?: Maybe; + setLineItemTaxAmount?: Maybe; + setLineItemTaxRate?: Maybe; + setLineItemTotalPrice?: Maybe; + setLocale?: Maybe; + setShippingAddress?: Maybe; + setShippingMethod?: Maybe; + setShippingMethodTaxAmount?: Maybe; + setShippingMethodTaxRate?: Maybe; + setShippingRateInput?: Maybe; + updateItemShippingAddress?: Maybe; +}; + +export type CartValueInput = { + dummy?: Maybe; +}; + +export type CartValueType = ShippingRateInputType & { + __typename?: "CartValueType"; + type: Scalars["String"]; +}; + +export type Category = Versioned & { + __typename?: "Category"; + id: Scalars["String"]; + key?: Maybe; + version: Scalars["Long"]; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales: Array; + ancestorsRef: Array; + ancestors: Array; + parentRef?: Maybe; + parent?: Maybe; + orderHint: Scalars["String"]; + externalId?: Maybe; + metaTitle?: Maybe; + metaKeywords?: Maybe; + metaDescription?: Maybe; + /** Number of a products in the category subtree. */ + productCount: Scalars["Int"]; + /** Number of staged products in the category subtree. */ + stagedProductCount: Scalars["Int"]; + /** Number of direct child categories. */ + childCount: Scalars["Int"]; + /** Direct child categories. */ + children?: Maybe>; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + assets: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type CategoryNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryMetaTitleArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryMetaKeywordsArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryMetaDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategoryCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategoryDraft = { + key?: Maybe; + name: Array; + description?: Maybe>; + custom?: Maybe; + slug: Array; + externalId?: Maybe; + metaTitle?: Maybe>; + metaDescription?: Maybe>; + metaKeywords?: Maybe>; + orderHint?: Maybe; + parent?: Maybe; + assets?: Maybe>; +}; + +export type CategoryOrderHint = { + __typename?: "CategoryOrderHint"; + categoryId: Scalars["String"]; + orderHint: Scalars["String"]; +}; + +export type CategoryOrderHintInput = { + uuid: Scalars["String"]; + orderHint: Scalars["String"]; +}; + +export type CategoryQueryResult = { + __typename?: "CategoryQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CategorySearch = { + __typename?: "CategorySearch"; + id: Scalars["String"]; + key?: Maybe; + version: Scalars["Long"]; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales: Array; + ancestorsRef: Array; + ancestors: Array; + parentRef?: Maybe; + parent?: Maybe; + externalId?: Maybe; + productCount: Scalars["Int"]; + stagedProductCount: Scalars["Int"]; + childCount: Scalars["Int"]; + productTypeNames: Array; + /** Direct child categories. */ + children: Array; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + orderHint: Scalars["String"]; + assets: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type CategorySearchNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySearchDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySearchSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySearchCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategorySearchCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategorySearchResult = { + __typename?: "CategorySearchResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Int"]; + results: Array; +}; + +export type CategoryUpdateAction = { + addAsset?: Maybe; + changeAssetName?: Maybe; + changeAssetOrder?: Maybe; + changeName?: Maybe; + changeOrderHint?: Maybe; + changeSlug?: Maybe; + changeParent?: Maybe; + removeAsset?: Maybe; + setAssetCustomField?: Maybe; + setAssetCustomType?: Maybe; + setAssetDescription?: Maybe; + setAssetKey?: Maybe; + setAssetSources?: Maybe; + setAssetTags?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setMetaDescription?: Maybe; + setMetaKeywords?: Maybe; + setMetaTitle?: Maybe; + setExternalId?: Maybe; +}; + +export type ChangeAttributeName = { + attributeName: Scalars["String"]; + newAttributeName: Scalars["String"]; +}; + +export type ChangeAttributeOrder = { + attributeDefinitions: Array; +}; + +export type ChangeAttributeOrderByName = { + attributeNames: Array; +}; + +export type ChangeCartCustomLineItemMoney = { + customLineItemId: Scalars["String"]; + money: BaseMoneyInput; +}; + +export type ChangeCartCustomLineItemQuantity = { + customLineItemId: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ChangeCartDiscountCartPredicate = { + cartPredicate: Scalars["String"]; +}; + +export type ChangeCartDiscountIsActive = { + isActive: Scalars["Boolean"]; +}; + +export type ChangeCartDiscountName = { + name: Array; +}; + +export type ChangeCartDiscountRequiresDiscountCode = { + requiresDiscountCode: Scalars["Boolean"]; +}; + +export type ChangeCartDiscountSortOrder = { + sortOrder: Scalars["String"]; +}; + +export type ChangeCartDiscountStackingMode = { + stackingMode: StackingMode; +}; + +export type ChangeCartDiscountTarget = { + target: CartDiscountTargetInput; +}; + +export type ChangeCartDiscountValue = { + value: CartDiscountValueInput; +}; + +export type ChangeCartLineItemQuantity = { + lineItemId: Scalars["String"]; + quantity: Scalars["Long"]; + externalPrice?: Maybe; + externalTotalPrice?: Maybe; +}; + +export type ChangeCartTaxCalculationMode = { + taxCalculationMode: TaxCalculationMode; +}; + +export type ChangeCartTaxMode = { + taxMode: TaxMode; +}; + +export type ChangeCartTaxRoundingMode = { + taxRoundingMode: RoundingMode; +}; + +export type ChangeCategoryAssetName = { + name: Array; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type ChangeCategoryAssetOrder = { + assetOrder: Array; +}; + +export type ChangeCategoryName = { + name: Array; +}; + +export type ChangeCategoryOrderHint = { + orderHint: Scalars["String"]; +}; + +export type ChangeCategoryParent = { + parent: ResourceIdentifierInput; +}; + +export type ChangeCategorySlug = { + slug: Array; +}; + +export type ChangeCustomerAddress = { + addressId: Scalars["String"]; + address: AddressInput; +}; + +export type ChangeCustomerEmail = { + email: Scalars["String"]; +}; + +export type ChangeCustomerGroupName = { + name: Scalars["String"]; +}; + +export type ChangeDescription = { + description: Scalars["String"]; +}; + +export type ChangeDiscountCodeCartDiscounts = { + cartDiscounts: Array; +}; + +export type ChangeDiscountCodeGroups = { + groups: Array; +}; + +export type ChangeDiscountCodeIsActive = { + isActive: Scalars["Boolean"]; +}; + +export type ChangeEnumKey = { + attributeName: Scalars["String"]; + key: Scalars["String"]; + newKey: Scalars["String"]; +}; + +export type ChangeInputHint = { + attributeName: Scalars["String"]; + newValue: TextInputHint; +}; + +export type ChangeInventoryEntryQuantity = { + quantity: Scalars["Long"]; +}; + +export type ChangeIsSearchable = { + attributeName: Scalars["String"]; + isSearchable: Scalars["Boolean"]; +}; + +export type ChangeLabel = { + attributeName: Scalars["String"]; + label: Array; +}; + +export type ChangeLocalizedEnumValueLabel = { + attributeName: Scalars["String"]; + newValue: LocalizedEnumValueDraft; +}; + +export type ChangeLocalizedEnumValueOrder = { + attributeName: Scalars["String"]; + values: Array; +}; + +export type ChangeMyCartTaxMode = { + taxMode: TaxMode; +}; + +export type ChangeName = { + name: Scalars["String"]; +}; + +export type ChangeOrderPaymentState = { + paymentState: PaymentState; +}; + +export type ChangeOrderShipmentState = { + shipmentState: ShipmentState; +}; + +export type ChangeOrderState = { + orderState: OrderState; +}; + +export type ChangePlainEnumValueLabel = { + attributeName: Scalars["String"]; + newValue: PlainEnumValueDraft; +}; + +export type ChangePlainEnumValueOrder = { + attributeName: Scalars["String"]; + values: Array; +}; + +export type ChangeProductAssetName = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + name: Array; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type ChangeProductAssetOrder = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + assetOrder: Array; +}; + +export type ChangeProductDiscountIsActive = { + isActive: Scalars["Boolean"]; +}; + +export type ChangeProductDiscountName = { + name: Array; +}; + +export type ChangeProductDiscountPredicate = { + predicate: Scalars["String"]; +}; + +export type ChangeProductDiscountSortOrder = { + sortOrder: Scalars["String"]; +}; + +export type ChangeProductDiscountValue = { + value: ProductDiscountValueInput; +}; + +export type ChangeProductImageLabel = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + label?: Maybe; + staged?: Maybe; +}; + +export type ChangeProductMasterVariant = { + variantId?: Maybe; + sku?: Maybe; + staged?: Maybe; +}; + +export type ChangeProductName = { + name: Array; + staged?: Maybe; +}; + +export type ChangeProductPrice = { + priceId?: Maybe; + variantId?: Maybe; + sku?: Maybe; + price: ProductPriceDataInput; + catalog?: Maybe; + staged?: Maybe; +}; + +export type ChangeProductSlug = { + slug: Array; + staged?: Maybe; +}; + +export type ChangeProjectSettingsCountries = { + countries: Array; +}; + +export type ChangeProjectSettingsCurrencies = { + currencies: Array; +}; + +export type ChangeProjectSettingsLanguages = { + languages: Array; +}; + +export type ChangeProjectSettingsMessagesConfiguration = { + messagesConfiguration: MessagesConfigurationDraft; +}; + +export type ChangeProjectSettingsMessagesEnabled = { + messagesEnabled: Scalars["Boolean"]; +}; + +export type ChangeProjectSettingsName = { + name: Scalars["String"]; +}; + +export type ChangeShippingMethodIsDefault = { + isDefault: Scalars["Boolean"]; +}; + +export type ChangeShippingMethodName = { + name: Scalars["String"]; +}; + +export type ChangeShippingMethodTaxCategory = { + taxCategory: ResourceIdentifierInput; +}; + +export type ChangeShoppingListLineItemQuantity = { + lineItemId: Scalars["String"]; + quantity: Scalars["Int"]; +}; + +export type ChangeShoppingListLineItemsOrder = { + lineItemOrder: Array; +}; + +export type ChangeShoppingListName = { + name: Array; +}; + +export type ChangeShoppingListTextLineItemName = { + textLineItemId: Scalars["String"]; + name: Array; +}; + +export type ChangeShoppingListTextLineItemQuantity = { + textLineItemId: Scalars["String"]; + quantity: Scalars["Int"]; +}; + +export type ChangeShoppingListTextLineItemsOrder = { + textLineItemOrder: Array; +}; + +export type ChangeZoneName = { + name: Scalars["String"]; +}; + +export type Channel = Versioned & { + __typename?: "Channel"; + id: Scalars["String"]; + version: Scalars["Long"]; + key: Scalars["String"]; + roles: Array; + name?: Maybe; + nameAllLocales?: Maybe>; + description?: Maybe; + descriptionAllLocales?: Maybe>; + address?: Maybe
; + geoLocation?: Maybe; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ChannelNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ChannelDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ChannelCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ChannelQueryResult = { + __typename?: "ChannelQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ChannelReferenceIdentifier = { + __typename?: "ChannelReferenceIdentifier"; + typeId: Scalars["String"]; + id?: Maybe; + key?: Maybe; +}; + +export enum ChannelRole { + /** Role tells that this channel can be used to track inventory entries.Channels with this role can be treated as warehouses */ + InventorySupply = "InventorySupply", + /** Role tells that this channel can be used to expose products to a specific + * distribution channel. It can be used by the cart to select a product price. + */ + ProductDistribution = "ProductDistribution", + /** Role tells that this channel can be used to track order export activities. */ + OrderExport = "OrderExport", + /** Role tells that this channel can be used to track order import activities. */ + OrderImport = "OrderImport", + /** This role can be combined with some other roles (e.g. with `InventorySupply`) + * to represent the fact that this particular channel is the primary/master + * channel among the channels of the same type. + */ + Primary = "Primary" +} + +export type ClassificationShippingRateInput = ShippingRateInput & { + __typename?: "ClassificationShippingRateInput"; + key: Scalars["String"]; + type: Scalars["String"]; + labelAllLocales: Array; + label?: Maybe; +}; + +export type ClassificationShippingRateInputLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ClassificationShippingRateInputDraft = { + key: Scalars["String"]; +}; + +export type CreateApiClient = { + name: Scalars["String"]; + scope: Scalars["String"]; +}; + +export type CreateStore = { + key: Scalars["String"]; + name?: Maybe>; + languages?: Maybe>; +}; + +export type CreateZone = { + name: Scalars["String"]; + key?: Maybe; + description?: Maybe; + locations?: Maybe>; +}; + +/** A customer is a person purchasing products. Carts, Orders and Reviews can be associated to a customer. */ +export type Customer = Versioned & { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A customer is a person purchasing products. Carts, Orders and Reviews can be associated to a customer. */ +export type CustomerCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A customer is a person purchasing products. Carts, Orders and Reviews can be associated to a customer. */ +export type CustomerCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A field to access a customer's active cart. */ +export type CustomerActiveCartInterface = { + customerActiveCart?: Maybe; +}; + +/** A field to access a customer's active cart. */ +export type CustomerActiveCartInterfaceCustomerActiveCartArgs = { + customerId: Scalars["String"]; +}; + +/** A customer can be a member in a customer group (e.g. reseller, gold member). A + * customer group can be used in price calculations with special prices being + * assigned to certain customer groups. + */ +export type CustomerGroup = Versioned & { + __typename?: "CustomerGroup"; + id: Scalars["String"]; + version: Scalars["Long"]; + name: Scalars["String"]; + key?: Maybe; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A customer can be a member in a customer group (e.g. reseller, gold member). A + * customer group can be used in price calculations with special prices being + * assigned to certain customer groups. + */ +export type CustomerGroupCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A customer can be a member in a customer group (e.g. reseller, gold member). A + * customer group can be used in price calculations with special prices being + * assigned to certain customer groups. + */ +export type CustomerGroupCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CustomerGroupDraft = { + groupName: Scalars["String"]; + key?: Maybe; + custom?: Maybe; +}; + +export type CustomerGroupQueryResult = { + __typename?: "CustomerGroupQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CustomerGroupUpdateAction = { + changeName?: Maybe; + setKey?: Maybe; + setCustomType?: Maybe; + setCustomField?: Maybe; +}; + +/** Fields to access customer accounts. Includes direct access to a single customer and searching for customers. */ +export type CustomerQueryInterface = { + customer?: Maybe; + customers: CustomerQueryResult; +}; + +/** Fields to access customer accounts. Includes direct access to a single customer and searching for customers. */ +export type CustomerQueryInterfaceCustomerArgs = { + emailToken?: Maybe; + passwordToken?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +/** Fields to access customer accounts. Includes direct access to a single customer and searching for customers. */ +export type CustomerQueryInterfaceCustomersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type CustomerQueryResult = { + __typename?: "CustomerQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CustomerSignInDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + anonymousCartId?: Maybe; + anonymousCartSignInMode?: Maybe; + anonymousId?: Maybe; + updateProductData?: Maybe; +}; + +export type CustomerSignInResult = { + __typename?: "CustomerSignInResult"; + customer: Customer; + cart?: Maybe; +}; + +export type CustomerSignMeInDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + activeCartSignInMode?: Maybe; + updateProductData?: Maybe; +}; + +export type CustomerSignMeUpDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + addresses?: Maybe>; + /** The index of the address in the `addresses` list. The + * `defaultBillingAddressId` of the customer will be set to the ID of that address. + */ + defaultBillingAddress?: Maybe; + /** The index of the address in the `addresses` list. The + * `defaultShippingAddressId` of the customer will be set to the ID of that address. + */ + defaultShippingAddress?: Maybe; + /** The indices of the shipping addresses in the `addresses` list. The + * `shippingAddressIds` of the `Customer` will be set to the IDs of that addresses. + */ + shippingAddresses?: Maybe>; + /** The indices of the billing addresses in the `addresses` list. The + * `billingAddressIds` of the customer will be set to the IDs of that addresses. + */ + billingAddresses?: Maybe>; + custom?: Maybe; + locale?: Maybe; + salutation?: Maybe; + key?: Maybe; + stores?: Maybe>; +}; + +export type CustomerSignUpDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + addresses?: Maybe>; + /** The index of the address in the `addresses` list. The + * `defaultBillingAddressId` of the customer will be set to the ID of that address. + */ + defaultBillingAddress?: Maybe; + /** The index of the address in the `addresses` list. The + * `defaultShippingAddressId` of the customer will be set to the ID of that address. + */ + defaultShippingAddress?: Maybe; + /** The indices of the shipping addresses in the `addresses` list. The + * `shippingAddressIds` of the `Customer` will be set to the IDs of that addresses. + */ + shippingAddresses?: Maybe>; + /** The indices of the billing addresses in the `addresses` list. The + * `billingAddressIds` of the customer will be set to the IDs of that addresses. + */ + billingAddresses?: Maybe>; + custom?: Maybe; + locale?: Maybe; + salutation?: Maybe; + key?: Maybe; + stores?: Maybe>; + customerNumber?: Maybe; + anonymousCartId?: Maybe; + externalId?: Maybe; + customerGroup?: Maybe; + isEmailVerified?: Maybe; + anonymousId?: Maybe; +}; + +export type CustomerToken = { + __typename?: "CustomerToken"; + id: Scalars["String"]; + customerId: Scalars["String"]; + createdAt: Scalars["DateTime"]; + expiresAt: Scalars["DateTime"]; + value: Scalars["String"]; +}; + +export type CustomerPasswordToken = { + customerId: Scalars["String"]; + expiresAt: Scalars["DateTime"]; + value: Scalars["String"]; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +} + +export type CustomerUpdateAction = { + addAddress?: Maybe; + addBillingAddressId?: Maybe; + addShippingAddressId?: Maybe; + addStore?: Maybe; + changeAddress?: Maybe; + changeEmail?: Maybe; + removeAddress?: Maybe; + removeBillingAddressId?: Maybe; + removeShippingAddressId?: Maybe; + removeStore?: Maybe; + setCompanyName?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setCustomerGroup?: Maybe; + setKey?: Maybe; + setLocale?: Maybe; + setCustomerNumber?: Maybe; + setDateOfBirth?: Maybe; + setDefaultBillingAddress?: Maybe; + setDefaultShippingAddress?: Maybe; + setExternalId?: Maybe; + setFirstName?: Maybe; + setLastName?: Maybe; + setMiddleName?: Maybe; + setSalutation?: Maybe; + setStores?: Maybe; + setTitle?: Maybe; + setVatId?: Maybe; +}; + +export type CustomField = { + name: Scalars["String"]; +}; + +/** A key-value pair representing the field name and value of one single custom field. + * + * The value of this custom field consists of escaped JSON based on the FieldDefinition of the Type. + * + * Examples for `value`: + * + * * FieldType `String`: `"\"This is a string\""` + * * FieldType `DateTimeType`: `"\"2001-09-11T14:00:00.000Z\""` + * * FieldType `Number`: `"4"` + * * FieldType `Set` with an elementType of `String`: `"[\"This is a string\", \"This is another string\"]"` + * * FieldType `Reference`: `"{\"id\", \"b911b62d-353a-4388-93ee-8d488d9af962\", \"typeId\", \"product\"}"` + */ +export type CustomFieldInput = { + name: Scalars["String"]; + /** The value of this custom field consists of escaped JSON based on the FieldDefinition of the Type. + * + * Examples for `value`: + * + * * FieldType `String`: `"\"This is a string\""` + * * FieldType `DateTimeType`: `"\"2001-09-11T14:00:00.000Z\""` + * * FieldType `Number`: `"4"` + * * FieldType `Set` with an elementType of `String`: `"[\"This is a string\", \"This is another string\"]"` + * * FieldType `Reference`: `"{\"id\", \"b911b62d-353a-4388-93ee-8d488d9af962\", \"typeId\", \"product\"}"` + */ + value: Scalars["String"]; +}; + +export type CustomFieldsDraft = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; +}; + +export type CustomFieldsType = { + __typename?: "CustomFieldsType"; + typeRef: Reference; + type?: Maybe; + /** This field contains non-typed data. For a typed alternative, have a look at `customFields`. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields: Type; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type CustomFieldsTypeCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CustomFieldsTypeCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItem = { + __typename?: "CustomLineItem"; + id: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + money: BaseMoney; + totalPrice: Money; + slug: Scalars["String"]; + quantity: Scalars["Long"]; + state: Array; + taxCategory?: Maybe; + taxCategoryRef?: Maybe; + taxRate?: Maybe; + discountedPricePerQuantity: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItemCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItemCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CustomLineItemDraft = { + name: Array; + money: BaseMoneyInput; + slug: Scalars["String"]; + taxCategory?: Maybe; + externalTaxRate?: Maybe; + quantity?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; +}; + +export type CustomLineItemReturnItem = ReturnItem & { + __typename?: "CustomLineItemReturnItem"; + type: Scalars["String"]; + customLineItemId: Scalars["String"]; + id: Scalars["String"]; + quantity: Scalars["Long"]; + comment?: Maybe; + shipmentState: ReturnShipmentState; + paymentState: ReturnPaymentState; + lastModifiedAt: Scalars["DateTime"]; + createdAt: Scalars["DateTime"]; +}; + +export type CustomLineItemsTarget = CartDiscountTarget & { + __typename?: "CustomLineItemsTarget"; + predicate: Scalars["String"]; + type: Scalars["String"]; +}; + +export type CustomLineItemsTargetInput = { + predicate: Scalars["String"]; +}; + +export type CustomSuggestTokenizerInput = { + text: Scalars["String"]; + suggestTokenizer?: Maybe; +}; + +export type DateAttribute = Attribute & { + __typename?: "DateAttribute"; + value: Scalars["Date"]; + name: Scalars["String"]; +}; + +export type DateAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "DateAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type DateField = CustomField & { + __typename?: "DateField"; + value: Scalars["Date"]; + name: Scalars["String"]; +}; + +export type DateTimeAttribute = Attribute & { + __typename?: "DateTimeAttribute"; + value: Scalars["DateTime"]; + name: Scalars["String"]; +}; + +export type DateTimeAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "DateTimeAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type DateTimeField = CustomField & { + __typename?: "DateTimeField"; + value: Scalars["DateTime"]; + name: Scalars["String"]; +}; + +export type DateTimeType = FieldType & { + __typename?: "DateTimeType"; + name: Scalars["String"]; +}; + +export type DateType = FieldType & { + __typename?: "DateType"; + name: Scalars["String"]; +}; + +export type Delivery = { + __typename?: "Delivery"; + id: Scalars["String"]; + createdAt: Scalars["DateTime"]; + items: Array; + parcels: Array; + address?: Maybe
; +}; + +export type DeliveryItem = { + __typename?: "DeliveryItem"; + id: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type DeliveryItemDraftType = { + id: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type Dimensions = { + __typename?: "Dimensions"; + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +export type DimensionsInput = { + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCode = Versioned & { + __typename?: "DiscountCode"; + code: Scalars["String"]; + isActive: Scalars["Boolean"]; + maxApplications?: Maybe; + maxApplicationsPerCustomer?: Maybe; + cartPredicate?: Maybe; + applicationVersion?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + groups: Array; + name?: Maybe; + description?: Maybe; + cartDiscounts: Array; + nameAllLocales?: Maybe>; + descriptionAllLocales?: Maybe>; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** How many times this discount code was applied (only applications that were part of a successful checkout are considered) */ + applicationCount: Scalars["Long"]; + cartDiscountRefs: Array; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type DiscountCodeDraft = { + code: Scalars["String"]; + name?: Maybe>; + description?: Maybe>; + cartDiscounts: Array; + isActive?: Maybe; + maxApplications?: Maybe; + maxApplicationsPerCustomer?: Maybe; + cartPredicate?: Maybe; + custom?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + groups?: Maybe>; +}; + +export type DiscountCodeInfo = { + __typename?: "DiscountCodeInfo"; + discountCodeRef: Reference; + state?: Maybe; + discountCode?: Maybe; +}; + +export type DiscountCodeQueryResult = { + __typename?: "DiscountCodeQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum DiscountCodeState { + /** The discount code is active and none of the discounts were applied because the + * discount application was stopped by one discount that has the StackingMode of + * StopAfterThisDiscount defined + */ + ApplicationStoppedByPreviousDiscount = "ApplicationStoppedByPreviousDiscount", + /** The discount code is not valid or it does not contain any valid cart + * discounts. Validity is determined based on the validFrom and validUntil dates + */ + NotValid = "NotValid", + /** maxApplications or maxApplicationsPerCustomer for discountCode has been reached. */ + MaxApplicationReached = "MaxApplicationReached", + /** The discount code is active and it contains at least one active and valid + * CartDiscount. The discount code cartPredicate matches the cart and at least + * one of the contained active discount’s cart predicates matches the cart. + */ + MatchesCart = "MatchesCart", + /** The discount code is active and it contains at least one active and valid + * CartDiscount. But its cart predicate does not match the cart or none of the + * contained active discount’s cart predicates match the cart + */ + DoesNotMatchCart = "DoesNotMatchCart", + /** The discount code is not active or it does not contain any active cart discounts. */ + NotActive = "NotActive" +} + +export type DiscountCodeUpdateAction = { + changeCartDiscounts?: Maybe; + changeGroups?: Maybe; + changeIsActive?: Maybe; + setCartPredicate?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDescription?: Maybe; + setMaxApplications?: Maybe; + setMaxApplicationsPerCustomer?: Maybe< + SetDiscountCodeMaxApplicationsPerCustomer + >; + setName?: Maybe; + setValidFrom?: Maybe; + setValidFromAndUntil?: Maybe; + setValidUntil?: Maybe; +}; + +export type DiscountedLineItemPortion = { + __typename?: "DiscountedLineItemPortion"; + discount?: Maybe; + discountRef: Reference; + discountedAmount: BaseMoney; +}; + +export type DiscountedLineItemPrice = { + __typename?: "DiscountedLineItemPrice"; + value: BaseMoney; + includedDiscounts: Array; +}; + +export type DiscountedLineItemPriceForQuantity = { + __typename?: "DiscountedLineItemPriceForQuantity"; + quantity: Scalars["Long"]; + discountedPrice: DiscountedLineItemPrice; +}; + +export type DiscountedProductPriceValue = { + __typename?: "DiscountedProductPriceValue"; + value: BaseMoney; + discountRef: Reference; + discount?: Maybe; + /** Temporal. Will be renamed some time in the future. Please use 'discount'. */ + discountRel?: Maybe; +}; + +export type DiscountedProductPriceValueInput = { + value: BaseMoneyInput; + discount: ReferenceInput; +}; + +export type EnumAttribute = Attribute & { + __typename?: "EnumAttribute"; + key: Scalars["String"]; + label: Scalars["String"]; + name: Scalars["String"]; +}; + +export type EnumAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "EnumAttributeDefinitionType"; + values: PlainEnumValueResult; + name: Scalars["String"]; +}; + +export type EnumAttributeDefinitionTypeValuesArgs = { + includeKeys?: Maybe>; + excludeKeys?: Maybe>; + limit?: Maybe; + offset?: Maybe; + sort?: Maybe>; +}; + +export type EnumField = CustomField & { + __typename?: "EnumField"; + key: Scalars["String"]; + name: Scalars["String"]; +}; + +export type EnumType = FieldType & { + __typename?: "EnumType"; + values: Array; + name: Scalars["String"]; +}; + +export type EnumTypeDraft = { + values: Array; +}; + +export type EnumValue = { + __typename?: "EnumValue"; + key: Scalars["String"]; + label: Scalars["String"]; +}; + +export type ExternalDiscountValue = ProductDiscountValue & { + __typename?: "ExternalDiscountValue"; + type: Scalars["String"]; +}; + +export type ExternalDiscountValueInput = { + dummy?: Maybe; +}; + +export type ExternalLineItemTotalPriceDraft = { + price: BaseMoneyInput; + totalPrice: MoneyInput; +}; + +export type ExternalOAuth = { + __typename?: "ExternalOAuth"; + url: Scalars["String"]; + authorizationHeader: Scalars["String"]; +}; + +export type ExternalOAuthDraft = { + url: Scalars["String"]; + authorizationHeader: Scalars["String"]; +}; + +export type ExternalTaxAmountDraft = { + totalGross: MoneyInput; + taxRate: ExternalTaxRateDraft; +}; + +export type ExternalTaxRateDraft = { + name: Scalars["String"]; + amount: Scalars["Float"]; + country: Scalars["Country"]; + state?: Maybe; + subRates?: Maybe>; + includedInPrice?: Maybe; +}; + +/** Field definitions describe custom fields and allow you to define some meta-information associated with the field. */ +export type FieldDefinition = { + __typename?: "FieldDefinition"; + name: Scalars["String"]; + required: Scalars["Boolean"]; + inputHint: TextInputHint; + label?: Maybe; + labelAllLocales: Array; + type: FieldType; +}; + +/** Field definitions describe custom fields and allow you to define some meta-information associated with the field. */ +export type FieldDefinitionLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type FieldType = { + name: Scalars["String"]; +}; + +export type Geometry = { + type: Scalars["String"]; +}; + +export type GiftLineItemValue = CartDiscountValue & { + __typename?: "GiftLineItemValue"; + type: Scalars["String"]; + variantId: Scalars["Int"]; + productRef: ProductReferenceIdentifier; + distributionChannelRef?: Maybe; + supplyChannelRef?: Maybe; +}; + +export type GiftLineItemValueInput = { + product: ResourceIdentifierInput; + variantId: Scalars["Int"]; + distributionChannel?: Maybe; + supplyChannel?: Maybe; +}; + +export type HighPrecisionMoney = BaseMoney & { + __typename?: "HighPrecisionMoney"; + type: Scalars["String"]; + currencyCode: Scalars["Currency"]; + preciseAmount: Scalars["Long"]; + centAmount: Scalars["Long"]; + fractionDigits: Scalars["Int"]; +}; + +export type HighPrecisionMoneyInput = { + currencyCode: Scalars["Currency"]; + preciseAmount: Scalars["Long"]; + fractionDigits: Scalars["Int"]; + centAmount?: Maybe; +}; + +export type Image = { + __typename?: "Image"; + url: Scalars["String"]; + dimensions: Dimensions; + label?: Maybe; +}; + +export type ImageInput = { + url: Scalars["String"]; + label?: Maybe; + dimensions: DimensionsInput; +}; + +export type ImportOrderCustomLineItemState = { + customLineItemId: Scalars["String"]; + state: Array; +}; + +export type ImportOrderLineItemState = { + lineItemId: Scalars["String"]; + state: Array; +}; + +export type Initiator = { + __typename?: "Initiator"; + isPlatformClient?: Maybe; + user?: Maybe; + externalUserId?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + clientId?: Maybe; +}; + +export type InStore = CartQueryInterface & + CustomerActiveCartInterface & + OrderQueryInterface & + CustomerQueryInterface & + ShippingMethodsByCartInterface & + MeFieldInterface & { + __typename?: "InStore"; + /** This field can only be used with an access token created with the password flow or with an anonymous session. + * + * It gives access to the data that is specific to the customer or the anonymous session linked to the access token. + */ + me: InStoreMe; + shippingMethodsByCart: Array; + customer?: Maybe; + customers: CustomerQueryResult; + cart?: Maybe; + carts: CartQueryResult; + customerActiveCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + }; + +export type InStoreShippingMethodsByCartArgs = { + id: Scalars["String"]; +}; + +export type InStoreCustomerArgs = { + emailToken?: Maybe; + passwordToken?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type InStoreCustomersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreCartArgs = { + id: Scalars["String"]; +}; + +export type InStoreCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreCustomerActiveCartArgs = { + customerId: Scalars["String"]; +}; + +export type InStoreOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type InStoreOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreMe = MeQueryInterface & { + __typename?: "InStoreMe"; + customer?: Maybe; + cart?: Maybe; + carts: CartQueryResult; + activeCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +export type InStoreMeCartArgs = { + id: Scalars["String"]; +}; + +export type InStoreMeCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreMeOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type InStoreMeOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreMeShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type InStoreMeShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InterfaceInteractionsRaw = { + __typename?: "InterfaceInteractionsRaw"; + typeRef: Reference; + type?: Maybe; + fields: Array; +}; + +export type InterfaceInteractionsRawFieldsArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type InterfaceInteractionsRawResult = { + __typename?: "InterfaceInteractionsRawResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +/** Inventory allows you to track stock quantity per SKU and optionally per supply channel */ +export type InventoryEntry = Versioned & { + __typename?: "InventoryEntry"; + sku: Scalars["String"]; + supplyChannel?: Maybe; + quantityOnStock: Scalars["Long"]; + availableQuantity: Scalars["Long"]; + restockableInDays?: Maybe; + expectedDelivery?: Maybe; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** Inventory allows you to track stock quantity per SKU and optionally per supply channel */ +export type InventoryEntryCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Inventory allows you to track stock quantity per SKU and optionally per supply channel */ +export type InventoryEntryCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type InventoryEntryDraft = { + sku: Scalars["String"]; + quantityOnStock?: Maybe; + restockableInDays?: Maybe; + expectedDelivery?: Maybe; + supplyChannel?: Maybe; + custom?: Maybe; +}; + +export type InventoryEntryQueryResult = { + __typename?: "InventoryEntryQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type InventoryEntryUpdateAction = { + addQuantity?: Maybe; + changeQuantity?: Maybe; + removeQuantity?: Maybe; + setRestockableInDays?: Maybe; + setExpectedDelivery?: Maybe; + setSupplyChannel?: Maybe; + setCustomType?: Maybe; + setCustomField?: Maybe; +}; + +export enum InventoryMode { + /** Adding items to cart and ordering is independent of inventory. No inventory checks or modifications. + * This is the default mode for a new cart. + */ + None = "None", + /** Creating an order will fail with an OutOfStock error if an unavailable line item exists. Line items in the cart + * are only reserved for the duration of the ordering transaction. + */ + ReserveOnOrder = "ReserveOnOrder", + /** Orders are tracked on inventory. That means, ordering a LineItem will decrement the available quantity on the + * respective InventoryEntry. Creating an order will succeed even if the line item’s available quantity is zero or + * negative. But creating an order will fail with an OutOfStock error if no matching inventory entry exists for a + * line item. + */ + TrackOnly = "TrackOnly" +} + +export type IOsUserType = Type & { + __typename?: "iOSUserType"; + typeRef: Reference; + type: TypeDefinition; + apnsToken?: Maybe; + myStore?: Maybe; +}; + +export type ItemShippingDetails = { + __typename?: "ItemShippingDetails"; + targets: Array; + valid: Scalars["Boolean"]; +}; + +export type ItemShippingDetailsDraft = { + targets: Array; +}; + +export type ItemShippingDetailsDraftType = { + targets: Array; +}; + +export type ItemShippingTarget = { + __typename?: "ItemShippingTarget"; + addressKey: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ItemState = { + __typename?: "ItemState"; + quantity: Scalars["Long"]; + stateRef: Reference; + state?: Maybe; +}; + +export type ItemStateDraftType = { + quantity: Scalars["Long"]; + state: ReferenceInput; +}; + +export type KeyReference = { + __typename?: "KeyReference"; + typeId: Scalars["String"]; + key: Scalars["String"]; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItem = { + __typename?: "LineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + productSlug?: Maybe; + productType?: Maybe; + productTypeRef?: Maybe; + variant?: Maybe; + price: ProductPrice; + taxedPrice?: Maybe; + totalPrice?: Maybe; + quantity: Scalars["Long"]; + state: Array; + taxRate?: Maybe; + supplyChannel?: Maybe; + supplyChannelRef?: Maybe; + distributionChannel?: Maybe; + distributionChannelRef?: Maybe; + discountedPricePerQuantity: Array; + lineItemMode: LineItemMode; + priceMode: LineItemPriceMode; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + inventoryMode?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemProductSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type LineItemDraft = { + productId?: Maybe; + sku?: Maybe; + quantity?: Maybe; + variantId?: Maybe; + supplyChannel?: Maybe; + distributionChannel?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + externalTaxRate?: Maybe; + externalPrice?: Maybe; + externalTotalPrice?: Maybe; +}; + +export enum LineItemMode { + /** The line item was added automatically, because a discount has added a free gift to the cart. + * The quantity can not be increased, and it won’t be merged when the same product variant is added. + * If the gift is removed, an entry is added to the "refusedGifts" array and the discount won’t be applied again + * to the cart. The price can not be changed externally. + * All other updates, such as the ones related to custom fields, can be used. + */ + GiftLineItem = "GiftLineItem", + /** The line item was added during cart creation or with the update action addLineItem. Its quantity can be + * changed without restrictions. + */ + Standard = "Standard" +} + +export enum LineItemPriceMode { + /** The price is selected form the product variant. This is the default mode. */ + Platform = "Platform", + /** The line item price was set externally. Cart discounts can apply to line items + * with this price mode. All update actions that change the quantity of a line + * item with this price mode require the externalPrice field to be given. + */ + ExternalPrice = "ExternalPrice", + /** The line item price with the total was set externally. */ + ExternalTotal = "ExternalTotal" +} + +export type LineItemReturnItem = ReturnItem & { + __typename?: "LineItemReturnItem"; + type: Scalars["String"]; + lineItemId: Scalars["String"]; + id: Scalars["String"]; + quantity: Scalars["Long"]; + comment?: Maybe; + shipmentState: ReturnShipmentState; + paymentState: ReturnPaymentState; + lastModifiedAt: Scalars["DateTime"]; + createdAt: Scalars["DateTime"]; +}; + +export type LineItemsTarget = CartDiscountTarget & { + __typename?: "LineItemsTarget"; + predicate: Scalars["String"]; + type: Scalars["String"]; +}; + +export type LineItemsTargetInput = { + predicate: Scalars["String"]; +}; + +export type LocalizableEnumAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "LocalizableEnumAttributeDefinitionType"; + values: LocalizableEnumValueTypeResult; + name: Scalars["String"]; +}; + +export type LocalizableEnumAttributeDefinitionTypeValuesArgs = { + includeKeys?: Maybe>; + excludeKeys?: Maybe>; + limit?: Maybe; + offset?: Maybe; + sort?: Maybe>; +}; + +export type LocalizableEnumTypeDraft = { + values: Array; +}; + +export type LocalizableEnumValueType = { + __typename?: "LocalizableEnumValueType"; + key: Scalars["String"]; + label?: Maybe; + labelAllLocales: Array; +}; + +export type LocalizableEnumValueTypeLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type LocalizableEnumValueTypeResult = { + __typename?: "LocalizableEnumValueTypeResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +export type LocalizableTextAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "LocalizableTextAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type LocalizedEnumAttribute = Attribute & { + __typename?: "LocalizedEnumAttribute"; + key: Scalars["String"]; + label?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedEnumAttributeLabelArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedEnumField = CustomField & { + __typename?: "LocalizedEnumField"; + key: Scalars["String"]; + label?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedEnumFieldLabelArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedEnumType = FieldType & { + __typename?: "LocalizedEnumType"; + values: Array; + name: Scalars["String"]; +}; + +export type LocalizedEnumValue = { + __typename?: "LocalizedEnumValue"; + key: Scalars["String"]; + label?: Maybe; + labelAllLocales: Array; +}; + +export type LocalizedEnumValueLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type LocalizedEnumValueDraft = { + key: Scalars["String"]; + label: Array; +}; + +export type LocalizedEnumValueInput = { + key: Scalars["String"]; + label: Array; +}; + +export type LocalizedString = { + __typename?: "LocalizedString"; + locale: Scalars["Locale"]; + value: Scalars["String"]; +}; + +export type LocalizedStringAttribute = Attribute & { + __typename?: "LocalizedStringAttribute"; + value?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedStringAttributeValueArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedStringField = CustomField & { + __typename?: "LocalizedStringField"; + value?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedStringFieldValueArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedStringItemInputType = { + locale: Scalars["Locale"]; + value: Scalars["String"]; +}; + +export type LocalizedStringType = FieldType & { + __typename?: "LocalizedStringType"; + name: Scalars["String"]; +}; + +export type LocalizedText = { + text: Scalars["String"]; + locale: Scalars["Locale"]; +}; + +export type Location = { + __typename?: "Location"; + country: Scalars["Country"]; + state?: Maybe; +}; + +/** Sunrise Product Data Set Structure */ +export type MainProductType = ProductType & { + __typename?: "mainProductType"; + productTypeId: Scalars["String"]; + creationDate?: Maybe; + articleNumberManufacturer?: Maybe; + articleNumberMax?: Maybe; + matrixId?: Maybe; + baseId?: Maybe; + designer?: Maybe; + madeInItaly?: Maybe; + completeTheLook?: Maybe>; + commonSize?: Maybe; + size?: Maybe; + color?: Maybe; + colorFreeDefinition?: Maybe; + details?: Maybe>; + style?: Maybe; + gender?: Maybe; + season?: Maybe; + isOnStock?: Maybe; + isLook?: Maybe; + lookProducts?: Maybe>; + seasonNew?: Maybe; + sapExternalId?: Maybe; +}; + +export type Me = MeQueryInterface & { + __typename?: "Me"; + customer?: Maybe; + cart?: Maybe; + carts: CartQueryResult; + activeCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +export type MeCartArgs = { + id: Scalars["String"]; +}; + +export type MeCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MeOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type MeShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +/** The me field gives access to the data that is specific to the customer or anonymous session linked to the access token. */ +export type MeFieldInterface = { + me: MeQueryInterface; +}; + +export type MeQueryInterface = { + cart?: Maybe; + carts: CartQueryResult; + activeCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +export type MeQueryInterfaceCartArgs = { + id: Scalars["String"]; +}; + +export type MeQueryInterfaceCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeQueryInterfaceOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MeQueryInterfaceOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeQueryInterfaceShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type MeQueryInterfaceShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MessagesConfiguration = { + __typename?: "MessagesConfiguration"; + enabled: Scalars["Boolean"]; + deleteDaysAfterCreation?: Maybe; +}; + +export type MessagesConfigurationDraft = { + enabled: Scalars["Boolean"]; + deleteDaysAfterCreation: Scalars["Int"]; +}; + +export type Money = BaseMoney & { + __typename?: "Money"; + type: Scalars["String"]; + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; + /** For the `Money` it equals to the default number of fraction digits used with the currency. */ + fractionDigits: Scalars["Int"]; +}; + +export type MoneyAttribute = Attribute & { + __typename?: "MoneyAttribute"; + centAmount: Scalars["Long"]; + currencyCode: Scalars["Currency"]; + name: Scalars["String"]; +}; + +export type MoneyAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "MoneyAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type MoneyDraft = { + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; +}; + +export type MoneyField = CustomField & { + __typename?: "MoneyField"; + centAmount: Scalars["Long"]; + currencyCode: Scalars["Currency"]; + name: Scalars["String"]; +}; + +export type MoneyInput = { + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; +}; + +export type MoneyType = FieldType & { + __typename?: "MoneyType"; + name: Scalars["String"]; +}; + +export type MoveProductImageToPosition = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + position: Scalars["Int"]; + staged?: Maybe; +}; + +export type MultiBuyCustomLineItemsTarget = CartDiscountTarget & { + __typename?: "MultiBuyCustomLineItemsTarget"; + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode: SelectionMode; + type: Scalars["String"]; +}; + +export type MultiBuyCustomLineItemsTargetInput = { + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode?: Maybe; +}; + +export type MultiBuyLineItemsTarget = CartDiscountTarget & { + __typename?: "MultiBuyLineItemsTarget"; + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode: SelectionMode; + type: Scalars["String"]; +}; + +export type MultiBuyLineItemsTargetInput = { + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode?: Maybe; +}; + +export type Mutation = { + __typename?: "Mutation"; + createCustomerGroup?: Maybe; + updateCustomerGroup?: Maybe; + deleteCustomerGroup?: Maybe; + createCategory?: Maybe; + updateCategory?: Maybe; + deleteCategory?: Maybe; + createProductType?: Maybe; + updateProductType?: Maybe; + deleteProductType?: Maybe; + createShippingMethod?: Maybe; + updateShippingMethod?: Maybe; + deleteShippingMethod?: Maybe; + createZone?: Maybe; + updateZone?: Maybe; + deleteZone?: Maybe; + createTaxCategory?: Maybe; + updateTaxCategory?: Maybe; + deleteTaxCategory?: Maybe; + createDiscountCode?: Maybe; + updateDiscountCode?: Maybe; + deleteDiscountCode?: Maybe; + createCartDiscount?: Maybe; + updateCartDiscount?: Maybe; + deleteCartDiscount?: Maybe; + createProductDiscount?: Maybe; + updateProductDiscount?: Maybe; + deleteProductDiscount?: Maybe; + createProduct?: Maybe; + updateProduct?: Maybe; + deleteProduct?: Maybe; + /** Creates a customer. If an anonymous cart is given then the cart is assigned to + * the created customer and the version number of the Cart will increase. If the + * id of an anonymous session is given, all carts and orders will be assigned to + * the created customer. + */ + customerSignUp: CustomerSignInResult; + /** Retrieves the authenticated customer (a customer that matches the given email/password pair). + * + * There may be carts and orders created before the sign in that should be + * assigned to the customer account. With the `anonymousCartId`, a single + * anonymous cart can be assigned. With the `anonymousId`, all orders and carts + * that have this `anonymousId` set will be assigned to the customer. + * If both `anonymousCartId` and `anonymousId` are given, the anonymous cart must have the `anonymousId`. + * + * Additionally, there might also exist one or more active customer carts from an + * earlier session. On customer sign in there are several ways how to proceed + * with this cart and the cart referenced by the `anonymousCartId`. + * + * * If the customer does not have a cart yet, the anonymous cart becomes the customer's cart. + * * If the customer already has one or more carts, the content of the anonymous + * cart will be copied to the customer's active cart that has been modified most recently. + * + * In this case the `CartState` of the anonymous cart gets changed to `Merged` + * while the customer's cart remains the `Active` cart. + * + * If a `LineItem` in the anonymous cart matches an existing line item, or a + * `CustomLineItem` matches an existing custom line item in the customer's cart, + * the maximum quantity of both line items is used as the new quantity. + * + * `ItemShippingDetails` are copied from the item with the highest quantity. + * + * If `itemShippingAddresses` are different in the two carts, the resulting cart + * contains the addresses of both the customer cart and the anonymous cart. + * + * Note, that it is not possible to merge carts that differ in their currency (set during creation of the cart). + * + * If a cart is is returned as part of the `CustomerSignInResult`, it has been + * recalculated (it will have up-to-date prices, taxes and discounts, and invalid + * line items have been removed). + */ + customerSignIn: CustomerSignInResult; + updateCustomer?: Maybe; + deleteCustomer?: Maybe; + customerChangePassword?: Maybe; + /** The following workflow can be used to reset the customer’s password: + * + * 1. Create a password reset token and send it embedded in a link to the customer. + * 2. When the customer clicks on the link, you may optionally retrieve customer by password token. + * 3. When the customer entered new password, use reset customer’s password to reset the password. + */ + customerResetPassword?: Maybe; + /** Verifies customer's email using a token. */ + customerConfirmEmail?: Maybe; + /** The token value is used to reset the password of the customer with the given + * email. The token is valid only for 10 minutes. + */ + customerCreatePasswordResetToken?: Maybe; + customerCreateEmailVerificationToken: CustomerToken; + /** If used with an access token for Anonymous Sessions, all orders and carts + * belonging to the anonymousId will be assigned to the newly created customer. + */ + customerSignMeUp: CustomerSignInResult; + /** Retrieves the authenticated customer (a customer that matches the given email/password pair). + * + * If used with an access token for Anonymous Sessions, all orders and carts + * belonging to the `anonymousId` will be assigned to the newly created customer. + * + * * If the customer does not have a cart yet, the anonymous cart that was + * modified most recently becomes the customer's cart. + * * If the customer already has a cart, the most recently modified anonymous + * cart will be handled according to the `AnonymousCartSignInMode`. + * + * If a cart is is returned as part of the `CustomerSignInResult`, it has been + * recalculated (it will have up-to-date prices, taxes and discounts, and invalid + * line items have been removed). + */ + customerSignMeIn: CustomerSignInResult; + updateMyCustomer?: Maybe; + deleteMyCustomer?: Maybe; + customerChangeMyPassword?: Maybe; + customerConfirmMyEmail?: Maybe; + customerResetMyPassword?: Maybe; + createInventoryEntry?: Maybe; + updateInventoryEntry?: Maybe; + deleteInventoryEntry?: Maybe; + createCart?: Maybe; + updateCart?: Maybe; + deleteCart?: Maybe; + replicateCart?: Maybe; + createMyCart?: Maybe; + updateMyCart?: Maybe; + deleteMyCart?: Maybe; + createOrderFromCart?: Maybe; + updateOrder?: Maybe; + deleteOrder?: Maybe; + createMyOrderFromCart?: Maybe; + createShoppingList?: Maybe; + updateShoppingList?: Maybe; + deleteShoppingList?: Maybe; + createMyShoppingList?: Maybe; + updateMyShoppingList?: Maybe; + deleteMyShoppingList?: Maybe; + updateProject?: Maybe; + createStore?: Maybe; + updateStore?: Maybe; + deleteStore?: Maybe; + createApiClient?: Maybe; + deleteApiClient?: Maybe; +}; + +export type MutationCreateCustomerGroupArgs = { + draft: CustomerGroupDraft; +}; + +export type MutationUpdateCustomerGroupArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCustomerGroupArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateCategoryArgs = { + draft: CategoryDraft; +}; + +export type MutationUpdateCategoryArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCategoryArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateProductTypeArgs = { + draft: ProductTypeDraft; +}; + +export type MutationUpdateProductTypeArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteProductTypeArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateShippingMethodArgs = { + draft: ShippingMethodDraft; +}; + +export type MutationUpdateShippingMethodArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteShippingMethodArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateZoneArgs = { + draft: CreateZone; +}; + +export type MutationUpdateZoneArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteZoneArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateTaxCategoryArgs = { + draft: TaxCategoryDraft; +}; + +export type MutationUpdateTaxCategoryArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteTaxCategoryArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateDiscountCodeArgs = { + draft: DiscountCodeDraft; +}; + +export type MutationUpdateDiscountCodeArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationDeleteDiscountCodeArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +export type MutationCreateCartDiscountArgs = { + draft: CartDiscountDraft; +}; + +export type MutationUpdateCartDiscountArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCartDiscountArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateProductDiscountArgs = { + draft: ProductDiscountDraft; +}; + +export type MutationUpdateProductDiscountArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteProductDiscountArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateProductArgs = { + draft: ProductDraft; +}; + +export type MutationUpdateProductArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteProductArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCustomerSignUpArgs = { + draft: CustomerSignUpDraft; + storeKey?: Maybe; +}; + +export type MutationCustomerSignInArgs = { + draft: CustomerSignInDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateCustomerArgs = { + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCustomerArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCustomerChangePasswordArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + currentPassword: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerResetPasswordArgs = { + version?: Maybe; + tokenValue: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerConfirmEmailArgs = { + version?: Maybe; + tokenValue: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerCreatePasswordResetTokenArgs = { + email: Scalars["String"]; + ttlMinutes?: Maybe; + storeKey?: Maybe; +}; + +export type MutationCustomerCreateEmailVerificationTokenArgs = { + id: Scalars["String"]; + version?: Maybe; + ttlMinutes: Scalars["Int"]; + storeKey?: Maybe; +}; + +export type MutationCustomerSignMeUpArgs = { + draft: CustomerSignMeUpDraft; + storeKey?: Maybe; +}; + +export type MutationCustomerSignMeInArgs = { + draft: CustomerSignMeInDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateMyCustomerArgs = { + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; +}; + +export type MutationDeleteMyCustomerArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; +}; + +export type MutationCustomerChangeMyPasswordArgs = { + version: Scalars["Long"]; + currentPassword: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerConfirmMyEmailArgs = { + tokenValue: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerResetMyPasswordArgs = { + tokenValue: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCreateInventoryEntryArgs = { + draft: InventoryEntryDraft; +}; + +export type MutationUpdateInventoryEntryArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationDeleteInventoryEntryArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +export type MutationCreateCartArgs = { + draft: CartDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; +}; + +export type MutationDeleteCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; +}; + +export type MutationReplicateCartArgs = { + reference: ReferenceInput; +}; + +export type MutationCreateMyCartArgs = { + draft: MyCartDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateMyCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; +}; + +export type MutationDeleteMyCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + storeKey?: Maybe; +}; + +export type MutationCreateOrderFromCartArgs = { + draft: OrderCartCommand; + storeKey?: Maybe; +}; + +export type MutationUpdateOrderArgs = { + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MutationDeleteOrderArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MutationCreateMyOrderFromCartArgs = { + draft: OrderMyCartCommand; + storeKey?: Maybe; +}; + +export type MutationCreateShoppingListArgs = { + draft: ShoppingListDraft; +}; + +export type MutationUpdateShoppingListArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteShoppingListArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateMyShoppingListArgs = { + draft: MyShoppingListDraft; +}; + +export type MutationUpdateMyShoppingListArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationDeleteMyShoppingListArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +export type MutationUpdateProjectArgs = { + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationCreateStoreArgs = { + draft: CreateStore; +}; + +export type MutationUpdateStoreArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteStoreArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateApiClientArgs = { + draft: CreateApiClient; +}; + +export type MutationDeleteApiClientArgs = { + id: Scalars["String"]; +}; + +export type MyCartDraft = { + currency: Scalars["Currency"]; + country?: Maybe; + inventoryMode?: Maybe; + custom?: Maybe; + customerEmail?: Maybe; + shippingAddress?: Maybe; + billingAddress?: Maybe; + shippingMethod?: Maybe; + taxMode?: Maybe; + locale?: Maybe; + deleteDaysAfterLastModification?: Maybe; + itemShippingAddresses?: Maybe>; + discountCodes?: Maybe>; + lineItems?: Maybe>; +}; + +export type MyCartUpdateAction = { + addDiscountCode?: Maybe; + addItemShippingAddress?: Maybe; + addLineItem?: Maybe; + addPayment?: Maybe; + addShoppingList?: Maybe; + applyDeltaToLineItemShippingDetailsTargets?: Maybe< + ApplyCartDeltaToLineItemShippingDetailsTargets + >; + changeLineItemQuantity?: Maybe; + changeTaxMode?: Maybe; + recalculate?: Maybe; + removeDiscountCode?: Maybe; + removeItemShippingAddress?: Maybe; + removeLineItem?: Maybe; + removePayment?: Maybe; + setBillingAddress?: Maybe; + setCountry?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setCustomerEmail?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetCartDeleteDaysAfterLastModification + >; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setLineItemShippingDetails?: Maybe; + setLocale?: Maybe; + setShippingMethod?: Maybe; + setShippingAddress?: Maybe; + updateItemShippingAddress?: Maybe; +}; + +export type MyCustomerUpdateAction = { + addAddress?: Maybe; + addBillingAddressId?: Maybe; + addShippingAddressId?: Maybe; + changeAddress?: Maybe; + changeEmail?: Maybe; + removeAddress?: Maybe; + removeBillingAddressId?: Maybe; + removeShippingAddressId?: Maybe; + setCompanyName?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setLocale?: Maybe; + setDateOfBirth?: Maybe; + setDefaultBillingAddress?: Maybe; + setDefaultShippingAddress?: Maybe; + setFirstName?: Maybe; + setLastName?: Maybe; + setMiddleName?: Maybe; + setSalutation?: Maybe; + setTitle?: Maybe; + setVatId?: Maybe; +}; + +export type MyLineItemDraft = { + productId?: Maybe; + sku?: Maybe; + quantity?: Maybe; + variantId?: Maybe; + supplyChannel?: Maybe; + distributionChannel?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; +}; + +export type MyShoppingListDraft = { + name: Array; + description?: Maybe>; + lineItems?: Maybe>; + textLineItems?: Maybe>; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; +}; + +export type MyShoppingListUpdateAction = { + addLineItem?: Maybe; + addTextLineItem?: Maybe; + changeLineItemQuantity?: Maybe; + changeLineItemsOrder?: Maybe; + changeName?: Maybe; + changeTextLineItemName?: Maybe; + changeTextLineItemQuantity?: Maybe; + changeTextLineItemsOrder?: Maybe; + removeLineItem?: Maybe; + removeTextLineItem?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetShoppingListDeleteDaysAfterLastModification + >; + setDescription?: Maybe; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setTextLineItemCustomField?: Maybe; + setTextLineItemCustomType?: Maybe; + setTextLineItemDescription?: Maybe; +}; + +export type NestedAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "NestedAttributeDefinitionType"; + typeReference: Reference; + name: Scalars["String"]; +}; + +export type NumberAttribute = Attribute & { + __typename?: "NumberAttribute"; + value: Scalars["BigDecimal"]; + name: Scalars["String"]; +}; + +export type NumberAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "NumberAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type NumberField = CustomField & { + __typename?: "NumberField"; + value: Scalars["BigDecimal"]; + name: Scalars["String"]; +}; + +export type NumberType = FieldType & { + __typename?: "NumberType"; + name: Scalars["String"]; +}; + +/** An order can be created from a cart, usually after a checkout process has been completed. + * [documentation](https://docs.commercetools.com/http-api-projects-orders.html) + */ +export type Order = Versioned & { + __typename?: "Order"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + completedAt?: Maybe; + orderNumber?: Maybe; + orderState: OrderState; + stateRef?: Maybe; + state?: Maybe; + shipmentState?: Maybe; + paymentState?: Maybe; + syncInfo: Array; + returnInfo: Array; + lastMessageSequenceNumber: Scalars["Long"]; + cartRef?: Maybe; + cart?: Maybe; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** An order can be created from a cart, usually after a checkout process has been completed. + * [documentation](https://docs.commercetools.com/http-api-projects-orders.html) + */ +export type OrderCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** An order can be created from a cart, usually after a checkout process has been completed. + * [documentation](https://docs.commercetools.com/http-api-projects-orders.html) + */ +export type OrderCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type OrderCartCommand = { + id: Scalars["String"]; + version: Scalars["Long"]; + paymentState?: Maybe; + orderState?: Maybe; + state?: Maybe; + shipmentState?: Maybe; + orderNumber?: Maybe; +}; + +export type OrderMyCartCommand = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +/** Fields to access orders. Includes direct access to a single order and searching for orders. */ +export type OrderQueryInterface = { + order?: Maybe; + orders: OrderQueryResult; +}; + +/** Fields to access orders. Includes direct access to a single order and searching for orders. */ +export type OrderQueryInterfaceOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +/** Fields to access orders. Includes direct access to a single order and searching for orders. */ +export type OrderQueryInterfaceOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type OrderQueryResult = { + __typename?: "OrderQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum OrderState { + Confirmed = "Confirmed", + Cancelled = "Cancelled", + Complete = "Complete", + Open = "Open" +} + +export type OrderUpdateAction = { + addDelivery?: Maybe; + addItemShippingAddress?: Maybe; + addParcelToDelivery?: Maybe; + addPayment?: Maybe; + addReturnInfo?: Maybe; + changeOrderState?: Maybe; + changePaymentState?: Maybe; + changeShipmentState?: Maybe; + importCustomLineItemState?: Maybe; + importLineItemState?: Maybe; + removeDelivery?: Maybe; + removeItemShippingAddress?: Maybe; + removeParcelFromDelivery?: Maybe; + removePayment?: Maybe; + setBillingAddress?: Maybe; + setCustomField?: Maybe; + setCustomLineItemCustomField?: Maybe; + setCustomLineItemCustomType?: Maybe; + setCustomLineItemShippingDetails?: Maybe< + SetOrderCustomLineItemShippingDetails + >; + setCustomType?: Maybe; + setCustomerEmail?: Maybe; + setCustomerId?: Maybe; + setDeliveryAddress?: Maybe; + setDeliveryItems?: Maybe; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setLineItemShippingDetails?: Maybe; + setLocale?: Maybe; + setOrderNumber?: Maybe; + setParcelItems?: Maybe; + setParcelMeasurements?: Maybe; + setParcelTrackingData?: Maybe; + setReturnPaymentState?: Maybe; + setReturnShipmentState?: Maybe; + setShippingAddress?: Maybe; + transitionCustomLineItemState?: Maybe; + transitionLineItemState?: Maybe; + transitionState?: Maybe; + updateItemShippingAddress?: Maybe; + updateSyncInfo?: Maybe; +}; + +export type Parcel = { + __typename?: "Parcel"; + id: Scalars["String"]; + createdAt: Scalars["DateTime"]; + measurements?: Maybe; + trackingData?: Maybe; + items: Array; +}; + +export type ParcelMeasurements = { + __typename?: "ParcelMeasurements"; + heightInMillimeter?: Maybe; + lengthInMillimeter?: Maybe; + widthInMillimeter?: Maybe; + weightInGram?: Maybe; +}; + +export type ParcelMeasurementsDraftType = { + heightInMillimeter?: Maybe; + lengthInMillimeter?: Maybe; + widthInMillimeter?: Maybe; + weightInGram?: Maybe; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type Payment = Versioned & { + __typename?: "Payment"; + key?: Maybe; + customerRef?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + interfaceId?: Maybe; + amountPlanned: Money; + amountAuthorized?: Maybe; + authorizedUntil?: Maybe; + amountPaid?: Maybe; + amountRefunded?: Maybe; + paymentMethodInfo: PaymentMethodInfo; + paymentStatus: PaymentStatus; + transactions: Array; + interfaceInteractionsRaw: InterfaceInteractionsRawResult; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type PaymentInterfaceInteractionsRawArgs = { + limit?: Maybe; + offset?: Maybe; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type PaymentCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type PaymentCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type PaymentInfo = { + __typename?: "PaymentInfo"; + payments: Array; + paymentRefs: Array; +}; + +export type PaymentMethodInfo = { + __typename?: "PaymentMethodInfo"; + paymentInterface?: Maybe; + method?: Maybe; + name?: Maybe; + nameAllLocales?: Maybe>; +}; + +export type PaymentMethodInfoNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type PaymentQueryResult = { + __typename?: "PaymentQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum PaymentState { + Paid = "Paid", + CreditOwed = "CreditOwed", + Pending = "Pending", + Failed = "Failed", + BalanceDue = "BalanceDue" +} + +export type PaymentStatus = { + __typename?: "PaymentStatus"; + interfaceCode?: Maybe; + interfaceText?: Maybe; + stateRef?: Maybe; + state?: Maybe; +}; + +export type PlainEnumValue = { + __typename?: "PlainEnumValue"; + key: Scalars["String"]; + label: Scalars["String"]; +}; + +export type PlainEnumValueDraft = { + key: Scalars["String"]; + label: Scalars["String"]; +}; + +export type PlainEnumValueResult = { + __typename?: "PlainEnumValueResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +export type Point = Geometry & { + __typename?: "Point"; + coordinates: Array; + type: Scalars["String"]; +}; + +export type PriceFunction = { + __typename?: "PriceFunction"; + function: Scalars["String"]; + currencyCode: Scalars["Currency"]; +}; + +export type PriceFunctionDraft = { + function: Scalars["String"]; + currencyCode: Scalars["Currency"]; +}; + +export type Product = Versioned & { + __typename?: "Product"; + id: Scalars["String"]; + key?: Maybe; + version: Scalars["Long"]; + productTypeRef: Reference; + productType?: Maybe; + masterData: ProductCatalogData; + catalogData?: Maybe; + skus: Array; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + stateRef?: Maybe; + state?: Maybe; + taxCategoryRef?: Maybe; + taxCategory?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ProductCatalogDataArgs = { + id: Scalars["String"]; +}; + +export type ProductAttributeInput = { + name: Scalars["String"]; + value: Scalars["String"]; +}; + +export type ProductCatalogData = { + __typename?: "ProductCatalogData"; + current?: Maybe; + staged?: Maybe; + published: Scalars["Boolean"]; + hasStagedChanges: Scalars["Boolean"]; +}; + +export type ProductData = { + __typename?: "ProductData"; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + categoryOrderHint?: Maybe; + categoryOrderHints: Array; + categoriesRef: Array; + categories: Array; + searchKeyword?: Maybe>; + searchKeywords: Array; + metaTitle?: Maybe; + metaKeywords?: Maybe; + metaDescription?: Maybe; + masterVariant: ProductVariant; + variants: Array; + allVariants: Array; + variant?: Maybe; + skus: Array; +}; + +export type ProductDataNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataCategoryOrderHintArgs = { + categoryId: Scalars["String"]; +}; + +export type ProductDataSearchKeywordArgs = { + locale: Scalars["Locale"]; +}; + +export type ProductDataMetaTitleArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataMetaKeywordsArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataMetaDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataVariantsArgs = { + skus?: Maybe>; + isOnStock?: Maybe; + stockChannelIds?: Maybe>; + hasImages?: Maybe; +}; + +export type ProductDataAllVariantsArgs = { + skus?: Maybe>; + isOnStock?: Maybe; + stockChannelIds?: Maybe>; + hasImages?: Maybe; +}; + +export type ProductDataVariantArgs = { + sku?: Maybe; + key?: Maybe; +}; + +/** A product price can be discounted in two ways: + * + * * with a relative or an absolute product discount, which will be automatically + * applied to all prices in a product that match a discount predicate. + * A relative discount reduces the matching price by a fraction (for example 10 % + * off). An absolute discount reduces the matching price by a fixed amount (for + * example 10€ off). If more than one product discount matches a price, the + * discount sort order determines which one will be applied. + * * with an external product discount, which can then be used to explicitly set a + * discounted value on a particular product price. + * + * The discounted price is stored in the discounted field of the Product Price. + * + * Note that when a discount is created, updated or removed it can take up to 15 + * minutes to update all the prices with the discounts. + * + * The maximum number of ProductDiscounts that can be active at the same time is **200**. + */ +export type ProductDiscount = Versioned & { + __typename?: "ProductDiscount"; + predicate: Scalars["String"]; + validFrom?: Maybe; + validUntil?: Maybe; + isActive: Scalars["Boolean"]; + isValid: Scalars["Boolean"]; + sortOrder: Scalars["String"]; + key?: Maybe; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + value: ProductDiscountValue; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +/** A product price can be discounted in two ways: + * + * * with a relative or an absolute product discount, which will be automatically + * applied to all prices in a product that match a discount predicate. + * A relative discount reduces the matching price by a fraction (for example 10 % + * off). An absolute discount reduces the matching price by a fixed amount (for + * example 10€ off). If more than one product discount matches a price, the + * discount sort order determines which one will be applied. + * * with an external product discount, which can then be used to explicitly set a + * discounted value on a particular product price. + * + * The discounted price is stored in the discounted field of the Product Price. + * + * Note that when a discount is created, updated or removed it can take up to 15 + * minutes to update all the prices with the discounts. + * + * The maximum number of ProductDiscounts that can be active at the same time is **200**. + */ +export type ProductDiscountNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A product price can be discounted in two ways: + * + * * with a relative or an absolute product discount, which will be automatically + * applied to all prices in a product that match a discount predicate. + * A relative discount reduces the matching price by a fraction (for example 10 % + * off). An absolute discount reduces the matching price by a fixed amount (for + * example 10€ off). If more than one product discount matches a price, the + * discount sort order determines which one will be applied. + * * with an external product discount, which can then be used to explicitly set a + * discounted value on a particular product price. + * + * The discounted price is stored in the discounted field of the Product Price. + * + * Note that when a discount is created, updated or removed it can take up to 15 + * minutes to update all the prices with the discounts. + * + * The maximum number of ProductDiscounts that can be active at the same time is **200**. + */ +export type ProductDiscountDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDiscountDraft = { + value: ProductDiscountValueInput; + predicate: Scalars["String"]; + sortOrder: Scalars["String"]; + name: Array; + description?: Maybe>; + validFrom?: Maybe; + validUntil?: Maybe; + isActive?: Maybe; + key?: Maybe; +}; + +export type ProductDiscountQueryResult = { + __typename?: "ProductDiscountQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ProductDiscountUpdateAction = { + changeIsActive?: Maybe; + changeName?: Maybe; + changePredicate?: Maybe; + changeSortOrder?: Maybe; + changeValue?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setValidFrom?: Maybe; + setValidFromAndUntil?: Maybe; + setValidUntil?: Maybe; +}; + +export type ProductDiscountValue = { + type: Scalars["String"]; +}; + +export type ProductDiscountValueInput = { + relative?: Maybe; + absolute?: Maybe; + external?: Maybe; +}; + +export type ProductDraft = { + name: Array; + productType: ResourceIdentifierInput; + slug: Array; + key?: Maybe; + description?: Maybe>; + categories?: Maybe>; + categoryOrderHints?: Maybe>; + metaTitle?: Maybe>; + metaDescription?: Maybe>; + metaKeywords?: Maybe>; + masterVariant?: Maybe; + variants?: Maybe>; + taxCategory?: Maybe; + state?: Maybe; + searchKeywords?: Maybe>; + publish?: Maybe; +}; + +export type ProductPrice = { + __typename?: "ProductPrice"; + id?: Maybe; + value: BaseMoney; + country?: Maybe; + customerGroup?: Maybe; + channel?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + discounted?: Maybe; + tiers?: Maybe>; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type ProductPriceCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ProductPriceCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ProductPriceDataInput = { + value: BaseMoneyInput; + country?: Maybe; + customerGroup?: Maybe; + channel?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + tiers?: Maybe>; + custom?: Maybe; +}; + +export type ProductPriceTier = { + __typename?: "ProductPriceTier"; + minimumQuantity: Scalars["Int"]; + value: BaseMoney; +}; + +export type ProductPriceTierInput = { + minimumQuantity: Scalars["Int"]; + value: BaseMoneyInput; +}; + +export type ProductQueryResult = { + __typename?: "ProductQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ProductReferenceIdentifier = { + __typename?: "ProductReferenceIdentifier"; + typeId: Scalars["String"]; + id?: Maybe; + key?: Maybe; +}; + +export type ProductType = { + productTypeId: Scalars["String"]; +}; + +export type ProductTypeDefinition = Versioned & { + __typename?: "ProductTypeDefinition"; + key?: Maybe; + name: Scalars["String"]; + description: Scalars["String"]; + attributeDefinitions: AttributeDefinitionResult; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ProductTypeDefinitionAttributeDefinitionsArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; + limit?: Maybe; + offset?: Maybe; + sort?: Maybe>; +}; + +export type ProductTypeDefinitionQueryResult = { + __typename?: "ProductTypeDefinitionQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ProductTypeDraft = { + name: Scalars["String"]; + description: Scalars["String"]; + key?: Maybe; + attributeDefinitions?: Maybe>; +}; + +export type ProductTypeUpdateAction = { + setKey?: Maybe; + changeName?: Maybe; + changeDescription?: Maybe; + removeAttributeDefinition?: Maybe; + changeLabel?: Maybe; + setInputTip?: Maybe; + changeIsSearchable?: Maybe; + changeInputHint?: Maybe; + addAttributeDefinition?: Maybe; + changeAttributeOrder?: Maybe; + changeAttributeOrderByName?: Maybe; + removeEnumValues?: Maybe; + addPlainEnumValue?: Maybe; + changePlainEnumValueLabel?: Maybe; + changePlainEnumValueOrder?: Maybe; + addLocalizedEnumValue?: Maybe; + changeLocalizedEnumValueLabel?: Maybe; + changeLocalizedEnumValueOrder?: Maybe; + changeAttributeName?: Maybe; + changeEnumKey?: Maybe; +}; + +export type ProductUpdateAction = { + moveImageToPosition?: Maybe; + setSearchKeywords?: Maybe; + revertStagedChanges?: Maybe; + revertStagedVariantChanges?: Maybe; + publish?: Maybe; + unpublish?: Maybe; + transitionState?: Maybe; + addAsset?: Maybe; + addExternalImage?: Maybe; + addPrice?: Maybe; + addToCategory?: Maybe; + addVariant?: Maybe; + changeAssetName?: Maybe; + changeAssetOrder?: Maybe; + changeMasterVariant?: Maybe; + changeImageLabel?: Maybe; + changeName?: Maybe; + changePrice?: Maybe; + changeSlug?: Maybe; + removeAsset?: Maybe; + removeFromCategory?: Maybe; + removeImage?: Maybe; + removePrice?: Maybe; + removeVariant?: Maybe; + setAssetCustomField?: Maybe; + setAssetCustomType?: Maybe; + setAssetDescription?: Maybe; + setAssetKey?: Maybe; + setAssetSources?: Maybe; + setAssetTags?: Maybe; + setCategoryOrderHint?: Maybe; + setDiscountedPrice?: Maybe; + setAttribute?: Maybe; + setAttributeInAllVariants?: Maybe; + setDescription?: Maybe; + setImageLabel?: Maybe; + setKey?: Maybe; + setMetaAttributes?: Maybe; + setMetaDescription?: Maybe; + setMetaKeywords?: Maybe; + setMetaTitle?: Maybe; + setProductPriceCustomField?: Maybe; + setProductPriceCustomType?: Maybe; + setPrices?: Maybe; + setSku?: Maybe; + setTaxCategory?: Maybe; + setProductVariantKey?: Maybe; +}; + +export type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + /** Returns a single price based on the price selection rules. */ + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + /** This field contains non-typed data. Consider using `attributes` as a typed alternative. */ + attributesRaw: Array; + /** Product attributes */ + attributes: ProductType; + /** Product attributes are returned as a list instead of an object structure. */ + attributeList: Array; +}; + +export type ProductVariantPriceArgs = { + currency: Scalars["Currency"]; + country?: Maybe; + customerGroupId?: Maybe; + channelId?: Maybe; + date?: Maybe; +}; + +export type ProductVariantAttributesRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ProductVariantAttributeListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Product variant availabilities */ +export type ProductVariantAvailabilitiesResult = { + __typename?: "ProductVariantAvailabilitiesResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +/** Product variant availability */ +export type ProductVariantAvailability = { + __typename?: "ProductVariantAvailability"; + isOnStock: Scalars["Boolean"]; + restockableInDays?: Maybe; + availableQuantity?: Maybe; +}; + +export type ProductVariantAvailabilityWithChannel = { + __typename?: "ProductVariantAvailabilityWithChannel"; + channelRef: Reference; + channel?: Maybe; + availability: ProductVariantAvailability; +}; + +export type ProductVariantAvailabilityWithChannels = { + __typename?: "ProductVariantAvailabilityWithChannels"; + noChannel?: Maybe; + channels: ProductVariantAvailabilitiesResult; +}; + +export type ProductVariantAvailabilityWithChannelsChannelsArgs = { + includeChannelIds?: Maybe>; + excludeChannelIds?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type ProductVariantInput = { + sku?: Maybe; + key?: Maybe; + prices?: Maybe>; + images?: Maybe>; + attributes?: Maybe>; + assets?: Maybe>; +}; + +/** Project contains information about project. */ +export type ProjectProjection = { + __typename?: "ProjectProjection"; + key: Scalars["String"]; + name: Scalars["String"]; + languages: Array; + createdAt: Scalars["DateTime"]; + trialUntil?: Maybe; + version: Scalars["Long"]; + externalOAuth?: Maybe; + messages: MessagesConfiguration; + countries: Array; + currencies: Array; + shippingRateInputType?: Maybe; +}; + +export type ProjectSettingsUpdateAction = { + changeCountries?: Maybe; + changeCurrencies?: Maybe; + changeLanguages?: Maybe; + changeMessagesConfiguration?: Maybe< + ChangeProjectSettingsMessagesConfiguration + >; + changeMessagesEnabled?: Maybe; + changeName?: Maybe; + setExternalOAuth?: Maybe; + setShippingRateInputType?: Maybe; +}; + +export type PublishProduct = { + scope?: Maybe; +}; + +export enum PublishScope { + /** Publishes the complete staged projection */ + All = "All", + /** Publishes only prices on the staged projection */ + Prices = "Prices" +} + +export type Query = CartQueryInterface & + CustomerActiveCartInterface & + OrderQueryInterface & + CustomerQueryInterface & + ShoppingListQueryInterface & + ShippingMethodsByCartInterface & + MeFieldInterface & { + __typename?: "Query"; + /** This field can only be used with an access token created with the password flow or with an anonymous session. + * + * It gives access to the data that is specific to the customer or the anonymous session linked to the access token. + */ + me: Me; + /** This field gives access to the resources (such as carts) that are inside the given store. */ + inStore: InStore; + /** This field gives access to the resources (such as carts) that are inside one of the given stores. */ + inStores: InStore; + customerGroup?: Maybe; + customerGroups: CustomerGroupQueryResult; + category?: Maybe; + categories: CategoryQueryResult; + /** Autocomplete the categories based on category fields like name, description, etc. */ + categoryAutocomplete: CategorySearchResult; + /** Search the categories using full-text search, filtering and sorting */ + categorySearch: CategorySearchResult; + channel?: Maybe; + channels: ChannelQueryResult; + productType?: Maybe; + productTypes: ProductTypeDefinitionQueryResult; + typeDefinition?: Maybe; + typeDefinitions: TypeDefinitionQueryResult; + shippingMethod?: Maybe; + shippingMethods: ShippingMethodQueryResult; + shippingMethodsByCart: Array; + shippingMethodsByLocation: Array; + zone?: Maybe; + zones: ZoneQueryResult; + taxCategory?: Maybe; + taxCategories: TaxCategoryQueryResult; + discountCode?: Maybe; + discountCodes: DiscountCodeQueryResult; + cartDiscount?: Maybe; + cartDiscounts: CartDiscountQueryResult; + productDiscount?: Maybe; + productDiscounts: ProductDiscountQueryResult; + product?: Maybe; + products: ProductQueryResult; + state?: Maybe; + states: StateQueryResult; + customer?: Maybe; + customers: CustomerQueryResult; + inventoryEntry?: Maybe; + inventoryEntries: InventoryEntryQueryResult; + cart?: Maybe; + carts: CartQueryResult; + customerActiveCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; + payment?: Maybe; + payments: PaymentQueryResult; + project: ProjectProjection; + store?: Maybe; + stores: StoreQueryResult; + apiClient?: Maybe; + apiClients: ApiClientWithoutSecretQueryResult; + }; + +export type QueryInStoreArgs = { + key: Scalars["KeyReferenceInput"]; +}; + +export type QueryInStoresArgs = { + keys: Array; +}; + +export type QueryCustomerGroupArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryCustomerGroupsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCategoryArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryCategoriesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCategoryAutocompleteArgs = { + locale: Scalars["Locale"]; + text: Scalars["String"]; + limit?: Maybe; + offset?: Maybe; + filters?: Maybe>; + experimental?: Maybe; +}; + +export type QueryCategorySearchArgs = { + fulltext?: Maybe; + limit?: Maybe; + offset?: Maybe; + queryFilters?: Maybe>; + filters?: Maybe>; + sorts?: Maybe>; + experimental?: Maybe; +}; + +export type QueryChannelArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryChannelsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryProductTypeArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryProductTypesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryTypeDefinitionArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryTypeDefinitionsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryShippingMethodArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryShippingMethodsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryShippingMethodsByCartArgs = { + id: Scalars["String"]; +}; + +export type QueryShippingMethodsByLocationArgs = { + country: Scalars["Country"]; + state?: Maybe; + currency?: Maybe; +}; + +export type QueryZoneArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryZonesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryTaxCategoryArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryTaxCategoriesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryDiscountCodeArgs = { + id: Scalars["String"]; +}; + +export type QueryDiscountCodesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCartDiscountArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryCartDiscountsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryProductDiscountArgs = { + id: Scalars["String"]; +}; + +export type QueryProductDiscountsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryProductArgs = { + sku?: Maybe; + variantKey?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type QueryProductsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; + skus?: Maybe>; +}; + +export type QueryStateArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryStatesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCustomerArgs = { + emailToken?: Maybe; + passwordToken?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type QueryCustomersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryInventoryEntryArgs = { + id: Scalars["String"]; +}; + +export type QueryInventoryEntriesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCartArgs = { + id: Scalars["String"]; +}; + +export type QueryCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCustomerActiveCartArgs = { + customerId: Scalars["String"]; +}; + +export type QueryOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type QueryOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryPaymentArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryPaymentsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryStoreArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryStoresArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryApiClientArgs = { + id: Scalars["String"]; +}; + +export type QueryApiClientsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type RawCustomField = { + __typename?: "RawCustomField"; + name: Scalars["String"]; + value: Scalars["Json"]; +}; + +export type RawProductAttribute = { + __typename?: "RawProductAttribute"; + name: Scalars["String"]; + value: Scalars["Json"]; + attributeDefinition?: Maybe; +}; + +export type RecalculateCart = { + updateProductData?: Maybe; +}; + +export type Reference = { + __typename?: "Reference"; + typeId: Scalars["String"]; + id: Scalars["String"]; +}; + +export type ReferenceAttribute = Attribute & { + __typename?: "ReferenceAttribute"; + typeId: Scalars["String"]; + id: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "ReferenceAttributeDefinitionType"; + referenceTypeId: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceField = CustomField & { + __typename?: "ReferenceField"; + typeId: Scalars["String"]; + id: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceInput = { + typeId: Scalars["String"]; + id: Scalars["String"]; +}; + +export type ReferenceType = FieldType & { + __typename?: "ReferenceType"; + referenceTypeId: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceTypeDefinitionDraft = { + referenceTypeId: Scalars["String"]; +}; + +export type RelativeDiscountValue = CartDiscountValue & + ProductDiscountValue & { + __typename?: "RelativeDiscountValue"; + permyriad: Scalars["Int"]; + type: Scalars["String"]; + }; + +export type RelativeDiscountValueInput = { + permyriad: Scalars["Int"]; +}; + +export type RemoveAttributeDefinition = { + name: Scalars["String"]; +}; + +export type RemoveCartCustomLineItem = { + customLineItemId: Scalars["String"]; +}; + +export type RemoveCartDiscountCode = { + discountCode: ReferenceInput; +}; + +export type RemoveCartItemShippingAddress = { + addressKey: Scalars["String"]; +}; + +export type RemoveCartLineItem = { + lineItemId: Scalars["String"]; + quantity?: Maybe; + externalPrice?: Maybe; + externalTotalPrice?: Maybe; + shippingDetailsToRemove?: Maybe; +}; + +export type RemoveCartPayment = { + payment: ResourceIdentifierInput; +}; + +export type RemoveCategoryAsset = { + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type RemoveCustomerAddress = { + addressId: Scalars["String"]; +}; + +export type RemoveCustomerBillingAddressId = { + addressId: Scalars["String"]; +}; + +export type RemoveCustomerShippingAddressId = { + addressId: Scalars["String"]; +}; + +export type RemoveCustomerStore = { + store: ResourceIdentifierInput; +}; + +export type RemoveEnumValues = { + attributeName: Scalars["String"]; + keys: Array; +}; + +export type RemoveInventoryEntryQuantity = { + quantity: Scalars["Long"]; +}; + +export type RemoveOrderDelivery = { + deliveryId: Scalars["String"]; +}; + +export type RemoveOrderItemShippingAddress = { + addressKey: Scalars["String"]; +}; + +export type RemoveOrderParcelFromDelivery = { + parcelId: Scalars["String"]; +}; + +export type RemoveOrderPayment = { + payment: ResourceIdentifierInput; +}; + +export type RemoveProductAsset = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type RemoveProductFromCategory = { + category: ResourceIdentifierInput; + staged?: Maybe; +}; + +export type RemoveProductImage = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + staged?: Maybe; +}; + +export type RemoveProductPrice = { + priceId?: Maybe; + variantId?: Maybe; + sku?: Maybe; + price?: Maybe; + catalog?: Maybe; + staged?: Maybe; +}; + +export type RemoveProductVariant = { + id?: Maybe; + sku?: Maybe; + staged?: Maybe; +}; + +export type RemoveShippingMethodShippingRate = { + zone: ResourceIdentifierInput; + shippingRate: ShippingRateDraft; +}; + +export type RemoveShippingMethodZone = { + zone: ResourceIdentifierInput; +}; + +export type RemoveShoppingListLineItem = { + lineItemId: Scalars["String"]; + quantity?: Maybe; +}; + +export type RemoveShoppingListTextLineItem = { + textLineItemId: Scalars["String"]; + quantity?: Maybe; +}; + +export type RemoveZoneLocation = { + location: ZoneLocation; +}; + +export type ReservationOrderType = Type & { + __typename?: "reservationOrderType"; + typeRef: Reference; + type: TypeDefinition; + isReservation?: Maybe; +}; + +export type ResourceIdentifierInput = { + typeId?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +/** Stores information about returns connected to this order. */ +export type ReturnInfo = { + __typename?: "ReturnInfo"; + items: Array; + returnTrackingId?: Maybe; + returnDate?: Maybe; +}; + +export type ReturnItem = { + type: Scalars["String"]; + id: Scalars["String"]; + quantity: Scalars["Long"]; + comment?: Maybe; + shipmentState: ReturnShipmentState; + paymentState: ReturnPaymentState; + lastModifiedAt: Scalars["DateTime"]; + createdAt: Scalars["DateTime"]; +}; + +export type ReturnItemDraftType = { + quantity: Scalars["Long"]; + lineItemId?: Maybe; + customLineItemId?: Maybe; + comment?: Maybe; + shipmentState: ReturnShipmentState; +}; + +export enum ReturnPaymentState { + NotRefunded = "NotRefunded", + Refunded = "Refunded", + Initial = "Initial", + NonRefundable = "NonRefundable" +} + +export enum ReturnShipmentState { + Unusable = "Unusable", + BackInStock = "BackInStock", + Returned = "Returned", + Advised = "Advised" +} + +export type RevertStagedChanges = { + dummy?: Maybe; +}; + +export type RevertStagedVariantChanges = { + variantId: Scalars["Int"]; +}; + +export enum RoundingMode { + /** [Round half down](https://en.wikipedia.org/wiki/Rounding#Round_half_down). + * Rounding mode used by, e.g., [Avalara Sales TaxII](https://help.avalara.com/kb/001/How_does_Rounding_with_SalesTaxII_work%3F) + */ + HalfDown = "HalfDown", + /** [Round half up](https://en.wikipedia.org/wiki/Rounding#Round_half_up) */ + HalfUp = "HalfUp", + /** [Round half to even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even). + * Default rounding mode as used in IEEE 754 computing functions and operators. + */ + HalfEven = "HalfEven" +} + +export type ScoreShippingRateInput = ShippingRateInput & { + __typename?: "ScoreShippingRateInput"; + score: Scalars["Int"]; + type: Scalars["String"]; +}; + +export type ScoreShippingRateInputDraft = { + score: Scalars["Int"]; +}; + +export type SearchKeyword = { + __typename?: "SearchKeyword"; + text: Scalars["String"]; +}; + +export type SearchKeywordInput = { + locale: Scalars["Locale"]; + keywords: Array; +}; + +export type SearchKeywords = { + __typename?: "SearchKeywords"; + locale: Scalars["Locale"]; + searchKeywords: Array; +}; + +/** In order to decide which of the matching items will actually be discounted */ +export enum SelectionMode { + MostExpensive = "MostExpensive", + Cheapest = "Cheapest" +} + +export type SetAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "SetAttributeDefinitionType"; + elementType: AttributeDefinitionType; + name: Scalars["String"]; +}; + +export type SetCartAnonymousId = { + anonymousId?: Maybe; +}; + +export type SetCartBillingAddress = { + address?: Maybe; +}; + +export type SetCartCountry = { + country?: Maybe; +}; + +export type SetCartCustomerEmail = { + email?: Maybe; +}; + +export type SetCartCustomerGroup = { + customerGroup?: Maybe; +}; + +export type SetCartCustomerId = { + customerId?: Maybe; +}; + +export type SetCartCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartCustomLineItemCustomField = { + customLineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartCustomLineItemCustomType = { + customLineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartCustomLineItemShippingDetails = { + customLineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetCartCustomLineItemTaxAmount = { + customLineItemId: Scalars["String"]; + externalTaxAmount?: Maybe; +}; + +export type SetCartCustomLineItemTaxRate = { + customLineItemId: Scalars["String"]; + externalTaxRate?: Maybe; +}; + +export type SetCartCustomShippingMethod = { + shippingMethodName: Scalars["String"]; + shippingRate: ShippingRateDraft; + taxCategory?: Maybe; + externalTaxRate?: Maybe; +}; + +export type SetCartCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartDeleteDaysAfterLastModification = { + deleteDaysAfterLastModification?: Maybe; +}; + +export type SetCartDiscountCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartDiscountCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartDiscountDescription = { + description?: Maybe>; +}; + +export type SetCartDiscountKey = { + key?: Maybe; +}; + +export type SetCartDiscountValidFrom = { + validFrom?: Maybe; +}; + +export type SetCartDiscountValidFromAndUntil = { + validFrom?: Maybe; + validUntil?: Maybe; +}; + +export type SetCartDiscountValidUntil = { + validUntil?: Maybe; +}; + +export type SetCartLineItemCustomField = { + lineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartLineItemCustomType = { + lineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartLineItemPrice = { + lineItemId: Scalars["String"]; + externalPrice?: Maybe; +}; + +export type SetCartLineItemShippingDetails = { + lineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetCartLineItemTaxAmount = { + lineItemId: Scalars["String"]; + externalTaxAmount?: Maybe; +}; + +export type SetCartLineItemTaxRate = { + lineItemId: Scalars["String"]; + externalTaxRate?: Maybe; +}; + +export type SetCartLineItemTotalPrice = { + lineItemId: Scalars["String"]; + externalTotalPrice?: Maybe; +}; + +export type SetCartLocale = { + locale?: Maybe; +}; + +export type SetCartShippingAddress = { + address?: Maybe; +}; + +export type SetCartShippingMethod = { + shippingMethod?: Maybe; + externalTaxRate?: Maybe; +}; + +export type SetCartShippingMethodTaxAmount = { + externalTaxAmount?: Maybe; +}; + +export type SetCartShippingMethodTaxRate = { + externalTaxRate?: Maybe; +}; + +export type SetCartShippingRateInput = { + shippingRateInput?: Maybe; +}; + +export type SetCartTotalTax = { + externalTotalGross?: Maybe; + externalTaxPortions?: Maybe>; +}; + +export type SetCategoryAssetCustomField = { + value?: Maybe; + name: Scalars["String"]; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetCustomType = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetDescription = { + description?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetKey = { + assetKey?: Maybe; + assetId: Scalars["String"]; +}; + +export type SetCategoryAssetSources = { + sources?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetTags = { + tags?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCategoryCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCategoryDescription = { + description?: Maybe>; +}; + +export type SetCategoryExternalId = { + externalId?: Maybe; +}; + +export type SetCategoryKey = { + key?: Maybe; +}; + +export type SetCategoryMetaDescription = { + metaDescription?: Maybe>; +}; + +export type SetCategoryMetaKeywords = { + metaKeywords?: Maybe>; +}; + +export type SetCategoryMetaTitle = { + metaTitle?: Maybe>; +}; + +export type SetCustomerCompanyName = { + companyName?: Maybe; +}; + +export type SetCustomerCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCustomerCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCustomerDateOfBirth = { + dateOfBirth?: Maybe; +}; + +export type SetCustomerDefaultBillingAddress = { + addressId?: Maybe; +}; + +export type SetCustomerDefaultShippingAddress = { + addressId?: Maybe; +}; + +export type SetCustomerExternalId = { + externalId?: Maybe; +}; + +export type SetCustomerFirstName = { + firstName?: Maybe; +}; + +export type SetCustomerGroup = { + customerGroup?: Maybe; +}; + +export type SetCustomerGroupCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCustomerGroupCustomType = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; +}; + +export type SetCustomerGroupKey = { + key?: Maybe; +}; + +export type SetCustomerKey = { + key?: Maybe; +}; + +export type SetCustomerLastName = { + lastName?: Maybe; +}; + +export type SetCustomerLocale = { + locale?: Maybe; +}; + +export type SetCustomerMiddleName = { + middleName?: Maybe; +}; + +export type SetCustomerNumber = { + customerNumber?: Maybe; +}; + +export type SetCustomerSalutation = { + salutation?: Maybe; +}; + +export type SetCustomerStores = { + stores: Array; +}; + +export type SetCustomerTitle = { + title?: Maybe; +}; + +export type SetCustomerVatId = { + vatId?: Maybe; +}; + +export type SetDiscountCodeCartPredicate = { + cartPredicate?: Maybe; +}; + +export type SetDiscountCodeCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetDiscountCodeCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetDiscountCodeDescription = { + description?: Maybe>; +}; + +export type SetDiscountCodeMaxApplications = { + maxApplications?: Maybe; +}; + +export type SetDiscountCodeMaxApplicationsPerCustomer = { + maxApplicationsPerCustomer?: Maybe; +}; + +export type SetDiscountCodeName = { + name?: Maybe>; +}; + +export type SetDiscountCodeValidFrom = { + validFrom?: Maybe; +}; + +export type SetDiscountCodeValidFromAndUntil = { + validFrom?: Maybe; + validUntil?: Maybe; +}; + +export type SetDiscountCodeValidUntil = { + validUntil?: Maybe; +}; + +export type SetInputTip = { + attributeName: Scalars["String"]; + inputTip?: Maybe>; +}; + +export type SetInventoryEntryCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetInventoryEntryCustomType = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; +}; + +export type SetInventoryEntryExpectedDelivery = { + expectedDelivery?: Maybe; +}; + +export type SetInventoryEntryRestockableInDays = { + restockableInDays?: Maybe; +}; + +export type SetInventoryEntrySupplyChannel = { + supplyChannel?: Maybe; +}; + +export type SetKey = { + key?: Maybe; +}; + +export type SetMyCartShippingMethod = { + shippingMethod?: Maybe; +}; + +export type SetOrderBillingAddress = { + address?: Maybe; +}; + +export type SetOrderCustomerEmail = { + email?: Maybe; +}; + +export type SetOrderCustomerId = { + customerId?: Maybe; +}; + +export type SetOrderCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetOrderCustomLineItemCustomField = { + customLineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetOrderCustomLineItemCustomType = { + customLineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetOrderCustomLineItemShippingDetails = { + customLineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetOrderCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetOrderDeliveryAddress = { + deliveryId: Scalars["String"]; + address?: Maybe; +}; + +export type SetOrderDeliveryItems = { + deliveryId: Scalars["String"]; + items: Array; +}; + +export type SetOrderLineItemCustomField = { + lineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetOrderLineItemCustomType = { + lineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetOrderLineItemShippingDetails = { + lineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetOrderLocale = { + locale?: Maybe; +}; + +export type SetOrderNumber = { + orderNumber?: Maybe; +}; + +export type SetOrderParcelItems = { + parcelId: Scalars["String"]; + items: Array; +}; + +export type SetOrderParcelMeasurements = { + parcelId: Scalars["String"]; + measurements?: Maybe; +}; + +export type SetOrderParcelTrackingData = { + parcelId: Scalars["String"]; + trackingData?: Maybe; +}; + +export type SetOrderReturnPaymentState = { + returnItemId: Scalars["String"]; + paymentState: ReturnPaymentState; +}; + +export type SetOrderReturnShipmentState = { + returnItemId: Scalars["String"]; + shipmentState: ReturnShipmentState; +}; + +export type SetOrderShippingAddress = { + address?: Maybe; +}; + +export type SetProductAssetCustomField = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + value?: Maybe; + name: Scalars["String"]; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetCustomType = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetDescription = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + description?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetKey = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + assetKey?: Maybe; + assetId: Scalars["String"]; +}; + +export type SetProductAssetSources = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + sources?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetTags = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + tags?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAttribute = { + variantId?: Maybe; + sku?: Maybe; + name: Scalars["String"]; + value?: Maybe; + staged?: Maybe; +}; + +export type SetProductAttributeInAllVariants = { + name: Scalars["String"]; + value?: Maybe; + staged?: Maybe; +}; + +export type SetProductCategoryOrderHint = { + categoryId: Scalars["String"]; + orderHint?: Maybe; + staged?: Maybe; +}; + +export type SetProductDescription = { + description?: Maybe>; + staged?: Maybe; +}; + +export type SetProductDiscountDescription = { + description?: Maybe>; +}; + +export type SetProductDiscountedPrice = { + priceId: Scalars["String"]; + discounted?: Maybe; + catalog?: Maybe; + staged?: Maybe; +}; + +export type SetProductDiscountKey = { + key?: Maybe; +}; + +export type SetProductDiscountValidFrom = { + validFrom?: Maybe; +}; + +export type SetProductDiscountValidFromAndUntil = { + validFrom?: Maybe; + validUntil?: Maybe; +}; + +export type SetProductDiscountValidUntil = { + validUntil?: Maybe; +}; + +export type SetProductImageLabel = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + label?: Maybe; + staged?: Maybe; +}; + +export type SetProductKey = { + key?: Maybe; +}; + +export type SetProductMetaAttributes = { + metaDescription?: Maybe>; + metaKeywords?: Maybe>; + metaTitle?: Maybe>; + staged?: Maybe; +}; + +export type SetProductMetaDescription = { + metaDescription?: Maybe>; + staged?: Maybe; +}; + +export type SetProductMetaKeywords = { + metaKeywords?: Maybe>; + staged?: Maybe; +}; + +export type SetProductMetaTitle = { + metaTitle?: Maybe>; + staged?: Maybe; +}; + +export type SetProductPriceCustomField = { + priceId: Scalars["String"]; + catalog?: Maybe; + staged?: Maybe; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetProductPriceCustomType = { + priceId: Scalars["String"]; + catalog?: Maybe; + staged?: Maybe; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetProductPrices = { + variantId?: Maybe; + sku?: Maybe; + prices: Array; + catalog?: Maybe; + staged?: Maybe; +}; + +export type SetProductSku = { + variantId: Scalars["Int"]; + sku?: Maybe; + staged?: Maybe; +}; + +export type SetProductTaxCategory = { + taxCategory?: Maybe; +}; + +export type SetProductVariantKey = { + variantId?: Maybe; + sku?: Maybe; + key?: Maybe; + staged?: Maybe; +}; + +export type SetProjectSettingsExternalOAuth = { + externalOAuth?: Maybe; +}; + +export type SetProjectSettingsShippingRateInputType = { + shippingRateInputType?: Maybe; +}; + +export type SetSearchKeywords = { + searchKeywords: Array; + staged?: Maybe; +}; + +export type SetShippingMethodDescription = { + description?: Maybe; +}; + +export type SetShippingMethodKey = { + key?: Maybe; +}; + +export type SetShippingMethodPredicate = { + predicate?: Maybe; +}; + +export type SetShoppingListAnonymousId = { + anonymousId?: Maybe; +}; + +export type SetShoppingListCustomer = { + customer?: Maybe; +}; + +export type SetShoppingListCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetShoppingListCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetShoppingListDeleteDaysAfterLastModification = { + deleteDaysAfterLastModification?: Maybe; +}; + +export type SetShoppingListDescription = { + description?: Maybe>; +}; + +export type SetShoppingListKey = { + key?: Maybe; +}; + +export type SetShoppingListLineItemCustomField = { + lineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetShoppingListLineItemCustomType = { + lineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetShoppingListSlug = { + slug?: Maybe>; +}; + +export type SetShoppingListTextLineItemCustomField = { + textLineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetShoppingListTextLineItemCustomType = { + textLineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetShoppingListTextLineItemDescription = { + textLineItemId: Scalars["String"]; + description?: Maybe>; +}; + +export type SetStoreLanguages = { + languages?: Maybe>; +}; + +export type SetStoreName = { + name?: Maybe>; +}; + +export type SetTaxCategoryKey = { + key?: Maybe; +}; + +export type SetType = FieldType & { + __typename?: "SetType"; + elementType: FieldType; + name: Scalars["String"]; +}; + +export type SetZoneDescription = { + description?: Maybe; +}; + +export type SetZoneKey = { + key?: Maybe; +}; + +export enum ShipmentState { + Delayed = "Delayed", + Backorder = "Backorder", + Partial = "Partial", + Pending = "Pending", + Ready = "Ready", + Shipped = "Shipped" +} + +export type ShippingInfo = { + __typename?: "ShippingInfo"; + shippingMethodName: Scalars["String"]; + price: Money; + shippingRate: ShippingRate; + taxRate?: Maybe; + taxCategory?: Maybe; + deliveries: Array; + discountedPrice?: Maybe; + taxedPrice?: Maybe; + shippingMethodState: ShippingMethodState; + shippingMethod?: Maybe; + shippingMethodRef?: Maybe; +}; + +export type ShippingMethod = Versioned & { + __typename?: "ShippingMethod"; + id: Scalars["String"]; + version: Scalars["Long"]; + name: Scalars["String"]; + description?: Maybe; + zoneRates: Array; + isDefault: Scalars["Boolean"]; + predicate?: Maybe; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + key?: Maybe; + lastModifiedBy?: Maybe; + createdBy?: Maybe; + taxCategoryRef?: Maybe; + taxCategory?: Maybe; +}; + +export type ShippingMethodDraft = { + name: Scalars["String"]; + description?: Maybe; + taxCategory: ResourceIdentifierInput; + zoneRates?: Maybe>; + isDefault: Scalars["Boolean"]; + predicate?: Maybe; + key?: Maybe; +}; + +export type ShippingMethodQueryResult = { + __typename?: "ShippingMethodQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +/** A field to retrieve available shipping methods for a cart. */ +export type ShippingMethodsByCartInterface = { + shippingMethodsByCart: Array; +}; + +/** A field to retrieve available shipping methods for a cart. */ +export type ShippingMethodsByCartInterfaceShippingMethodsByCartArgs = { + id: Scalars["String"]; +}; + +export enum ShippingMethodState { + /** Either there is no predicate defined for the ShippingMethod or the given predicate matches the cart */ + MatchesCart = "MatchesCart", + /** The ShippingMethod predicate does not match the cart. Ordering this cart will + * fail with error ShippingMethodDoesNotMatchCart + */ + DoesNotMatchCart = "DoesNotMatchCart" +} + +export type ShippingMethodUpdateAction = { + addShippingRate?: Maybe; + addZone?: Maybe; + changeIsDefault?: Maybe; + changeName?: Maybe; + changeTaxCategory?: Maybe; + removeShippingRate?: Maybe; + removeZone?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setPredicate?: Maybe; +}; + +/** Shipping Rate */ +export type ShippingRate = { + __typename?: "ShippingRate"; + price: Money; + freeAbove?: Maybe; + isMatching?: Maybe; + tiers: Array; +}; + +export type ShippingRateCartClassificationPriceTier = ShippingRatePriceTier & { + __typename?: "ShippingRateCartClassificationPriceTier"; + value: Scalars["String"]; + price: Money; + isMatching?: Maybe; + type: Scalars["String"]; +}; + +export type ShippingRateCartScorePriceTier = ShippingRatePriceTier & { + __typename?: "ShippingRateCartScorePriceTier"; + score: Scalars["Int"]; + price?: Maybe; + priceFunction?: Maybe; + isMatching?: Maybe; + type: Scalars["String"]; +}; + +export type ShippingRateCartValuePriceTier = ShippingRatePriceTier & { + __typename?: "ShippingRateCartValuePriceTier"; + minimumCentAmount: Scalars["Int"]; + price: Money; + isMatching?: Maybe; + type: Scalars["String"]; +}; + +export type ShippingRateDraft = { + price: MoneyDraft; + freeAbove?: Maybe; + tiers?: Maybe>; +}; + +export type ShippingRateInput = { + type: Scalars["String"]; +}; + +export type ShippingRateInputDraft = { + Classification?: Maybe; + Score?: Maybe; +}; + +export type ShippingRateInputLocalizedEnumValue = { + __typename?: "ShippingRateInputLocalizedEnumValue"; + key: Scalars["String"]; + label?: Maybe; + labelAllLocales: Array; +}; + +export type ShippingRateInputLocalizedEnumValueLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShippingRateInputType = { + type: Scalars["String"]; +}; + +export type ShippingRateInputTypeInput = { + CartValue?: Maybe; + CartClassification?: Maybe; + CartScore?: Maybe; +}; + +export type ShippingRatePriceTier = { + type: Scalars["String"]; +}; + +export type ShippingRatePriceTierCartClassificationDraft = { + value: Scalars["String"]; + price: MoneyDraft; +}; + +export type ShippingRatePriceTierCartScoreDraft = { + score: Scalars["Int"]; + price?: Maybe; + priceFunction?: Maybe; +}; + +export type ShippingRatePriceTierCartValueDraft = { + minimumCentAmount: Scalars["Int"]; + price: MoneyDraft; +}; + +export type ShippingRatePriceTierDraft = { + CartValue?: Maybe; + CartClassification?: Maybe; + CartScore?: Maybe; +}; + +export type ShippingTarget = CartDiscountTarget & { + __typename?: "ShippingTarget"; + type: Scalars["String"]; +}; + +export type ShippingTargetDraft = { + addressKey: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ShippingTargetDraftType = { + addressKey: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ShippingTargetInput = { + dummy?: Maybe; +}; + +export type ShoppingList = Versioned & { + __typename?: "ShoppingList"; + key?: Maybe; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales?: Maybe>; + customerRef?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + textLineItems: Array; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ShoppingListNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListDraft = { + name: Array; + description?: Maybe>; + lineItems?: Maybe>; + textLineItems?: Maybe>; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + key?: Maybe; + customer?: Maybe; + slug?: Maybe>; + anonymousId?: Maybe; +}; + +export type ShoppingListLineItem = { + __typename?: "ShoppingListLineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + variantId?: Maybe; + productTypeRef: Reference; + productType: ProductTypeDefinition; + quantity: Scalars["Int"]; + addedAt: Scalars["DateTime"]; + name?: Maybe; + nameAllLocales: Array; + deactivatedAt?: Maybe; + custom?: Maybe; + productSlug?: Maybe; + variant?: Maybe; +}; + +export type ShoppingListLineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListLineItemProductSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListLineItemDraft = { + productId?: Maybe; + sku?: Maybe; + variantId?: Maybe; + quantity?: Maybe; + custom?: Maybe; + addedAt?: Maybe; +}; + +/** Fields to access shopping lists. Includes direct access to a single list and searching for shopping lists. */ +export type ShoppingListQueryInterface = { + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +/** Fields to access shopping lists. Includes direct access to a single list and searching for shopping lists. */ +export type ShoppingListQueryInterfaceShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +/** Fields to access shopping lists. Includes direct access to a single list and searching for shopping lists. */ +export type ShoppingListQueryInterfaceShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type ShoppingListQueryResult = { + __typename?: "ShoppingListQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ShoppingListUpdateAction = { + addLineItem?: Maybe; + addTextLineItem?: Maybe; + changeLineItemQuantity?: Maybe; + changeLineItemsOrder?: Maybe; + changeName?: Maybe; + changeTextLineItemName?: Maybe; + changeTextLineItemQuantity?: Maybe; + changeTextLineItemsOrder?: Maybe; + removeLineItem?: Maybe; + removeTextLineItem?: Maybe; + setAnonymousId?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setCustomer?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetShoppingListDeleteDaysAfterLastModification + >; + setDescription?: Maybe; + setKey?: Maybe; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setSlug?: Maybe; + setTextLineItemCustomField?: Maybe; + setTextLineItemCustomType?: Maybe; + setTextLineItemDescription?: Maybe; +}; + +export type SimpleAttributeTypeDraft = { + dummy?: Maybe; +}; + +/** Describes how this discount interacts with other discounts */ +export enum StackingMode { + /** Don’t apply any more matching discounts after this one. */ + StopAfterThisDiscount = "StopAfterThisDiscount", + /** Default. Continue applying other matching discounts after applying this one. */ + Stacking = "Stacking" +} + +/** [State](http://dev.commercetools.com/http-api-projects-states.html) */ +export type State = Versioned & { + __typename?: "State"; + id: Scalars["String"]; + version: Scalars["Long"]; + key?: Maybe; + type: StateType; + roles: Array; + name?: Maybe; + nameAllLocales?: Maybe>; + description?: Maybe; + descriptionAllLocales?: Maybe>; + builtIn: Scalars["Boolean"]; + transitionsRef?: Maybe>; + transitions?: Maybe>; + initial: Scalars["Boolean"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +/** [State](http://dev.commercetools.com/http-api-projects-states.html) */ +export type StateNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** [State](http://dev.commercetools.com/http-api-projects-states.html) */ +export type StateDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type StateQueryResult = { + __typename?: "StateQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum StateRole { + Return = "Return", + ReviewIncludedInStatistics = "ReviewIncludedInStatistics" +} + +export enum StateType { + OrderState = "OrderState", + ProductState = "ProductState", + ReviewState = "ReviewState", + PaymentState = "PaymentState", + LineItemState = "LineItemState" +} + +/** [BETA] Stores allow defining different contexts for a project. */ +export type Store = Versioned & { + __typename?: "Store"; + id: Scalars["String"]; + version: Scalars["Long"]; + key: Scalars["String"]; + name?: Maybe; + nameAllLocales?: Maybe>; + languages?: Maybe>; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + distributionChannels: Array; + supplyChannels: Array; +}; + +/** [BETA] Stores allow defining different contexts for a project. */ +export type StoreNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type StoreQueryResult = { + __typename?: "StoreQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type StoreUpdateAction = { + setLanguages?: Maybe; + setName?: Maybe; +}; + +export type StringAttribute = Attribute & { + __typename?: "StringAttribute"; + value: Scalars["String"]; + name: Scalars["String"]; +}; + +export type StringField = CustomField & { + __typename?: "StringField"; + value: Scalars["String"]; + name: Scalars["String"]; +}; + +export type StringType = FieldType & { + __typename?: "StringType"; + name: Scalars["String"]; +}; + +export type SubRate = { + __typename?: "SubRate"; + name: Scalars["String"]; + amount: Scalars["Float"]; +}; + +export type SubRateDraft = { + name: Scalars["String"]; + amount: Scalars["Float"]; +}; + +/** Stores information about order synchronization activities (like export or import). */ +// This file is generated do not change it. + +export type SyncInfo = { + __typename?: "SyncInfo"; + channelRef: Reference; + channel?: Maybe; + externalId?: Maybe; + syncedAt: Scalars["DateTime"]; +}; + +export enum TaxCalculationMode { + /** This calculation mode calculates the taxes on the unit price before multiplying with the quantity. + * E.g. `($1.08 * 1.19 = $1.2852 -> $1.29 rounded) * 3 = $3.87` + */ + UnitPriceLevel = "UnitPriceLevel", + /** Default. This calculation mode calculates the taxes after the unit price is multiplied with the quantity. + * E.g. `($1.08 * 3 = $3.24) * 1.19 = $3.8556 -> $3.86 rounded` + */ + LineItemLevel = "LineItemLevel" +} + +/** Tax Categories define how products are to be taxed in different countries. */ +export type TaxCategory = Versioned & { + __typename?: "TaxCategory"; + name: Scalars["String"]; + description?: Maybe; + rates: Array; + key?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type TaxCategoryAddTaxRate = { + taxRate: TaxRateDraft; +}; + +export type TaxCategoryChangeName = { + name: Scalars["String"]; +}; + +export type TaxCategoryDraft = { + name: Scalars["String"]; + description?: Maybe; + rates?: Maybe>; + key?: Maybe; +}; + +export type TaxCategoryQueryResult = { + __typename?: "TaxCategoryQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type TaxCategoryRemoveTaxRate = { + taxRateId: Scalars["String"]; +}; + +export type TaxCategoryReplaceTaxRate = { + taxRateId: Scalars["String"]; + taxRate: TaxRateDraft; +}; + +export type TaxCategorySetDescription = { + description?: Maybe; +}; + +export type TaxCategoryUpdateAction = { + changeName?: Maybe; + setDescription?: Maybe; + addTaxRate?: Maybe; + replaceTaxRate?: Maybe; + removeTaxRate?: Maybe; + setKey?: Maybe; +}; + +export type TaxedItemPrice = { + __typename?: "TaxedItemPrice"; + totalNet: Money; + totalGross: Money; +}; + +export type TaxedPrice = { + __typename?: "TaxedPrice"; + totalNet: Money; + totalGross: Money; + taxPortions: Array; +}; + +export enum TaxMode { + /** No taxes are added to the cart. */ + Disabled = "Disabled", + /** The tax amounts and the tax rates as well as the tax portions are set externally per ExternalTaxAmountDraft. + * A cart with this tax mode can only be ordered if the cart itself and all line items, all custom line items and + * the shipping method have an external tax amount and rate set + */ + ExternalAmount = "ExternalAmount", + /** The tax rates are set externally per ExternalTaxRateDraft. A cart with this tax mode can only be ordered if all + * line items, all custom line items and the shipping method have an external tax rate set. The totalNet and + * totalGross as well as the taxPortions fields are calculated by the platform according to the taxRoundingMode. + */ + External = "External", + /** The tax rates are selected by the platform from the TaxCategories based on the cart shipping address. + * The totalNet and totalGross as well as the taxPortions fields are calculated by the platform according to the + * taxRoundingMode. + */ + Platform = "Platform" +} + +/** Represents the portions that sum up to the totalGross field of a TaxedPrice. The portions are calculated + * from the TaxRates. If a tax rate has SubRates, they are used and can be identified by name. Tax portions + * from line items that have the same rate and name will be accumulated to the same tax portion. + */ +export type TaxPortion = { + __typename?: "TaxPortion"; + rate: Scalars["Float"]; + amount: Money; + name?: Maybe; +}; + +export type TaxPortionDraft = { + name?: Maybe; + rate: Scalars["Float"]; + amount: MoneyInput; +}; + +export type TaxRate = { + __typename?: "TaxRate"; + name: Scalars["String"]; + amount: Scalars["Float"]; + includedInPrice: Scalars["Boolean"]; + country: Scalars["Country"]; + state?: Maybe; + id?: Maybe; + subRates: Array; +}; + +export type TaxRateDraft = { + name: Scalars["String"]; + amount?: Maybe; + includedInPrice: Scalars["Boolean"]; + country: Scalars["Country"]; + state?: Maybe; + subRates?: Maybe>; +}; + +export type TextAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "TextAttributeDefinitionType"; + name: Scalars["String"]; +}; + +/** UI hint telling what kind of edit control should be displayed for a text attribute. */ +export enum TextInputHint { + MultiLine = "MultiLine", + SingleLine = "SingleLine" +} + +export type TextLineItem = { + __typename?: "TextLineItem"; + id: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + quantity: Scalars["Int"]; + custom?: Maybe; + addedAt: Scalars["DateTime"]; +}; + +export type TextLineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type TextLineItemDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type TextLineItemDraft = { + name: Array; + description?: Maybe>; + quantity?: Maybe; + custom?: Maybe; + addedAt?: Maybe; +}; + +export type TimeAttribute = Attribute & { + __typename?: "TimeAttribute"; + value: Scalars["Time"]; + name: Scalars["String"]; +}; + +export type TimeAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "TimeAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type TimeField = CustomField & { + __typename?: "TimeField"; + value: Scalars["Time"]; + name: Scalars["String"]; +}; + +export type TimeType = FieldType & { + __typename?: "TimeType"; + name: Scalars["String"]; +}; + +export type TrackingData = { + __typename?: "TrackingData"; + trackingId?: Maybe; + carrier?: Maybe; + provider?: Maybe; + providerTransaction?: Maybe; + isReturn: Scalars["Boolean"]; +}; + +export type TrackingDataDraftType = { + trackingId?: Maybe; + carrier?: Maybe; + provider?: Maybe; + providerTransaction?: Maybe; + isReturn?: Maybe; +}; + +export type Transaction = { + __typename?: "Transaction"; + id: Scalars["String"]; + timestamp?: Maybe; + type?: Maybe; + amount: Money; + interactionId?: Maybe; + state: TransactionState; +}; + +export enum TransactionState { + Failure = "Failure", + Success = "Success", + Pending = "Pending", + Initial = "Initial" +} + +export enum TransactionType { + Chargeback = "Chargeback", + Refund = "Refund", + Charge = "Charge", + CancelAuthorization = "CancelAuthorization", + Authorization = "Authorization" +} + +export type TransitionOrderCustomLineItemState = { + customLineItemId: Scalars["String"]; + quantity: Scalars["Long"]; + fromState: ResourceIdentifierInput; + toState: ResourceIdentifierInput; + actualTransitionDate?: Maybe; +}; + +export type TransitionOrderLineItemState = { + lineItemId: Scalars["String"]; + quantity: Scalars["Long"]; + fromState: ResourceIdentifierInput; + toState: ResourceIdentifierInput; + actualTransitionDate?: Maybe; +}; + +export type TransitionOrderState = { + state: ResourceIdentifierInput; + force?: Maybe; +}; + +export type TransitionProductState = { + state: ReferenceInput; + force?: Maybe; +}; + +export type Type = { + typeRef: Reference; + type?: Maybe; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinition = Versioned & { + __typename?: "TypeDefinition"; + key: Scalars["String"]; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + resourceTypeIds: Array; + fieldDefinitions: Array; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinitionNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinitionDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinitionFieldDefinitionsArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type TypeDefinitionQueryResult = { + __typename?: "TypeDefinitionQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type UnpublishProduct = { + dummy?: Maybe; +}; + +export type UpdateCartItemShippingAddress = { + address: AddressInput; +}; + +export type UpdateOrderItemShippingAddress = { + address: AddressInput; +}; + +export type UpdateOrderSyncInfo = { + channel: ResourceIdentifierInput; + syncedAt?: Maybe; + externalId?: Maybe; +}; + +/** Versioned object have an ID and version and modification. Every update of this object changes it's version. */ +export type Versioned = { + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type WhitespaceSuggestTokenizerInput = { + dummy?: Maybe; +}; + +/** Zones allow defining ShippingRates for specific Locations. */ +export type Zone = Versioned & { + __typename?: "Zone"; + name: Scalars["String"]; + key?: Maybe; + description?: Maybe; + locations: Array; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ZoneLocation = { + country: Scalars["Country"]; + state?: Maybe; +}; + +export type ZoneQueryResult = { + __typename?: "ZoneQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ZoneRate = { + __typename?: "ZoneRate"; + shippingRates: Array; + zoneRef?: Maybe; + zone?: Maybe; +}; + +export type ZoneRateDraft = { + zone: ResourceIdentifierInput; + shippingRates?: Maybe>; +}; + +export type ZoneUpdateAction = { + addLocation?: Maybe; + changeName?: Maybe; + removeLocation?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; +}; diff --git a/packages/commercetools/api-client/src/types/setup.ts b/packages/commercetools/api-client/src/types/setup.ts new file mode 100644 index 0000000000..c6778bfff3 --- /dev/null +++ b/packages/commercetools/api-client/src/types/setup.ts @@ -0,0 +1,91 @@ +/* eslint-disable camelcase */ +import SdkAuth, { TokenProvider } from '@commercetools/sdk-auth'; +import ApolloClient, { ApolloClientOptions } from 'apollo-client'; + +export interface ClientInstance extends ApolloClient { + sdkAuth?: SdkAuth; + tokenProvider?: TokenProvider; +} + +export interface ApiConfig { + uri: string; + authHost: string; + projectKey: string; + clientId: string; + clientSecret: string; + scopes: string[]; +} + +export interface Token { + access_token: string; + expires_at: number; + expires_in: number; + scope: string; + token_type: string; + refresh_token: string; +} + +export interface CookiesConfig { + currencyCookieName: string; + countryCookieName: string; + localeCookieName: string; + storeCookieName: string; +} + +export interface LocaleItem { + name: string; + label: string; +} + +export interface Auth { + onTokenChange?: (token: Token) => void; + onTokenRead?: () => string; + onTokenRemove?: () => void; +} + +export interface StoreService { + changeCurrentStore: (id: string) => void, +} + +export interface SetupConfig { + api?: ApiConfig; + customOptions?: ApolloClientOptions; + currency?: string; + locale?: string; + country?: string; + countries?: LocaleItem[]; + currencies?: LocaleItem[]; + locales?: LocaleItem[]; + languageMap?: Record; + acceptLanguage?: string[]; + store: string; + cookies?: CookiesConfig; + auth?: Auth; + storeService?: StoreService; + forceToken?: boolean; +} + +export interface CustomerCredentials { + username: string; + password: string; +} + +export interface Config { + client?: ApolloClient; + api: ApiConfig; + customOptions?: ApolloClientOptions; + currency: string; + locale: string; + country: string; + countries: LocaleItem[]; + currencies: LocaleItem[]; + locales: LocaleItem[]; + languageMap: Record; + acceptLanguage: string[]; + store: string; + cookies: CookiesConfig; + auth?: Auth; + storeService?: StoreService; + forceToken?: boolean; + handleIsTokenUserSession: (token: Token) => boolean; +} diff --git a/packages/commercetools/api-client/tsconfig.json b/packages/commercetools/api-client/tsconfig.json new file mode 100644 index 0000000000..7c1bf51095 --- /dev/null +++ b/packages/commercetools/api-client/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "es5", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declarationMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "lib": ["es6", "es7", "ES2017", "ES2018", "ES2019", "dom"], + "strict": false, + "preserveSymlinks": true, + "paths": { + "@vue-storefront/core": ["core/core/src"], + }, + "resolveJsonModule": true + }, + "exclude": ["node_modules", "**/*.spec.ts", "lib"], + "include": ["src"], + "files": ["typings.d.ts"], +} diff --git a/packages/commercetools/api-client/typings.d.ts b/packages/commercetools/api-client/typings.d.ts new file mode 100644 index 0000000000..d05e066fb9 --- /dev/null +++ b/packages/commercetools/api-client/typings.d.ts @@ -0,0 +1,6 @@ +declare module '*.graphql' { + import { DocumentNode } from 'graphql'; + const Schema: DocumentNode; + + export = Schema; +} diff --git a/packages/commercetools/composables/.gitignore b/packages/commercetools/composables/.gitignore new file mode 100644 index 0000000000..7c4bc8070a --- /dev/null +++ b/packages/commercetools/composables/.gitignore @@ -0,0 +1,3 @@ +lib +node_modules +coverage diff --git a/packages/commercetools/composables/.npmignore b/packages/commercetools/composables/.npmignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/packages/commercetools/composables/.npmignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/commercetools/composables/__tests__/_mountComposable.ts b/packages/commercetools/composables/__tests__/_mountComposable.ts new file mode 100644 index 0000000000..1a2949722f --- /dev/null +++ b/packages/commercetools/composables/__tests__/_mountComposable.ts @@ -0,0 +1,15 @@ +import { shallowMount } from '@vue/test-utils'; +import { createComponent } from '@vue/composition-api'; + +const mountComposable = (composableFn) => { + const component = createComponent({ + template: '
my component
', + setup() { + return composableFn(); + } + }); + + return shallowMount(component); +}; + +export default mountComposable; diff --git a/packages/commercetools/composables/__tests__/getters/_createPrice.spec.ts b/packages/commercetools/composables/__tests__/getters/_createPrice.spec.ts new file mode 100644 index 0000000000..38cf6d201e --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/_createPrice.spec.ts @@ -0,0 +1,97 @@ +import { createPrice } from './../../src/getters/_utils'; + +describe('[commercetools-getters] utils/createPrice', () => { + it('returns null prices when there is no product', () => { + const price = createPrice(null); + + expect(price).toEqual({ regular: null, special: null }); + }); + + it('returns null prices when there is no price', () => { + const price = createPrice({ price: null } as any); + + expect(price).toEqual({ regular: null, special: null }); + }); + + it('returns regular price when there is no discount', () => { + const price = createPrice({ + price: { value: { centAmount: 250 } } + } as any); + + expect(price).toEqual({ regular: 2.5, special: null }); + }); + + it('returns regular price when discount is inactive', () => { + const price = createPrice({ + price: { + value: { centAmount: 250 }, + discounted: { + discount: { + isActive: false + }, + value: { centAmount: 350 } + } + } + } as any); + + expect(price).toEqual({ regular: 2.5, special: null }); + }); + + it('returns regular price for lineItem when discount is inactive and there is no cart discount', () => { + const price = createPrice({ + __typename: 'LineItem', + discountedPricePerQuantity: [], + quantity: 1, + price: { + value: { centAmount: 250 }, + discounted: { + discount: { + isActive: false + }, + value: { centAmount: 350 } + } + } + } as any); + + expect(price).toEqual({ regular: 2.5, special: null }); + }); + + it('returns regular and special price', () => { + const price = createPrice({ + price: { + value: { centAmount: 250 }, + discounted: { + discount: { + isActive: true + }, + value: { centAmount: 350 } + } + } + } as any); + + expect(price).toEqual({ regular: 2.5, special: 3.5 }); + }); + + it('returns regular and special price for line item', () => { + const price = createPrice({ + __typename: 'LineItem', + quantity: 1, + discountedPricePerQuantity: [{ + discountedPrice: { + value: { centAmount: 2400 } + } + }], + price: { + value: { centAmount: 250 }, + discounted: { + discount: { + isActive: true + }, + value: { centAmount: 350 } + } + } + } as any); + + expect(price).toEqual({ regular: 2.5, special: 24 }); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/_formatAttributeList.spec.ts b/packages/commercetools/composables/__tests__/getters/_formatAttributeList.spec.ts new file mode 100644 index 0000000000..47eff94a9e --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/_formatAttributeList.spec.ts @@ -0,0 +1,36 @@ +import { formatAttributeList } from './../../src/getters/_utils'; + +describe('[commercetools-getters] internal utilities helpers', () => { + const attributesRaw = [ + { attributeDefinition: { type: { name: 'text' } }, value: 'val', name: 'color', _translated: 'val' }, + { attributeDefinition: { type: { name: 'date' } }, value: 'val', name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'datetime' } }, value: 'val', name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'time' } }, value: 'val', name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'number' } }, value: 'val', name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'enum' } }, value: { label: 'Val', key: 'val' }, name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'lenum' } }, value: { label: { en: 'Val' }, key: 'val' }, name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'ltext' } }, value: { en: 'Val' }, name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'money' } }, value: 'val', name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'boolean' } }, value: 'val', name: 'name', _translated: 'val' }, + { attributeDefinition: { type: { name: 'reference' } }, value: { id: 'val', typeId: 'val' }, name: 'name', _translated: null }, + { attributeDefinition: { type: { name: 'uknnowb' } }, value: null, name: 'name', _translated: null } + ]; + + it('transforms custom value attribute fields to normalized "value" and copies "value" to "label" if it is empty', () => { + const normalziedAttributeList = [ + { value: 'val', name: 'color', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: { en: 'Val'}, name: 'name', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: 'val', name: 'name', label: 'val' }, + { value: { typeId: 'val', id: 'val' }, name: 'name', label: null }, + { value: null, name: 'name', label: null } + ]; + expect(formatAttributeList(attributesRaw)).toEqual(normalziedAttributeList); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/_getVariantByAttribute.spec.ts b/packages/commercetools/composables/__tests__/getters/_getVariantByAttribute.spec.ts new file mode 100644 index 0000000000..97fdb646e2 --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/_getVariantByAttribute.spec.ts @@ -0,0 +1,94 @@ +import { getVariantByAttributes } from './../../src/getters/_utils'; + +const product = { + _name: 'variant 1', + _slug: 'variant-1', + price: { + value: { centAmount: 1200 } + }, + attributesRaw: [ + { + name: 'articleNumberManufacturer', + value: 'H805 C195 85072', + attributeDefinition: { type: { name: 'text'} } + } + ], + images: [{ url: 'imageV11/url.jpg' }, { url: 'imageV12/url.jpg' }] +} as any; + +describe('[commercetools-getters] getVariantByAttribute', () => { + it('returns configured product', () => { + const variants = [ + { + ...product, + _master: true, + attributesRaw: [ + { + name: 'size', + value: '36', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'color', + value: 'white', + attributeDefinition: { type: { name: 'text'} } + } + ] + }, + { + ...product, + attributesRaw: [ + { + name: 'size', + value: '38', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'color', + value: 'black', + attributeDefinition: { type: { name: 'text'} } + } + ] + } + ] as any; + + const configuration = { + size: '38', + color: 'black' + } as any; + + expect(getVariantByAttributes(null, configuration)).toEqual(null); + expect(getVariantByAttributes(variants, {})).toEqual({ + ...product, + _master: true, + attributesRaw: [ + { + name: 'size', + value: '36', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'color', + value: 'white', + attributeDefinition: { type: { name: 'text'} } + } + ] + }); + + expect(getVariantByAttributes(variants, configuration)).toEqual({ + ...product, + attributesRaw: [ + { + name: 'size', + value: '38', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'color', + value: 'black', + attributeDefinition: { type: { name: 'text'} } + } + ] + }); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/cartHelpers.spec.ts b/packages/commercetools/composables/__tests__/getters/cartHelpers.spec.ts new file mode 100644 index 0000000000..2669b9f9f4 --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/cartHelpers.spec.ts @@ -0,0 +1,137 @@ +import { + getCartTotals, + getCartShippingPrice, + getCartItems, + getCartItemName, + getCartItemImage, + getCartItemPrice, + getCartItemAttributes, + getCartItemSku, + getCartTotalItems +} from './../../src/getters/cartGetters'; +import { getProductAttributes } from './../../src/getters/productGetters'; + +import * as utils from './../../src/getters/_utils'; + +jest.spyOn(utils, 'createPrice').mockImplementation((product) => ({ + special: product?.price.value.centAmount / 100, + regular: product?.price.value.centAmount / 100 +} as any)); + +const price = (p) => ({ value: { centAmount: p } }); +const variant = (p = {}) => ({ + ...p, + images: [{ url: 'a.jpg' }, { url: 'b.jpg' }] +}); + +const cart = { + lineItems: [ + { name: 'prod1', + id: 1, + price: price(1100), + variant: variant(), + quantity: 1 + }, + { name: 'prod2', + id: 2, + price: price(1500), + variant: variant(), + quantity: 2 + } + ], + totalPrice: { + centAmount: 3044 + }, + shippingInfo: { + price: { + centAmount: 444 + } + } +} as any; + +describe('[commercetools-getters] cart helpers', () => { + it('returns default values', () => { + expect(getCartItems(null)).toEqual([]); + }); + + it('returns products', () => { + expect(getCartItems(cart)).toEqual(cart.lineItems); + }); + + it('returns cart total price', () => { + expect(getCartTotals(null)).toEqual({ + special: 0, + total: 0, + subtotal: 0 + }); + expect(getCartTotals(cart).total).toEqual(30.44); + }); + + it('returns cart subtotal price', () => { + expect(getCartTotals(cart).subtotal).toEqual(26); + }); + + it('returns cart shipping price', () => { + expect(getCartShippingPrice(cart)).toEqual(4.44); + expect(getCartShippingPrice({ ...cart, + shippingInfo: null })).toEqual(0); + + expect(getCartShippingPrice({ ...cart, + shippingInfo: { + shippingMethod: { + zoneRates: [ + { + shippingRates: [ + { + freeAbove: { + centAmount: 1000 + } + } + ] + } + ] + } + } + })).toEqual(0); + }); + + it('returns cart total items', () => { + expect(getCartTotalItems(null)).toEqual(0); + expect(getCartTotalItems(cart)).toEqual(3); + }); + + it('returns cart product name', () => { + expect(getCartItemName({ name: 'test' } as any)).toEqual('test'); + }); + + it('returns cart product image', () => { + expect( + getCartItemImage({ variant: { images: [{ url: 'image.jpg' }]}} as any) + ).toEqual('image.jpg'); + }); + + it('returns cart product price', () => { + expect(getCartItemPrice({ price: { value: { centAmount: 111 }}} as any)).toEqual({ regular: 1.11, special: 1.11 }); + }); + + it('returns cart product attributes', () => { + const args = { + variant: 'test variant', + filters: ['filter'] + }; + // eslint-disable-next-line + (getProductAttributes as any) = jest.fn() + .mockImplementation((variant, filters) => ({ + variant, + filters + })); + + expect( + getCartItemAttributes({ variant: 'test variant' } as any, ['filter']) + ).toEqual(args); + }); + + it('returns cart product sku', () => { + expect(getCartItemSku({ variant: { sku: 'XXX1' }} as any)).toEqual('XXX1'); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/categoryHelpers.spec.ts b/packages/commercetools/composables/__tests__/getters/categoryHelpers.spec.ts new file mode 100644 index 0000000000..6086331595 --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/categoryHelpers.spec.ts @@ -0,0 +1,306 @@ +import { getCategoryTree } from './../../src/getters/categoryGetters'; + +const category = { + id: '6e6d2ca4-2431-42f9-ba5d-747bdb499786', + slug: 'men-clothing-jackets', + name: 'Jackets', + description: null, + childCount: 0, + parent: { + id: '4f69311e-a78a-42c1-8d50-0af562e60113', + slug: 'men-clothing', + name: 'Clothing', + childCount: 8, + children: [ + { + id: '6e6d2ca4-2431-42f9-ba5d-747bdb499786', + slug: 'men-clothing-jackets', + name: 'Jackets', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '5d640bc0-b726-448d-a160-fa08456794ed', + slug: 'men-clothing-tops', + name: 'Tops', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'dbce6736-5ead-4bbb-876f-51774d2fe7ee', + slug: 'men-clothing-shirts', + name: 'Shirts', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '8525f590-2174-4afb-9f5a-7044727267bb', + slug: 'men-clothing-trousers', + name: 'Trousers', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'c78aaa0c-e73c-445d-9db5-b17b6bc1b6a8', + slug: 'men-clothing-jeans', + name: 'Jeans', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '6f2cef09-cd76-4da2-b7cd-f06e5ebda052', + slug: 'men-clothing-blazer', + name: 'Blazer', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '5ef717b0-1b49-4e93-b2fe-7e6f274e5b78', + slug: 'men-clothing-suits', + name: 'Suits', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '7209d360-2c20-4b70-996e-f90b22676f24', + slug: 'men-clothing-t-shirts', + name: 'T-shirts', + childCount: 0, + __typename: 'Category', + children: [] + } + ], + __typename: 'Category', + parent: { + id: '724b250d-9805-4657-ae73-3c02a63a9a13', + slug: 'men', + name: 'Men', + childCount: 4, + children: [ + { + id: '4f69311e-a78a-42c1-8d50-0af562e60113', + slug: 'men-clothing', + name: 'Clothing', + childCount: 8, + __typename: 'Category', + children: [ + { + id: '6e6d2ca4-2431-42f9-ba5d-747bdb499786', + slug: 'men-clothing-jackets', + name: 'Jackets', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '5d640bc0-b726-448d-a160-fa08456794ed', + slug: 'men-clothing-tops', + name: 'Tops', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'dbce6736-5ead-4bbb-876f-51774d2fe7ee', + slug: 'men-clothing-shirts', + name: 'Shirts', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '8525f590-2174-4afb-9f5a-7044727267bb', + slug: 'men-clothing-trousers', + name: 'Trousers', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'c78aaa0c-e73c-445d-9db5-b17b6bc1b6a8', + slug: 'men-clothing-jeans', + name: 'Jeans', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '6f2cef09-cd76-4da2-b7cd-f06e5ebda052', + slug: 'men-clothing-blazer', + name: 'Blazer', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '5ef717b0-1b49-4e93-b2fe-7e6f274e5b78', + slug: 'men-clothing-suits', + name: 'Suits', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '7209d360-2c20-4b70-996e-f90b22676f24', + slug: 'men-clothing-t-shirts', + name: 'T-shirts', + childCount: 0, + __typename: 'Category', + children: [] + } + ] + }, + { + id: 'b4fd559b-d1a9-4f94-8ff0-c741b40ce48b', + slug: 'men-shoes', + name: 'Shoes', + childCount: 5, + __typename: 'Category', + children: [ + { + id: '5a6fcbc9-754a-438d-92c0-dd0828897657', + slug: 'men-shoes-sneakers', + name: 'Sneakers', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '8013f3cd-87b8-4d35-a6f3-66a544c949a4', + slug: 'men-shoes-boots', + name: 'Boots', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'ff66a557-173c-4a79-b010-bbcf31c3ca66', + slug: 'men-shoes-lace-up-shoes', + name: 'Lace-up shoes', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'ddb8df67-7d2b-4db0-823f-3fc00b1fc0aa', + slug: 'men-shoes-loafers', + name: 'Loafers', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '384efe63-2ce8-4a39-9de8-d278de273b71', + slug: 'men-shoes-sandals', + name: 'Sandals', + childCount: 0, + __typename: 'Category', + children: [] + } + ] + }, + { + id: '3be0d588-34ea-4098-b850-e14148ebd70e', + slug: 'men-bags', + name: 'Bags', + childCount: 6, + __typename: 'Category', + children: [ + { + id: 'b0207792-2d58-4fb7-aac9-81081034ddeb', + slug: 'men-bags-clutches', + name: 'Clutches', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'eb1a1863-c693-4ce0-a37a-c4785769556c', + slug: 'men-bags-shoulder-bags', + name: 'Shoulder bags', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'b110f9a2-e3a2-493b-933b-5a4555ad7338', + slug: 'men-bags-shopper', + name: 'Shopper', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '033170ad-2414-452f-974c-c891760cef18', + slug: 'men-bags-handbag', + name: 'Handbag', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: 'd25a0ca0-1fba-4087-a2ba-f7f13ad45145', + slug: 'men-bags-wallets', + name: 'Wallets', + childCount: 0, + __typename: 'Category', + children: [] + }, + { + id: '4567129a-0564-4e37-8d5c-fe98c32c5959', + slug: 'men-bags-bucket-and-packbag', + name: 'Bucketbag & packbag', + childCount: 0, + __typename: 'Category', + children: [] + } + ] + }, + { + id: '80a0a3ae-9a2a-4895-a9c6-07416c94ac97', + slug: 'men-looks', + name: 'Looks', + childCount: 0, + __typename: 'Category', + children: [] + } + ], + __typename: 'Category', + parent: null + } + }, + children: [], + __typename: 'Category', + _products: [ + { _name: 'prod1', + _master: true }, + { _name: 'prod2', + _master: false }, + { _name: 'prod3', + _master: false }, + { _name: 'prod4', + _master: false }, + { _name: 'prod5', + _master: true }, + { _name: 'prod6', + _master: false } + ] +} as any; + +describe('[commercetools-getters] category helpers', () => { + it('returns null when there is no category', () => { + expect(getCategoryTree(null)).toBe(null); + }); + + it('returns category tree', () => { + const categoryRoot = getCategoryTree(category); + expect(categoryRoot.slug).toBe('men'); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/facetHelpers.spec.ts b/packages/commercetools/composables/__tests__/getters/facetHelpers.spec.ts new file mode 100644 index 0000000000..02d15c71ce --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/facetHelpers.spec.ts @@ -0,0 +1,227 @@ +import facetGetters from './../../src/getters/facetGetters'; +import { getProductFiltered } from './../../src/getters/productGetters'; +import { getCategoryTree } from './../../src/getters/categoryGetters'; + +jest.mock('./../../src/getters/productGetters', () => ({ + getProductFiltered: jest.fn() +})); + +jest.mock('./../../src/getters/categoryGetters', () => ({ + getCategoryTree: jest.fn() +})); + +describe('[commercetools-getters] facet getters', () => { + it('returns sorting options', () => { + expect(facetGetters.getSortOptions({ input: { sort: null } } as any)).toEqual({ + options: [ + { type: 'sort', id: 'latest', selected: false, value: 'Latest', count: null }, + { + type: 'sort', + id: 'price-up', + selected: false, + value: 'Price from low to high', + count: null + }, + { + type: 'sort', + id: 'price-down', + selected: false, + value: 'Price from high to low', + count: null + } + ], + selected: 'latest' + }); + + expect(facetGetters.getSortOptions({ input: { sort: 'latest' } } as any)).toEqual({ + options: [ + { type: 'sort', id: 'latest', selected: true, value: 'Latest', count: null }, + { + type: 'sort', + id: 'price-up', + selected: false, + value: 'Price from low to high', + count: null + }, + { + type: 'sort', + id: 'price-down', + selected: false, + value: 'Price from high to low', + count: null + } + ], + selected: 'latest' + }); + }); + + it('returns grouped facets', () => { + expect(facetGetters.getGrouped({} as any)).toEqual([]); + + const searchData = { + input: {}, + data: { + facets: { + color: { + type: 'LocalizedEnumAttribute', + options: [ + { label: 'white', value: 'white' }, + { label: 'black', value: 'black' } + ] + }, + size: { + type: 'StringAttribute', + options: [ + { label: '34', value: '34' }, + { label: 'M', value: 'M' } + ] + } + } + } + } as any; + + const facets = facetGetters.getGrouped(searchData); + + expect(facets).toEqual([ + { + count: null, + id: 'color', + label: 'color', + options: [ + { attrName: 'color', count: null, id: 'white', selected: false, type: 'attribute', value: 'white' }, + { attrName: 'color', count: null, id: 'black', selected: false, type: 'attribute', value: 'black' } + ] + }, + { + count: null, + id: 'size', + label: 'size', + options: [ + { attrName: 'size', count: null, id: '34', selected: false, type: 'attribute', value: '34' }, + { attrName: 'size', count: null, id: 'M', selected: false, type: 'attribute', value: 'M' } + ] + } + ]); + }); + + it('returns facets', () => { + expect(facetGetters.getGrouped({} as any)).toEqual([]); + + const searchData = { + input: {}, + data: { + facets: { + color: { + type: 'LocalizedEnumAttribute', + options: [ + { label: 'white', value: 'white' }, + { label: 'black', value: 'black' } + ] + }, + size: { + type: 'StringAttribute', + options: [ + { label: '34', value: '34' }, + { label: 'M', value: 'M' } + ] + } + } + } + } as any; + + const facets = facetGetters.getAll(searchData); + + expect(facets).toEqual([ + { attrName: 'color', id: 'white', value: 'white', count: null, selected: false, type: 'attribute' }, + { attrName: 'color', id: 'black', value: 'black', count: null, selected: false, type: 'attribute' }, + { attrName: 'size', id: '34', value: '34', count: null, selected: false, type: 'attribute' }, + { attrName: 'size', id: 'M', value: 'M', count: null, selected: false, type: 'attribute' } + ]); + }); + + it('returns search results', () => { + const searchData = { + input: {}, + data: { + products: [] + } + } as any; + + facetGetters.getProducts(searchData); + + expect(getProductFiltered).toBeCalled(); + }); + + it('returns category tree', () => { + expect(facetGetters.getCategoryTree({ data: null } as any)).toEqual({}); + + const searchData = { + input: {}, + data: { + products: [], + categories: [{ cat: 1 }] + } + } as any; + + facetGetters.getCategoryTree(searchData); + + expect(getCategoryTree).toBeCalled(); + }); + + it('returns breadcrumbs', () => { + expect(facetGetters.getBreadcrumbs({ data: null } as any)).toEqual([]); + + const searchData = { + input: {}, + data: { + categories: [{ + name: 'cat3', + slug: 'cat-3', + parent: { + name: 'cat2', + slug: 'cat-2', + parent: { + name: 'cat1', + slug: 'cat-1' + } + } + }] + } + } as any; + + const breadcrumbs = facetGetters.getBreadcrumbs(searchData); + + expect(breadcrumbs).toEqual([ + { link: '/', text: 'Home' }, + { link: '/c/cat-1', text: 'cat1' }, + { link: '/c/cat-1/cat-2', text: 'cat2' }, + { link: '/c/cat-1/cat-2/cat-3', text: 'cat3' } + ]); + }); + + it('returns pagination info', () => { + expect(facetGetters.getPagination({ data: null } as any)).toEqual({}); + + const searchData = { + input: { + page: 2, + itemsPerPage: 10 + }, + data: { + total: 120, + itemsPerPage: 10, + perPageOptions: [10, 20, 50] + } + } as any; + + const paginationInfo = facetGetters.getPagination(searchData); + + expect(paginationInfo).toEqual({ + currentPage: 2, + itemsPerPage: 10, + pageOptions: [10, 20, 50], + totalItems: 120, + totalPages: 12 + }); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/orderHelpers.spec.ts b/packages/commercetools/composables/__tests__/getters/orderHelpers.spec.ts new file mode 100644 index 0000000000..abc4361985 --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/orderHelpers.spec.ts @@ -0,0 +1,106 @@ +import { + getOrderDate, + getOrderId, + getOrderStatus, + getOrderPrice, + getOrderItems, + getOrderItemSku, + getOrderItemName, + getOrderItemQty, + getFormattedPrice, + getOrdersTotal +} from './../../src/getters/orderGetters'; +import { OrderState, Order } from './../../src/types/GraphQL'; + +const order: Order = { + createdAt: 123456789, + id: '645ygdf', + orderState: OrderState.Complete, + totalPrice: { + centAmount: 12345, + currencyCode: 'USD' + }, + lineItems: [ + { + name: 'item-1', + productId: 'item-id-1', + quantity: 10 + }, + { + name: 'item-2', + productId: 'item-id-2', + quantity: 20 + } + ] +} as any; + +const mockedResults = [order, order, order]; + +const orders = { + results: mockedResults, + total: mockedResults.length, + offset: 0, + count: 0 +}; + +describe('[commercetools-getters] order getters', () => { + it('returns default values', () => { + expect(getOrderDate(null)).toBe(''); + expect(getOrderId(null)).toBe(''); + expect(getOrderStatus(null)).toBe(''); + expect(getOrderPrice(null)).toBe(0); + expect(getOrderItems(null)).toHaveLength(0); + expect(getOrderItemSku(null)).toBe(''); + expect(getOrderItemName(null)).toBe(''); + expect(getOrderItemQty(null)).toBe(0); + expect(getFormattedPrice(null)).toBe(null); + expect(getOrdersTotal({ results: [], total: 0, offset: 0, count: 0 })).toBe(0); + }); + + it('returns date', () => { + expect(getOrderDate(order)).toEqual(123456789); + }); + + it('returns order number', () => { + expect(getOrderId(order)).toEqual('645ygdf'); + }); + + it('returns status', () => { + expect(getOrderStatus(order)).toEqual(OrderState.Complete); + }); + + it('returns total gross', () => { + expect(getOrderPrice(order)).toEqual(123.45); + }); + + it('returns orders total', () => { + expect(getOrdersTotal(orders)).toEqual(3); + }); + + describe('order items', () => { + let items; + + beforeEach(() => { + items = getOrderItems(order); + }); + + it('returns all items', () => { + expect(items).toHaveLength(2); + }); + + it('returns items sku', () => { + expect(getOrderItemSku(items[0])).toBe('item-id-1'); + expect(getOrderItemSku(items[1])).toBe('item-id-2'); + }); + + it('returns items name', () => { + expect(getOrderItemName(items[0])).toBe('item-1'); + expect(getOrderItemName(items[1])).toBe('item-2'); + }); + + it('returns items quantity', () => { + expect(getOrderItemQty(items[0])).toBe(10); + expect(getOrderItemQty(items[1])).toBe(20); + }); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/productHelpers.spec.ts b/packages/commercetools/composables/__tests__/getters/productHelpers.spec.ts new file mode 100644 index 0000000000..f2928be267 --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/productHelpers.spec.ts @@ -0,0 +1,240 @@ +import { + getProductName, + getProductSlug, + getProductPrice, + getProductGallery, + getProductCoverImage, + getProductFiltered, + getProductAttributes, + getProductCategoryIds, + getProductId +} from './../../src/getters/productGetters'; +import * as utils from './../../src/getters/_utils'; + +jest.spyOn(utils, 'createPrice').mockImplementation((product) => ({ + special: product?.price, + regular: product?.price +} as any)); + +const product = { + _name: 'variant 1', + _slug: 'variant-1', + _id: 1234, + price: 12, + attributesRaw: [ + { + name: 'articleNumberManufacturer', + value: 'H805 C195 85072', + _translated: 'H805 C195 85072', + attributeDefinition: { type: { name: 'text'} } + } + ], + images: [{ url: 'imageV11/url.jpg' }, { url: 'imageV12/url.jpg' }], + _categoriesRef: [ + 'catA', + 'catB' + ] +} as any; + +describe('[commercetools-getters] product getters', () => { + it('returns default values', () => { + expect(getProductName(null)).toBe(''); + expect(getProductSlug(null)).toBe(''); + expect(getProductGallery(null)).toEqual([]); + expect(getProductFiltered(null)).toEqual([]); + }); + + it('returns name', () => { + expect(getProductName(product)).toBe('variant 1'); + }); + + it('returns slug', () => { + expect(getProductSlug(product)).toBe('variant-1'); + }); + + it('returns price', () => { + expect(getProductPrice(product)).toEqual({ regular: 12, special: 12 }); + }); + + it('returns gallery', () => { + expect(getProductGallery(product)).toEqual([ + { + small: 'imageV11/url.jpg', + big: 'imageV11/url.jpg', + normal: 'imageV11/url.jpg' + }, + { + small: 'imageV12/url.jpg', + big: 'imageV12/url.jpg', + normal: 'imageV12/url.jpg' + } + ]); + }); + + it('returns cover image', () => { + expect(getProductCoverImage({ images: [] } as any)).toEqual(''); + expect(getProductCoverImage(product)).toEqual('imageV11/url.jpg'); + }); + + it('returns master variant', () => { + const variants = [ + { _name: 'variant 1', + _master: false }, + { _name: 'variant 2', + _master: true } + ]; + expect(getProductFiltered(variants as any, { master: true })).toEqual([{ + _name: 'variant 2', + _master: true + }]); + }); + + it('returns master variants', () => { + const variants = [ + { _name: 'variant 1_1', + _master: false }, + { _name: 'variant 1_2', + _master: true }, + { _name: 'variant 2_1', + _master: true }, + { _name: 'variant 2_2', + _master: false } + ]; + expect(getProductFiltered(variants as any, { master: true })).toEqual([ + { _name: 'variant 1_2', + _master: true }, + { _name: 'variant 2_1', + _master: true } + ]); + }); + + it('returns all variants', () => { + const variants = [ + { _name: 'variant 1', + _master: false }, + { _name: 'variant 2', + _master: true } + ]; + expect(getProductFiltered(variants as any)).toEqual(variants); + }); + + it('returns product by given attributes', () => { + const variant1 = { + ...product, + attributesRaw: [ + { + name: 'size', + value: '36', + _translated: '26', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'color', + value: 'white', + _translated: 'white', + attributeDefinition: { type: { name: 'text'} } + } + ] + }; + const variant2 = { + ...product, + attributesRaw: [ + { + name: 'size', + value: '38', + _translated: '38', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'color', + value: 'black', + _translated: 'black', + attributeDefinition: { type: { name: 'text'} } + } + ] + }; + + const variants = [variant1, variant2]; + + const attributes = { color: 'black', + size: '38' }; + expect(getProductFiltered(variants, { attributes })).toEqual([variant2]); + }); + + // Attributes + + it('returns product attributes', () => { + expect(getProductAttributes([product])).toEqual({ + articleNumberManufacturer: [{ label: 'H805 C195 85072', + value: 'H805 C195 85072' }] + }); + }); + + it('returns attributes of single product', () => { + expect(getProductAttributes(product)).toEqual({ articleNumberManufacturer: 'H805 C195 85072' }); + }); + + it('returns product unique attributes', () => { + const prod = { + ...product, + attributesRaw: [ + { + name: 'articleNumberManufacturer', + value: 'H805 C195 85072', + _translated: 'H805 C195 85072', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'articleNumberManufacturer', + value: 'H805 C195 85072', + _translated: 'H805 C195 85072', + attributeDefinition: { type: { name: 'text'} } + } + ] + } as any; + + expect(getProductAttributes([prod])).toEqual({ + articleNumberManufacturer: [{ label: 'H805 C195 85072', + value: 'H805 C195 85072' }] + }); + }); + + it('returns filtered product attributes', () => { + const product = { + attributesRaw: [ + { + name: 'articleNumberManufacturer', + value: 'H805 C195 85072', + _translated: 'H805 C195 85072', + attributeDefinition: { type: { name: 'text'} } + }, + { + name: 'color', + value: 'H805 C195 85072', + _translated: 'H805 C195 85072', + attributeDefinition: { type: { name: 'text'} } + } + ] + } as any; + + expect(getProductAttributes([product], ['color'])).toEqual({ + color: [{ value: 'H805 C195 85072', + label: 'H805 C195 85072' }] + }); + }); + + it('returns product categories', () => { + expect(getProductCategoryIds(product)).toEqual([ + 'catA', + 'catB' + ]); + }); + + it('returns product ID', () => { + expect(getProductId(product)).toEqual(1234); + }); + + it('returns empty array if there is no product', () => { + expect(getProductAttributes(null)).toEqual({}); + }); +}); diff --git a/packages/commercetools/composables/__tests__/getters/storeGetters.spec.ts b/packages/commercetools/composables/__tests__/getters/storeGetters.spec.ts new file mode 100644 index 0000000000..436627f352 --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/storeGetters.spec.ts @@ -0,0 +1,184 @@ +import { storeGetters } from '../../src/getters'; +import { StoresData } from '../../src/types'; +import { AgnosticStore } from '@vue-storefront/core'; + +function makeStore (storeId, storeName, storeKey, channelID = null, channelName = null) { + return { + name: `${storeName}${channelName ? ` - ${channelName}` : ''}`, + id: `${storeId}${channelID ? `/${channelID}` : ''}`, + description: '', + geoLocation: null, + locales: [], + key: `${storeKey}${channelID ? `/${channelID}` : ''}`, + address: { addressLine1: '', addressLine2: '', _address: null }, + _storeID: storeId, + _channelID: channelID + } as AgnosticStore; +} + +const distributionChannels = (id) => ([ + { id: 'd1', name: 'distribution channel 1', key: `key-${id}`}, + { id: 'd2', name: 'distribution channel 2', key: `key-${id}`}, + { id: 'd3', name: 'distribution channel 3', key: `key-${id}` } +]); + +const supplyChannels = (id) => ([ + { id: 's1', name: 'supply channel 1', key: `key-${id}` }, + { id: 's2', name: 'supply channel 2', key: `key-${id}` }, + { id: 's3', name: 'supply channel 3', key: `key-${id}` } +]); + +const results = [ + { id: '1', name: 'store-1', key: 'key-1', distributionChannels: distributionChannels('1'), supplyChannels: supplyChannels('1') }, + { id: '2', name: 'store-2', key: 'key-2', distributionChannels: [], supplyChannels: supplyChannels('2') }, + { id: '3', name: 'store-3', key: 'key-3', distributionChannels: distributionChannels('3'), supplyChannels: [] }, + { id: '4', name: 'store-4', key: 'key-4', distributionChannels: [], supplyChannels: [] } +]; + +const stores = { + offset: 0, + count: 0, + total: 0, + _selectedStore: 'key-3', + results +}; + +describe('[commercetools-getters] store getters', () => { + + describe('getItems', () => { + + it('returns array of stores', () => { + const expected = [ + makeStore('1', 'store-1', 'key-1', 'd1', 'distribution channel 1'), + makeStore('1', 'store-1', 'key-1', 'd2', 'distribution channel 2'), + makeStore('1', 'store-1', 'key-1', 'd3', 'distribution channel 3'), + makeStore('1', 'store-1', 'key-1', 's1', 'supply channel 1'), + makeStore('1', 'store-1', 'key-1', 's2', 'supply channel 2'), + makeStore('1', 'store-1', 'key-1', 's3', 'supply channel 3'), + makeStore('2', 'store-2', 'key-2', 's1', 'supply channel 1'), + makeStore('2', 'store-2', 'key-2', 's2', 'supply channel 2'), + makeStore('2', 'store-2', 'key-2', 's3', 'supply channel 3'), + makeStore('3', 'store-3', 'key-3', 'd1', 'distribution channel 1'), + makeStore('3', 'store-3', 'key-3', 'd2', 'distribution channel 2'), + makeStore('3', 'store-3', 'key-3', 'd3', 'distribution channel 3'), + makeStore('4', 'store-4', 'key-4') + ]; + + expect(storeGetters.getItems(stores as StoresData)).toStrictEqual(expected); + }); + + it('returns empty array for invalid stores data', () => { + const expected = []; + expect(storeGetters.getItems(null as StoresData)).toStrictEqual(expected); + }); + + it('returns array of stores with store filter criteria', () => { + const expected = [ + makeStore('1', 'store-1', 'key-1', 'd1', 'distribution channel 1'), + makeStore('1', 'store-1', 'key-1', 'd2', 'distribution channel 2'), + makeStore('1', 'store-1', 'key-1', 'd3', 'distribution channel 3'), + makeStore('1', 'store-1', 'key-1', 's1', 'supply channel 1'), + makeStore('1', 'store-1', 'key-1', 's2', 'supply channel 2'), + makeStore('1', 'store-1', 'key-1', 's3', 'supply channel 3') + ]; + + const criteria = { + store: { + id: '1' + } + }; + + expect(storeGetters.getItems(stores as StoresData, criteria)).toStrictEqual(expected); + }); + + it('returns array of stores with callable store filter criteria', () => { + const expected = [ + makeStore('1', 'store-1', 'key-1', 'd1', 'distribution channel 1'), + makeStore('1', 'store-1', 'key-1', 'd2', 'distribution channel 2'), + makeStore('1', 'store-1', 'key-1', 'd3', 'distribution channel 3'), + makeStore('1', 'store-1', 'key-1', 's1', 'supply channel 1'), + makeStore('1', 'store-1', 'key-1', 's2', 'supply channel 2'), + makeStore('1', 'store-1', 'key-1', 's3', 'supply channel 3'), + makeStore('3', 'store-3', 'key-3', 'd1', 'distribution channel 1'), + makeStore('3', 'store-3', 'key-3', 'd2', 'distribution channel 2'), + makeStore('3', 'store-3', 'key-3', 'd3', 'distribution channel 3') + ]; + + const criteria = { + store: { + id(value) { + return value === '1' || value === '3'; + } + } + }; + + expect(storeGetters.getItems(stores as StoresData, criteria)).toStrictEqual(expected); + }); + + it('returns array of stores with channel filter criteria', () => { + const expected = [ + makeStore('1', 'store-1', 'key-1', 'd3', 'distribution channel 3'), + makeStore('3', 'store-3', 'key-3', 'd3', 'distribution channel 3') + ]; + + const criteria = { + channel: { + id: 'd3' + } + }; + + expect(storeGetters.getItems(stores as StoresData, criteria)).toStrictEqual(expected); + }); + + it('returns array of stores with callable channel filter criteria', () => { + const expected = [ + makeStore('1', 'store-1', 'key-1', 'd3', 'distribution channel 3'), + makeStore('1', 'store-1', 'key-1', 's2', 'supply channel 2'), + makeStore('2', 'store-2', 'key-2', 's2', 'supply channel 2'), + makeStore('3', 'store-3', 'key-3', 'd3', 'distribution channel 3') + ]; + + const criteria = { + channel: { + id (value) { + return value === 's2' || value === 'd3'; + } + } + }; + + expect(storeGetters.getItems(stores as StoresData, criteria)).toStrictEqual(expected); + }); + + it('returns empty array for criteria mismatch', () => { + const expected = []; + + const criteria = { + store: { + id: 'z1' + } + }; + + expect(storeGetters.getItems(stores as StoresData, criteria)).toStrictEqual(expected); + }); + + }); + + describe('getSelected', () => { + + it('returns selected store', () => { + const expected = makeStore('3', 'store-3', 'key-3', 'd1', 'distribution channel 1'); + expect(storeGetters.getSelected(stores as StoresData)).toStrictEqual(expected); + }); + + it('returns undefined for invalid stores data', () => { + expect(storeGetters.getSelected(null as StoresData)).toBeUndefined(); + }); + + it('returns undefined for criteria mismatch', () => { + const given = { ...stores, _selectedStore: '5/z1' }; + expect(storeGetters.getSelected(given as StoresData)).toBeUndefined(); + }); + + }); + +}); diff --git a/packages/commercetools/composables/__tests__/getters/userHelpers.spec.ts b/packages/commercetools/composables/__tests__/getters/userHelpers.spec.ts new file mode 100644 index 0000000000..5fea2788eb --- /dev/null +++ b/packages/commercetools/composables/__tests__/getters/userHelpers.spec.ts @@ -0,0 +1,30 @@ +import { + getUserFirstName, + getUserLastName, + getUserFullName +} from './../../src/getters/userGetters'; + +const user = { + firstName: 'John', + lastName: 'Doe' +} as any; + +describe('[commercetools-getters] user getters', () => { + it('returns default values', () => { + expect(getUserFirstName(null)).toBe(''); + expect(getUserLastName(null)).toBe(''); + expect(getUserFullName(null)).toBe(''); + }); + + it('returns first name', () => { + expect(getUserFirstName(user)).toBe('John'); + }); + + it('returns last name', () => { + expect(getUserLastName(user)).toBe('Doe'); + }); + + it('returns full name', () => { + expect(getUserFullName(user)).toBe('John Doe'); + }); +}); diff --git a/packages/commercetools/composables/__tests__/helpers/__snapshots__/enhanceProduct.spec.ts.snap b/packages/commercetools/composables/__tests__/helpers/__snapshots__/enhanceProduct.spec.ts.snap new file mode 100644 index 0000000000..68ed03c584 --- /dev/null +++ b/packages/commercetools/composables/__tests__/helpers/__snapshots__/enhanceProduct.spec.ts.snap @@ -0,0 +1,1154 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`[commercetools-composables] enhanceProduct returns category response with the products inside 1`] = ` +Object { + "data": Object { + "_variants": Array [ + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod1", + "_rating": undefined, + "_slug": "prod-1", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod1", + "_rating": undefined, + "_slug": "prod-1", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod1", + "_rating": undefined, + "_slug": "prod-1", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod2", + "_rating": undefined, + "_slug": "prod-2", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod2", + "_rating": undefined, + "_slug": "prod-2", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod2", + "_rating": undefined, + "_slug": "prod-2", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod3", + "_rating": undefined, + "_slug": "prod-3", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod3", + "_rating": undefined, + "_slug": "prod-3", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod3", + "_rating": undefined, + "_slug": "prod-3", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod4", + "_rating": undefined, + "_slug": "prod-4", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod4", + "_rating": undefined, + "_slug": "prod-4", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "_categoriesRef": Array [ + "aaa", + ], + "_description": undefined, + "_id": undefined, + "_key": undefined, + "_master": false, + "_name": "prod4", + "_rating": undefined, + "_slug": "prod-4", + "attributesRaw": Array [ + Object { + "_translated": "34", + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "_translated": "size", + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "_translated": "color", + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + ], + "products": Object { + "results": Array [ + Object { + "masterData": Object { + "current": Object { + "allVariants": Array [ + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + ], + "categoriesRef": Array [ + Object { + "id": "aaa", + }, + ], + "masterVariant": Object { + "id": "sde213", + }, + "name": "prod1", + "slug": "prod-1", + }, + }, + }, + Object { + "masterData": Object { + "current": Object { + "allVariants": Array [ + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + ], + "categoriesRef": Array [ + Object { + "id": "aaa", + }, + ], + "masterVariant": Object { + "id": "34s42d", + }, + "name": "prod2", + "slug": "prod-2", + }, + }, + }, + Object { + "masterData": Object { + "current": Object { + "allVariants": Array [ + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + ], + "categoriesRef": Array [ + Object { + "id": "aaa", + }, + ], + "masterVariant": Object { + "id": "fdf334", + }, + "name": "prod3", + "slug": "prod-3", + }, + }, + }, + Object { + "masterData": Object { + "current": Object { + "allVariants": Array [ + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "123", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "456", + }, + Object { + "attributesRaw": Array [ + Object { + "attributeDefinition": Object { + "type": Object { + "name": "number", + }, + }, + "name": "attr1", + "value": "34", + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "ltext", + }, + }, + "name": "attr2", + "value": Object { + "en": "size", + }, + }, + Object { + "attributeDefinition": Object { + "type": Object { + "name": "lenum", + }, + }, + "name": "attr3", + "value": Object { + "label": Object { + "en": "color", + }, + }, + }, + ], + "id": "789", + }, + ], + "categoriesRef": Array [ + Object { + "id": "aaa", + }, + ], + "masterVariant": Object { + "id": "dfsdf3", + }, + "name": "prod4", + "slug": "prod-4", + }, + }, + }, + ], + }, + }, +} +`; diff --git a/packages/commercetools/composables/__tests__/helpers/enhanceProduct.spec.ts b/packages/commercetools/composables/__tests__/helpers/enhanceProduct.spec.ts new file mode 100644 index 0000000000..f25d7487c5 --- /dev/null +++ b/packages/commercetools/composables/__tests__/helpers/enhanceProduct.spec.ts @@ -0,0 +1,50 @@ +import enhanceProduct from './../../src/helpers/internals/enhanceProduct'; + +const attributesRaw = () => [ + { name: 'attr1', attributeDefinition: { type: { name: 'number'} }, value: '34' }, + { name: 'attr2', attributeDefinition: { type: { name: 'ltext'} }, value: { en: 'size' } }, + { name: 'attr3', attributeDefinition: { type: { name: 'lenum'} }, value: { label: { en: 'color' } } } +]; + +const product = (name, slug, id) => ({ + masterData: { + current: { + name, + slug, + masterVariant: { + id + }, + categoriesRef: [{ id: 'aaa' }], + allVariants: [ + { id: '123', attributesRaw: attributesRaw() }, + { id: '456', attributesRaw: attributesRaw() }, + { id: '789', attributesRaw: attributesRaw() } + ] + } + } +}); + +const productResponse = { + data: { + products: { + results: [ + product('prod1', 'prod-1', 'sde213'), + product('prod2', 'prod-2', '34s42d'), + product('prod3', 'prod-3', 'fdf334'), + product('prod4', 'prod-4', 'dfsdf3') + ] + } + } +} as any; + +const context = { + $ct: { + config: { locale: 'en' } + } +} as any; + +describe('[commercetools-composables] enhanceProduct', () => { + it('returns category response with the products inside', () => { + expect(enhanceProduct(productResponse, context)).toMatchSnapshot(); + }); +}); diff --git a/packages/commercetools/composables/__tests__/helpers/getCouponsFromCart.spec.ts b/packages/commercetools/composables/__tests__/helpers/getCouponsFromCart.spec.ts new file mode 100644 index 0000000000..6735add06d --- /dev/null +++ b/packages/commercetools/composables/__tests__/helpers/getCouponsFromCart.spec.ts @@ -0,0 +1,28 @@ +import getCouponsFromCart from '../../src/helpers/internals/getCouponsFromCart'; + +const cart = { + discountCodes: [ + { + discountCode: { + id: '1', + name: 'NAME', + code: 'CODE' + } + } + ] +}; + +describe('[commercetools-composables] getCouponsFromCart', () => { + it('returns undefined if cart doesn\'t have discount codes', () => { + expect(getCouponsFromCart({} as any)).toBeUndefined(); + }); + + it('adds \'value\' property', () => { + expect(getCouponsFromCart(cart as any)).toEqual([ + { + ...cart.discountCodes[0].discountCode, + value: null + } + ]); + }); +}); diff --git a/packages/commercetools/composables/__tests__/helpers/getFiltersFromProductsAttributes.spec.ts b/packages/commercetools/composables/__tests__/helpers/getFiltersFromProductsAttributes.spec.ts new file mode 100644 index 0000000000..e453f1535d --- /dev/null +++ b/packages/commercetools/composables/__tests__/helpers/getFiltersFromProductsAttributes.spec.ts @@ -0,0 +1,54 @@ +import getFiltersFromProductsAttributes from '../../src/helpers/internals/getFiltersFromProductsAttributes'; +import { ProductVariant, Attribute } from '../../src/types/GraphQL'; + +describe('[commercetools-composables] getFiltersFromProductsAttributes', () => { + describe('returns epmty object in case of', () => { + it('empty products list', () => { + expect(getFiltersFromProductsAttributes([])).toEqual({}); + }); + + it('no products list given', () => { + expect(getFiltersFromProductsAttributes(null)).toEqual({}); + }); + }); + + it('returns filters based on attributes', () => { + const products: ProductVariant[] = [ + { + attributesRaw: [ + { + name: 'someAttribute', + attributeDefinition: { type: { name: 'text' } }, + value: 'value' + } as Attribute, + { + name: 'someAttribute', + attributeDefinition: { type: { name: 'text' } }, + value: 'value2', + label: 'valueLabel' + } as Attribute, + { + name: 'someNumberAttribute', + attributeDefinition: { type: { name: 'number' } }, + value: 1 + } as Attribute + ] + } as ProductVariant + ]; + expect(getFiltersFromProductsAttributes(products)).toEqual({ + someAttribute: { + type: 'text', + options: [ + { label: 'value', value: 'value', selected: false }, + { label: 'valueLabel', value: 'value2', selected: false } + ] + }, + someNumberAttribute: { + type: 'number', + options: [ + { label: null, value: 1, selected: false } + ] + } + }); + }); +}); diff --git a/packages/commercetools/composables/__tests__/helpers/mapPaginationParams.spec.ts b/packages/commercetools/composables/__tests__/helpers/mapPaginationParams.spec.ts new file mode 100644 index 0000000000..696365d6cc --- /dev/null +++ b/packages/commercetools/composables/__tests__/helpers/mapPaginationParams.spec.ts @@ -0,0 +1,13 @@ +import mapPaginationParams from '../../src/helpers/internals/mapPaginationParams'; + +describe('[commercetools-composables] mapPaginationParams', () => { + it('maps pagination params to format expected by CT gql API', () => { + const input = { perPage: 20, page: 3 }; + expect(mapPaginationParams(input)).toEqual({ limit: 20, offset: 40 }); + }); + + it('returns nothing if params are missing', () => { + const input = { perPage: 2 }; + expect(mapPaginationParams(input)).toBeUndefined(); + }); +}); diff --git a/packages/commercetools/composables/__tests__/setup.ts b/packages/commercetools/composables/__tests__/setup.ts new file mode 100644 index 0000000000..e98a3f1270 --- /dev/null +++ b/packages/commercetools/composables/__tests__/setup.ts @@ -0,0 +1,8 @@ +require('jsdom-global')(); +import Vue from 'vue'; +import VueCompositionApi from '@vue/composition-api'; + +Vue.config.productionTip = false; +Vue.config.devtools = false; + +Vue.use(VueCompositionApi); diff --git a/packages/commercetools/composables/__tests__/useBilling/useBilling.spec.ts b/packages/commercetools/composables/__tests__/useBilling/useBilling.spec.ts new file mode 100644 index 0000000000..59602b4ce0 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useBilling/useBilling.spec.ts @@ -0,0 +1,106 @@ +import { useBilling } from '../../src/useBilling'; +import { useCart } from '../../src/useCart'; + +jest.mock('@vue-storefront/commercetools-api', () => ({ + cartActions: { + setBillingAddressAction: () => {} + } +})); + +jest.mock('../../src/useCart', () => ({ + useCart: jest.fn() +})); + +jest.mock('@vue-storefront/core', () => ({ + useBillingFactory: (params) => () => params +})); + +describe('[commercetools-composables] useBilling', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('provides cart', async () => { + const { provide } = useBilling() as any; + const mockedCart = '12312312'; + (useCart as jest.Mock).mockImplementation(() => mockedCart); + + const toProvide = provide(); + + expect(toProvide).toMatchObject({ cart: mockedCart }); + expect(useCart).toHaveBeenCalled(); + }); + + it('loads billing address via request if cart is not present', async () => { + const { load } = useBilling() as any; + const loadedBillingAddress = 'loadedBillingAddress'; + const context = { + cart: { + cart: { + value: { + billingAddress: null + } + }, + load: jest.fn(() => { + context.cart.cart.value.billingAddress = loadedBillingAddress; + }) + } + }; + + const response = await load(context, {}); + + expect(response).toBe(loadedBillingAddress); + expect(context.cart.load).toHaveBeenCalled(); + }); + + it('loads billing address from cart if cart is present', async () => { + const { load } = useBilling() as any; + const loadedBillingAddress = 'loadedBillingAddress'; + const context = { + cart: { + cart: { + value: { + billingAddress: loadedBillingAddress + } + }, + load: jest.fn() + } + }; + + const response = await load(context, {}); + + expect(response).toBe(loadedBillingAddress); + expect(context.cart.load).not.toHaveBeenCalled(); + }); + + it('saves billing details, updates cart and returns billing details', async () => { + const { save } = useBilling() as any; + const newBillingAddress = 'newBillingAddress'; + const context = { + cart: { + cart: { + value: { + billingAddress: null + } + }, + setCart: jest.fn(address => { + context.cart.cart.value.billingAddress = address; + }) + }, + $ct: { + api: { + updateCart: jest.fn(() => ({ + data: { + cart: newBillingAddress + } + })) + } + } + }; + + const response = await save(context, { billingAddress: newBillingAddress }); + + expect(response).toBe(newBillingAddress); + expect(context.cart.setCart).toHaveBeenCalledWith(newBillingAddress); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useCart/currentCart.spec.ts b/packages/commercetools/composables/__tests__/useCart/currentCart.spec.ts new file mode 100644 index 0000000000..0d63be2b8c --- /dev/null +++ b/packages/commercetools/composables/__tests__/useCart/currentCart.spec.ts @@ -0,0 +1,44 @@ +import loadCurrentCart from '../../src/useCart/currentCart'; + +const cart = { id: 'cartid' }; +const cartResponse = { data: { cart } }; + +const context = { + $ct: { + api: { + getMe: jest.fn(() => ({ data: { me: { activeCart: cart } } })), + createCart: jest.fn(() => cartResponse) + } + } +} as any; + +describe('[commercetools-composables] useCart/currentCart', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('loads current cart', async () => { + + const response = await loadCurrentCart(context); + + expect(response).toEqual(cart); + expect(context.$ct.api.getMe).toBeCalled(); + expect(context.$ct.api.createCart).not.toBeCalled(); + }); + + it('creates cart when could not be loaded', async () => { + (context.$ct.api.getMe as any).mockReturnValue({ data: { me: { activeCart: null } } }); + + const response = await loadCurrentCart(context); + + expect(response).toEqual(cart); + expect(context.$ct.api.createCart).toBeCalled(); + }); + + it('creates new cart when there is no id of the current one', async () => { + const response = await loadCurrentCart(context); + + expect(context.$ct.api.createCart).toBeCalled(); + expect(response).toEqual(cart); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useCart/useCart.spec.ts b/packages/commercetools/composables/__tests__/useCart/useCart.spec.ts new file mode 100644 index 0000000000..7d5d0d3524 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useCart/useCart.spec.ts @@ -0,0 +1,147 @@ +import { useCart } from './../../src/useCart'; +import loadCurrentCart from './../../src/useCart/currentCart'; + +const MOCKED_CART = 'some cart'; +// @TODO: add more complete mocks to test cart + +const context = { + $ct: { + api: { + addToCart: jest.fn(() => ({ data: { cart: MOCKED_CART } })), + removeFromCart: jest.fn(() => ({ data: { cart: MOCKED_CART } })), + updateCartQuantity: jest.fn(() => ({ data: { cart: MOCKED_CART } })), + applyCartCoupon: jest.fn(() => ({ data: { cart: MOCKED_CART } })), + removeCartCoupon: jest.fn(() => ({ data: { cart: 'current cart' } })), + getSettings: jest.fn(() => ({ currentToken: 1 })), + isTokenUserSession: jest.fn(), + deleteCart: jest.fn(() => ({ data: { cart: MOCKED_CART } })) + } + } +}; + +jest.mock('./../../src/useCart/currentCart'); +jest.mock('@vue-storefront/core', () => ({ + useCartFactory: (params) => () => params +})); + +const customQuery = undefined; + +describe('[commercetools-composables] useCart', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('adds to cart', async () => { + const { addItem } = useCart() as any; + const currentCart = { id: 1, version: 1 }; + const response = await addItem(context, { currentCart, product: 'product1', quantity: 3 }); + + expect(response).toEqual(MOCKED_CART); + expect(context.$ct.api.addToCart).toBeCalledWith(currentCart, { + product: 'product1', + quantity: 3 + }, customQuery); + }); + + it('creates a new cart and add an item', async () => { + const { addItem } = useCart() as any; + (loadCurrentCart as any).mockReturnValue(MOCKED_CART); + const response = await addItem(context, { currentCart: null, product: 'product1', quantity: 3 }); + expect(loadCurrentCart).toBeCalled(); + + expect(response).toEqual(MOCKED_CART); + expect(context.$ct.api.addToCart).toBeCalledWith({ id: undefined, version: undefined }, { + product: 'product1', + quantity: 3 + }, customQuery); + }); + + it('removes from cart', async () => { + const { removeItem } = useCart() as any; + const currentCart = { id: 1, version: 1 }; + const response = await removeItem(context, { currentCart, product: 'product1' }); + + expect(response).toEqual(MOCKED_CART); + expect(context.$ct.api.removeFromCart).toBeCalledWith(currentCart, 'product1', customQuery); + }); + + it('updates quantity', async () => { + const { updateItemQty } = useCart() as any; + const currentCart = { id: 1, version: 1 }; + const response = await updateItemQty(context, { + currentCart, + product: { name: 'product1' }, + quantity: 5 + }); + + expect(response).toEqual(MOCKED_CART); + expect(context.$ct.api.updateCartQuantity).toBeCalledWith(currentCart, { name: 'product1', quantity: 5 }, customQuery); + }); + + it('clears cart', async () => { + const { clear } = useCart() as any; + const currentCart = { id: 1, version: 1 }; + const response = await clear(context, { currentCart }); + + expect(response).toEqual(MOCKED_CART); + }); + + it('applies coupon', async () => { + const { applyCoupon } = useCart() as any; + const currentCart = { id: 1, version: 1 }; + const response = await applyCoupon(context, { currentCart, coupon: 'X123' }); + + expect(response).toEqual({ updatedCart: MOCKED_CART }); + }); + + it('removes coupon', async () => { + const { removeCoupon } = useCart() as any; + const response = await removeCoupon(context, { + currentCart: { + discountCodes: [ + { + discountCode: { + id: 'asdasdas', + name: 'asdasdas', + code: 'XA12345' + } + } + ] + }, + couponCode: 'XA12345' + }); + + expect(response).toEqual({ updatedCart: 'current cart' }); + }); + + describe('isInCart', () => { + const { isInCart } = useCart() as any; + + it('returns false if product does not exists in cart', () => { + const currentCart: any = { + lineItems: [] + }; + + const product: any = { + _id: 123 + }; + + expect(isInCart(context, { currentCart, product })).toEqual(false); + }); + + it('returns true if product exists in cart', () => { + const currentCart: any = { + lineItems: [{ + productId: 123 + }] + }; + + const product: any = { + _id: 123 + }; + + expect(isInCart(context, { currentCart, product })).toEqual(true); + }); + }); + +}); diff --git a/packages/commercetools/composables/__tests__/useCategory/useCategory.spec.ts b/packages/commercetools/composables/__tests__/useCategory/useCategory.spec.ts new file mode 100644 index 0000000000..08e2bcd912 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useCategory/useCategory.spec.ts @@ -0,0 +1,40 @@ +import { useCategory } from './../../src/useCategory'; + +const categoriesResult = [ + { name: 'cat1', + id: 'bbb' }, + { name: 'cat2', + id: 'aaa' }, + { name: 'cat3', + id: 'fcd' } +]; + +jest.mock('@vue-storefront/core', () => ({ + useCategoryFactory: (params) => () => params +})); + +const context = { + $ct: { + api: { + getCategory: jest.fn(() => + Promise.resolve({ + data: { + categories: { + results: categoriesResult + } + } + })) + } + } +}; + +describe('[commercetools-composables] useCategory', () => { + it('loads categories', async () => { + const { categorySearch } = useCategory('test-category') as any; + + const response = await categorySearch(context, { catId: 'xxx1' }); + + expect(response).toEqual(categoriesResult); + expect(context.$ct.api.getCategory).toBeCalledWith({ catId: 'xxx1' }, undefined); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useFacet/useFacet.spec.ts b/packages/commercetools/composables/__tests__/useFacet/useFacet.spec.ts new file mode 100644 index 0000000000..509d400eb0 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useFacet/useFacet.spec.ts @@ -0,0 +1,69 @@ +import { useFacet } from '../../src/useFacet'; +import { enhanceProduct, getFiltersFromProductsAttributes } from './../../src/helpers/internals'; + +jest.mock('./../../src/helpers/internals', () => ({ + enhanceProduct: jest.fn((productResponse) => ({ ...productResponse, _variants: [] })), + getFiltersFromProductsAttributes: jest.fn(), + getChannelId: jest.fn() +})); + +jest.mock('@vue-storefront/commercetools-api', () => ({ + AttributeType: { + STRING: 1 + } +})); + +const context = { + $ct: { + config: { + store: '' + }, + api: { + getProduct: jest.fn(() => ({ + data: { + products: { + results: [{ id: 1, name: 'prod1' }] + } + } + })), + getCategory: jest.fn(() => ({ + data: { + categories: { + results: [{ id: 1, name: 'cat1' }] + } + } + })) + } + } +}; + +jest.mock('@vue-storefront/core', () => ({ + useFacetFactory: (factoryParams) => () => { + + return { + search: factoryParams.search + }; + } +})); + +describe('[commercetools-composables] useFacet', () => { + it('triggers faceting search', async () => { + const { search } = useFacet() as any; + + await search(context, { + input: { + itemsPerPage: [10, 20, 50], + categorySlug: 'cat-1', + filters: { + color: ['blue', 'green'], + size: ['s', 'm'] + } + } + } as any); + + expect(context.$ct.api.getCategory).toBeCalled(); + expect(context.$ct.api.getProduct).toBeCalled(); + expect(enhanceProduct).toBeCalled(); + expect(getFiltersFromProductsAttributes).toBeCalled(); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useForgotPassword/useForgotPassword.spec.ts b/packages/commercetools/composables/__tests__/useForgotPassword/useForgotPassword.spec.ts new file mode 100644 index 0000000000..00f6163558 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useForgotPassword/useForgotPassword.spec.ts @@ -0,0 +1,53 @@ +import { useForgotPassword } from '../../src/useForgotPassword'; + +jest.mock('@vue-storefront/core', () => ({ + useForgotPasswordFactory: (params) => () => params +})); + +const mockedStringValue = '1234'; + +const context = { + $ct: { + api: { + customerCreatePasswordResetToken: jest.fn(() => + Promise.resolve({ + data: { + customerCreatePasswordResetToken: { + value: mockedStringValue + } + } + })), + customerResetPassword: jest.fn(() => + Promise.resolve(Boolean({ + data: { + customerResetPassword: { + id: mockedStringValue + } + } + }))) + } + } +}; + +describe('[commercetools-composables] useForgotPassword', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('generates reset password token', async () => { + const { resetPassword } = useForgotPassword() as any; + + const response = await resetPassword(context, { email: 'xxx1' }); + + expect(response).toEqual({ resetPasswordResult: { data: { customerCreatePasswordResetToken: { value: mockedStringValue}}}}); + expect(context.$ct.api.customerCreatePasswordResetToken).toBeCalledWith('xxx1', undefined); + }); + it('sets new password after reset', async () => { + const { setNewPassword } = useForgotPassword() as any; + + const response = await setNewPassword(context, { tokenValue: mockedStringValue, newPassword: mockedStringValue }); + + expect(response).toEqual({ setNewPasswordResult: true }); + expect(context.$ct.api.customerResetPassword).toBeCalledWith(mockedStringValue, mockedStringValue, undefined); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useProduct/useProduct.spec.ts b/packages/commercetools/composables/__tests__/useProduct/useProduct.spec.ts new file mode 100644 index 0000000000..77d65249ce --- /dev/null +++ b/packages/commercetools/composables/__tests__/useProduct/useProduct.spec.ts @@ -0,0 +1,57 @@ +import { useProduct } from '../../src/useProduct'; +import enhanceProducts from './../../src/helpers/internals/enhanceProduct'; + +const product = (name, slug, id) => ({ + masterData: { + current: { + name, + slug, + masterVariant: { + id + }, + categoriesRef: [{ id: 'aaa' }], + allVariants: [{ id: '123' }, { id: '456' }, { id: '789' }] + } + } +}); + +const productResponse = { + data: { + products: { + total: 54, + results: [ + product('prod1', 'prod-1', 'sde213') + ] + }, + _variants: [product('prod1', 'prod-1', 'xxx1'), product('prod2', 'prod-2', 'xxx2')] + } +}; + +jest.mock('./../../src/helpers/internals/enhanceProduct', () => jest.fn((args) => args)); + +jest.mock('@vue-storefront/core', () => ({ + useProductFactory: (params) => () => params +})); + +const context = { + $ct: { + config: { + store: '' + }, + api: { + getProduct: jest.fn(() => Promise.resolve(productResponse)) + } + } +}; + +describe('[commercetools-composables] useProduct', () => { + it('loads product variants', async () => { + const { productsSearch } = useProduct('test-product') as any; + + const response = await productsSearch(context, { id: 'product-id' }); + + expect(response).toEqual([product('prod1', 'prod-1', 'xxx1'), product('prod2', 'prod-2', 'xxx2')]); + expect(context.$ct.api.getProduct).toBeCalledWith({ id: 'product-id' }, undefined); + expect(enhanceProducts).toBeCalledWith(productResponse, context); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useShipping/useShipping.spec.ts b/packages/commercetools/composables/__tests__/useShipping/useShipping.spec.ts new file mode 100644 index 0000000000..4b376df482 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useShipping/useShipping.spec.ts @@ -0,0 +1,108 @@ +import { useShipping } from '../../src/useShipping'; +import { useCart } from '../../src/useCart'; + +jest.mock('@vue-storefront/commercetools-api', () => ({ + cartActions: { + setShippingMethodAction: () => {}, + setShippingAddressAction: () => {} + } +})); + +jest.mock('../../src/useCart', () => ({ + useCart: jest.fn() +})); + +jest.mock('@vue-storefront/core', () => ({ + useShippingFactory: (params) => () => params +})); + +describe('[commercetools-composables] useShipping', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('provides cart', async () => { + const { provide } = useShipping() as any; + const mockedCart = '12312312'; + (useCart as jest.Mock).mockImplementation(() => mockedCart); + + const toProvide = provide(); + + expect(toProvide).toMatchObject({ cart: mockedCart }); + expect(useCart).toHaveBeenCalled(); + }); + + it('loads shipping address via request if cart is not present', async () => { + const { load } = useShipping() as any; + const loadedShippingAddress = 'loadedShippingAddress'; + const context = { + cart: { + cart: { + value: { + shippingAddress: null + } + }, + load: jest.fn(() => { + context.cart.cart.value.shippingAddress = loadedShippingAddress; + }) + } + }; + + const response = await load(context, {}); + + expect(response).toBe(loadedShippingAddress); + expect(context.cart.load).toHaveBeenCalled(); + }); + + it('loads shipping address from cart if cart is present', async () => { + const { load } = useShipping() as any; + const loadedShippingAddress = 'loadedShippingAddress'; + const context = { + cart: { + cart: { + value: { + shippingAddress: loadedShippingAddress + } + }, + load: jest.fn() + } + }; + + const response = await load(context, {}); + + expect(response).toBe(loadedShippingAddress); + expect(context.cart.load).not.toHaveBeenCalled(); + }); + + it('saves shipping details, updates cart and returns shipping details', async () => { + const { save } = useShipping() as any; + const newShippingAddress = 'newShippingAddress'; + const context = { + cart: { + cart: { + value: { + shippingAddress: null + } + }, + setCart: jest.fn(address => { + context.cart.cart.value.shippingAddress = address; + }) + }, + $ct: { + api: { + updateCart: jest.fn(() => ({ + data: { + cart: newShippingAddress + } + })) + } + } + }; + + const response = await save(context, { shippingDetails: newShippingAddress }); + + expect(response).toBe(newShippingAddress); + expect(context.cart.setCart).toHaveBeenCalledWith(newShippingAddress); + }); +}); + diff --git a/packages/commercetools/composables/__tests__/useStore/factoryParams.spec.ts b/packages/commercetools/composables/__tests__/useStore/factoryParams.spec.ts new file mode 100644 index 0000000000..ae0f47c5a7 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useStore/factoryParams.spec.ts @@ -0,0 +1,69 @@ +import { useStoreFactoryParams } from '../../src/useStore/'; +import { Context, UseStoreFactoryChangeParamArguments } from '@vue-storefront/core'; + +describe('[commercetools-composables] useStore factoryParams', () => { + it('loads stores data', async () => { + + const storesData = { + stores: 'stores data' + }; + + const api = { + getStores: jest.fn().mockResolvedValue(storesData) + }; + + const config = { + store: 'default store' + }; + + const $ct = { + api, + config + }; + + const context = { + $ct + }; + + const expected = { + ...storesData, + _selectedStore: config.store + }; + + expect(useStoreFactoryParams.load((context as unknown) as Context, {} as any)).resolves.toStrictEqual(expected); + expect(api.getStores).toHaveBeenCalledWith({ customQuery: undefined }); + }); + + it('changes selected store and reloads page', async () => { + const STORES_ID = 'stores id'; + + Object.defineProperty(window, 'location', { + writable: true, + value: { reload: jest.fn() } + }); + + const storeService = { + changeCurrentStore: jest.fn() + }; + + const config = { + storeService + }; + + const $ct = { + config + }; + + const context = { + $ct + }; + + const params = { + store: { key: STORES_ID } + }; + + expect(await useStoreFactoryParams.change((context as unknown) as Context, (params as unknown) as UseStoreFactoryChangeParamArguments)).toBe(null); + expect(storeService.changeCurrentStore).toHaveBeenCalledWith(STORES_ID); + expect(window.location.reload).toHaveBeenCalled(); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useUser/authenticate.spec.ts b/packages/commercetools/composables/__tests__/useUser/authenticate.spec.ts new file mode 100644 index 0000000000..2f8c880946 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useUser/authenticate.spec.ts @@ -0,0 +1,40 @@ +import { authenticate } from '../../src/useUser/authenticate'; + +const consoleErrorSpy = jest.spyOn(console, 'error'); +const customer = { email: 'test@test.com', password: '123456' }; + +class GraphQLMockError extends Error { + graphQLErrors: any; + + constructor(message) { + super(); + this.graphQLErrors = [{ message }]; + } +} + +describe('[commercetools-composables] useUser/authenticate', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns logged user data', async() => { + const callback = async userData => ({ data: { user: { customer: userData }}}); + + expect(await authenticate(customer, callback)).toEqual({ customer }); + }); + + describe('error is called by a console error', () => { + it('with first message from graphQL errors array', async () => { + consoleErrorSpy.mockImplementationOnce(() => {}); + const callback = jest.fn().mockRejectedValueOnce(new GraphQLMockError('GraphQL message')); + await expect(authenticate(customer, callback)).rejects.toThrow('GraphQL message'); + }); + + it('with message from exception', async () => { + consoleErrorSpy.mockImplementationOnce(() => {}); + const callback = jest.fn().mockRejectedValue(new Error('There is an error')); + await expect(authenticate(customer, callback)).rejects.toThrow('There is an error'); + }); + }); +}); + diff --git a/packages/commercetools/composables/__tests__/useUser/factoryParams.spec.ts b/packages/commercetools/composables/__tests__/useUser/factoryParams.spec.ts new file mode 100644 index 0000000000..e0d4bb5415 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useUser/factoryParams.spec.ts @@ -0,0 +1,111 @@ +import { useUserFactoryParams } from '../../src/useUser/factoryParams'; +import { authenticate } from '../../src/useUser/authenticate'; +import { useCart } from '../../src/useCart'; + +jest.mock('../../src/useCart', () => ({ + useCart: jest.fn() +})); + +jest.mock('../../src/useUser/authenticate', () => ({ + authenticate: jest.fn() +})); + +const customer: any = { + email: 'test@test.pl', + password: '123456', + firstName: 'Don', + lastName: 'Jon', + version: '1' +}; + +const refreshCartMock = jest.fn(() => {}); + +const context = { + $ct: { + api: { + getMe: jest.fn(), + customerSignOut: jest.fn(), + customerChangeMyPassword: jest.fn(), + createCart: jest.fn(), + customerUpdateMe: jest.fn(), + getSettings: jest.fn(() => ({ currentToken: 1 })), + isGuest: jest.fn() + }, + config: { + auth: { + onTokenRead: () => true, + onTokenRemove: jest.fn() + } + } + }, + setCart: jest.fn() +} as any; + +describe('[commercetools-composables] factoryParams', () => { + it('load return customer data', async () => { + (context.$ct.api.getMe as jest.Mock).mockReturnValueOnce({ data: { me: { customer } }}); + expect(await useUserFactoryParams.load(context as any, {} as any)).toEqual(customer); + expect(context.$ct.api.getMe).toHaveBeenNthCalledWith(1, {customer: true}, undefined); + + (context.$ct.api.getMe as jest.Mock).mockReturnValueOnce({ data: { me: { customer: null } }}); + expect(await useUserFactoryParams.load(context, {customQuery: {key: 'customQuery'}})).toEqual(null); + expect(context.$ct.api.getMe).toHaveBeenNthCalledWith(2, {customer: true}, {key: 'customQuery'}); + }); + + it('does not loading the user without user session', async () => { + (context.$ct.api.isGuest as any).mockReturnValue(true); + expect(await useUserFactoryParams.load(context as any, {} as any)).toEqual(null); + }); + + it('logOut method calls API log out method', async () => { + (context.$ct.api.createCart as jest.Mock).mockReturnValueOnce({ data: { cart: {} }}); + (useCart as jest.Mock).mockReturnValueOnce({refreshCart: refreshCartMock}); + await useUserFactoryParams.logOut(context as any, {currentUser: {key: 'user'} as any}); + expect(context.$ct.api.customerSignOut).toHaveBeenCalled(); + }); + + it('updateUser return updated user', async () => { + const user = {currentUser: 'Jon', updatedUserData: 'Bob'} as any; + (context.$ct.api.customerUpdateMe as jest.Mock).mockReturnValueOnce({ user }); + expect(await useUserFactoryParams.updateUser(context as any, user)).toEqual(user); + }); + + it('updates the user and loads when it is not available', async () => { + const user = {currentUser: null, updatedUserData: 'Bob'} as any; + (context.$ct.api.getMe as jest.Mock).mockReturnValueOnce({ data: { me: user } }); + + (context.$ct.api.customerUpdateMe as jest.Mock).mockReturnValueOnce({ user }); + expect(await useUserFactoryParams.updateUser(context as any, user)).toEqual(user); + }); + + it('register method return a new customer', async () => { + (authenticate as jest.Mock).mockReturnValueOnce({ customer }); + expect(await useUserFactoryParams.register(context as any, customer)).toEqual(customer); + }); + + it('logIn method return a logged in customer', async () => { + (useCart as jest.Mock).mockReturnValueOnce({refreshCart: refreshCartMock}); + (authenticate as jest.Mock).mockReturnValueOnce({ customer }); + expect(await useUserFactoryParams.logIn(context as any, customer)).toEqual(customer); + }); + + describe('changePassword', () => { + it('register method return a new customer', async () => { + (authenticate as jest.Mock).mockReturnValueOnce({ customer }); + expect(await useUserFactoryParams.register(context as any, customer)).toEqual(customer); + }); + + it('succeed returning logged user', async () => { + const cart = {}; + const changePasswordParams: any = { currentUser: customer, currentPassword: '', newPassword: '' }; + + (context.$ct.api.createCart as jest.Mock).mockReturnValueOnce({ data: { cart }}); + (useCart as jest.Mock).mockReturnValueOnce({ refreshCart: refreshCartMock }); + (context.$ct.api.customerChangeMyPassword as jest.Mock).mockReturnValueOnce({ data: { user: customer }}); + (authenticate as jest.Mock).mockReturnValueOnce({ customer, cart }); + + expect(await useUserFactoryParams.changePassword(context as any, changePasswordParams)).toEqual(customer); + expect(context.$ct.api.customerSignOut).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useUser/index.spec.ts b/packages/commercetools/composables/__tests__/useUser/index.spec.ts new file mode 100644 index 0000000000..8180529a18 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useUser/index.spec.ts @@ -0,0 +1,18 @@ +jest.mock('@vue-storefront/core', () => ({ + useUserFactory: jest.fn(() => () => ({ user: 'api'})) +})); + +jest.mock('../../src/useUser/factoryParams', () => ({ + params: {} +})); + +import { useUserFactory } from '@vue-storefront/core'; +import { useUserFactoryParams } from '../../src/useUser/factoryParams'; +import { useUser } from '../../src/useUser'; + +describe('[commercetools-composables] useUser', () => { + it('returns useUserFactory functions', () => { + expect(useUserFactory).toHaveBeenCalledWith(useUserFactoryParams); + expect(useUser()).toEqual({user: 'api'}); + }); +}); diff --git a/packages/commercetools/composables/__tests__/useUserOrders/useUserOrder.spec.ts b/packages/commercetools/composables/__tests__/useUserOrders/useUserOrder.spec.ts new file mode 100644 index 0000000000..9d0d44e8f9 --- /dev/null +++ b/packages/commercetools/composables/__tests__/useUserOrders/useUserOrder.spec.ts @@ -0,0 +1,69 @@ +import { useUserOrder } from '../../src/useUserOrder'; + +const mockedResults = ['order1', 'order2', 'order3']; +const mockedTotal = 3; +const mockedEmptyResponse = { count: 0, offset: 0, results: [], total: 0}; + +jest.mock('@vue-storefront/commercetools-api', () => ({ + getOrders: jest.fn(async () => ({ + data: { + me: { + orders: { results: mockedResults, total: mockedTotal } + } + } + })) +})); + +jest.mock('@vue-storefront/core', () => ({ + useUserOrderFactory: ({ searchOrders }) => () => ({ search: searchOrders }) +})); + +const context = { + $ct: { + api: { + getOrders: jest.fn(async () => ({ + data: { + me: { + orders: { results: mockedResults, total: mockedTotal } + } + } + })) + } + } +}; + +describe('[commercetools-composables] useUserOrder', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('loads user orders with criteria', async () => { + const { search } = useUserOrder() as any; + + const response = await search(context, { param: 'param1' }); + + expect(response).toEqual({ results: mockedResults, total: mockedTotal }); + expect(context.$ct.api.getOrders).toBeCalledWith({ param: 'param1' }, undefined); + }); + + it('loads user all orders', async () => { + const { search } = useUserOrder() as any; + + const response = await search(context); + + expect(response).toEqual({ results: mockedResults, total: mockedTotal }); + expect(context.$ct.api.getOrders).toBeCalled(); + }); + + it('loads user orders with empty response', async () => { + (context.$ct.api.getOrders as jest.Mock).mockReturnValue({ data: null }); + + const { search } = useUserOrder() as any; + + const response = await search(context, { param: 'param1' }); + + expect(response).toEqual(mockedEmptyResponse); + expect(context.$ct.api.getOrders).toBeCalledWith({ param: 'param1' }, undefined); + }); +}); + diff --git a/packages/commercetools/composables/api-extractor.json b/packages/commercetools/composables/api-extractor.json new file mode 100644 index 0000000000..746ad8ecec --- /dev/null +++ b/packages/commercetools/composables/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../api-extractor.base.json", + "mainEntryPointFilePath": "./lib/index.d.ts", + "dtsRollup": { + "untrimmedFilePath": "./lib/.d.ts" + }, + "docModel": { + "apiJsonFilePath": "/core/docs/commercetools/api-reference/.api.json" + } +} diff --git a/packages/commercetools/composables/jest.config.js b/packages/commercetools/composables/jest.config.js new file mode 100644 index 0000000000..aa3a7af6f5 --- /dev/null +++ b/packages/commercetools/composables/jest.config.js @@ -0,0 +1,15 @@ +const baseConfig = require('./../../jest.base.config'); + +module.exports = { + ...baseConfig, + rootDir: __dirname, + setupFilesAfterEnv: ['./__tests__/setup.ts'], + coveragePathIgnorePatterns: [ + '/node_modules/', + '/src/context.d.ts', + + // Ignore mocked composables + 'UserBilling', + 'UserShipping' + ] +}; diff --git a/packages/commercetools/composables/nuxt/README.MD b/packages/commercetools/composables/nuxt/README.MD new file mode 100644 index 0000000000..a6da09e16b --- /dev/null +++ b/packages/commercetools/composables/nuxt/README.MD @@ -0,0 +1,116 @@ +# Vue Storefront Next x Commerctools Module +## How to install +1. Open `nuxt.config.js` +2. At the bottom of `buildModules` add: +```js +['@vue-storefront/commercetools/nuxt', { + api: { + uri: 'https://api.commercetools.com//graphql', + authHost: 'https://auth.sphere.io', + projectKey: 'vsf-ct-dev', + clientId: '', + clientSecret: '', + scopes: [ + 'create_anonymous_token:vsf-ct-dev', + 'manage_my_orders:vsf-ct-dev', + 'manage_my_profile:vsf-ct-dev', + 'manage_my_shopping_lists:vsf-ct-dev', + 'manage_my_payments:vsf-ct-dev', + 'view_products:vsf-ct-dev', + 'view_published_products:vsf-ct-dev' + ] + }, + locale: 'en', + currency: 'USD', + country: 'US', + acceptLanguage: ['en', 'de'], + countries: [ + { name: 'US', + label: 'United States' }, + { name: 'AT', + label: 'Austria' }, + { name: 'DE', + label: 'Germany' }, + { name: 'NL', + label: 'Netherlands' } + ], + currencies: [ + { name: 'EUR', + label: 'Euro' }, + { name: 'USD', + label: 'Dollar' } + ], + locales: [ + { name: 'en', + label: 'English' }, + { name: 'de', + label: 'German' } + ] + }] +}] +``` + +## Generate token middleware +Commercetools nuxt module injects [middleware](https://github.com/DivanteLtd/vue-storefront/blob/next/packages/commercetools/composables/nuxt/token-middleware.js) that is responsible for createing/refreshing token if needed - when user refreshes page. In some rare case, you might want to disable [this middleware](https://github.com/DivanteLtd/vue-storefront/blob/next/packages/commercetools/composables/nuxt/token-middleware.js). If so, add `disableGenerateTokenMiddleware` to the `@vue-storefront/commercetools/nuxt`'s options module in `buildModules`: +```js +['@vue-storefront/commercetools/nuxt', { + // ... + disableGenerateTokenMiddleware: true + // ... +}] +``` + +## Example config +```js +export const config = { + api: { + uri: 'https://mysite.com/vsf-ct-dev/graphql', + authHost: 'https://auth.com', + projectKey: 'vsf-ct-dev', + clientId: 'yourClientId', + clientSecret: 'yourClientSecret', + scopes: [ + 'create_anonymous_token:vsf-ct-dev', + 'manage_my_orders:vsf-ct-dev', + 'manage_my_profile:vsf-ct-dev', + 'manage_my_shopping_lists:vsf-ct-dev', + 'manage_my_payments:vsf-ct-dev', + 'view_products:vsf-ct-dev', + 'view_published_products:vsf-ct-dev' + ] + }, + locale: 'en', + acceptLanguage: ['en', 'de'], + currency: 'USD', + country: 'US', + store: 'luxury-brand', + countries: [ + { name: 'US', + label: 'United States' }, + { name: 'AT', + label: 'Austria' }, + { name: 'DE', + label: 'Germany' }, + { name: 'NL', + label: 'Netherlands' } + ], + currencies: [ + { name: 'EUR', + label: 'Euro' }, + { name: 'USD', + label: 'Dollar' } + ], + locales: [ + { name: 'en', + label: 'English' }, + { name: 'de', + label: 'German' } + ], + cookies: { + currencyCookieName: 'vsf-currency', + countryCookieName: 'vsf-country', + localeCookieName: 'vsf-locale', + storeCookieName: 'vsf-store' + } +}; +``` \ No newline at end of file diff --git a/packages/commercetools/composables/nuxt/defaultConfig.js b/packages/commercetools/composables/nuxt/defaultConfig.js new file mode 100644 index 0000000000..b94f43b967 --- /dev/null +++ b/packages/commercetools/composables/nuxt/defaultConfig.js @@ -0,0 +1,11 @@ +import { VSF_LOCALE_COOKIE, VSF_CURRENCY_COOKIE, VSF_COUNTRY_COOKIE, VSF_STORE_COOKIE } from '@vue-storefront/core'; + +export default { + disableGenerateTokenMiddleware: false, + cookies: { + currencyCookieName: VSF_CURRENCY_COOKIE, + countryCookieName: VSF_COUNTRY_COOKIE, + localeCookieName: VSF_LOCALE_COOKIE, + storeCookieName: VSF_STORE_COOKIE + } +}; diff --git a/packages/commercetools/composables/nuxt/helpers/index.js b/packages/commercetools/composables/nuxt/helpers/index.js new file mode 100644 index 0000000000..476bdb5b44 --- /dev/null +++ b/packages/commercetools/composables/nuxt/helpers/index.js @@ -0,0 +1,35 @@ +import defaultConfig from '@vue-storefront/commercetools/nuxt/defaultConfig'; + +const getLocaleSettings = (moduleOptions, app) => { + + const cookies = { + ...defaultConfig.cookies, + ...moduleOptions.cookies + }; + + const localeSettings = { + locale: app.$cookies.get(cookies.localeCookieName), + country: app.$cookies.get(cookies.countryCookieName), + currency: app.$cookies.get(cookies.currencyCookieName), + store: app.$cookies.get(cookies.storeCookieName) + }; + + return { + locale: app.i18n.locale || (localeSettings.locale || moduleOptions.locale || defaultConfig.locale), + country: localeSettings.country || moduleOptions.country || defaultConfig.country, + currency: localeSettings.currency || moduleOptions.currency || defaultConfig.currency, + store: localeSettings.store || moduleOptions.store || defaultConfig.store, + cookies + }; +}; + +export const mapConfigToSetupObject = ({ moduleOptions, app, additionalProperties = {} }) => { + return { + ...defaultConfig, + ...moduleOptions, + ...additionalProperties, + ...getLocaleSettings(moduleOptions, app) + }; +}; + +export const CT_TOKEN_COOKIE_NAME = 'vsf-commercetools-token'; diff --git a/packages/commercetools/composables/nuxt/index.js b/packages/commercetools/composables/nuxt/index.js new file mode 100644 index 0000000000..c60622c40a --- /dev/null +++ b/packages/commercetools/composables/nuxt/index.js @@ -0,0 +1,39 @@ +import path from 'path'; + +const mapI18nSettings = (i18n) => ({ + locale: i18n.defaultLocale, + currency: i18n.currency, + country: i18n.country, + acceptLanguage: i18n.locales.map(({ code }) => code), + countries: i18n.countries, + currencies: i18n.currencies, + locales: i18n.locales.map(({ label, code }) => ({ name: code, label })) +}); + +const isNuxtI18nUsed = (moduleOptions) => moduleOptions.i18n && moduleOptions.i18n.useNuxtI18nConfig; + +const getMissingFields = (options) => + ['locale', 'currency', 'country', 'acceptLanguage', 'countries', 'currencies', 'locales'] + .filter(o => options[o] === undefined); + +export default function (moduleOptions) { + const options = isNuxtI18nUsed(moduleOptions) + ? { ...moduleOptions, ...mapI18nSettings(this.options.i18n) } + : moduleOptions; + + const missingFields = getMissingFields(options); + + if (missingFields.length > 0) { + throw new Error(`Please provide missing i18n fields: (${missingFields.join(', ')})`); + } + + this.extendBuild(config => { + config.resolve.alias['@vue-storefront/commercetools-api$'] = require.resolve('@vue-storefront/commercetools-api'); + }); + + this.addPlugin({ + src: path.resolve(__dirname, './plugin.js'), + options + }); + +} diff --git a/packages/commercetools/composables/nuxt/plugin.js b/packages/commercetools/composables/nuxt/plugin.js new file mode 100644 index 0000000000..2e7037d09a --- /dev/null +++ b/packages/commercetools/composables/nuxt/plugin.js @@ -0,0 +1,54 @@ +import { mapConfigToSetupObject, CT_TOKEN_COOKIE_NAME } from '@vue-storefront/commercetools/nuxt/helpers' +import { integrationPlugin } from '@vue-storefront/core' + +const moduleOptions = <%= serialize(options) %>; + +export default integrationPlugin(({ app, integration }) => { + const onTokenChange = (newToken) => { + try { + const currentToken = app.$cookies.get(CT_TOKEN_COOKIE_NAME); + + if (!currentToken || currentToken.access_token !== newToken.access_token) { + app.$cookies.set(CT_TOKEN_COOKIE_NAME, newToken); + } + } catch (e) { + // Cookies on is set after request has sent. + } + }; + + const onTokenRemove = () => { + app.$cookies.remove(CT_TOKEN_COOKIE_NAME); + } + + const onTokenRead = () => { + return app.$cookies.get(CT_TOKEN_COOKIE_NAME); + }; + + /** + * changeCurrentStore + * @param {string} id + * @returns {void} + */ + const changeCurrentStore = (id) => { + app.$cookies.set( + app.$vsf.$ct.config.cookies.storeCookieName, id + ); + } + + const settings = mapConfigToSetupObject({ + moduleOptions, + app, + additionalProperties: { + auth: { + onTokenChange, + onTokenRead, + onTokenRemove + }, + storeService: { + changeCurrentStore + } + } + }) + + integration.configure('ct', settings) +}); diff --git a/packages/commercetools/composables/package.json b/packages/commercetools/composables/package.json new file mode 100644 index 0000000000..a47e10f810 --- /dev/null +++ b/packages/commercetools/composables/package.json @@ -0,0 +1,30 @@ +{ + "name": "@vue-storefront/commercetools", + "version": "1.3.1", + "sideEffects": false, + "main": "lib/index.cjs.js", + "module": "lib/index.es.js", + "tsModule": "src/index.ts", + "types": "lib/index.d.ts", + "scripts": { + "build": "rimraf lib && rollup -c", + "dev": "rollup -c -w", + "test": "jest", + "prepublish": "yarn build" + }, + "dependencies": { + "@vue-storefront/commercetools-api": "~1.3.1", + "@vue-storefront/core": "~2.4.1", + "js-cookie": "^2.2.1", + "vue": "^2.6.x" + }, + "devDependencies": { + "@vue/test-utils": "^1.0.0-beta.30", + "jsdom": "^16.6.0", + "jsdom-global": "^3.0.2", + "vue-template-compiler": "^2.6.x" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/commercetools/composables/rollup.config.js b/packages/commercetools/composables/rollup.config.js new file mode 100644 index 0000000000..5738726918 --- /dev/null +++ b/packages/commercetools/composables/rollup.config.js @@ -0,0 +1,4 @@ +import pkg from './package.json'; +import { generateBaseConfig } from '../../rollup.base.config'; + +export default generateBaseConfig(pkg, true); diff --git a/packages/commercetools/composables/src/context.d.ts b/packages/commercetools/composables/src/context.d.ts new file mode 100644 index 0000000000..758973ea33 --- /dev/null +++ b/packages/commercetools/composables/src/context.d.ts @@ -0,0 +1,8 @@ +import { IntegrationContext } from '@vue-storefront/core'; +import { ClientInstance, CommercetoolsMethods, Config } from '@vue-storefront/commercetools-api'; + +declare module '@vue-storefront/core' { + export interface Context { + $ct: IntegrationContext; + } +} diff --git a/packages/commercetools/composables/src/getters/_utils.ts b/packages/commercetools/composables/src/getters/_utils.ts new file mode 100644 index 0000000000..2bfa93003d --- /dev/null +++ b/packages/commercetools/composables/src/getters/_utils.ts @@ -0,0 +1,105 @@ +import { AgnosticAttribute, AgnosticPrice } from '@vue-storefront/core'; +import { ProductVariant, ProductPrice, DiscountedProductPriceValue, LineItem } from './../types/GraphQL'; +import { DiscountedLineItemPrice } from '../types/GraphQL'; +import { getCartItemQty } from './cartGetters'; + +export const getAttributeValue = (attribute) => { + + /** + * List of attribute types: https://docs.commercetools.com/api/projects/productTypes#attributetype + */ + switch (attribute.attributeDefinition.type.name) { + case 'text': + case 'ltext': + case 'boolean': + case 'number': + case 'date': + case 'time': + case 'datetime': + case 'money': + case 'set': + return attribute.value; + + case 'lenum': + case 'enum': + return attribute.value.key; + + case 'reference': + return { typeId: attribute.value.typeId, id: attribute.value.id }; + + default: + return null; + } +}; + +export const formatAttributeList = (attributes: Array): AgnosticAttribute[] => + attributes.map((attr) => { + const attrValue = getAttributeValue(attr); + return { + name: attr.name, + value: attrValue, + label: attr._translated + }; + }); + +export const getVariantByAttributes = (products: ProductVariant[] | Readonly, attributes: any): ProductVariant => { + if (!products || products.length === 0) { + return null; + } + + const configurationKeys = Object.keys(attributes); + + return products.find((product) => { + const currentAttributes = formatAttributeList(product.attributesRaw); + + return configurationKeys.every((attrName) => + currentAttributes.find(({ name, value }) => attrName === name && attributes[attrName] === value) + ); + }); +}; + +const getPrice = (price: ProductPrice | DiscountedProductPriceValue | DiscountedLineItemPrice) => { + return price ? price.value.centAmount / 100 : null; +}; + +const getDiscount = (product: ProductVariant | LineItem) => product.price?.discounted; + +const getSpecialPrice = (product: ProductVariant | LineItem) => { + const discount = getDiscount(product); + + if (isLineItem(product)) { + const { discountedPricePerQuantity } = product; + const discountsLength = discountedPricePerQuantity.length; + + if (discountsLength > 0) { + return getPrice(discountedPricePerQuantity[discountsLength - 1].discountedPrice); + } + } + + if (discount?.discount.isActive) { + return getPrice(discount); + } + + return null; +}; + +export const createPrice = (product: ProductVariant | LineItem): AgnosticPrice => { + if (!product) { + return { regular: null, special: null }; + } + + const quantity = isLineItem(product) ? getCartItemQty(product) : 1; + const regularPriceSingleItem = getPrice(product.price); + const regularPrice = regularPriceSingleItem ? regularPriceSingleItem * quantity : regularPriceSingleItem; + const specialPriceSingleItem = getSpecialPrice(product); + const specialPrice = specialPriceSingleItem ? specialPriceSingleItem * quantity : specialPriceSingleItem; + + return { + regular: regularPrice, + special: specialPrice + }; +}; + +function isLineItem (product: ProductVariant | LineItem): product is LineItem { + return product.__typename === 'LineItem'; +} diff --git a/packages/commercetools/composables/src/getters/cartGetters.ts b/packages/commercetools/composables/src/getters/cartGetters.ts new file mode 100644 index 0000000000..3a9b3d5c81 --- /dev/null +++ b/packages/commercetools/composables/src/getters/cartGetters.ts @@ -0,0 +1,100 @@ +import { CartGetters, AgnosticCoupon, AgnosticPrice, AgnosticTotals, AgnosticDiscount } from '@vue-storefront/core'; +import { Cart, LineItem } from './../types/GraphQL'; +import { getProductAttributes } from './productGetters'; +import { createPrice } from './_utils'; +import { getCouponsFromCart } from '../helpers/internals'; + +export const getCartItems = (cart: Cart): LineItem[] => { + if (!cart) { + return []; + } + + return cart.lineItems; +}; + +export const getCartItemName = (product: LineItem): string => product?.name || ''; + +export const getCartItemImage = (product: LineItem): string => product?.variant?.images[0]?.url || ''; + +export const getCartItemPrice = (product: LineItem): AgnosticPrice => createPrice(product); + +export const getCartItemQty = (product: LineItem): number => product?.quantity || 0; + +export const getCartItemAttributes = (product: LineItem, filterByAttributeName?: Array) => + getProductAttributes(product.variant, filterByAttributeName); + +export const getCartItemSku = (product: LineItem): string => product?.variant?.sku || ''; + +const getCartSubtotalPrice = (cart: Cart, selectSpecialPrices = false): number => { + return getCartItems(cart).reduce((total, cartItem) => { + const { special, regular } = getCartItemPrice(cartItem); + const itemPrice = (selectSpecialPrices && special) || regular; + + return total + itemPrice; + }, 0); +}; + +export const getCartTotals = (cart: Cart): AgnosticTotals => { + if (!cart) { + return { + total: 0, + subtotal: 0, + special: 0 + }; + } + + return { + total: cart.totalPrice.centAmount / 100, + subtotal: getCartSubtotalPrice(cart), + special: getCartSubtotalPrice(cart, true) + }; +}; + +export const getCartShippingPrice = (cart: Cart): number => { + const total = cart?.totalPrice?.centAmount; + const shippingInfo = cart?.shippingInfo; + const centAmount = shippingInfo?.shippingMethod?.zoneRates[0].shippingRates[0].freeAbove?.centAmount; + + if (!shippingInfo || !total || (centAmount && total >= centAmount)) { + return 0; + } + + return shippingInfo.price.centAmount / 100; +}; + +export const getCartTotalItems = (cart: Cart): number => { + if (!cart) { + return 0; + } + + return cart.lineItems.reduce((previous, current) => previous + current.quantity, 0); +}; + +export const getFormattedPrice = (price: number) => price as any as string; + +export const getCoupons = (cart: Cart): AgnosticCoupon[] => { + return getCouponsFromCart(cart); +}; + +// eslint-disable-next-line +export const getDiscounts = (cart: Cart): AgnosticDiscount[] => { + return []; +}; + +const cartGetters: CartGetters = { + getTotals: getCartTotals, + getShippingPrice: getCartShippingPrice, + getItems: getCartItems, + getItemName: getCartItemName, + getItemImage: getCartItemImage, + getItemPrice: getCartItemPrice, + getItemQty: getCartItemQty, + getItemAttributes: getCartItemAttributes, + getItemSku: getCartItemSku, + getTotalItems: getCartTotalItems, + getFormattedPrice, + getCoupons, + getDiscounts +}; + +export default cartGetters; diff --git a/packages/commercetools/composables/src/getters/categoryGetters.ts b/packages/commercetools/composables/src/getters/categoryGetters.ts new file mode 100644 index 0000000000..dad423c138 --- /dev/null +++ b/packages/commercetools/composables/src/getters/categoryGetters.ts @@ -0,0 +1,25 @@ +import { CategoryGetters, AgnosticCategoryTree } from '@vue-storefront/core'; +import { Category } from './../types/GraphQL'; + +export const getCategoryTree = (category: Category): AgnosticCategoryTree | null => { + const getRoot = (category: Category): Category => (category.parent ? getRoot(category.parent) : category); + const buildTree = (rootCategory: Category) => ({ + label: rootCategory.name, + slug: rootCategory.slug, + id: rootCategory.id, + isCurrent: rootCategory.id === category.id, + items: rootCategory.children.map(buildTree) + }); + + if (!category) { + return null; + } + + return buildTree(getRoot(category)); +}; + +const categoryGetters: CategoryGetters = { + getTree: getCategoryTree +}; + +export default categoryGetters; diff --git a/packages/commercetools/composables/src/getters/facetGetters.ts b/packages/commercetools/composables/src/getters/facetGetters.ts new file mode 100644 index 0000000000..80d2cc77bf --- /dev/null +++ b/packages/commercetools/composables/src/getters/facetGetters.ts @@ -0,0 +1,80 @@ +import { + FacetsGetters, + AgnosticCategoryTree, + AgnosticGroupedFacet, + AgnosticPagination, + AgnosticSort, + AgnosticBreadcrumb, + AgnosticFacet +} from '@vue-storefront/core'; +import { ProductVariant } from './../types/GraphQL'; +import { getProductFiltered } from './productGetters'; +import { getCategoryTree as buildCategoryTree } from './categoryGetters'; +import { buildBreadcrumbs, buildFacets, reduceForGroupedFacets, reduceForFacets } from './../useFacet/_utils'; +import { FacetResultsData, SearchData } from './../types'; + +const getAll = (searchData: SearchData, criteria?: string[]): AgnosticFacet[] => buildFacets(searchData, reduceForFacets, criteria); + +const getGrouped = (searchData: SearchData, criteria?: string[]): AgnosticGroupedFacet[] => + buildFacets(searchData, reduceForGroupedFacets, criteria); + +const getSortOptions = (searchData: SearchData): AgnosticSort => { + const options = [ + { type: 'sort', id: 'latest', value: 'Latest', count: null }, + { type: 'sort', id: 'price-up', value: 'Price from low to high', count: null }, + { type: 'sort', id: 'price-down', value: 'Price from high to low', count: null } + ].map(o => ({ ...o, selected: o.id === searchData.input.sort })); + + const selected = options.find(o => o.id === searchData.input.sort)?.id || 'latest'; + + return { options, selected }; +}; + +const getCategoryTree = (searchData: SearchData): AgnosticCategoryTree => { + if (!searchData.data) { + return {} as any; + } + + return buildCategoryTree(searchData.data.categories[0]); +}; + +const getProducts = (searchData: SearchData): ProductVariant[] => { + return getProductFiltered(searchData.data?.products || [], { master: true }); +}; + +const getPagination = (searchData: SearchData): AgnosticPagination => { + if (!searchData.data) { + return {} as any; + } + + return { + currentPage: searchData.input.page, + totalPages: Math.ceil(searchData.data.total / searchData.data.itemsPerPage), + totalItems: searchData.data.total, + itemsPerPage: searchData.input.itemsPerPage, + pageOptions: searchData.data.perPageOptions + }; +}; + +const getBreadcrumbs = (searchData: SearchData): AgnosticBreadcrumb[] => { + if (!searchData.data) { + return []; + } + + return [ + { text: 'Home', link: '/' }, + ...buildBreadcrumbs(searchData.data.categories[0]).map(b => ({ ...b, link: `/c${b.link}` })) + ]; +}; + +const facetGetters: FacetsGetters = { + getSortOptions, + getGrouped, + getAll, + getProducts, + getCategoryTree, + getBreadcrumbs, + getPagination +}; + +export default facetGetters; diff --git a/packages/commercetools/composables/src/getters/forgotPasswordGetters.ts b/packages/commercetools/composables/src/getters/forgotPasswordGetters.ts new file mode 100644 index 0000000000..6a506560d6 --- /dev/null +++ b/packages/commercetools/composables/src/getters/forgotPasswordGetters.ts @@ -0,0 +1,12 @@ +import { ForgotPasswordGetters } from '@vue-storefront/core'; +import { ForgotPasswordResult } from '../types'; + +export const getResetPasswordToken = (result: ForgotPasswordResult) => result?.resetPasswordResult?.data?.customerCreatePasswordResetToken?.value; +export const isPasswordChanged = (result: ForgotPasswordResult) => Boolean(result?.setNewPasswordResult?.data?.customerResetPassword?.email); + +const forgotPasswordGetters: ForgotPasswordGetters = { + getResetPasswordToken, + isPasswordChanged +}; + +export default forgotPasswordGetters; diff --git a/packages/commercetools/composables/src/getters/index.ts b/packages/commercetools/composables/src/getters/index.ts new file mode 100644 index 0000000000..6efb465af2 --- /dev/null +++ b/packages/commercetools/composables/src/getters/index.ts @@ -0,0 +1,28 @@ +/* istanbul ignore file */ +import cartGetters from './cartGetters'; +import categoryGetters from './categoryGetters'; +import productGetters from './productGetters'; +import reviewGetters from './reviewGetters'; +import userGetters from './userGetters'; +import userShippingGetters from './userShippingGetters'; +import userBillingGetters from './userBillingGetters'; +import orderGetters from './orderGetters'; +import wishlistGetters from './wishlistGetters'; +import facetGetters from './facetGetters'; +import forgotPasswordGetters from './forgotPasswordGetters'; +import storeGetters from './storeGetters'; + +export { + cartGetters, + categoryGetters, + productGetters, + reviewGetters, + userGetters, + userShippingGetters, + userBillingGetters, + orderGetters, + wishlistGetters, + facetGetters, + forgotPasswordGetters, + storeGetters +}; diff --git a/packages/commercetools/composables/src/getters/orderGetters.ts b/packages/commercetools/composables/src/getters/orderGetters.ts new file mode 100644 index 0000000000..8e1cc653e4 --- /dev/null +++ b/packages/commercetools/composables/src/getters/orderGetters.ts @@ -0,0 +1,47 @@ +import { UserOrderGetters, AgnosticOrderStatus } from '@vue-storefront/core'; +import { Order, OrderState, LineItem, OrderQueryResult } from './../types/GraphQL'; + +export const getOrderDate = (order: Order): string => order?.createdAt || ''; + +export const getOrderId = (order: Order): string => order?.id || ''; + +const orderStatusMap = { + [OrderState.Open]: AgnosticOrderStatus.Open, + [OrderState.Confirmed]: AgnosticOrderStatus.Confirmed, + [OrderState.Complete]: AgnosticOrderStatus.Complete, + [OrderState.Cancelled]: AgnosticOrderStatus.Cancelled +}; + +export const getOrderStatus = (order: Order): AgnosticOrderStatus | '' => order?.orderState ? orderStatusMap[order.orderState] : ''; + +export const getOrderPrice = (order: Order): number => order ? order.totalPrice.centAmount / 100 : 0; + +export const getOrderItems = (order: Order): LineItem[] => order?.lineItems || []; + +export const getOrderItemSku = (item: LineItem): string => item?.productId || ''; + +export const getOrderItemName = (item: LineItem): string => item?.name || ''; + +export const getOrderItemQty = (item: LineItem): number => item?.quantity || 0; + +export const getOrderItemPrice = (item: LineItem): number => item ? item.price.value.centAmount / 100 : 0; + +export const getFormattedPrice = (price: number) => price as any as string; + +export const getOrdersTotal = (orders: OrderQueryResult) => orders.total; + +const orderGetters: UserOrderGetters = { + getDate: getOrderDate, + getId: getOrderId, + getStatus: getOrderStatus, + getPrice: getOrderPrice, + getItems: getOrderItems, + getItemSku: getOrderItemSku, + getItemName: getOrderItemName, + getItemQty: getOrderItemQty, + getItemPrice: getOrderItemPrice, + getFormattedPrice, + getOrdersTotal +}; + +export default orderGetters; diff --git a/packages/commercetools/composables/src/getters/productGetters.ts b/packages/commercetools/composables/src/getters/productGetters.ts new file mode 100644 index 0000000000..fd3150c7b4 --- /dev/null +++ b/packages/commercetools/composables/src/getters/productGetters.ts @@ -0,0 +1,114 @@ +import { ProductGetters, AgnosticMediaGalleryItem, AgnosticAttribute, AgnosticPrice } from '@vue-storefront/core'; +import { ProductVariant, Image } from './../types/GraphQL'; +import { formatAttributeList, getVariantByAttributes, createPrice } from './_utils'; + +interface ProductVariantFilters { + master?: boolean; + attributes?: Record; +} + +export const getProductName = (product: ProductVariant | Readonly): string => (product as any)?._name || ''; + +export const getProductSlug = (product: ProductVariant | Readonly): string => (product as any)?._slug || ''; + +export const getProductPrice = (product: ProductVariant | Readonly): AgnosticPrice => createPrice(product); + +export const getProductGallery = (product: ProductVariant): AgnosticMediaGalleryItem[] => { + const images = product?.images || []; + + return images.map((image: Image) => ({ + small: image.url, + big: image.url, + normal: image.url + })); +}; + +export const getProductCoverImage = (product: ProductVariant): string => product?.images?.[0]?.url || ''; + +export const getProductFiltered = (products: ProductVariant[], filters: ProductVariantFilters | any = {}): ProductVariant[] => { + if (!products) { + return []; + } + + if (filters.attributes && Object.keys(filters.attributes).length > 0) { + return [getVariantByAttributes(products, filters.attributes)]; + } + + if (filters.master) { + return products.filter((product) => (product as any)._master); + } + + return products; +}; + +export const getProductAttributes = (products: ProductVariant[] | ProductVariant, filterByAttributeName?: string[]): Record => { + const isSingleProduct = !Array.isArray(products); + const productList = (isSingleProduct ? [products] : products) as ProductVariant[]; + + if (!products || productList.length === 0) { + return {} as any; + } + + const formatAttributes = (product: ProductVariant): AgnosticAttribute[] => + formatAttributeList(product.attributesRaw).filter((attribute) => filterByAttributeName ? filterByAttributeName.includes(attribute.name) : attribute); + + const reduceToUniques = (prev, curr) => { + const isAttributeExist = prev.some((el) => el.name === curr.name && el.value === curr.value); + + if (!isAttributeExist) { + return [...prev, curr]; + } + + return prev; + }; + + const reduceByAttributeName = (prev, curr) => ({ + ...prev, + [curr.name]: isSingleProduct ? curr.value : [ + ...(prev[curr.name] || []), + { + value: curr.value, + label: curr.label + } + ] + }); + + return productList + .map((product) => formatAttributes(product)) + .reduce((prev, curr) => [...prev, ...curr], []) + .reduce(reduceToUniques, []) + .reduce(reduceByAttributeName, {}); +}; + +export const getProductDescription = (product: ProductVariant): any => (product as any)?._description || ''; + +export const getProductCategoryIds = (product: ProductVariant): string[] => (product as any)?._categoriesRef || ''; + +export const getProductId = (product: ProductVariant): string => (product as any)?._id || ''; + +export const getFormattedPrice = (price: number) => price as any as string; + +export const getTotalReviews = (product: ProductVariant): number => (product as any)?._rating?.count || 0; + +export const getAverageRating = (product: ProductVariant): number => (product as any)?._rating?.averageRating || 0; + +export const getProductSku = (product: ProductVariant): any => (product as any)?.sku || ''; + +const productGetters: ProductGetters = { + getName: getProductName, + getSlug: getProductSlug, + getPrice: getProductPrice, + getGallery: getProductGallery, + getCoverImage: getProductCoverImage, + getFiltered: getProductFiltered, + getAttributes: getProductAttributes, + getDescription: getProductDescription, + getCategoryIds: getProductCategoryIds, + getId: getProductId, + getSku: getProductSku, + getFormattedPrice, + getTotalReviews, + getAverageRating +}; + +export default productGetters; diff --git a/packages/commercetools/composables/src/getters/reviewGetters.ts b/packages/commercetools/composables/src/getters/reviewGetters.ts new file mode 100644 index 0000000000..32b60bb639 --- /dev/null +++ b/packages/commercetools/composables/src/getters/reviewGetters.ts @@ -0,0 +1,52 @@ +/* istanbul ignore file */ + +import { ReviewGetters, AgnosticRateCount } from '@vue-storefront/core'; + +// TODO: Replace with GraphQL types when they get updated +type Review = any; +type ReviewItem = any; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getItems = (review: Review): ReviewItem[] => []; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getReviewId = (item: ReviewItem): string => ''; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getReviewAuthor = (item: ReviewItem): string => ''; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getReviewMessage = (item: ReviewItem): string => ''; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getReviewRating = (item: ReviewItem): number => 0; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getReviewDate = (item: ReviewItem): string => ''; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getTotalReviews = (review: Review): number => 0; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getAverageRating = (review: Review): number => 0; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getRatesCount = (review: Review): AgnosticRateCount[] => []; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getReviewsPage = (review: Review): number => 1; + +const reviewGetters: ReviewGetters = { + getItems, + getReviewId, + getReviewAuthor, + getReviewMessage, + getReviewRating, + getReviewDate, + getTotalReviews, + getAverageRating, + getRatesCount, + getReviewsPage +}; + +export default reviewGetters; diff --git a/packages/commercetools/composables/src/getters/storeGetters.ts b/packages/commercetools/composables/src/getters/storeGetters.ts new file mode 100644 index 0000000000..6716d46a9f --- /dev/null +++ b/packages/commercetools/composables/src/getters/storeGetters.ts @@ -0,0 +1,105 @@ +import { AgnosticAddress, AgnosticLocale, AgnosticStore, UseStoreGetters } from '@vue-storefront/core'; +import { Store, Channel, Address } from '../types/GraphQL'; +import { StoresData } from '../types'; +import { FilterCriteriaRecord, Localized, filterArrayByCriteriaRecord } from '../helpers/internals'; + +interface StoreFilterCriteria { + store?: FilterCriteriaRecord; + channel?: FilterCriteriaRecord +} + +type StoreChannelSetKeys = + 'distributionChannels' | + 'supplyChannels'; + +function mapToAddress(address: Address): AgnosticAddress { + return { + addressLine1: `${address?.country ?? ''} ${address?.city ?? ''} ${address?.postalCode ?? ''}`.trim(), + addressLine2: `${address?.streetName ?? ''} ${address?.streetNumber ?? ''}`.trim(), + _address: address ?? null + }; +} + +function mapToLocale (localized: T): AgnosticLocale { + return { + code: localized.locale, + + /** + * TODO + * CT GraphQL "LocalizedString" type does not provide locale label value. + * The default value is empty string to match the "AgnosticLocale" type. + * Maybe AgnosticLocale["label"] property should be optional? + */ + label: '' + }; +} + +function mapToLocales(localized: Localized[]): AgnosticLocale[] { + return localized?.map(mapToLocale) ?? null; +} + +function mapStoreAndChannelToAgnosticStore (store: Store) { + return function (channel?: Channel): AgnosticStore { + return { + name: `${store?.name ?? ''}${channel?.name ? ` - ${channel?.name}` : ''}`.trim(), + id: `${store?.id ?? ''}${channel?.id ? `/${channel?.id}` : ''}`.trim(), + description: channel?.description ?? '', + geoLocation: channel?.geoLocation ?? null, + locales: (mapToLocales(channel?.descriptionAllLocales) ?? store.languages) ?? [], + address: mapToAddress(channel?.address), + key: `${store?.key ?? ''}${channel?.id ? `/${channel?.id}` : ''}`.trim(), + _storeID: store?.id ?? null, + _channelID: channel?.id ?? null + }; + }; +} + +function mapChannelSet (store: Store, channels: Channel[]): AgnosticStore[] { + return channels?.map(mapStoreAndChannelToAgnosticStore(store)) ?? []; +} + +function mapChannelSetByKey (store: Store, + key: StoreChannelSetKeys, + criteria?: FilterCriteriaRecord): AgnosticStore[] { + return mapChannelSet( + store, + filterArrayByCriteriaRecord( + store?.[key], + criteria + ) + ); +} + +function gainAgnosticStoreItems (criteria?: FilterCriteriaRecord) { + return function (acc: AgnosticStore[], store: Store): AgnosticStore[] { + const mappedStores = [ + ...mapChannelSetByKey(store, 'distributionChannels', criteria), + ...mapChannelSetByKey(store, 'supplyChannels', criteria) + ]; + + return [ + ...acc, + ...(!mappedStores.length && !criteria + ? [mapStoreAndChannelToAgnosticStore(store)()] + : mappedStores) + ]; + }; +} + +function getItems (stores: StoresData, criteria: StoreFilterCriteria = {}): AgnosticStore[] { + return filterArrayByCriteriaRecord( + stores?.results, + criteria?.store) + ?.reduce(gainAgnosticStoreItems(criteria.channel), []) ?? []; +} + +function getSelected (stores: StoresData): AgnosticStore | undefined { + return getItems(stores, { store: { key: (stores?._selectedStore ?? '') }})[0]; +} + +const storeGetters: UseStoreGetters = { + getItems, + getSelected +}; + +export default storeGetters; diff --git a/packages/commercetools/composables/src/getters/userBillingGetters.ts b/packages/commercetools/composables/src/getters/userBillingGetters.ts new file mode 100644 index 0000000000..fd3983ac90 --- /dev/null +++ b/packages/commercetools/composables/src/getters/userBillingGetters.ts @@ -0,0 +1,33 @@ +import { UserBillingGetters } from '@vue-storefront/core'; + +const userBillingGetters: UserBillingGetters = { + getAddresses: (billing, criteria?: Record) => { + if (!criteria || !Object.keys(criteria).length) { + return billing.addresses; + } + + const entries = Object.entries(criteria); + return billing.addresses.filter(address => entries.every(([key, value]) => address[key] === value)); + }, + getDefault: billing => billing.addresses.find(({ isDefault }) => isDefault), + getTotal: billing => billing.addresses.length, + + getPostCode: address => address?.postalCode || '', + getStreetName: address => address?.streetName || '', + getStreetNumber: address => address?.streetNumber || '', + getCity: address => address?.city || '', + getFirstName: address => address?.firstName || '', + getLastName: address => address?.lastName || '', + getCountry: address => address?.country || '', + getPhone: address => address?.phone || '', + getEmail: address => address?.email || '', + getProvince: address => address?.state || '', + getCompanyName: address => address?.company || '', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getTaxNumber: address => '', + getId: address => address?.id || '', + getApartmentNumber: address => address?.apartment || '', + isDefault: address => address?.isDefault || false +}; + +export default userBillingGetters; diff --git a/packages/commercetools/composables/src/getters/userGetters.ts b/packages/commercetools/composables/src/getters/userGetters.ts new file mode 100644 index 0000000000..7cf72f2a9d --- /dev/null +++ b/packages/commercetools/composables/src/getters/userGetters.ts @@ -0,0 +1,19 @@ +import { UserGetters } from '@vue-storefront/core'; +import { Customer } from './../types/GraphQL'; + +export const getUserFirstName = (user: Customer): string => user?.firstName || ''; + +export const getUserLastName = (user: Customer): string => user?.lastName || ''; + +export const getUserFullName = (user: Customer): string => user ? `${user.firstName} ${user.lastName}` : ''; + +export const getUserEmailAddress = (user: Customer): string => user?.email || ''; + +const userGetters: UserGetters = { + getFirstName: getUserFirstName, + getLastName: getUserLastName, + getFullName: getUserFullName, + getEmailAddress: getUserEmailAddress +}; + +export default userGetters; diff --git a/packages/commercetools/composables/src/getters/userShippingGetters.ts b/packages/commercetools/composables/src/getters/userShippingGetters.ts new file mode 100644 index 0000000000..0a94939c46 --- /dev/null +++ b/packages/commercetools/composables/src/getters/userShippingGetters.ts @@ -0,0 +1,33 @@ +import { UserShippingGetters } from '@vue-storefront/core'; + +const userShippingGetters: UserShippingGetters = { + getAddresses: (shipping, criteria?: Record) => { + if (!criteria || !Object.keys(criteria).length) { + return shipping.addresses; + } + + const entries = Object.entries(criteria); + return shipping.addresses.filter(address => entries.every(([key, value]) => address[key] === value)); + }, + getDefault: shipping => shipping.addresses.find(({ isDefault }) => isDefault), + getTotal: shipping => shipping.addresses.length, + + getPostCode: address => address?.postalCode || '', + getStreetName: address => address?.streetName || '', + getStreetNumber: address => address?.streetNumber || '', + getCity: address => address?.city || '', + getFirstName: address => address?.firstName || '', + getLastName: address => address?.lastName || '', + getCountry: address => address?.country || '', + getPhone: address => address?.phone || '', + getEmail: address => address?.email || '', + getProvince: address => address?.state || '', + getCompanyName: address => address?.company || '', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getTaxNumber: address => '', + getId: address => address?.id || '', + getApartmentNumber: address => address?.apartment || '', + isDefault: address => address?.isDefault || false +}; + +export default userShippingGetters; diff --git a/packages/commercetools/composables/src/getters/wishlistGetters.ts b/packages/commercetools/composables/src/getters/wishlistGetters.ts new file mode 100644 index 0000000000..91e0778558 --- /dev/null +++ b/packages/commercetools/composables/src/getters/wishlistGetters.ts @@ -0,0 +1,59 @@ +/* istanbul ignore file */ + +import { + WishlistGetters, + AgnosticPrice, + AgnosticTotals +} from '@vue-storefront/core'; +import { LineItem } from './../types/GraphQL'; + +type Wishlist = any; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistItems = (wishlist: Wishlist): LineItem[] => []; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistItemName = (product: LineItem): string => ''; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistItemImage = (product: LineItem): string => ''; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistItemPrice = (product: LineItem): AgnosticPrice => ({ regular: 0, special: 0 }); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistItemQty = (product: LineItem): number => 1; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistItemAttributes = (product: LineItem, filterByAttributeName?: string[]) => ({'': ''}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistItemSku = (product: LineItem): string => ''; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistTotals = (wishlist: Wishlist): AgnosticTotals => ({ total: 0, subtotal: 0 }); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistShippingPrice = (wishlist: Wishlist): number => 0; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getWishlistTotalItems = (wishlist: Wishlist): number => 0; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const getFormattedPrice = (price: number): string => ''; + +const wishlistGetters: WishlistGetters = { + getTotals: getWishlistTotals, + getShippingPrice: getWishlistShippingPrice, + getItems: getWishlistItems, + getItemName: getWishlistItemName, + getItemImage: getWishlistItemImage, + getItemPrice: getWishlistItemPrice, + getItemQty: getWishlistItemQty, + getItemAttributes: getWishlistItemAttributes, + getItemSku: getWishlistItemSku, + getTotalItems: getWishlistTotalItems, + getFormattedPrice +}; + +export default wishlistGetters; diff --git a/packages/commercetools/composables/src/helpers/internals/enhanceProduct.ts b/packages/commercetools/composables/src/helpers/internals/enhanceProduct.ts new file mode 100644 index 0000000000..a0df506e50 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/enhanceProduct.ts @@ -0,0 +1,48 @@ +import { ApolloQueryResult } from 'apollo-client'; +import { Context } from '@vue-storefront/core'; +import { ProductQueryResult } from './../../types/GraphQL'; + +interface ProductData { + products: ProductQueryResult; +} + +const getTranslated = (rawAttribute, context) => { + const { locale } = context.$ct.config; + if (rawAttribute.attributeDefinition.type.name === 'ltext') { + return rawAttribute.value[locale]; + } + + if (rawAttribute.attributeDefinition.type.name === 'lenum') { + return rawAttribute.value.label[locale]; + } + + return rawAttribute.value; +}; + +const enhanceProduct = (productResponse: ApolloQueryResult, context: Context): ApolloQueryResult => { + (productResponse.data as any)._variants = productResponse.data.products.results + .map((product) => { + const current = product.masterData.current; + + return current.allVariants.map((variant) => ({ + ...variant, + attributesRaw: variant.attributesRaw.map((raw) => ({ + ...raw, + _translated: getTranslated(raw, context) + })), + _name: current.name, + _slug: current.slug, + _id: product.id, + _key: product.key, + _master: current.masterVariant.id === variant.id, + _description: current.description, + _categoriesRef: current.categoriesRef.map((cr) => cr.id), + _rating: (product as any).reviewRatingStatistics + })); + }) + .reduce((prev, curr) => [...prev, ...curr], []); + + return productResponse; +}; + +export default enhanceProduct; diff --git a/packages/commercetools/composables/src/helpers/internals/filterByCriteria.ts b/packages/commercetools/composables/src/helpers/internals/filterByCriteria.ts new file mode 100644 index 0000000000..74cbd02ec2 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/filterByCriteria.ts @@ -0,0 +1,49 @@ +export interface Predicate { + (param: T): boolean; +} + +export interface Callable { + (...xs: any): any; +} + +export interface Localized { + locale: string; +} + +export type FilterCriteriaRecord > = { + [K in keyof T]?: T[K] | Predicate; +} + +export type FilterCriteriaRecordValue > = + FilterCriteriaRecord[keyof T] + +export type FilterCriteriaRecordField > = [ + keyof T, FilterCriteriaRecordValue +]; + +export function isCallableCriteriaRecordValue (value: FilterCriteriaRecordValue): value is Predicate { + return typeof value === 'function'; +} + +export function filterByCriteriaRecordField (target: T) { + return function ([key, value]: FilterCriteriaRecordField): boolean { + return isCallableCriteriaRecordValue(value) + ? value(target?.[key]) + : target?.[key] === value; + }; +} + +export function filterByCriteriaRecord (criteria: FilterCriteriaRecord) { + return function (target: T): boolean { + return Object.entries>(criteria) + .every( + filterByCriteriaRecordField(target) as Callable + ); + }; +} + +export function filterArrayByCriteriaRecord (array: T[], criteria?: FilterCriteriaRecord): T[] { + return criteria + ? array?.filter(filterByCriteriaRecord(criteria)) + : array; +} diff --git a/packages/commercetools/composables/src/helpers/internals/getCouponsFromCart.ts b/packages/commercetools/composables/src/helpers/internals/getCouponsFromCart.ts new file mode 100644 index 0000000000..7f776b9be1 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/getCouponsFromCart.ts @@ -0,0 +1,15 @@ +import { Cart } from './../../types/GraphQL'; +import { AgnosticCoupon } from '@vue-storefront/core'; + +export default (cart: Cart): AgnosticCoupon[] => { + const coupons = cart?.discountCodes; + if (!coupons) { + return; + } + return coupons.map(coupon => ({ + id: coupon.discountCode.id, + name: coupon.discountCode.name, + code: coupon.discountCode.code, + value: null + })); +}; diff --git a/packages/commercetools/composables/src/helpers/internals/getFiltersFromProductsAttributes.ts b/packages/commercetools/composables/src/helpers/internals/getFiltersFromProductsAttributes.ts new file mode 100644 index 0000000000..bfa7c752d2 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/getFiltersFromProductsAttributes.ts @@ -0,0 +1,34 @@ +import { ProductVariant, Attribute } from '../../types/GraphQL'; +import { getAttributeValue } from '../../getters/_utils'; +import { Filter, FilterOption } from '@vue-storefront/commercetools-api'; + +const extractAttributes = (product: ProductVariant): Attribute[] => product.attributesRaw; + +const flattenAttributes = (prev: Attribute[], curr: Attribute[]): Attribute[] => [...prev, ...(curr || [])]; + +const getFilterFromAttribute = (attribute: Attribute, prev) => { + const attrValue = getAttributeValue(attribute); + const filter = prev[attribute.name] || { + type: (attribute as any).attributeDefinition.type.name, + options: [] + }; + const option: FilterOption = { + value: attrValue, + label: (attribute as any).label || (typeof attrValue === 'string' ? attrValue : null), + selected: false + }; + const hasSuchOption = filter.options.some(opt => opt.value === option.value); + hasSuchOption || filter.options.push(option); + return filter; +}; + +export default (products: ProductVariant[]): Record => { + if (!products) { + return {}; + } + + return products.map(extractAttributes).reduce(flattenAttributes, []).reduce((prev, attribute) => { + prev[attribute.name] = getFilterFromAttribute(attribute, prev); + return prev; + }, {}); +}; diff --git a/packages/commercetools/composables/src/helpers/internals/getStoreCredentials.ts b/packages/commercetools/composables/src/helpers/internals/getStoreCredentials.ts new file mode 100644 index 0000000000..32b362f4b4 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/getStoreCredentials.ts @@ -0,0 +1,17 @@ +export interface StoreCredentials { + storeKey?: string; + channelId?: string; +} + +export function getStoreCredentials (store: string | undefined): StoreCredentials { + const [storeKey, channelId] = store?.split('/') ?? []; + return { storeKey, channelId }; +} + +export function getStoreKey (store: string | undefined): StoreCredentials['storeKey'] { + return getStoreCredentials(store).storeKey; +} + +export function getChannelId (store: string | undefined): StoreCredentials['channelId'] { + return getStoreCredentials(store).channelId; +} diff --git a/packages/commercetools/composables/src/helpers/internals/index.ts b/packages/commercetools/composables/src/helpers/internals/index.ts new file mode 100644 index 0000000000..4e10e46779 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/index.ts @@ -0,0 +1,8 @@ +export { default as enhanceProduct } from './enhanceProduct'; +export { default as mapPaginationParams } from './mapPaginationParams'; +export { default as getFiltersFromProductsAttributes } from './getFiltersFromProductsAttributes'; +export { default as getCouponsFromCart } from './getCouponsFromCart'; +export { default as makeId } from './makeId'; + +export * from './filterByCriteria'; +export * from './getStoreCredentials'; diff --git a/packages/commercetools/composables/src/helpers/internals/makeId.ts b/packages/commercetools/composables/src/helpers/internals/makeId.ts new file mode 100644 index 0000000000..4d4b307ae0 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/makeId.ts @@ -0,0 +1 @@ +export default () => Math.random().toString().substr(2); diff --git a/packages/commercetools/composables/src/helpers/internals/mapPaginationParams.ts b/packages/commercetools/composables/src/helpers/internals/mapPaginationParams.ts new file mode 100644 index 0000000000..e3b83849f2 --- /dev/null +++ b/packages/commercetools/composables/src/helpers/internals/mapPaginationParams.ts @@ -0,0 +1,17 @@ +import { BaseSearch } from '@vue-storefront/commercetools-api'; + +export default ({ page, perPage }: { + perPage?: number; + page?: number; + sort?: any; + term?: any; + filters?: any; + [x: string]: any; +}): Pick | undefined => { + if (perPage && page) { + return { + limit: perPage, + offset: (page - 1) * perPage + }; + } +}; diff --git a/packages/commercetools/composables/src/index.ts b/packages/commercetools/composables/src/index.ts new file mode 100644 index 0000000000..6de189ce7a --- /dev/null +++ b/packages/commercetools/composables/src/index.ts @@ -0,0 +1,35 @@ +/** + * `composables` for commercetools integration for Vue Storefront 2. + * + * @remarks + * The `@vue-storefront/commercetools` library includes everything needed to fetch data from the + * Server Middleware and display them in agnostic and formatted form. This includes composables + * and getters. + * + * @packageDocumentation + */ + +/* istanbul ignore file */ +import { track } from '@vue-storefront/core'; + +track('VSFCommercetools'); + +export { useBilling, useBillingProviderFactoryParams } from './useBilling'; +export { useCart, useCartFactoryParams } from './useCart'; +export { useCategory, useCategoryFactoryParams } from './useCategory'; +export { useFacet, useFacetFactoryParams } from './useFacet'; +export { useMakeOrder, useMakeOrderFactoryParams } from './useMakeOrder'; +export { useProduct, useProductFactoryParams } from './useProduct'; +export { useReview, useReviewFactoryParams } from './useReview'; +export { useShipping, useShippingFactoryParams } from './useShipping'; +export { useShippingProvider, useShippingProviderFactoryParams } from './useShippingProvider'; +export { useUser, useUserFactoryParams } from './useUser'; +export { useUserBilling, useUserBillingFactoryParams } from './useUserBilling'; +export { useUserOrder, useUserOrderFactoryParams } from './useUserOrder'; +export { useUserShipping, useUserShippingFactoryParams } from './useUserShipping'; +export { useWishlist, useWishlistFactoryParams } from './useWishlist'; +export { useForgotPassword, useForgotPasswordFactoryParams } from './useForgotPassword'; +export { useStore, useStoreFactoryParams } from './useStore'; + +export * from './getters'; +export * from './types'; diff --git a/packages/commercetools/composables/src/types/GraphQL.ts b/packages/commercetools/composables/src/types/GraphQL.ts new file mode 100644 index 0000000000..26f05e1314 --- /dev/null +++ b/packages/commercetools/composables/src/types/GraphQL.ts @@ -0,0 +1,7309 @@ +/* istanbul ignore file */ + +export type Maybe = T | null; +/** All built-in and custom scalars, mapped to their actual values */ +export type Scalars = { + ID: string; + String: string; + Boolean: boolean; + Int: number; + Float: number; + /** The `Long` scalar type represents non-fractional signed whole numeric values. + * Long can represent values between -(2^63) and 2^63 - 1. + */ + Long: any; + /** DateTime is a scalar value that represents an ISO8601 formatted date and time. */ + DateTime: any; + /** [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) country code. */ + Country: any; + /** Locale is a scalar value represented as a string language tag. */ + Locale: any; + /** DateTime is a scalar value that represents an ISO8601 formatted date. */ + Date: any; + /** Raw JSON value */ + Json: any; + /** Represents a currency. Currencies are identified by their [ISO + * 4217](http://www.iso.org/iso/home/standards/currency_codes.htm) currency codes. + */ + Currency: any; + /** A key that references a resource. */ + KeyReferenceInput: any; + /** Search filter. It is represented as a string and has th same format as in REST API: "field:filter_criteria" */ + SearchFilter: any; + /** Search sort */ + SearchSort: any; + /** YearMonth is a scalar value that represents an ISO8601 formatted year and month. */ + YearMonth: any; + /** The `BigDecimal` scalar type represents signed fractional values with arbitrary precision. */ + BigDecimal: any; + /** Time is a scalar value that represents an ISO8601 formatted time. */ + Time: any; +}; + +export type AbsoluteDiscountValue = CartDiscountValue & + ProductDiscountValue & { + __typename?: "AbsoluteDiscountValue"; + money: Array; + type: Scalars["String"]; + }; + +export type AbsoluteDiscountValueInput = { + money: Array; +}; + +/** A field to access the active cart. */ +export type ActiveCartInterface = { + activeCart?: Maybe; +}; + +export type AddAttributeDefinition = { + attributeDefinition: AttributeDefinitionDraft; +}; + +export type AddCartCustomLineItem = { + shippingDetails?: Maybe; + custom?: Maybe; + quantity?: Maybe; + externalTaxRate?: Maybe; + taxCategory?: Maybe; + slug: Scalars["String"]; + money: BaseMoneyInput; + name: Array; +}; + +export type AddCartDiscountCode = { + code: Scalars["String"]; + validateDuplicates?: Maybe; +}; + +export type AddCartItemShippingAddress = { + address: AddressInput; +}; + +export type AddCartLineItem = { + shippingDetails?: Maybe; + externalTotalPrice?: Maybe; + externalPrice?: Maybe; + externalTaxRate?: Maybe; + custom?: Maybe; + catalog?: Maybe; + distributionChannel?: Maybe; + supplyChannel?: Maybe; + variantId?: Maybe; + quantity?: Maybe; + sku?: Maybe; + productId?: Maybe; +}; + +export type AddCartPayment = { + payment: ResourceIdentifierInput; +}; + +export type AddCartShoppingList = { + shoppingList: ResourceIdentifierInput; + supplyChannel?: Maybe; + distributionChannel?: Maybe; +}; + +export type AddCategoryAsset = { + position?: Maybe; + asset: AssetDraftInput; +}; + +export type AddCustomerAddress = { + address: AddressInput; +}; + +export type AddCustomerBillingAddressId = { + addressId: Scalars["String"]; +}; + +export type AddCustomerShippingAddressId = { + addressId: Scalars["String"]; +}; + +export type AddCustomerStore = { + store: ResourceIdentifierInput; +}; + +export type AddInventoryEntryQuantity = { + quantity: Scalars["Long"]; +}; + +export type AddLocalizedEnumValue = { + attributeName: Scalars["String"]; + value: LocalizedEnumValueDraft; +}; + +export type AddMyCartLineItem = { + shippingDetails?: Maybe; + custom?: Maybe; + catalog?: Maybe; + distributionChannel?: Maybe; + supplyChannel?: Maybe; + variantId?: Maybe; + quantity?: Maybe; + sku?: Maybe; + productId?: Maybe; +}; + +export type AddOrderDelivery = { + items?: Maybe>; + parcels?: Maybe>; + address?: Maybe; +}; + +export type AddOrderItemShippingAddress = { + address: AddressInput; +}; + +export type AddOrderParcelToDelivery = { + deliveryId: Scalars["String"]; + measurements?: Maybe; + trackingData?: Maybe; + items?: Maybe>; +}; + +export type AddOrderPayment = { + payment: ResourceIdentifierInput; +}; + +export type AddOrderReturnInfo = { + items: Array; + returnDate?: Maybe; + returnTrackingId?: Maybe; +}; + +export type AddPlainEnumValue = { + attributeName: Scalars["String"]; + value: PlainEnumValueDraft; +}; + +export type AddProductAsset = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + position?: Maybe; + asset: AssetDraftInput; +}; + +export type AddProductExternalImage = { + variantId?: Maybe; + sku?: Maybe; + image: ImageInput; + staged?: Maybe; +}; + +export type AddProductPrice = { + variantId?: Maybe; + sku?: Maybe; + price: ProductPriceDataInput; + catalog?: Maybe; + staged?: Maybe; +}; + +export type AddProductToCategory = { + category: ResourceIdentifierInput; + orderHint?: Maybe; + staged?: Maybe; +}; + +export type AddProductVariant = { + assets?: Maybe>; + attributes?: Maybe>; + images?: Maybe>; + prices?: Maybe>; + key?: Maybe; + sku?: Maybe; + staged?: Maybe; +}; + +/** An address represents a postal address. */ +export type Address = { + __typename?: "Address"; + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + contactInfo: AddressContactInfo; + phone?: Maybe; + email?: Maybe; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; +}; + +export type AddressContactInfo = { + __typename?: "AddressContactInfo"; + phone?: Maybe; + mobile?: Maybe; + email?: Maybe; + fax?: Maybe; +}; + +export type AddressInput = { + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + phone?: Maybe; + mobile?: Maybe; + email?: Maybe; + fax?: Maybe; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; +}; + +export type AddShippingMethodShippingRate = { + zone: ResourceIdentifierInput; + shippingRate: ShippingRateDraft; +}; + +export type AddShippingMethodZone = { + zone: ResourceIdentifierInput; +}; + +export type AddShoppingListLineItem = { + addedAt?: Maybe; + custom?: Maybe; + quantity?: Maybe; + variantId?: Maybe; + sku?: Maybe; + productId?: Maybe; +}; + +export type AddShoppingListTextLineItem = { + addedAt?: Maybe; + custom?: Maybe; + quantity?: Maybe; + description?: Maybe>; + name: Array; +}; + +export type AddZoneLocation = { + location: ZoneLocation; +}; + +export enum AnonymousCartSignInMode { + /** The anonymous cart is used as new active customer cart. No `LineItem`s get merged. */ + UseAsNewActiveCustomerCart = "UseAsNewActiveCustomerCart", + /** `LineItem`s of the anonymous cart will be copied to the customer’s active cart that has been modified most recently. + * + * The `CartState` of the anonymous cart gets changed to `Merged` while the + * `CartState` of the customer’s cart remains `Active`. + * + * `CustomLineItems` and `CustomFields` of the anonymous cart will not be copied to the customers cart. + * + * If a `LineItem` in the anonymous cart matches an existing line item in the + * customer’s cart (same product ID and variant ID), the maximum quantity of both + * LineItems is used as the new quantity. In that case `CustomFields` on the + * `LineItem` of the anonymous cart will not be in the resulting `LineItem`. + */ + MergeWithExistingCustomerCart = "MergeWithExistingCustomerCart" +} + +/** API Clients can be used to obtain OAuth 2 access tokens */ +export type ApiClientWithoutSecret = { + __typename?: "APIClientWithoutSecret"; + id: Scalars["String"]; + name: Scalars["String"]; + scope: Scalars["String"]; + createdAt?: Maybe; + lastUsedAt?: Maybe; +}; + +export type ApiClientWithoutSecretQueryResult = { + __typename?: "APIClientWithoutSecretQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +/** API Clients can be used to obtain OAuth 2 access tokens. The secret is only + * shown once in the response of creating the API Client. + */ +export type ApiClientWithSecret = { + __typename?: "APIClientWithSecret"; + id: Scalars["String"]; + name: Scalars["String"]; + scope: Scalars["String"]; + createdAt?: Maybe; + lastUsedAt?: Maybe; + secret: Scalars["String"]; +}; + +export type ApplyCartDeltaToCustomLineItemShippingDetailsTargets = { + customLineItemId: Scalars["String"]; + targetsDelta: Array; +}; + +export type ApplyCartDeltaToLineItemShippingDetailsTargets = { + lineItemId: Scalars["String"]; + targetsDelta: Array; +}; + +export type Asset = { + __typename?: "Asset"; + id: Scalars["String"]; + key?: Maybe; + sources: Array; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + tags: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type AssetNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AssetDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AssetCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type AssetCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type AssetDimensions = { + __typename?: "AssetDimensions"; + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +export type AssetDimensionsInput = { + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +export type AssetDraftInput = { + key?: Maybe; + name: Array; + description?: Maybe>; + custom?: Maybe; + sources?: Maybe>; + tags?: Maybe>; + type?: Maybe; +}; + +export type AssetSource = { + __typename?: "AssetSource"; + uri: Scalars["String"]; + key?: Maybe; + dimensions?: Maybe; + contentType?: Maybe; +}; + +export type AssetSourceInput = { + uri: Scalars["String"]; + key?: Maybe; + dimensions?: Maybe; + contentType?: Maybe; +}; + +export type Attribute = { + name: Scalars["String"]; +}; + +export enum AttributeConstraint { + /** No constraints are applied to the attribute */ + None = "None", + /** Attribute value should be different in each variant */ + Unique = "Unique", + /** A set of attributes, that have this constraint, should have different combinations in each variant */ + CombinationUnique = "CombinationUnique", + /** Attribute value should be the same in all variants */ + SameForAll = "SameForAll" +} + +export type AttributeDefinition = { + __typename?: "AttributeDefinition"; + type: AttributeDefinitionType; + name: Scalars["String"]; + label?: Maybe; + isRequired: Scalars["Boolean"]; + attributeConstraint: AttributeConstraint; + inputTip?: Maybe; + inputHint: TextInputHint; + isSearchable: Scalars["Boolean"]; + labelAllLocales: Array; + inputTipAllLocales?: Maybe>; +}; + +export type AttributeDefinitionLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AttributeDefinitionInputTipArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type AttributeDefinitionDraft = { + type: AttributeTypeDraft; + name: Scalars["String"]; + label: Array; + isRequired: Scalars["Boolean"]; + attributeConstraint?: Maybe; + inputTip?: Maybe>; + inputHint?: Maybe; + isSearchable: Scalars["Boolean"]; +}; + +export type AttributeDefinitionResult = { + __typename?: "AttributeDefinitionResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +/** (https://dev.commercetools.com/http-api-projects-productTypes.html#attributetype)[https://dev.commercetools.com/http-api-projects-productTypes.html#attributetype] */ +export type AttributeDefinitionType = { + name: Scalars["String"]; +}; + +export type AttributeSetElementTypeDraft = { + text?: Maybe; + number?: Maybe; + money?: Maybe; + date?: Maybe; + time?: Maybe; + datetime?: Maybe; + boolean?: Maybe; + reference?: Maybe; + enum?: Maybe; + lenum?: Maybe; + ltext?: Maybe; +}; + +export type AttributeSetTypeDraft = { + elementType: AttributeSetElementTypeDraft; +}; + +export type AttributeTypeDraft = { + set?: Maybe; + text?: Maybe; + number?: Maybe; + money?: Maybe; + date?: Maybe; + time?: Maybe; + datetime?: Maybe; + boolean?: Maybe; + reference?: Maybe; + enum?: Maybe; + lenum?: Maybe; + ltext?: Maybe; +}; + +export type BaseMoney = { + type: Scalars["String"]; + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; + fractionDigits: Scalars["Int"]; +}; + +export type BaseMoneyInput = { + centPrecision?: Maybe; + highPrecision?: Maybe; +}; + +export type BaseSearchKeywordInput = { + whitespace?: Maybe; + custom?: Maybe; +}; + +export type BooleanAttribute = Attribute & { + __typename?: "BooleanAttribute"; + value: Scalars["Boolean"]; + name: Scalars["String"]; +}; + +export type BooleanAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "BooleanAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type BooleanField = CustomField & { + __typename?: "BooleanField"; + value: Scalars["Boolean"]; + name: Scalars["String"]; +}; + +export type BooleanType = FieldType & { + __typename?: "BooleanType"; + name: Scalars["String"]; +}; + +/** A shopping cart holds product variants and can be ordered. Each cart either + * belongs to a registered customer or is an anonymous cart. + */ +export type Cart = Versioned & { + __typename?: "Cart"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + cartState: CartState; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A shopping cart holds product variants and can be ordered. Each cart either + * belongs to a registered customer or is an anonymous cart. + */ +export type CartCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A shopping cart holds product variants and can be ordered. Each cart either + * belongs to a registered customer or is an anonymous cart. + */ +export type CartCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CartClassificationInput = { + values: Array; +}; + +export type CartClassificationType = ShippingRateInputType & { + __typename?: "CartClassificationType"; + values: Array; + type: Scalars["String"]; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscount = Versioned & { + __typename?: "CartDiscount"; + cartPredicate: Scalars["String"]; + validFrom?: Maybe; + validUntil?: Maybe; + stackingMode: StackingMode; + isActive: Scalars["Boolean"]; + requiresDiscountCode: Scalars["Boolean"]; + sortOrder: Scalars["String"]; + key?: Maybe; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + value: CartDiscountValue; + target?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Cart discounts are recalculated every time LineItems or CustomLineItems are + * added or removed from the Cart or an order is created from the cart. + * + * The number of active cart discounts that do not require a discount code + * (isActive=true and requiresDiscountCode=false) is limited to 100. + */ +export type CartDiscountCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CartDiscountDraft = { + value: CartDiscountValueInput; + cartPredicate: Scalars["String"]; + target?: Maybe; + sortOrder: Scalars["String"]; + name: Array; + description?: Maybe>; + validFrom?: Maybe; + validUntil?: Maybe; + stackingMode?: Maybe; + requiresDiscountCode?: Maybe; + isActive?: Maybe; + custom?: Maybe; + key?: Maybe; +}; + +export type CartDiscountQueryResult = { + __typename?: "CartDiscountQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CartDiscountTarget = { + type: Scalars["String"]; +}; + +export type CartDiscountTargetInput = { + lineItems?: Maybe; + customLineItems?: Maybe; + shipping?: Maybe; + multiBuyLineItems?: Maybe; + multiBuyCustomLineItems?: Maybe; +}; + +export type CartDiscountUpdateAction = { + changeCartPredicate?: Maybe; + changeIsActive?: Maybe; + changeName?: Maybe; + changeRequiresDiscountCode?: Maybe; + changeSortOrder?: Maybe; + changeStackingMode?: Maybe; + changeTarget?: Maybe; + changeValue?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setValidFrom?: Maybe; + setValidFromAndUntil?: Maybe; + setValidUntil?: Maybe; +}; + +export type CartDiscountValue = { + type: Scalars["String"]; +}; + +export type CartDiscountValueInput = { + relative?: Maybe; + absolute?: Maybe; + giftLineItem?: Maybe; +}; + +export type CartDraft = { + currency: Scalars["Currency"]; + country?: Maybe; + inventoryMode?: Maybe; + custom?: Maybe; + customerEmail?: Maybe; + shippingAddress?: Maybe; + billingAddress?: Maybe; + shippingMethod?: Maybe; + taxMode?: Maybe; + locale?: Maybe; + deleteDaysAfterLastModification?: Maybe; + itemShippingAddresses?: Maybe>; + discountCodes?: Maybe>; + lineItems?: Maybe>; + customLineItems?: Maybe>; + customerId?: Maybe; + externalTaxRateForShippingMethod?: Maybe; + anonymousId?: Maybe; + taxRoundingMode?: Maybe; + taxCalculationMode?: Maybe; + customerGroup?: Maybe; + shippingRateInput?: Maybe; + origin?: Maybe; + store?: Maybe; +}; + +export enum CartOrigin { + /** The cart was created by the merchant on behalf of the customer */ + Merchant = "Merchant", + /** The cart was created by the customer. This is the default value */ + Customer = "Customer" +} + +/** Fields to access carts. Includes direct access to a single cart and searching for carts. */ +export type CartQueryInterface = { + cart?: Maybe; + carts: CartQueryResult; +}; + +/** Fields to access carts. Includes direct access to a single cart and searching for carts. */ +export type CartQueryInterfaceCartArgs = { + id: Scalars["String"]; +}; + +/** Fields to access carts. Includes direct access to a single cart and searching for carts. */ +export type CartQueryInterfaceCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type CartQueryResult = { + __typename?: "CartQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CartScoreInput = { + dummy?: Maybe; +}; + +export type CartScoreType = ShippingRateInputType & { + __typename?: "CartScoreType"; + type: Scalars["String"]; +}; + +export enum CartState { + /** The cart was ordered. No further operations on the cart are allowed. */ + Ordered = "Ordered", + /** Anonymous cart whose content was merged into a customers cart on signin. No further operations on the cart are allowed. */ + Merged = "Merged", + /** The cart can be updated and ordered. It is the default state. */ + Active = "Active" +} + +export type CartUpdateAction = { + addCustomLineItem?: Maybe; + addDiscountCode?: Maybe; + addItemShippingAddress?: Maybe; + addLineItem?: Maybe; + addPayment?: Maybe; + addShoppingList?: Maybe; + applyDeltaToCustomLineItemShippingDetailsTargets?: Maybe< + ApplyCartDeltaToCustomLineItemShippingDetailsTargets + >; + applyDeltaToLineItemShippingDetailsTargets?: Maybe< + ApplyCartDeltaToLineItemShippingDetailsTargets + >; + changeCustomLineItemMoney?: Maybe; + changeCustomLineItemQuantity?: Maybe; + changeLineItemQuantity?: Maybe; + changeTaxCalculationMode?: Maybe; + changeTaxMode?: Maybe; + changeTaxRoundingMode?: Maybe; + recalculate?: Maybe; + removeCustomLineItem?: Maybe; + removeDiscountCode?: Maybe; + removeItemShippingAddress?: Maybe; + removeLineItem?: Maybe; + removePayment?: Maybe; + setAnonymousId?: Maybe; + setBillingAddress?: Maybe; + setCartTotalTax?: Maybe; + setCountry?: Maybe; + setCustomField?: Maybe; + setCustomLineItemCustomField?: Maybe; + setCustomLineItemCustomType?: Maybe; + setCustomLineItemShippingDetails?: Maybe< + SetCartCustomLineItemShippingDetails + >; + setCustomLineItemTaxAmount?: Maybe; + setCustomLineItemTaxRate?: Maybe; + setCustomShippingMethod?: Maybe; + setCustomType?: Maybe; + setCustomerEmail?: Maybe; + setCustomerGroup?: Maybe; + setCustomerId?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetCartDeleteDaysAfterLastModification + >; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setLineItemPrice?: Maybe; + setLineItemShippingDetails?: Maybe; + setLineItemTaxAmount?: Maybe; + setLineItemTaxRate?: Maybe; + setLineItemTotalPrice?: Maybe; + setLocale?: Maybe; + setShippingAddress?: Maybe; + setShippingMethod?: Maybe; + setShippingMethodTaxAmount?: Maybe; + setShippingMethodTaxRate?: Maybe; + setShippingRateInput?: Maybe; + updateItemShippingAddress?: Maybe; +}; + +export type CartValueInput = { + dummy?: Maybe; +}; + +export type CartValueType = ShippingRateInputType & { + __typename?: "CartValueType"; + type: Scalars["String"]; +}; + +export type Category = Versioned & { + __typename?: "Category"; + id: Scalars["String"]; + key?: Maybe; + version: Scalars["Long"]; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales: Array; + ancestorsRef: Array; + ancestors: Array; + parentRef?: Maybe; + parent?: Maybe; + orderHint: Scalars["String"]; + externalId?: Maybe; + metaTitle?: Maybe; + metaKeywords?: Maybe; + metaDescription?: Maybe; + /** Number of a products in the category subtree. */ + productCount: Scalars["Int"]; + /** Number of staged products in the category subtree. */ + stagedProductCount: Scalars["Int"]; + /** Number of direct child categories. */ + childCount: Scalars["Int"]; + /** Direct child categories. */ + children?: Maybe>; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + assets: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type CategoryNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryMetaTitleArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryMetaKeywordsArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryMetaDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategoryCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategoryCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategoryDraft = { + key?: Maybe; + name: Array; + description?: Maybe>; + custom?: Maybe; + slug: Array; + externalId?: Maybe; + metaTitle?: Maybe>; + metaDescription?: Maybe>; + metaKeywords?: Maybe>; + orderHint?: Maybe; + parent?: Maybe; + assets?: Maybe>; +}; + +export type CategoryOrderHint = { + __typename?: "CategoryOrderHint"; + categoryId: Scalars["String"]; + orderHint: Scalars["String"]; +}; + +export type CategoryOrderHintInput = { + uuid: Scalars["String"]; + orderHint: Scalars["String"]; +}; + +export type CategoryQueryResult = { + __typename?: "CategoryQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CategorySearch = { + __typename?: "CategorySearch"; + id: Scalars["String"]; + key?: Maybe; + version: Scalars["Long"]; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales: Array; + ancestorsRef: Array; + ancestors: Array; + parentRef?: Maybe; + parent?: Maybe; + externalId?: Maybe; + productCount: Scalars["Int"]; + stagedProductCount: Scalars["Int"]; + childCount: Scalars["Int"]; + productTypeNames: Array; + /** Direct child categories. */ + children: Array; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + orderHint: Scalars["String"]; + assets: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type CategorySearchNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySearchDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySearchSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type CategorySearchCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategorySearchCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CategorySearchResult = { + __typename?: "CategorySearchResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Int"]; + results: Array; +}; + +export type CategoryUpdateAction = { + addAsset?: Maybe; + changeAssetName?: Maybe; + changeAssetOrder?: Maybe; + changeName?: Maybe; + changeOrderHint?: Maybe; + changeSlug?: Maybe; + changeParent?: Maybe; + removeAsset?: Maybe; + setAssetCustomField?: Maybe; + setAssetCustomType?: Maybe; + setAssetDescription?: Maybe; + setAssetKey?: Maybe; + setAssetSources?: Maybe; + setAssetTags?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setMetaDescription?: Maybe; + setMetaKeywords?: Maybe; + setMetaTitle?: Maybe; + setExternalId?: Maybe; +}; + +export type ChangeAttributeName = { + attributeName: Scalars["String"]; + newAttributeName: Scalars["String"]; +}; + +export type ChangeAttributeOrder = { + attributeDefinitions: Array; +}; + +export type ChangeAttributeOrderByName = { + attributeNames: Array; +}; + +export type ChangeCartCustomLineItemMoney = { + customLineItemId: Scalars["String"]; + money: BaseMoneyInput; +}; + +export type ChangeCartCustomLineItemQuantity = { + customLineItemId: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ChangeCartDiscountCartPredicate = { + cartPredicate: Scalars["String"]; +}; + +export type ChangeCartDiscountIsActive = { + isActive: Scalars["Boolean"]; +}; + +export type ChangeCartDiscountName = { + name: Array; +}; + +export type ChangeCartDiscountRequiresDiscountCode = { + requiresDiscountCode: Scalars["Boolean"]; +}; + +export type ChangeCartDiscountSortOrder = { + sortOrder: Scalars["String"]; +}; + +export type ChangeCartDiscountStackingMode = { + stackingMode: StackingMode; +}; + +export type ChangeCartDiscountTarget = { + target: CartDiscountTargetInput; +}; + +export type ChangeCartDiscountValue = { + value: CartDiscountValueInput; +}; + +export type ChangeCartLineItemQuantity = { + lineItemId: Scalars["String"]; + quantity: Scalars["Long"]; + externalPrice?: Maybe; + externalTotalPrice?: Maybe; +}; + +export type ChangeCartTaxCalculationMode = { + taxCalculationMode: TaxCalculationMode; +}; + +export type ChangeCartTaxMode = { + taxMode: TaxMode; +}; + +export type ChangeCartTaxRoundingMode = { + taxRoundingMode: RoundingMode; +}; + +export type ChangeCategoryAssetName = { + name: Array; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type ChangeCategoryAssetOrder = { + assetOrder: Array; +}; + +export type ChangeCategoryName = { + name: Array; +}; + +export type ChangeCategoryOrderHint = { + orderHint: Scalars["String"]; +}; + +export type ChangeCategoryParent = { + parent: ResourceIdentifierInput; +}; + +export type ChangeCategorySlug = { + slug: Array; +}; + +export type ChangeCustomerAddress = { + addressId: Scalars["String"]; + address: AddressInput; +}; + +export type ChangeCustomerEmail = { + email: Scalars["String"]; +}; + +export type ChangeCustomerGroupName = { + name: Scalars["String"]; +}; + +export type ChangeDescription = { + description: Scalars["String"]; +}; + +export type ChangeDiscountCodeCartDiscounts = { + cartDiscounts: Array; +}; + +export type ChangeDiscountCodeGroups = { + groups: Array; +}; + +export type ChangeDiscountCodeIsActive = { + isActive: Scalars["Boolean"]; +}; + +export type ChangeEnumKey = { + attributeName: Scalars["String"]; + key: Scalars["String"]; + newKey: Scalars["String"]; +}; + +export type ChangeInputHint = { + attributeName: Scalars["String"]; + newValue: TextInputHint; +}; + +export type ChangeInventoryEntryQuantity = { + quantity: Scalars["Long"]; +}; + +export type ChangeIsSearchable = { + attributeName: Scalars["String"]; + isSearchable: Scalars["Boolean"]; +}; + +export type ChangeLabel = { + attributeName: Scalars["String"]; + label: Array; +}; + +export type ChangeLocalizedEnumValueLabel = { + attributeName: Scalars["String"]; + newValue: LocalizedEnumValueDraft; +}; + +export type ChangeLocalizedEnumValueOrder = { + attributeName: Scalars["String"]; + values: Array; +}; + +export type ChangeMyCartTaxMode = { + taxMode: TaxMode; +}; + +export type ChangeName = { + name: Scalars["String"]; +}; + +export type ChangeOrderPaymentState = { + paymentState: PaymentState; +}; + +export type ChangeOrderShipmentState = { + shipmentState: ShipmentState; +}; + +export type ChangeOrderState = { + orderState: OrderState; +}; + +export type ChangePlainEnumValueLabel = { + attributeName: Scalars["String"]; + newValue: PlainEnumValueDraft; +}; + +export type ChangePlainEnumValueOrder = { + attributeName: Scalars["String"]; + values: Array; +}; + +export type ChangeProductAssetName = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + name: Array; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type ChangeProductAssetOrder = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + assetOrder: Array; +}; + +export type ChangeProductDiscountIsActive = { + isActive: Scalars["Boolean"]; +}; + +export type ChangeProductDiscountName = { + name: Array; +}; + +export type ChangeProductDiscountPredicate = { + predicate: Scalars["String"]; +}; + +export type ChangeProductDiscountSortOrder = { + sortOrder: Scalars["String"]; +}; + +export type ChangeProductDiscountValue = { + value: ProductDiscountValueInput; +}; + +export type ChangeProductImageLabel = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + label?: Maybe; + staged?: Maybe; +}; + +export type ChangeProductMasterVariant = { + variantId?: Maybe; + sku?: Maybe; + staged?: Maybe; +}; + +export type ChangeProductName = { + name: Array; + staged?: Maybe; +}; + +export type ChangeProductPrice = { + priceId?: Maybe; + variantId?: Maybe; + sku?: Maybe; + price: ProductPriceDataInput; + catalog?: Maybe; + staged?: Maybe; +}; + +export type ChangeProductSlug = { + slug: Array; + staged?: Maybe; +}; + +export type ChangeProjectSettingsCountries = { + countries: Array; +}; + +export type ChangeProjectSettingsCurrencies = { + currencies: Array; +}; + +export type ChangeProjectSettingsLanguages = { + languages: Array; +}; + +export type ChangeProjectSettingsMessagesConfiguration = { + messagesConfiguration: MessagesConfigurationDraft; +}; + +export type ChangeProjectSettingsMessagesEnabled = { + messagesEnabled: Scalars["Boolean"]; +}; + +export type ChangeProjectSettingsName = { + name: Scalars["String"]; +}; + +export type ChangeShippingMethodIsDefault = { + isDefault: Scalars["Boolean"]; +}; + +export type ChangeShippingMethodName = { + name: Scalars["String"]; +}; + +export type ChangeShippingMethodTaxCategory = { + taxCategory: ResourceIdentifierInput; +}; + +export type ChangeShoppingListLineItemQuantity = { + lineItemId: Scalars["String"]; + quantity: Scalars["Int"]; +}; + +export type ChangeShoppingListLineItemsOrder = { + lineItemOrder: Array; +}; + +export type ChangeShoppingListName = { + name: Array; +}; + +export type ChangeShoppingListTextLineItemName = { + textLineItemId: Scalars["String"]; + name: Array; +}; + +export type ChangeShoppingListTextLineItemQuantity = { + textLineItemId: Scalars["String"]; + quantity: Scalars["Int"]; +}; + +export type ChangeShoppingListTextLineItemsOrder = { + textLineItemOrder: Array; +}; + +export type ChangeZoneName = { + name: Scalars["String"]; +}; + +export type Channel = Versioned & { + __typename?: "Channel"; + id: Scalars["String"]; + version: Scalars["Long"]; + key: Scalars["String"]; + roles: Array; + name?: Maybe; + nameAllLocales?: Maybe>; + description?: Maybe; + descriptionAllLocales?: Maybe>; + address?: Maybe
; + geoLocation?: Maybe; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ChannelNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ChannelDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ChannelCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ChannelQueryResult = { + __typename?: "ChannelQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ChannelReferenceIdentifier = { + __typename?: "ChannelReferenceIdentifier"; + typeId: Scalars["String"]; + id?: Maybe; + key?: Maybe; +}; + +export enum ChannelRole { + /** Role tells that this channel can be used to track inventory entries.Channels with this role can be treated as warehouses */ + InventorySupply = "InventorySupply", + /** Role tells that this channel can be used to expose products to a specific + * distribution channel. It can be used by the cart to select a product price. + */ + ProductDistribution = "ProductDistribution", + /** Role tells that this channel can be used to track order export activities. */ + OrderExport = "OrderExport", + /** Role tells that this channel can be used to track order import activities. */ + OrderImport = "OrderImport", + /** This role can be combined with some other roles (e.g. with `InventorySupply`) + * to represent the fact that this particular channel is the primary/master + * channel among the channels of the same type. + */ + Primary = "Primary" +} + +export type ClassificationShippingRateInput = ShippingRateInput & { + __typename?: "ClassificationShippingRateInput"; + key: Scalars["String"]; + type: Scalars["String"]; + labelAllLocales: Array; + label?: Maybe; +}; + +export type ClassificationShippingRateInputLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ClassificationShippingRateInputDraft = { + key: Scalars["String"]; +}; + +export type CreateApiClient = { + name: Scalars["String"]; + scope: Scalars["String"]; +}; + +export type CreateStore = { + key: Scalars["String"]; + name?: Maybe>; + languages?: Maybe>; +}; + +export type CreateZone = { + name: Scalars["String"]; + key?: Maybe; + description?: Maybe; + locations?: Maybe>; +}; + +/** A customer is a person purchasing products. Carts, Orders and Reviews can be associated to a customer. */ +export type Customer = Versioned & { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A customer is a person purchasing products. Carts, Orders and Reviews can be associated to a customer. */ +export type CustomerCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A customer is a person purchasing products. Carts, Orders and Reviews can be associated to a customer. */ +export type CustomerCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A field to access a customer's active cart. */ +export type CustomerActiveCartInterface = { + customerActiveCart?: Maybe; +}; + +/** A field to access a customer's active cart. */ +export type CustomerActiveCartInterfaceCustomerActiveCartArgs = { + customerId: Scalars["String"]; +}; + +/** A customer can be a member in a customer group (e.g. reseller, gold member). A + * customer group can be used in price calculations with special prices being + * assigned to certain customer groups. + */ +export type CustomerGroup = Versioned & { + __typename?: "CustomerGroup"; + id: Scalars["String"]; + version: Scalars["Long"]; + name: Scalars["String"]; + key?: Maybe; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A customer can be a member in a customer group (e.g. reseller, gold member). A + * customer group can be used in price calculations with special prices being + * assigned to certain customer groups. + */ +export type CustomerGroupCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A customer can be a member in a customer group (e.g. reseller, gold member). A + * customer group can be used in price calculations with special prices being + * assigned to certain customer groups. + */ +export type CustomerGroupCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CustomerGroupDraft = { + groupName: Scalars["String"]; + key?: Maybe; + custom?: Maybe; +}; + +export type CustomerGroupQueryResult = { + __typename?: "CustomerGroupQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CustomerGroupUpdateAction = { + changeName?: Maybe; + setKey?: Maybe; + setCustomType?: Maybe; + setCustomField?: Maybe; +}; + +/** Fields to access customer accounts. Includes direct access to a single customer and searching for customers. */ +export type CustomerQueryInterface = { + customer?: Maybe; + customers: CustomerQueryResult; +}; + +/** Fields to access customer accounts. Includes direct access to a single customer and searching for customers. */ +export type CustomerQueryInterfaceCustomerArgs = { + emailToken?: Maybe; + passwordToken?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +/** Fields to access customer accounts. Includes direct access to a single customer and searching for customers. */ +export type CustomerQueryInterfaceCustomersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type CustomerQueryResult = { + __typename?: "CustomerQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type CustomerSignInDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + anonymousCartId?: Maybe; + anonymousCartSignInMode?: Maybe; + anonymousId?: Maybe; + updateProductData?: Maybe; +}; + +export type CustomerSignInResult = { + __typename?: "CustomerSignInResult"; + customer: Customer; + cart?: Maybe; +}; + +export type CustomerSignMeInDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + activeCartSignInMode?: Maybe; + updateProductData?: Maybe; +}; + +export type CustomerSignMeUpDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + addresses?: Maybe>; + /** The index of the address in the `addresses` list. The + * `defaultBillingAddressId` of the customer will be set to the ID of that address. + */ + defaultBillingAddress?: Maybe; + /** The index of the address in the `addresses` list. The + * `defaultShippingAddressId` of the customer will be set to the ID of that address. + */ + defaultShippingAddress?: Maybe; + /** The indices of the shipping addresses in the `addresses` list. The + * `shippingAddressIds` of the `Customer` will be set to the IDs of that addresses. + */ + shippingAddresses?: Maybe>; + /** The indices of the billing addresses in the `addresses` list. The + * `billingAddressIds` of the customer will be set to the IDs of that addresses. + */ + billingAddresses?: Maybe>; + custom?: Maybe; + locale?: Maybe; + salutation?: Maybe; + key?: Maybe; + stores?: Maybe>; +}; + +export type CustomerSignUpDraft = { + email: Scalars["String"]; + password: Scalars["String"]; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + addresses?: Maybe>; + /** The index of the address in the `addresses` list. The + * `defaultBillingAddressId` of the customer will be set to the ID of that address. + */ + defaultBillingAddress?: Maybe; + /** The index of the address in the `addresses` list. The + * `defaultShippingAddressId` of the customer will be set to the ID of that address. + */ + defaultShippingAddress?: Maybe; + /** The indices of the shipping addresses in the `addresses` list. The + * `shippingAddressIds` of the `Customer` will be set to the IDs of that addresses. + */ + shippingAddresses?: Maybe>; + /** The indices of the billing addresses in the `addresses` list. The + * `billingAddressIds` of the customer will be set to the IDs of that addresses. + */ + billingAddresses?: Maybe>; + custom?: Maybe; + locale?: Maybe; + salutation?: Maybe; + key?: Maybe; + stores?: Maybe>; + customerNumber?: Maybe; + anonymousCartId?: Maybe; + externalId?: Maybe; + customerGroup?: Maybe; + isEmailVerified?: Maybe; + anonymousId?: Maybe; +}; + +export type CustomerToken = { + __typename?: "CustomerToken"; + id: Scalars["String"]; + customerId: Scalars["String"]; + createdAt: Scalars["DateTime"]; + expiresAt: Scalars["DateTime"]; + value: Scalars["String"]; +}; + +export type CustomerPasswordToken = { + customerId: Scalars["String"]; + expiresAt: Scalars["DateTime"]; + value: Scalars["String"]; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +} + +export type CustomerUpdateAction = { + addAddress?: Maybe; + addBillingAddressId?: Maybe; + addShippingAddressId?: Maybe; + addStore?: Maybe; + changeAddress?: Maybe; + changeEmail?: Maybe; + removeAddress?: Maybe; + removeBillingAddressId?: Maybe; + removeShippingAddressId?: Maybe; + removeStore?: Maybe; + setCompanyName?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setCustomerGroup?: Maybe; + setKey?: Maybe; + setLocale?: Maybe; + setCustomerNumber?: Maybe; + setDateOfBirth?: Maybe; + setDefaultBillingAddress?: Maybe; + setDefaultShippingAddress?: Maybe; + setExternalId?: Maybe; + setFirstName?: Maybe; + setLastName?: Maybe; + setMiddleName?: Maybe; + setSalutation?: Maybe; + setStores?: Maybe; + setTitle?: Maybe; + setVatId?: Maybe; +}; + +export type CustomField = { + name: Scalars["String"]; +}; + +/** A key-value pair representing the field name and value of one single custom field. + * + * The value of this custom field consists of escaped JSON based on the FieldDefinition of the Type. + * + * Examples for `value`: + * + * * FieldType `String`: `"\"This is a string\""` + * * FieldType `DateTimeType`: `"\"2001-09-11T14:00:00.000Z\""` + * * FieldType `Number`: `"4"` + * * FieldType `Set` with an elementType of `String`: `"[\"This is a string\", \"This is another string\"]"` + * * FieldType `Reference`: `"{\"id\", \"b911b62d-353a-4388-93ee-8d488d9af962\", \"typeId\", \"product\"}"` + */ +export type CustomFieldInput = { + name: Scalars["String"]; + /** The value of this custom field consists of escaped JSON based on the FieldDefinition of the Type. + * + * Examples for `value`: + * + * * FieldType `String`: `"\"This is a string\""` + * * FieldType `DateTimeType`: `"\"2001-09-11T14:00:00.000Z\""` + * * FieldType `Number`: `"4"` + * * FieldType `Set` with an elementType of `String`: `"[\"This is a string\", \"This is another string\"]"` + * * FieldType `Reference`: `"{\"id\", \"b911b62d-353a-4388-93ee-8d488d9af962\", \"typeId\", \"product\"}"` + */ + value: Scalars["String"]; +}; + +export type CustomFieldsDraft = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; +}; + +export type CustomFieldsType = { + __typename?: "CustomFieldsType"; + typeRef: Reference; + type?: Maybe; + /** This field contains non-typed data. For a typed alternative, have a look at `customFields`. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields: Type; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type CustomFieldsTypeCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CustomFieldsTypeCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItem = { + __typename?: "CustomLineItem"; + id: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + money: BaseMoney; + totalPrice: Money; + slug: Scalars["String"]; + quantity: Scalars["Long"]; + state: Array; + taxCategory?: Maybe; + taxCategoryRef?: Maybe; + taxRate?: Maybe; + discountedPricePerQuantity: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItemCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A custom line item is a generic item that can be added to the cart but is not + * bound to a product. You can use it for discounts (negative money), vouchers, + * complex cart rules, additional services or fees. You control the lifecycle of this item. + */ +export type CustomLineItemCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type CustomLineItemDraft = { + name: Array; + money: BaseMoneyInput; + slug: Scalars["String"]; + taxCategory?: Maybe; + externalTaxRate?: Maybe; + quantity?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; +}; + +export type CustomLineItemReturnItem = ReturnItem & { + __typename?: "CustomLineItemReturnItem"; + type: Scalars["String"]; + customLineItemId: Scalars["String"]; + id: Scalars["String"]; + quantity: Scalars["Long"]; + comment?: Maybe; + shipmentState: ReturnShipmentState; + paymentState: ReturnPaymentState; + lastModifiedAt: Scalars["DateTime"]; + createdAt: Scalars["DateTime"]; +}; + +export type CustomLineItemsTarget = CartDiscountTarget & { + __typename?: "CustomLineItemsTarget"; + predicate: Scalars["String"]; + type: Scalars["String"]; +}; + +export type CustomLineItemsTargetInput = { + predicate: Scalars["String"]; +}; + +export type CustomSuggestTokenizerInput = { + text: Scalars["String"]; + suggestTokenizer?: Maybe; +}; + +export type DateAttribute = Attribute & { + __typename?: "DateAttribute"; + value: Scalars["Date"]; + name: Scalars["String"]; +}; + +export type DateAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "DateAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type DateField = CustomField & { + __typename?: "DateField"; + value: Scalars["Date"]; + name: Scalars["String"]; +}; + +export type DateTimeAttribute = Attribute & { + __typename?: "DateTimeAttribute"; + value: Scalars["DateTime"]; + name: Scalars["String"]; +}; + +export type DateTimeAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "DateTimeAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type DateTimeField = CustomField & { + __typename?: "DateTimeField"; + value: Scalars["DateTime"]; + name: Scalars["String"]; +}; + +export type DateTimeType = FieldType & { + __typename?: "DateTimeType"; + name: Scalars["String"]; +}; + +export type DateType = FieldType & { + __typename?: "DateType"; + name: Scalars["String"]; +}; + +export type Delivery = { + __typename?: "Delivery"; + id: Scalars["String"]; + createdAt: Scalars["DateTime"]; + items: Array; + parcels: Array; + address?: Maybe
; +}; + +export type DeliveryItem = { + __typename?: "DeliveryItem"; + id: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type DeliveryItemDraftType = { + id: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type Dimensions = { + __typename?: "Dimensions"; + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +export type DimensionsInput = { + width: Scalars["Int"]; + height: Scalars["Int"]; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCode = Versioned & { + __typename?: "DiscountCode"; + code: Scalars["String"]; + isActive: Scalars["Boolean"]; + maxApplications?: Maybe; + maxApplicationsPerCustomer?: Maybe; + cartPredicate?: Maybe; + applicationVersion?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + groups: Array; + name?: Maybe; + description?: Maybe; + cartDiscounts: Array; + nameAllLocales?: Maybe>; + descriptionAllLocales?: Maybe>; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** How many times this discount code was applied (only applications that were part of a successful checkout are considered) */ + applicationCount: Scalars["Long"]; + cartDiscountRefs: Array; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** With discount codes it is possible to give specific cart discounts to an + * eligible amount of users. They are defined by a string value which can be added + * to a cart so that specific cart discounts can be applied to the cart. + */ +export type DiscountCodeCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type DiscountCodeDraft = { + code: Scalars["String"]; + name?: Maybe>; + description?: Maybe>; + cartDiscounts: Array; + isActive?: Maybe; + maxApplications?: Maybe; + maxApplicationsPerCustomer?: Maybe; + cartPredicate?: Maybe; + custom?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + groups?: Maybe>; +}; + +export type DiscountCodeInfo = { + __typename?: "DiscountCodeInfo"; + discountCodeRef: Reference; + state?: Maybe; + discountCode?: Maybe; +}; + +export type DiscountCodeQueryResult = { + __typename?: "DiscountCodeQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum DiscountCodeState { + /** The discount code is active and none of the discounts were applied because the + * discount application was stopped by one discount that has the StackingMode of + * StopAfterThisDiscount defined + */ + ApplicationStoppedByPreviousDiscount = "ApplicationStoppedByPreviousDiscount", + /** The discount code is not valid or it does not contain any valid cart + * discounts. Validity is determined based on the validFrom and validUntil dates + */ + NotValid = "NotValid", + /** maxApplications or maxApplicationsPerCustomer for discountCode has been reached. */ + MaxApplicationReached = "MaxApplicationReached", + /** The discount code is active and it contains at least one active and valid + * CartDiscount. The discount code cartPredicate matches the cart and at least + * one of the contained active discount’s cart predicates matches the cart. + */ + MatchesCart = "MatchesCart", + /** The discount code is active and it contains at least one active and valid + * CartDiscount. But its cart predicate does not match the cart or none of the + * contained active discount’s cart predicates match the cart + */ + DoesNotMatchCart = "DoesNotMatchCart", + /** The discount code is not active or it does not contain any active cart discounts. */ + NotActive = "NotActive" +} + +export type DiscountCodeUpdateAction = { + changeCartDiscounts?: Maybe; + changeGroups?: Maybe; + changeIsActive?: Maybe; + setCartPredicate?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDescription?: Maybe; + setMaxApplications?: Maybe; + setMaxApplicationsPerCustomer?: Maybe< + SetDiscountCodeMaxApplicationsPerCustomer + >; + setName?: Maybe; + setValidFrom?: Maybe; + setValidFromAndUntil?: Maybe; + setValidUntil?: Maybe; +}; + +export type DiscountedLineItemPortion = { + __typename?: "DiscountedLineItemPortion"; + discount?: Maybe; + discountRef: Reference; + discountedAmount: BaseMoney; +}; + +export type DiscountedLineItemPrice = { + __typename?: "DiscountedLineItemPrice"; + value: BaseMoney; + includedDiscounts: Array; +}; + +export type DiscountedLineItemPriceForQuantity = { + __typename?: "DiscountedLineItemPriceForQuantity"; + quantity: Scalars["Long"]; + discountedPrice: DiscountedLineItemPrice; +}; + +export type DiscountedProductPriceValue = { + __typename?: "DiscountedProductPriceValue"; + value: BaseMoney; + discountRef: Reference; + discount?: Maybe; + /** Temporal. Will be renamed some time in the future. Please use 'discount'. */ + discountRel?: Maybe; +}; + +export type DiscountedProductPriceValueInput = { + value: BaseMoneyInput; + discount: ReferenceInput; +}; + +export type EnumAttribute = Attribute & { + __typename?: "EnumAttribute"; + key: Scalars["String"]; + label: Scalars["String"]; + name: Scalars["String"]; +}; + +export type EnumAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "EnumAttributeDefinitionType"; + values: PlainEnumValueResult; + name: Scalars["String"]; +}; + +export type EnumAttributeDefinitionTypeValuesArgs = { + includeKeys?: Maybe>; + excludeKeys?: Maybe>; + limit?: Maybe; + offset?: Maybe; + sort?: Maybe>; +}; + +export type EnumField = CustomField & { + __typename?: "EnumField"; + key: Scalars["String"]; + name: Scalars["String"]; +}; + +export type EnumType = FieldType & { + __typename?: "EnumType"; + values: Array; + name: Scalars["String"]; +}; + +export type EnumTypeDraft = { + values: Array; +}; + +export type EnumValue = { + __typename?: "EnumValue"; + key: Scalars["String"]; + label: Scalars["String"]; +}; + +export type ExternalDiscountValue = ProductDiscountValue & { + __typename?: "ExternalDiscountValue"; + type: Scalars["String"]; +}; + +export type ExternalDiscountValueInput = { + dummy?: Maybe; +}; + +export type ExternalLineItemTotalPriceDraft = { + price: BaseMoneyInput; + totalPrice: MoneyInput; +}; + +export type ExternalOAuth = { + __typename?: "ExternalOAuth"; + url: Scalars["String"]; + authorizationHeader: Scalars["String"]; +}; + +export type ExternalOAuthDraft = { + url: Scalars["String"]; + authorizationHeader: Scalars["String"]; +}; + +export type ExternalTaxAmountDraft = { + totalGross: MoneyInput; + taxRate: ExternalTaxRateDraft; +}; + +export type ExternalTaxRateDraft = { + name: Scalars["String"]; + amount: Scalars["Float"]; + country: Scalars["Country"]; + state?: Maybe; + subRates?: Maybe>; + includedInPrice?: Maybe; +}; + +/** Field definitions describe custom fields and allow you to define some meta-information associated with the field. */ +export type FieldDefinition = { + __typename?: "FieldDefinition"; + name: Scalars["String"]; + required: Scalars["Boolean"]; + inputHint: TextInputHint; + label?: Maybe; + labelAllLocales: Array; + type: FieldType; +}; + +/** Field definitions describe custom fields and allow you to define some meta-information associated with the field. */ +export type FieldDefinitionLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type FieldType = { + name: Scalars["String"]; +}; + +export type Geometry = { + type: Scalars["String"]; +}; + +export type GiftLineItemValue = CartDiscountValue & { + __typename?: "GiftLineItemValue"; + type: Scalars["String"]; + variantId: Scalars["Int"]; + productRef: ProductReferenceIdentifier; + distributionChannelRef?: Maybe; + supplyChannelRef?: Maybe; +}; + +export type GiftLineItemValueInput = { + product: ResourceIdentifierInput; + variantId: Scalars["Int"]; + distributionChannel?: Maybe; + supplyChannel?: Maybe; +}; + +export type HighPrecisionMoney = BaseMoney & { + __typename?: "HighPrecisionMoney"; + type: Scalars["String"]; + currencyCode: Scalars["Currency"]; + preciseAmount: Scalars["Long"]; + centAmount: Scalars["Long"]; + fractionDigits: Scalars["Int"]; +}; + +export type HighPrecisionMoneyInput = { + currencyCode: Scalars["Currency"]; + preciseAmount: Scalars["Long"]; + fractionDigits: Scalars["Int"]; + centAmount?: Maybe; +}; + +export type Image = { + __typename?: "Image"; + url: Scalars["String"]; + dimensions: Dimensions; + label?: Maybe; +}; + +export type ImageInput = { + url: Scalars["String"]; + label?: Maybe; + dimensions: DimensionsInput; +}; + +export type ImportOrderCustomLineItemState = { + customLineItemId: Scalars["String"]; + state: Array; +}; + +export type ImportOrderLineItemState = { + lineItemId: Scalars["String"]; + state: Array; +}; + +export type Initiator = { + __typename?: "Initiator"; + isPlatformClient?: Maybe; + user?: Maybe; + externalUserId?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + clientId?: Maybe; +}; + +export type InStore = CartQueryInterface & + CustomerActiveCartInterface & + OrderQueryInterface & + CustomerQueryInterface & + ShippingMethodsByCartInterface & + MeFieldInterface & { + __typename?: "InStore"; + /** This field can only be used with an access token created with the password flow or with an anonymous session. + * + * It gives access to the data that is specific to the customer or the anonymous session linked to the access token. + */ + me: InStoreMe; + shippingMethodsByCart: Array; + customer?: Maybe; + customers: CustomerQueryResult; + cart?: Maybe; + carts: CartQueryResult; + customerActiveCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + }; + +export type InStoreShippingMethodsByCartArgs = { + id: Scalars["String"]; +}; + +export type InStoreCustomerArgs = { + emailToken?: Maybe; + passwordToken?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type InStoreCustomersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreCartArgs = { + id: Scalars["String"]; +}; + +export type InStoreCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreCustomerActiveCartArgs = { + customerId: Scalars["String"]; +}; + +export type InStoreOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type InStoreOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreMe = MeQueryInterface & { + __typename?: "InStoreMe"; + customer?: Maybe; + cart?: Maybe; + carts: CartQueryResult; + activeCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +export type InStoreMeCartArgs = { + id: Scalars["String"]; +}; + +export type InStoreMeCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreMeOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type InStoreMeOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InStoreMeShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type InStoreMeShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type InterfaceInteractionsRaw = { + __typename?: "InterfaceInteractionsRaw"; + typeRef: Reference; + type?: Maybe; + fields: Array; +}; + +export type InterfaceInteractionsRawFieldsArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type InterfaceInteractionsRawResult = { + __typename?: "InterfaceInteractionsRawResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +/** Inventory allows you to track stock quantity per SKU and optionally per supply channel */ +export type InventoryEntry = Versioned & { + __typename?: "InventoryEntry"; + sku: Scalars["String"]; + supplyChannel?: Maybe; + quantityOnStock: Scalars["Long"]; + availableQuantity: Scalars["Long"]; + restockableInDays?: Maybe; + expectedDelivery?: Maybe; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** Inventory allows you to track stock quantity per SKU and optionally per supply channel */ +export type InventoryEntryCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Inventory allows you to track stock quantity per SKU and optionally per supply channel */ +export type InventoryEntryCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type InventoryEntryDraft = { + sku: Scalars["String"]; + quantityOnStock?: Maybe; + restockableInDays?: Maybe; + expectedDelivery?: Maybe; + supplyChannel?: Maybe; + custom?: Maybe; +}; + +export type InventoryEntryQueryResult = { + __typename?: "InventoryEntryQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type InventoryEntryUpdateAction = { + addQuantity?: Maybe; + changeQuantity?: Maybe; + removeQuantity?: Maybe; + setRestockableInDays?: Maybe; + setExpectedDelivery?: Maybe; + setSupplyChannel?: Maybe; + setCustomType?: Maybe; + setCustomField?: Maybe; +}; + +export enum InventoryMode { + /** Adding items to cart and ordering is independent of inventory. No inventory checks or modifications. + * This is the default mode for a new cart. + */ + None = "None", + /** Creating an order will fail with an OutOfStock error if an unavailable line item exists. Line items in the cart + * are only reserved for the duration of the ordering transaction. + */ + ReserveOnOrder = "ReserveOnOrder", + /** Orders are tracked on inventory. That means, ordering a LineItem will decrement the available quantity on the + * respective InventoryEntry. Creating an order will succeed even if the line item’s available quantity is zero or + * negative. But creating an order will fail with an OutOfStock error if no matching inventory entry exists for a + * line item. + */ + TrackOnly = "TrackOnly" +} + +export type IOsUserType = Type & { + __typename?: "iOSUserType"; + typeRef: Reference; + type: TypeDefinition; + apnsToken?: Maybe; + myStore?: Maybe; +}; + +export type ItemShippingDetails = { + __typename?: "ItemShippingDetails"; + targets: Array; + valid: Scalars["Boolean"]; +}; + +export type ItemShippingDetailsDraft = { + targets: Array; +}; + +export type ItemShippingDetailsDraftType = { + targets: Array; +}; + +export type ItemShippingTarget = { + __typename?: "ItemShippingTarget"; + addressKey: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ItemState = { + __typename?: "ItemState"; + quantity: Scalars["Long"]; + stateRef: Reference; + state?: Maybe; +}; + +export type ItemStateDraftType = { + quantity: Scalars["Long"]; + state: ReferenceInput; +}; + +export type KeyReference = { + __typename?: "KeyReference"; + typeId: Scalars["String"]; + key: Scalars["String"]; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItem = { + __typename?: "LineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + productSlug?: Maybe; + productType?: Maybe; + productTypeRef?: Maybe; + variant?: Maybe; + price: ProductPrice; + taxedPrice?: Maybe; + totalPrice?: Maybe; + quantity: Scalars["Long"]; + state: Array; + taxRate?: Maybe; + supplyChannel?: Maybe; + supplyChannelRef?: Maybe; + distributionChannel?: Maybe; + distributionChannelRef?: Maybe; + discountedPricePerQuantity: Array; + lineItemMode: LineItemMode; + priceMode: LineItemPriceMode; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + inventoryMode?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemProductSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** A line item is a snapshot of a product variant at the time it was added to the cart. + * + * Since a product variant may change at any time, the ProductVariant data is copied into the field variant. + * The relation to the Product is kept but the line item will not automatically update if the product variant changes. + * On the cart, the line item can be updated manually. The productSlug refers to the current version of the product. + * It can be used to link to the product. If the product has been deleted, the line item remains but refers to a + * non-existent product and the productSlug is left empty. + * + * Please also note that creating an order is impossible if the product or product + * variant a line item relates to has been deleted. + */ +export type LineItemCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type LineItemDraft = { + productId?: Maybe; + sku?: Maybe; + quantity?: Maybe; + variantId?: Maybe; + supplyChannel?: Maybe; + distributionChannel?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + externalTaxRate?: Maybe; + externalPrice?: Maybe; + externalTotalPrice?: Maybe; +}; + +export enum LineItemMode { + /** The line item was added automatically, because a discount has added a free gift to the cart. + * The quantity can not be increased, and it won’t be merged when the same product variant is added. + * If the gift is removed, an entry is added to the "refusedGifts" array and the discount won’t be applied again + * to the cart. The price can not be changed externally. + * All other updates, such as the ones related to custom fields, can be used. + */ + GiftLineItem = "GiftLineItem", + /** The line item was added during cart creation or with the update action addLineItem. Its quantity can be + * changed without restrictions. + */ + Standard = "Standard" +} + +export enum LineItemPriceMode { + /** The price is selected form the product variant. This is the default mode. */ + Platform = "Platform", + /** The line item price was set externally. Cart discounts can apply to line items + * with this price mode. All update actions that change the quantity of a line + * item with this price mode require the externalPrice field to be given. + */ + ExternalPrice = "ExternalPrice", + /** The line item price with the total was set externally. */ + ExternalTotal = "ExternalTotal" +} + +export type LineItemReturnItem = ReturnItem & { + __typename?: "LineItemReturnItem"; + type: Scalars["String"]; + lineItemId: Scalars["String"]; + id: Scalars["String"]; + quantity: Scalars["Long"]; + comment?: Maybe; + shipmentState: ReturnShipmentState; + paymentState: ReturnPaymentState; + lastModifiedAt: Scalars["DateTime"]; + createdAt: Scalars["DateTime"]; +}; + +export type LineItemsTarget = CartDiscountTarget & { + __typename?: "LineItemsTarget"; + predicate: Scalars["String"]; + type: Scalars["String"]; +}; + +export type LineItemsTargetInput = { + predicate: Scalars["String"]; +}; + +export type LocalizableEnumAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "LocalizableEnumAttributeDefinitionType"; + values: LocalizableEnumValueTypeResult; + name: Scalars["String"]; +}; + +export type LocalizableEnumAttributeDefinitionTypeValuesArgs = { + includeKeys?: Maybe>; + excludeKeys?: Maybe>; + limit?: Maybe; + offset?: Maybe; + sort?: Maybe>; +}; + +export type LocalizableEnumTypeDraft = { + values: Array; +}; + +export type LocalizableEnumValueType = { + __typename?: "LocalizableEnumValueType"; + key: Scalars["String"]; + label?: Maybe; + labelAllLocales: Array; +}; + +export type LocalizableEnumValueTypeLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type LocalizableEnumValueTypeResult = { + __typename?: "LocalizableEnumValueTypeResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +export type LocalizableTextAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "LocalizableTextAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type LocalizedEnumAttribute = Attribute & { + __typename?: "LocalizedEnumAttribute"; + key: Scalars["String"]; + label?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedEnumAttributeLabelArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedEnumField = CustomField & { + __typename?: "LocalizedEnumField"; + key: Scalars["String"]; + label?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedEnumFieldLabelArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedEnumType = FieldType & { + __typename?: "LocalizedEnumType"; + values: Array; + name: Scalars["String"]; +}; + +export type LocalizedEnumValue = { + __typename?: "LocalizedEnumValue"; + key: Scalars["String"]; + label?: Maybe; + labelAllLocales: Array; +}; + +export type LocalizedEnumValueLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type LocalizedEnumValueDraft = { + key: Scalars["String"]; + label: Array; +}; + +export type LocalizedEnumValueInput = { + key: Scalars["String"]; + label: Array; +}; + +export type LocalizedString = { + __typename?: "LocalizedString"; + locale: Scalars["Locale"]; + value: Scalars["String"]; +}; + +export type LocalizedStringAttribute = Attribute & { + __typename?: "LocalizedStringAttribute"; + value?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedStringAttributeValueArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedStringField = CustomField & { + __typename?: "LocalizedStringField"; + value?: Maybe; + name: Scalars["String"]; +}; + +export type LocalizedStringFieldValueArgs = { + locale: Scalars["Locale"]; +}; + +export type LocalizedStringItemInputType = { + locale: Scalars["Locale"]; + value: Scalars["String"]; +}; + +export type LocalizedStringType = FieldType & { + __typename?: "LocalizedStringType"; + name: Scalars["String"]; +}; + +export type LocalizedText = { + text: Scalars["String"]; + locale: Scalars["Locale"]; +}; + +export type Location = { + __typename?: "Location"; + country: Scalars["Country"]; + state?: Maybe; +}; + +/** Sunrise Product Data Set Structure */ +export type MainProductType = ProductType & { + __typename?: "mainProductType"; + productTypeId: Scalars["String"]; + creationDate?: Maybe; + articleNumberManufacturer?: Maybe; + articleNumberMax?: Maybe; + matrixId?: Maybe; + baseId?: Maybe; + designer?: Maybe; + madeInItaly?: Maybe; + completeTheLook?: Maybe>; + commonSize?: Maybe; + size?: Maybe; + color?: Maybe; + colorFreeDefinition?: Maybe; + details?: Maybe>; + style?: Maybe; + gender?: Maybe; + season?: Maybe; + isOnStock?: Maybe; + isLook?: Maybe; + lookProducts?: Maybe>; + seasonNew?: Maybe; + sapExternalId?: Maybe; +}; + +export type Me = MeQueryInterface & { + __typename?: "Me"; + customer?: Maybe; + cart?: Maybe; + carts: CartQueryResult; + activeCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +export type MeCartArgs = { + id: Scalars["String"]; +}; + +export type MeCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MeOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type MeShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +/** The me field gives access to the data that is specific to the customer or anonymous session linked to the access token. */ +export type MeFieldInterface = { + me: MeQueryInterface; +}; + +export type MeQueryInterface = { + cart?: Maybe; + carts: CartQueryResult; + activeCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +export type MeQueryInterfaceCartArgs = { + id: Scalars["String"]; +}; + +export type MeQueryInterfaceCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeQueryInterfaceOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MeQueryInterfaceOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MeQueryInterfaceShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type MeQueryInterfaceShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type MessagesConfiguration = { + __typename?: "MessagesConfiguration"; + enabled: Scalars["Boolean"]; + deleteDaysAfterCreation?: Maybe; +}; + +export type MessagesConfigurationDraft = { + enabled: Scalars["Boolean"]; + deleteDaysAfterCreation: Scalars["Int"]; +}; + +export type Money = BaseMoney & { + __typename?: "Money"; + type: Scalars["String"]; + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; + /** For the `Money` it equals to the default number of fraction digits used with the currency. */ + fractionDigits: Scalars["Int"]; +}; + +export type MoneyAttribute = Attribute & { + __typename?: "MoneyAttribute"; + centAmount: Scalars["Long"]; + currencyCode: Scalars["Currency"]; + name: Scalars["String"]; +}; + +export type MoneyAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "MoneyAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type MoneyDraft = { + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; +}; + +export type MoneyField = CustomField & { + __typename?: "MoneyField"; + centAmount: Scalars["Long"]; + currencyCode: Scalars["Currency"]; + name: Scalars["String"]; +}; + +export type MoneyInput = { + currencyCode: Scalars["Currency"]; + centAmount: Scalars["Long"]; +}; + +export type MoneyType = FieldType & { + __typename?: "MoneyType"; + name: Scalars["String"]; +}; + +export type MoveProductImageToPosition = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + position: Scalars["Int"]; + staged?: Maybe; +}; + +export type MultiBuyCustomLineItemsTarget = CartDiscountTarget & { + __typename?: "MultiBuyCustomLineItemsTarget"; + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode: SelectionMode; + type: Scalars["String"]; +}; + +export type MultiBuyCustomLineItemsTargetInput = { + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode?: Maybe; +}; + +export type MultiBuyLineItemsTarget = CartDiscountTarget & { + __typename?: "MultiBuyLineItemsTarget"; + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode: SelectionMode; + type: Scalars["String"]; +}; + +export type MultiBuyLineItemsTargetInput = { + predicate: Scalars["String"]; + triggerQuantity: Scalars["Long"]; + discountedQuantity: Scalars["Long"]; + maxOccurrence?: Maybe; + selectionMode?: Maybe; +}; + +export type Mutation = { + __typename?: "Mutation"; + createCustomerGroup?: Maybe; + updateCustomerGroup?: Maybe; + deleteCustomerGroup?: Maybe; + createCategory?: Maybe; + updateCategory?: Maybe; + deleteCategory?: Maybe; + createProductType?: Maybe; + updateProductType?: Maybe; + deleteProductType?: Maybe; + createShippingMethod?: Maybe; + updateShippingMethod?: Maybe; + deleteShippingMethod?: Maybe; + createZone?: Maybe; + updateZone?: Maybe; + deleteZone?: Maybe; + createTaxCategory?: Maybe; + updateTaxCategory?: Maybe; + deleteTaxCategory?: Maybe; + createDiscountCode?: Maybe; + updateDiscountCode?: Maybe; + deleteDiscountCode?: Maybe; + createCartDiscount?: Maybe; + updateCartDiscount?: Maybe; + deleteCartDiscount?: Maybe; + createProductDiscount?: Maybe; + updateProductDiscount?: Maybe; + deleteProductDiscount?: Maybe; + createProduct?: Maybe; + updateProduct?: Maybe; + deleteProduct?: Maybe; + /** Creates a customer. If an anonymous cart is given then the cart is assigned to + * the created customer and the version number of the Cart will increase. If the + * id of an anonymous session is given, all carts and orders will be assigned to + * the created customer. + */ + customerSignUp: CustomerSignInResult; + /** Retrieves the authenticated customer (a customer that matches the given email/password pair). + * + * There may be carts and orders created before the sign in that should be + * assigned to the customer account. With the `anonymousCartId`, a single + * anonymous cart can be assigned. With the `anonymousId`, all orders and carts + * that have this `anonymousId` set will be assigned to the customer. + * If both `anonymousCartId` and `anonymousId` are given, the anonymous cart must have the `anonymousId`. + * + * Additionally, there might also exist one or more active customer carts from an + * earlier session. On customer sign in there are several ways how to proceed + * with this cart and the cart referenced by the `anonymousCartId`. + * + * * If the customer does not have a cart yet, the anonymous cart becomes the customer's cart. + * * If the customer already has one or more carts, the content of the anonymous + * cart will be copied to the customer's active cart that has been modified most recently. + * + * In this case the `CartState` of the anonymous cart gets changed to `Merged` + * while the customer's cart remains the `Active` cart. + * + * If a `LineItem` in the anonymous cart matches an existing line item, or a + * `CustomLineItem` matches an existing custom line item in the customer's cart, + * the maximum quantity of both line items is used as the new quantity. + * + * `ItemShippingDetails` are copied from the item with the highest quantity. + * + * If `itemShippingAddresses` are different in the two carts, the resulting cart + * contains the addresses of both the customer cart and the anonymous cart. + * + * Note, that it is not possible to merge carts that differ in their currency (set during creation of the cart). + * + * If a cart is is returned as part of the `CustomerSignInResult`, it has been + * recalculated (it will have up-to-date prices, taxes and discounts, and invalid + * line items have been removed). + */ + customerSignIn: CustomerSignInResult; + updateCustomer?: Maybe; + deleteCustomer?: Maybe; + customerChangePassword?: Maybe; + /** The following workflow can be used to reset the customer’s password: + * + * 1. Create a password reset token and send it embedded in a link to the customer. + * 2. When the customer clicks on the link, you may optionally retrieve customer by password token. + * 3. When the customer entered new password, use reset customer’s password to reset the password. + */ + customerResetPassword?: Maybe; + /** Verifies customer's email using a token. */ + customerConfirmEmail?: Maybe; + /** The token value is used to reset the password of the customer with the given + * email. The token is valid only for 10 minutes. + */ + customerCreatePasswordResetToken?: Maybe; + customerCreateEmailVerificationToken: CustomerToken; + /** If used with an access token for Anonymous Sessions, all orders and carts + * belonging to the anonymousId will be assigned to the newly created customer. + */ + customerSignMeUp: CustomerSignInResult; + /** Retrieves the authenticated customer (a customer that matches the given email/password pair). + * + * If used with an access token for Anonymous Sessions, all orders and carts + * belonging to the `anonymousId` will be assigned to the newly created customer. + * + * * If the customer does not have a cart yet, the anonymous cart that was + * modified most recently becomes the customer's cart. + * * If the customer already has a cart, the most recently modified anonymous + * cart will be handled according to the `AnonymousCartSignInMode`. + * + * If a cart is is returned as part of the `CustomerSignInResult`, it has been + * recalculated (it will have up-to-date prices, taxes and discounts, and invalid + * line items have been removed). + */ + customerSignMeIn: CustomerSignInResult; + updateMyCustomer?: Maybe; + deleteMyCustomer?: Maybe; + customerChangeMyPassword?: Maybe; + customerConfirmMyEmail?: Maybe; + customerResetMyPassword?: Maybe; + createInventoryEntry?: Maybe; + updateInventoryEntry?: Maybe; + deleteInventoryEntry?: Maybe; + createCart?: Maybe; + updateCart?: Maybe; + deleteCart?: Maybe; + replicateCart?: Maybe; + createMyCart?: Maybe; + updateMyCart?: Maybe; + deleteMyCart?: Maybe; + createOrderFromCart?: Maybe; + updateOrder?: Maybe; + deleteOrder?: Maybe; + createMyOrderFromCart?: Maybe; + createShoppingList?: Maybe; + updateShoppingList?: Maybe; + deleteShoppingList?: Maybe; + createMyShoppingList?: Maybe; + updateMyShoppingList?: Maybe; + deleteMyShoppingList?: Maybe; + updateProject?: Maybe; + createStore?: Maybe; + updateStore?: Maybe; + deleteStore?: Maybe; + createApiClient?: Maybe; + deleteApiClient?: Maybe; +}; + +export type MutationCreateCustomerGroupArgs = { + draft: CustomerGroupDraft; +}; + +export type MutationUpdateCustomerGroupArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCustomerGroupArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateCategoryArgs = { + draft: CategoryDraft; +}; + +export type MutationUpdateCategoryArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCategoryArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateProductTypeArgs = { + draft: ProductTypeDraft; +}; + +export type MutationUpdateProductTypeArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteProductTypeArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateShippingMethodArgs = { + draft: ShippingMethodDraft; +}; + +export type MutationUpdateShippingMethodArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteShippingMethodArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateZoneArgs = { + draft: CreateZone; +}; + +export type MutationUpdateZoneArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteZoneArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateTaxCategoryArgs = { + draft: TaxCategoryDraft; +}; + +export type MutationUpdateTaxCategoryArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteTaxCategoryArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateDiscountCodeArgs = { + draft: DiscountCodeDraft; +}; + +export type MutationUpdateDiscountCodeArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationDeleteDiscountCodeArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +export type MutationCreateCartDiscountArgs = { + draft: CartDiscountDraft; +}; + +export type MutationUpdateCartDiscountArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCartDiscountArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateProductDiscountArgs = { + draft: ProductDiscountDraft; +}; + +export type MutationUpdateProductDiscountArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteProductDiscountArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateProductArgs = { + draft: ProductDraft; +}; + +export type MutationUpdateProductArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteProductArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCustomerSignUpArgs = { + draft: CustomerSignUpDraft; + storeKey?: Maybe; +}; + +export type MutationCustomerSignInArgs = { + draft: CustomerSignInDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateCustomerArgs = { + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteCustomerArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCustomerChangePasswordArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + currentPassword: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerResetPasswordArgs = { + version?: Maybe; + tokenValue: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerConfirmEmailArgs = { + version?: Maybe; + tokenValue: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerCreatePasswordResetTokenArgs = { + email: Scalars["String"]; + ttlMinutes?: Maybe; + storeKey?: Maybe; +}; + +export type MutationCustomerCreateEmailVerificationTokenArgs = { + id: Scalars["String"]; + version?: Maybe; + ttlMinutes: Scalars["Int"]; + storeKey?: Maybe; +}; + +export type MutationCustomerSignMeUpArgs = { + draft: CustomerSignMeUpDraft; + storeKey?: Maybe; +}; + +export type MutationCustomerSignMeInArgs = { + draft: CustomerSignMeInDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateMyCustomerArgs = { + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; +}; + +export type MutationDeleteMyCustomerArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; +}; + +export type MutationCustomerChangeMyPasswordArgs = { + version: Scalars["Long"]; + currentPassword: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerConfirmMyEmailArgs = { + tokenValue: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCustomerResetMyPasswordArgs = { + tokenValue: Scalars["String"]; + newPassword: Scalars["String"]; + storeKey?: Maybe; +}; + +export type MutationCreateInventoryEntryArgs = { + draft: InventoryEntryDraft; +}; + +export type MutationUpdateInventoryEntryArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationDeleteInventoryEntryArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +export type MutationCreateCartArgs = { + draft: CartDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; +}; + +export type MutationDeleteCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; +}; + +export type MutationReplicateCartArgs = { + reference: ReferenceInput; +}; + +export type MutationCreateMyCartArgs = { + draft: MyCartDraft; + storeKey?: Maybe; +}; + +export type MutationUpdateMyCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; +}; + +export type MutationDeleteMyCartArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + storeKey?: Maybe; +}; + +export type MutationCreateOrderFromCartArgs = { + draft: OrderCartCommand; + storeKey?: Maybe; +}; + +export type MutationUpdateOrderArgs = { + version: Scalars["Long"]; + actions: Array; + storeKey?: Maybe; + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MutationDeleteOrderArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + storeKey?: Maybe; + id?: Maybe; + orderNumber?: Maybe; +}; + +export type MutationCreateMyOrderFromCartArgs = { + draft: OrderMyCartCommand; + storeKey?: Maybe; +}; + +export type MutationCreateShoppingListArgs = { + draft: ShoppingListDraft; +}; + +export type MutationUpdateShoppingListArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteShoppingListArgs = { + version: Scalars["Long"]; + personalDataErasure?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateMyShoppingListArgs = { + draft: MyShoppingListDraft; +}; + +export type MutationUpdateMyShoppingListArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationDeleteMyShoppingListArgs = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +export type MutationUpdateProjectArgs = { + version: Scalars["Long"]; + actions: Array; +}; + +export type MutationCreateStoreArgs = { + draft: CreateStore; +}; + +export type MutationUpdateStoreArgs = { + version: Scalars["Long"]; + actions: Array; + id?: Maybe; + key?: Maybe; +}; + +export type MutationDeleteStoreArgs = { + version: Scalars["Long"]; + id?: Maybe; + key?: Maybe; +}; + +export type MutationCreateApiClientArgs = { + draft: CreateApiClient; +}; + +export type MutationDeleteApiClientArgs = { + id: Scalars["String"]; +}; + +export type MyCartDraft = { + currency: Scalars["Currency"]; + country?: Maybe; + inventoryMode?: Maybe; + custom?: Maybe; + customerEmail?: Maybe; + shippingAddress?: Maybe; + billingAddress?: Maybe; + shippingMethod?: Maybe; + taxMode?: Maybe; + locale?: Maybe; + deleteDaysAfterLastModification?: Maybe; + itemShippingAddresses?: Maybe>; + discountCodes?: Maybe>; + lineItems?: Maybe>; +}; + +export type MyCartUpdateAction = { + addDiscountCode?: Maybe; + addItemShippingAddress?: Maybe; + addLineItem?: Maybe; + addPayment?: Maybe; + addShoppingList?: Maybe; + applyDeltaToLineItemShippingDetailsTargets?: Maybe< + ApplyCartDeltaToLineItemShippingDetailsTargets + >; + changeLineItemQuantity?: Maybe; + changeTaxMode?: Maybe; + recalculate?: Maybe; + removeDiscountCode?: Maybe; + removeItemShippingAddress?: Maybe; + removeLineItem?: Maybe; + removePayment?: Maybe; + setBillingAddress?: Maybe; + setCountry?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setCustomerEmail?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetCartDeleteDaysAfterLastModification + >; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setLineItemShippingDetails?: Maybe; + setLocale?: Maybe; + setShippingMethod?: Maybe; + setShippingAddress?: Maybe; + updateItemShippingAddress?: Maybe; +}; + +export type MyCustomerUpdateAction = { + addAddress?: Maybe; + addBillingAddressId?: Maybe; + addShippingAddressId?: Maybe; + changeAddress?: Maybe; + changeEmail?: Maybe; + removeAddress?: Maybe; + removeBillingAddressId?: Maybe; + removeShippingAddressId?: Maybe; + setCompanyName?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setLocale?: Maybe; + setDateOfBirth?: Maybe; + setDefaultBillingAddress?: Maybe; + setDefaultShippingAddress?: Maybe; + setFirstName?: Maybe; + setLastName?: Maybe; + setMiddleName?: Maybe; + setSalutation?: Maybe; + setTitle?: Maybe; + setVatId?: Maybe; +}; + +export type MyLineItemDraft = { + productId?: Maybe; + sku?: Maybe; + quantity?: Maybe; + variantId?: Maybe; + supplyChannel?: Maybe; + distributionChannel?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; +}; + +export type MyShoppingListDraft = { + name: Array; + description?: Maybe>; + lineItems?: Maybe>; + textLineItems?: Maybe>; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; +}; + +export type MyShoppingListUpdateAction = { + addLineItem?: Maybe; + addTextLineItem?: Maybe; + changeLineItemQuantity?: Maybe; + changeLineItemsOrder?: Maybe; + changeName?: Maybe; + changeTextLineItemName?: Maybe; + changeTextLineItemQuantity?: Maybe; + changeTextLineItemsOrder?: Maybe; + removeLineItem?: Maybe; + removeTextLineItem?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetShoppingListDeleteDaysAfterLastModification + >; + setDescription?: Maybe; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setTextLineItemCustomField?: Maybe; + setTextLineItemCustomType?: Maybe; + setTextLineItemDescription?: Maybe; +}; + +export type NestedAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "NestedAttributeDefinitionType"; + typeReference: Reference; + name: Scalars["String"]; +}; + +export type NumberAttribute = Attribute & { + __typename?: "NumberAttribute"; + value: Scalars["BigDecimal"]; + name: Scalars["String"]; +}; + +export type NumberAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "NumberAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type NumberField = CustomField & { + __typename?: "NumberField"; + value: Scalars["BigDecimal"]; + name: Scalars["String"]; +}; + +export type NumberType = FieldType & { + __typename?: "NumberType"; + name: Scalars["String"]; +}; + +/** An order can be created from a cart, usually after a checkout process has been completed. + * [documentation](https://docs.commercetools.com/http-api-projects-orders.html) + */ +export type Order = Versioned & { + __typename?: "Order"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + completedAt?: Maybe; + orderNumber?: Maybe; + orderState: OrderState; + stateRef?: Maybe; + state?: Maybe; + shipmentState?: Maybe; + paymentState?: Maybe; + syncInfo: Array; + returnInfo: Array; + lastMessageSequenceNumber: Scalars["Long"]; + cartRef?: Maybe; + cart?: Maybe; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** An order can be created from a cart, usually after a checkout process has been completed. + * [documentation](https://docs.commercetools.com/http-api-projects-orders.html) + */ +export type OrderCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** An order can be created from a cart, usually after a checkout process has been completed. + * [documentation](https://docs.commercetools.com/http-api-projects-orders.html) + */ +export type OrderCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type OrderCartCommand = { + id: Scalars["String"]; + version: Scalars["Long"]; + paymentState?: Maybe; + orderState?: Maybe; + state?: Maybe; + shipmentState?: Maybe; + orderNumber?: Maybe; +}; + +export type OrderMyCartCommand = { + id: Scalars["String"]; + version: Scalars["Long"]; +}; + +/** Fields to access orders. Includes direct access to a single order and searching for orders. */ +export type OrderQueryInterface = { + order?: Maybe; + orders: OrderQueryResult; +}; + +/** Fields to access orders. Includes direct access to a single order and searching for orders. */ +export type OrderQueryInterfaceOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +/** Fields to access orders. Includes direct access to a single order and searching for orders. */ +export type OrderQueryInterfaceOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type OrderQueryResult = { + __typename?: "OrderQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum OrderState { + Confirmed = "Confirmed", + Cancelled = "Cancelled", + Complete = "Complete", + Open = "Open" +} + +export type OrderUpdateAction = { + addDelivery?: Maybe; + addItemShippingAddress?: Maybe; + addParcelToDelivery?: Maybe; + addPayment?: Maybe; + addReturnInfo?: Maybe; + changeOrderState?: Maybe; + changePaymentState?: Maybe; + changeShipmentState?: Maybe; + importCustomLineItemState?: Maybe; + importLineItemState?: Maybe; + removeDelivery?: Maybe; + removeItemShippingAddress?: Maybe; + removeParcelFromDelivery?: Maybe; + removePayment?: Maybe; + setBillingAddress?: Maybe; + setCustomField?: Maybe; + setCustomLineItemCustomField?: Maybe; + setCustomLineItemCustomType?: Maybe; + setCustomLineItemShippingDetails?: Maybe< + SetOrderCustomLineItemShippingDetails + >; + setCustomType?: Maybe; + setCustomerEmail?: Maybe; + setCustomerId?: Maybe; + setDeliveryAddress?: Maybe; + setDeliveryItems?: Maybe; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setLineItemShippingDetails?: Maybe; + setLocale?: Maybe; + setOrderNumber?: Maybe; + setParcelItems?: Maybe; + setParcelMeasurements?: Maybe; + setParcelTrackingData?: Maybe; + setReturnPaymentState?: Maybe; + setReturnShipmentState?: Maybe; + setShippingAddress?: Maybe; + transitionCustomLineItemState?: Maybe; + transitionLineItemState?: Maybe; + transitionState?: Maybe; + updateItemShippingAddress?: Maybe; + updateSyncInfo?: Maybe; +}; + +export type Parcel = { + __typename?: "Parcel"; + id: Scalars["String"]; + createdAt: Scalars["DateTime"]; + measurements?: Maybe; + trackingData?: Maybe; + items: Array; +}; + +export type ParcelMeasurements = { + __typename?: "ParcelMeasurements"; + heightInMillimeter?: Maybe; + lengthInMillimeter?: Maybe; + widthInMillimeter?: Maybe; + weightInGram?: Maybe; +}; + +export type ParcelMeasurementsDraftType = { + heightInMillimeter?: Maybe; + lengthInMillimeter?: Maybe; + widthInMillimeter?: Maybe; + weightInGram?: Maybe; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type Payment = Versioned & { + __typename?: "Payment"; + key?: Maybe; + customerRef?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + interfaceId?: Maybe; + amountPlanned: Money; + amountAuthorized?: Maybe; + authorizedUntil?: Maybe; + amountPaid?: Maybe; + amountRefunded?: Maybe; + paymentMethodInfo: PaymentMethodInfo; + paymentStatus: PaymentStatus; + transactions: Array; + interfaceInteractionsRaw: InterfaceInteractionsRawResult; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type PaymentInterfaceInteractionsRawArgs = { + limit?: Maybe; + offset?: Maybe; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type PaymentCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Payments hold information about the current state of receiving and/or refunding money. + * [documentation](https://docs.commercetools.com/http-api-projects-payments) + */ +export type PaymentCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type PaymentInfo = { + __typename?: "PaymentInfo"; + payments: Array; + paymentRefs: Array; +}; + +export type PaymentMethodInfo = { + __typename?: "PaymentMethodInfo"; + paymentInterface?: Maybe; + method?: Maybe; + name?: Maybe; + nameAllLocales?: Maybe>; +}; + +export type PaymentMethodInfoNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type PaymentQueryResult = { + __typename?: "PaymentQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum PaymentState { + Paid = "Paid", + CreditOwed = "CreditOwed", + Pending = "Pending", + Failed = "Failed", + BalanceDue = "BalanceDue" +} + +export type PaymentStatus = { + __typename?: "PaymentStatus"; + interfaceCode?: Maybe; + interfaceText?: Maybe; + stateRef?: Maybe; + state?: Maybe; +}; + +export type PlainEnumValue = { + __typename?: "PlainEnumValue"; + key: Scalars["String"]; + label: Scalars["String"]; +}; + +export type PlainEnumValueDraft = { + key: Scalars["String"]; + label: Scalars["String"]; +}; + +export type PlainEnumValueResult = { + __typename?: "PlainEnumValueResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +export type Point = Geometry & { + __typename?: "Point"; + coordinates: Array; + type: Scalars["String"]; +}; + +export type PriceFunction = { + __typename?: "PriceFunction"; + function: Scalars["String"]; + currencyCode: Scalars["Currency"]; +}; + +export type PriceFunctionDraft = { + function: Scalars["String"]; + currencyCode: Scalars["Currency"]; +}; + +export type Product = Versioned & { + __typename?: "Product"; + id: Scalars["String"]; + key?: Maybe; + version: Scalars["Long"]; + productTypeRef: Reference; + productType?: Maybe; + masterData: ProductCatalogData; + catalogData?: Maybe; + skus: Array; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + stateRef?: Maybe; + state?: Maybe; + taxCategoryRef?: Maybe; + taxCategory?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ProductCatalogDataArgs = { + id: Scalars["String"]; +}; + +export type ProductAttributeInput = { + name: Scalars["String"]; + value: Scalars["String"]; +}; + +export type ProductCatalogData = { + __typename?: "ProductCatalogData"; + current?: Maybe; + staged?: Maybe; + published: Scalars["Boolean"]; + hasStagedChanges: Scalars["Boolean"]; +}; + +export type ProductData = { + __typename?: "ProductData"; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + categoryOrderHint?: Maybe; + categoryOrderHints: Array; + categoriesRef: Array; + categories: Array; + searchKeyword?: Maybe>; + searchKeywords: Array; + metaTitle?: Maybe; + metaKeywords?: Maybe; + metaDescription?: Maybe; + masterVariant: ProductVariant; + variants: Array; + allVariants: Array; + variant?: Maybe; + skus: Array; +}; + +export type ProductDataNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataCategoryOrderHintArgs = { + categoryId: Scalars["String"]; +}; + +export type ProductDataSearchKeywordArgs = { + locale: Scalars["Locale"]; +}; + +export type ProductDataMetaTitleArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataMetaKeywordsArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataMetaDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDataVariantsArgs = { + skus?: Maybe>; + isOnStock?: Maybe; + stockChannelIds?: Maybe>; + hasImages?: Maybe; +}; + +export type ProductDataAllVariantsArgs = { + skus?: Maybe>; + isOnStock?: Maybe; + stockChannelIds?: Maybe>; + hasImages?: Maybe; +}; + +export type ProductDataVariantArgs = { + sku?: Maybe; + key?: Maybe; +}; + +/** A product price can be discounted in two ways: + * + * * with a relative or an absolute product discount, which will be automatically + * applied to all prices in a product that match a discount predicate. + * A relative discount reduces the matching price by a fraction (for example 10 % + * off). An absolute discount reduces the matching price by a fixed amount (for + * example 10€ off). If more than one product discount matches a price, the + * discount sort order determines which one will be applied. + * * with an external product discount, which can then be used to explicitly set a + * discounted value on a particular product price. + * + * The discounted price is stored in the discounted field of the Product Price. + * + * Note that when a discount is created, updated or removed it can take up to 15 + * minutes to update all the prices with the discounts. + * + * The maximum number of ProductDiscounts that can be active at the same time is **200**. + */ +export type ProductDiscount = Versioned & { + __typename?: "ProductDiscount"; + predicate: Scalars["String"]; + validFrom?: Maybe; + validUntil?: Maybe; + isActive: Scalars["Boolean"]; + isValid: Scalars["Boolean"]; + sortOrder: Scalars["String"]; + key?: Maybe; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + value: ProductDiscountValue; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +/** A product price can be discounted in two ways: + * + * * with a relative or an absolute product discount, which will be automatically + * applied to all prices in a product that match a discount predicate. + * A relative discount reduces the matching price by a fraction (for example 10 % + * off). An absolute discount reduces the matching price by a fixed amount (for + * example 10€ off). If more than one product discount matches a price, the + * discount sort order determines which one will be applied. + * * with an external product discount, which can then be used to explicitly set a + * discounted value on a particular product price. + * + * The discounted price is stored in the discounted field of the Product Price. + * + * Note that when a discount is created, updated or removed it can take up to 15 + * minutes to update all the prices with the discounts. + * + * The maximum number of ProductDiscounts that can be active at the same time is **200**. + */ +export type ProductDiscountNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** A product price can be discounted in two ways: + * + * * with a relative or an absolute product discount, which will be automatically + * applied to all prices in a product that match a discount predicate. + * A relative discount reduces the matching price by a fraction (for example 10 % + * off). An absolute discount reduces the matching price by a fixed amount (for + * example 10€ off). If more than one product discount matches a price, the + * discount sort order determines which one will be applied. + * * with an external product discount, which can then be used to explicitly set a + * discounted value on a particular product price. + * + * The discounted price is stored in the discounted field of the Product Price. + * + * Note that when a discount is created, updated or removed it can take up to 15 + * minutes to update all the prices with the discounts. + * + * The maximum number of ProductDiscounts that can be active at the same time is **200**. + */ +export type ProductDiscountDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ProductDiscountDraft = { + value: ProductDiscountValueInput; + predicate: Scalars["String"]; + sortOrder: Scalars["String"]; + name: Array; + description?: Maybe>; + validFrom?: Maybe; + validUntil?: Maybe; + isActive?: Maybe; + key?: Maybe; +}; + +export type ProductDiscountQueryResult = { + __typename?: "ProductDiscountQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ProductDiscountUpdateAction = { + changeIsActive?: Maybe; + changeName?: Maybe; + changePredicate?: Maybe; + changeSortOrder?: Maybe; + changeValue?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setValidFrom?: Maybe; + setValidFromAndUntil?: Maybe; + setValidUntil?: Maybe; +}; + +export type ProductDiscountValue = { + type: Scalars["String"]; +}; + +export type ProductDiscountValueInput = { + relative?: Maybe; + absolute?: Maybe; + external?: Maybe; +}; + +export type ProductDraft = { + name: Array; + productType: ResourceIdentifierInput; + slug: Array; + key?: Maybe; + description?: Maybe>; + categories?: Maybe>; + categoryOrderHints?: Maybe>; + metaTitle?: Maybe>; + metaDescription?: Maybe>; + metaKeywords?: Maybe>; + masterVariant?: Maybe; + variants?: Maybe>; + taxCategory?: Maybe; + state?: Maybe; + searchKeywords?: Maybe>; + publish?: Maybe; +}; + +export type ProductPrice = { + __typename?: "ProductPrice"; + id?: Maybe; + value: BaseMoney; + country?: Maybe; + customerGroup?: Maybe; + channel?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + discounted?: Maybe; + tiers?: Maybe>; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; +}; + +export type ProductPriceCustomFieldsRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ProductPriceCustomFieldListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ProductPriceDataInput = { + value: BaseMoneyInput; + country?: Maybe; + customerGroup?: Maybe; + channel?: Maybe; + validFrom?: Maybe; + validUntil?: Maybe; + tiers?: Maybe>; + custom?: Maybe; +}; + +export type ProductPriceTier = { + __typename?: "ProductPriceTier"; + minimumQuantity: Scalars["Int"]; + value: BaseMoney; +}; + +export type ProductPriceTierInput = { + minimumQuantity: Scalars["Int"]; + value: BaseMoneyInput; +}; + +export type ProductQueryResult = { + __typename?: "ProductQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ProductReferenceIdentifier = { + __typename?: "ProductReferenceIdentifier"; + typeId: Scalars["String"]; + id?: Maybe; + key?: Maybe; +}; + +export type ProductType = { + productTypeId: Scalars["String"]; +}; + +export type ProductTypeDefinition = Versioned & { + __typename?: "ProductTypeDefinition"; + key?: Maybe; + name: Scalars["String"]; + description: Scalars["String"]; + attributeDefinitions: AttributeDefinitionResult; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ProductTypeDefinitionAttributeDefinitionsArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; + limit?: Maybe; + offset?: Maybe; + sort?: Maybe>; +}; + +export type ProductTypeDefinitionQueryResult = { + __typename?: "ProductTypeDefinitionQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ProductTypeDraft = { + name: Scalars["String"]; + description: Scalars["String"]; + key?: Maybe; + attributeDefinitions?: Maybe>; +}; + +export type ProductTypeUpdateAction = { + setKey?: Maybe; + changeName?: Maybe; + changeDescription?: Maybe; + removeAttributeDefinition?: Maybe; + changeLabel?: Maybe; + setInputTip?: Maybe; + changeIsSearchable?: Maybe; + changeInputHint?: Maybe; + addAttributeDefinition?: Maybe; + changeAttributeOrder?: Maybe; + changeAttributeOrderByName?: Maybe; + removeEnumValues?: Maybe; + addPlainEnumValue?: Maybe; + changePlainEnumValueLabel?: Maybe; + changePlainEnumValueOrder?: Maybe; + addLocalizedEnumValue?: Maybe; + changeLocalizedEnumValueLabel?: Maybe; + changeLocalizedEnumValueOrder?: Maybe; + changeAttributeName?: Maybe; + changeEnumKey?: Maybe; +}; + +export type ProductUpdateAction = { + moveImageToPosition?: Maybe; + setSearchKeywords?: Maybe; + revertStagedChanges?: Maybe; + revertStagedVariantChanges?: Maybe; + publish?: Maybe; + unpublish?: Maybe; + transitionState?: Maybe; + addAsset?: Maybe; + addExternalImage?: Maybe; + addPrice?: Maybe; + addToCategory?: Maybe; + addVariant?: Maybe; + changeAssetName?: Maybe; + changeAssetOrder?: Maybe; + changeMasterVariant?: Maybe; + changeImageLabel?: Maybe; + changeName?: Maybe; + changePrice?: Maybe; + changeSlug?: Maybe; + removeAsset?: Maybe; + removeFromCategory?: Maybe; + removeImage?: Maybe; + removePrice?: Maybe; + removeVariant?: Maybe; + setAssetCustomField?: Maybe; + setAssetCustomType?: Maybe; + setAssetDescription?: Maybe; + setAssetKey?: Maybe; + setAssetSources?: Maybe; + setAssetTags?: Maybe; + setCategoryOrderHint?: Maybe; + setDiscountedPrice?: Maybe; + setAttribute?: Maybe; + setAttributeInAllVariants?: Maybe; + setDescription?: Maybe; + setImageLabel?: Maybe; + setKey?: Maybe; + setMetaAttributes?: Maybe; + setMetaDescription?: Maybe; + setMetaKeywords?: Maybe; + setMetaTitle?: Maybe; + setProductPriceCustomField?: Maybe; + setProductPriceCustomType?: Maybe; + setPrices?: Maybe; + setSku?: Maybe; + setTaxCategory?: Maybe; + setProductVariantKey?: Maybe; +}; + +export type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + /** Returns a single price based on the price selection rules. */ + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + /** This field contains non-typed data. Consider using `attributes` as a typed alternative. */ + attributesRaw: Array; + /** Product attributes */ + attributes: ProductType; + /** Product attributes are returned as a list instead of an object structure. */ + attributeList: Array; +}; + +export type ProductVariantPriceArgs = { + currency: Scalars["Currency"]; + country?: Maybe; + customerGroupId?: Maybe; + channelId?: Maybe; + date?: Maybe; +}; + +export type ProductVariantAttributesRawArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type ProductVariantAttributeListArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +/** Product variant availabilities */ +export type ProductVariantAvailabilitiesResult = { + __typename?: "ProductVariantAvailabilitiesResult"; + limit?: Maybe; + offset?: Maybe; + total: Scalars["Int"]; + results: Array; +}; + +/** Product variant availability */ +export type ProductVariantAvailability = { + __typename?: "ProductVariantAvailability"; + isOnStock: Scalars["Boolean"]; + restockableInDays?: Maybe; + availableQuantity?: Maybe; +}; + +export type ProductVariantAvailabilityWithChannel = { + __typename?: "ProductVariantAvailabilityWithChannel"; + channelRef: Reference; + channel?: Maybe; + availability: ProductVariantAvailability; +}; + +export type ProductVariantAvailabilityWithChannels = { + __typename?: "ProductVariantAvailabilityWithChannels"; + noChannel?: Maybe; + channels: ProductVariantAvailabilitiesResult; +}; + +export type ProductVariantAvailabilityWithChannelsChannelsArgs = { + includeChannelIds?: Maybe>; + excludeChannelIds?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type ProductVariantInput = { + sku?: Maybe; + key?: Maybe; + prices?: Maybe>; + images?: Maybe>; + attributes?: Maybe>; + assets?: Maybe>; +}; + +/** Project contains information about project. */ +export type ProjectProjection = { + __typename?: "ProjectProjection"; + key: Scalars["String"]; + name: Scalars["String"]; + languages: Array; + createdAt: Scalars["DateTime"]; + trialUntil?: Maybe; + version: Scalars["Long"]; + externalOAuth?: Maybe; + messages: MessagesConfiguration; + countries: Array; + currencies: Array; + shippingRateInputType?: Maybe; +}; + +export type ProjectSettingsUpdateAction = { + changeCountries?: Maybe; + changeCurrencies?: Maybe; + changeLanguages?: Maybe; + changeMessagesConfiguration?: Maybe< + ChangeProjectSettingsMessagesConfiguration + >; + changeMessagesEnabled?: Maybe; + changeName?: Maybe; + setExternalOAuth?: Maybe; + setShippingRateInputType?: Maybe; +}; + +export type PublishProduct = { + scope?: Maybe; +}; + +export enum PublishScope { + /** Publishes the complete staged projection */ + All = "All", + /** Publishes only prices on the staged projection */ + Prices = "Prices" +} + +export type Query = CartQueryInterface & + CustomerActiveCartInterface & + OrderQueryInterface & + CustomerQueryInterface & + ShoppingListQueryInterface & + ShippingMethodsByCartInterface & + MeFieldInterface & { + __typename?: "Query"; + /** This field can only be used with an access token created with the password flow or with an anonymous session. + * + * It gives access to the data that is specific to the customer or the anonymous session linked to the access token. + */ + me: Me; + /** This field gives access to the resources (such as carts) that are inside the given store. */ + inStore: InStore; + /** This field gives access to the resources (such as carts) that are inside one of the given stores. */ + inStores: InStore; + customerGroup?: Maybe; + customerGroups: CustomerGroupQueryResult; + category?: Maybe; + categories: CategoryQueryResult; + /** Autocomplete the categories based on category fields like name, description, etc. */ + categoryAutocomplete: CategorySearchResult; + /** Search the categories using full-text search, filtering and sorting */ + categorySearch: CategorySearchResult; + channel?: Maybe; + channels: ChannelQueryResult; + productType?: Maybe; + productTypes: ProductTypeDefinitionQueryResult; + typeDefinition?: Maybe; + typeDefinitions: TypeDefinitionQueryResult; + shippingMethod?: Maybe; + shippingMethods: ShippingMethodQueryResult; + shippingMethodsByCart: Array; + shippingMethodsByLocation: Array; + zone?: Maybe; + zones: ZoneQueryResult; + taxCategory?: Maybe; + taxCategories: TaxCategoryQueryResult; + discountCode?: Maybe; + discountCodes: DiscountCodeQueryResult; + cartDiscount?: Maybe; + cartDiscounts: CartDiscountQueryResult; + productDiscount?: Maybe; + productDiscounts: ProductDiscountQueryResult; + product?: Maybe; + products: ProductQueryResult; + state?: Maybe; + states: StateQueryResult; + customer?: Maybe; + customers: CustomerQueryResult; + inventoryEntry?: Maybe; + inventoryEntries: InventoryEntryQueryResult; + cart?: Maybe; + carts: CartQueryResult; + customerActiveCart?: Maybe; + order?: Maybe; + orders: OrderQueryResult; + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; + payment?: Maybe; + payments: PaymentQueryResult; + project: ProjectProjection; + store?: Maybe; + stores: StoreQueryResult; + apiClient?: Maybe; + apiClients: ApiClientWithoutSecretQueryResult; + }; + +export type QueryInStoreArgs = { + key: Scalars["KeyReferenceInput"]; +}; + +export type QueryInStoresArgs = { + keys: Array; +}; + +export type QueryCustomerGroupArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryCustomerGroupsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCategoryArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryCategoriesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCategoryAutocompleteArgs = { + locale: Scalars["Locale"]; + text: Scalars["String"]; + limit?: Maybe; + offset?: Maybe; + filters?: Maybe>; + experimental?: Maybe; +}; + +export type QueryCategorySearchArgs = { + fulltext?: Maybe; + limit?: Maybe; + offset?: Maybe; + queryFilters?: Maybe>; + filters?: Maybe>; + sorts?: Maybe>; + experimental?: Maybe; +}; + +export type QueryChannelArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryChannelsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryProductTypeArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryProductTypesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryTypeDefinitionArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryTypeDefinitionsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryShippingMethodArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryShippingMethodsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryShippingMethodsByCartArgs = { + id: Scalars["String"]; +}; + +export type QueryShippingMethodsByLocationArgs = { + country: Scalars["Country"]; + state?: Maybe; + currency?: Maybe; +}; + +export type QueryZoneArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryZonesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryTaxCategoryArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryTaxCategoriesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryDiscountCodeArgs = { + id: Scalars["String"]; +}; + +export type QueryDiscountCodesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCartDiscountArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryCartDiscountsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryProductDiscountArgs = { + id: Scalars["String"]; +}; + +export type QueryProductDiscountsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryProductArgs = { + sku?: Maybe; + variantKey?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type QueryProductsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; + skus?: Maybe>; +}; + +export type QueryStateArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryStatesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCustomerArgs = { + emailToken?: Maybe; + passwordToken?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +export type QueryCustomersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryInventoryEntryArgs = { + id: Scalars["String"]; +}; + +export type QueryInventoryEntriesArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCartArgs = { + id: Scalars["String"]; +}; + +export type QueryCartsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryCustomerActiveCartArgs = { + customerId: Scalars["String"]; +}; + +export type QueryOrderArgs = { + id?: Maybe; + orderNumber?: Maybe; +}; + +export type QueryOrdersArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryPaymentArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryPaymentsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryStoreArgs = { + id?: Maybe; + key?: Maybe; +}; + +export type QueryStoresArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type QueryApiClientArgs = { + id: Scalars["String"]; +}; + +export type QueryApiClientsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type RawCustomField = { + __typename?: "RawCustomField"; + name: Scalars["String"]; + value: Scalars["Json"]; +}; + +export type RawProductAttribute = { + __typename?: "RawProductAttribute"; + name: Scalars["String"]; + value: Scalars["Json"]; + attributeDefinition?: Maybe; +}; + +export type RecalculateCart = { + updateProductData?: Maybe; +}; + +export type Reference = { + __typename?: "Reference"; + typeId: Scalars["String"]; + id: Scalars["String"]; +}; + +export type ReferenceAttribute = Attribute & { + __typename?: "ReferenceAttribute"; + typeId: Scalars["String"]; + id: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "ReferenceAttributeDefinitionType"; + referenceTypeId: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceField = CustomField & { + __typename?: "ReferenceField"; + typeId: Scalars["String"]; + id: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceInput = { + typeId: Scalars["String"]; + id: Scalars["String"]; +}; + +export type ReferenceType = FieldType & { + __typename?: "ReferenceType"; + referenceTypeId: Scalars["String"]; + name: Scalars["String"]; +}; + +export type ReferenceTypeDefinitionDraft = { + referenceTypeId: Scalars["String"]; +}; + +export type RelativeDiscountValue = CartDiscountValue & + ProductDiscountValue & { + __typename?: "RelativeDiscountValue"; + permyriad: Scalars["Int"]; + type: Scalars["String"]; + }; + +export type RelativeDiscountValueInput = { + permyriad: Scalars["Int"]; +}; + +export type RemoveAttributeDefinition = { + name: Scalars["String"]; +}; + +export type RemoveCartCustomLineItem = { + customLineItemId: Scalars["String"]; +}; + +export type RemoveCartDiscountCode = { + discountCode: ReferenceInput; +}; + +export type RemoveCartItemShippingAddress = { + addressKey: Scalars["String"]; +}; + +export type RemoveCartLineItem = { + lineItemId: Scalars["String"]; + quantity?: Maybe; + externalPrice?: Maybe; + externalTotalPrice?: Maybe; + shippingDetailsToRemove?: Maybe; +}; + +export type RemoveCartPayment = { + payment: ResourceIdentifierInput; +}; + +export type RemoveCategoryAsset = { + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type RemoveCustomerAddress = { + addressId: Scalars["String"]; +}; + +export type RemoveCustomerBillingAddressId = { + addressId: Scalars["String"]; +}; + +export type RemoveCustomerShippingAddressId = { + addressId: Scalars["String"]; +}; + +export type RemoveCustomerStore = { + store: ResourceIdentifierInput; +}; + +export type RemoveEnumValues = { + attributeName: Scalars["String"]; + keys: Array; +}; + +export type RemoveInventoryEntryQuantity = { + quantity: Scalars["Long"]; +}; + +export type RemoveOrderDelivery = { + deliveryId: Scalars["String"]; +}; + +export type RemoveOrderItemShippingAddress = { + addressKey: Scalars["String"]; +}; + +export type RemoveOrderParcelFromDelivery = { + parcelId: Scalars["String"]; +}; + +export type RemoveOrderPayment = { + payment: ResourceIdentifierInput; +}; + +export type RemoveProductAsset = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type RemoveProductFromCategory = { + category: ResourceIdentifierInput; + staged?: Maybe; +}; + +export type RemoveProductImage = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + staged?: Maybe; +}; + +export type RemoveProductPrice = { + priceId?: Maybe; + variantId?: Maybe; + sku?: Maybe; + price?: Maybe; + catalog?: Maybe; + staged?: Maybe; +}; + +export type RemoveProductVariant = { + id?: Maybe; + sku?: Maybe; + staged?: Maybe; +}; + +export type RemoveShippingMethodShippingRate = { + zone: ResourceIdentifierInput; + shippingRate: ShippingRateDraft; +}; + +export type RemoveShippingMethodZone = { + zone: ResourceIdentifierInput; +}; + +export type RemoveShoppingListLineItem = { + lineItemId: Scalars["String"]; + quantity?: Maybe; +}; + +export type RemoveShoppingListTextLineItem = { + textLineItemId: Scalars["String"]; + quantity?: Maybe; +}; + +export type RemoveZoneLocation = { + location: ZoneLocation; +}; + +export type ReservationOrderType = Type & { + __typename?: "reservationOrderType"; + typeRef: Reference; + type: TypeDefinition; + isReservation?: Maybe; +}; + +export type ResourceIdentifierInput = { + typeId?: Maybe; + id?: Maybe; + key?: Maybe; +}; + +/** Stores information about returns connected to this order. */ +export type ReturnInfo = { + __typename?: "ReturnInfo"; + items: Array; + returnTrackingId?: Maybe; + returnDate?: Maybe; +}; + +export type ReturnItem = { + type: Scalars["String"]; + id: Scalars["String"]; + quantity: Scalars["Long"]; + comment?: Maybe; + shipmentState: ReturnShipmentState; + paymentState: ReturnPaymentState; + lastModifiedAt: Scalars["DateTime"]; + createdAt: Scalars["DateTime"]; +}; + +export type ReturnItemDraftType = { + quantity: Scalars["Long"]; + lineItemId?: Maybe; + customLineItemId?: Maybe; + comment?: Maybe; + shipmentState: ReturnShipmentState; +}; + +export enum ReturnPaymentState { + NotRefunded = "NotRefunded", + Refunded = "Refunded", + Initial = "Initial", + NonRefundable = "NonRefundable" +} + +export enum ReturnShipmentState { + Unusable = "Unusable", + BackInStock = "BackInStock", + Returned = "Returned", + Advised = "Advised" +} + +export type RevertStagedChanges = { + dummy?: Maybe; +}; + +export type RevertStagedVariantChanges = { + variantId: Scalars["Int"]; +}; + +export enum RoundingMode { + /** [Round half down](https://en.wikipedia.org/wiki/Rounding#Round_half_down). + * Rounding mode used by, e.g., [Avalara Sales TaxII](https://help.avalara.com/kb/001/How_does_Rounding_with_SalesTaxII_work%3F) + */ + HalfDown = "HalfDown", + /** [Round half up](https://en.wikipedia.org/wiki/Rounding#Round_half_up) */ + HalfUp = "HalfUp", + /** [Round half to even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even). + * Default rounding mode as used in IEEE 754 computing functions and operators. + */ + HalfEven = "HalfEven" +} + +export type ScoreShippingRateInput = ShippingRateInput & { + __typename?: "ScoreShippingRateInput"; + score: Scalars["Int"]; + type: Scalars["String"]; +}; + +export type ScoreShippingRateInputDraft = { + score: Scalars["Int"]; +}; + +export type SearchKeyword = { + __typename?: "SearchKeyword"; + text: Scalars["String"]; +}; + +export type SearchKeywordInput = { + locale: Scalars["Locale"]; + keywords: Array; +}; + +export type SearchKeywords = { + __typename?: "SearchKeywords"; + locale: Scalars["Locale"]; + searchKeywords: Array; +}; + +/** In order to decide which of the matching items will actually be discounted */ +export enum SelectionMode { + MostExpensive = "MostExpensive", + Cheapest = "Cheapest" +} + +export type SetAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "SetAttributeDefinitionType"; + elementType: AttributeDefinitionType; + name: Scalars["String"]; +}; + +export type SetCartAnonymousId = { + anonymousId?: Maybe; +}; + +export type SetCartBillingAddress = { + address?: Maybe; +}; + +export type SetCartCountry = { + country?: Maybe; +}; + +export type SetCartCustomerEmail = { + email?: Maybe; +}; + +export type SetCartCustomerGroup = { + customerGroup?: Maybe; +}; + +export type SetCartCustomerId = { + customerId?: Maybe; +}; + +export type SetCartCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartCustomLineItemCustomField = { + customLineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartCustomLineItemCustomType = { + customLineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartCustomLineItemShippingDetails = { + customLineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetCartCustomLineItemTaxAmount = { + customLineItemId: Scalars["String"]; + externalTaxAmount?: Maybe; +}; + +export type SetCartCustomLineItemTaxRate = { + customLineItemId: Scalars["String"]; + externalTaxRate?: Maybe; +}; + +export type SetCartCustomShippingMethod = { + shippingMethodName: Scalars["String"]; + shippingRate: ShippingRateDraft; + taxCategory?: Maybe; + externalTaxRate?: Maybe; +}; + +export type SetCartCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartDeleteDaysAfterLastModification = { + deleteDaysAfterLastModification?: Maybe; +}; + +export type SetCartDiscountCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartDiscountCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartDiscountDescription = { + description?: Maybe>; +}; + +export type SetCartDiscountKey = { + key?: Maybe; +}; + +export type SetCartDiscountValidFrom = { + validFrom?: Maybe; +}; + +export type SetCartDiscountValidFromAndUntil = { + validFrom?: Maybe; + validUntil?: Maybe; +}; + +export type SetCartDiscountValidUntil = { + validUntil?: Maybe; +}; + +export type SetCartLineItemCustomField = { + lineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCartLineItemCustomType = { + lineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCartLineItemPrice = { + lineItemId: Scalars["String"]; + externalPrice?: Maybe; +}; + +export type SetCartLineItemShippingDetails = { + lineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetCartLineItemTaxAmount = { + lineItemId: Scalars["String"]; + externalTaxAmount?: Maybe; +}; + +export type SetCartLineItemTaxRate = { + lineItemId: Scalars["String"]; + externalTaxRate?: Maybe; +}; + +export type SetCartLineItemTotalPrice = { + lineItemId: Scalars["String"]; + externalTotalPrice?: Maybe; +}; + +export type SetCartLocale = { + locale?: Maybe; +}; + +export type SetCartShippingAddress = { + address?: Maybe; +}; + +export type SetCartShippingMethod = { + shippingMethod?: Maybe; + externalTaxRate?: Maybe; +}; + +export type SetCartShippingMethodTaxAmount = { + externalTaxAmount?: Maybe; +}; + +export type SetCartShippingMethodTaxRate = { + externalTaxRate?: Maybe; +}; + +export type SetCartShippingRateInput = { + shippingRateInput?: Maybe; +}; + +export type SetCartTotalTax = { + externalTotalGross?: Maybe; + externalTaxPortions?: Maybe>; +}; + +export type SetCategoryAssetCustomField = { + value?: Maybe; + name: Scalars["String"]; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetCustomType = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetDescription = { + description?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetKey = { + assetKey?: Maybe; + assetId: Scalars["String"]; +}; + +export type SetCategoryAssetSources = { + sources?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryAssetTags = { + tags?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetCategoryCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCategoryCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCategoryDescription = { + description?: Maybe>; +}; + +export type SetCategoryExternalId = { + externalId?: Maybe; +}; + +export type SetCategoryKey = { + key?: Maybe; +}; + +export type SetCategoryMetaDescription = { + metaDescription?: Maybe>; +}; + +export type SetCategoryMetaKeywords = { + metaKeywords?: Maybe>; +}; + +export type SetCategoryMetaTitle = { + metaTitle?: Maybe>; +}; + +export type SetCustomerCompanyName = { + companyName?: Maybe; +}; + +export type SetCustomerCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCustomerCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetCustomerDateOfBirth = { + dateOfBirth?: Maybe; +}; + +export type SetCustomerDefaultBillingAddress = { + addressId?: Maybe; +}; + +export type SetCustomerDefaultShippingAddress = { + addressId?: Maybe; +}; + +export type SetCustomerExternalId = { + externalId?: Maybe; +}; + +export type SetCustomerFirstName = { + firstName?: Maybe; +}; + +export type SetCustomerGroup = { + customerGroup?: Maybe; +}; + +export type SetCustomerGroupCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetCustomerGroupCustomType = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; +}; + +export type SetCustomerGroupKey = { + key?: Maybe; +}; + +export type SetCustomerKey = { + key?: Maybe; +}; + +export type SetCustomerLastName = { + lastName?: Maybe; +}; + +export type SetCustomerLocale = { + locale?: Maybe; +}; + +export type SetCustomerMiddleName = { + middleName?: Maybe; +}; + +export type SetCustomerNumber = { + customerNumber?: Maybe; +}; + +export type SetCustomerSalutation = { + salutation?: Maybe; +}; + +export type SetCustomerStores = { + stores: Array; +}; + +export type SetCustomerTitle = { + title?: Maybe; +}; + +export type SetCustomerVatId = { + vatId?: Maybe; +}; + +export type SetDiscountCodeCartPredicate = { + cartPredicate?: Maybe; +}; + +export type SetDiscountCodeCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetDiscountCodeCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetDiscountCodeDescription = { + description?: Maybe>; +}; + +export type SetDiscountCodeMaxApplications = { + maxApplications?: Maybe; +}; + +export type SetDiscountCodeMaxApplicationsPerCustomer = { + maxApplicationsPerCustomer?: Maybe; +}; + +export type SetDiscountCodeName = { + name?: Maybe>; +}; + +export type SetDiscountCodeValidFrom = { + validFrom?: Maybe; +}; + +export type SetDiscountCodeValidFromAndUntil = { + validFrom?: Maybe; + validUntil?: Maybe; +}; + +export type SetDiscountCodeValidUntil = { + validUntil?: Maybe; +}; + +export type SetInputTip = { + attributeName: Scalars["String"]; + inputTip?: Maybe>; +}; + +export type SetInventoryEntryCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetInventoryEntryCustomType = { + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; +}; + +export type SetInventoryEntryExpectedDelivery = { + expectedDelivery?: Maybe; +}; + +export type SetInventoryEntryRestockableInDays = { + restockableInDays?: Maybe; +}; + +export type SetInventoryEntrySupplyChannel = { + supplyChannel?: Maybe; +}; + +export type SetKey = { + key?: Maybe; +}; + +export type SetMyCartShippingMethod = { + shippingMethod?: Maybe; +}; + +export type SetOrderBillingAddress = { + address?: Maybe; +}; + +export type SetOrderCustomerEmail = { + email?: Maybe; +}; + +export type SetOrderCustomerId = { + customerId?: Maybe; +}; + +export type SetOrderCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetOrderCustomLineItemCustomField = { + customLineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetOrderCustomLineItemCustomType = { + customLineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetOrderCustomLineItemShippingDetails = { + customLineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetOrderCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetOrderDeliveryAddress = { + deliveryId: Scalars["String"]; + address?: Maybe; +}; + +export type SetOrderDeliveryItems = { + deliveryId: Scalars["String"]; + items: Array; +}; + +export type SetOrderLineItemCustomField = { + lineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetOrderLineItemCustomType = { + lineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetOrderLineItemShippingDetails = { + lineItemId: Scalars["String"]; + shippingDetails?: Maybe; +}; + +export type SetOrderLocale = { + locale?: Maybe; +}; + +export type SetOrderNumber = { + orderNumber?: Maybe; +}; + +export type SetOrderParcelItems = { + parcelId: Scalars["String"]; + items: Array; +}; + +export type SetOrderParcelMeasurements = { + parcelId: Scalars["String"]; + measurements?: Maybe; +}; + +export type SetOrderParcelTrackingData = { + parcelId: Scalars["String"]; + trackingData?: Maybe; +}; + +export type SetOrderReturnPaymentState = { + returnItemId: Scalars["String"]; + paymentState: ReturnPaymentState; +}; + +export type SetOrderReturnShipmentState = { + returnItemId: Scalars["String"]; + shipmentState: ReturnShipmentState; +}; + +export type SetOrderShippingAddress = { + address?: Maybe; +}; + +export type SetProductAssetCustomField = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + value?: Maybe; + name: Scalars["String"]; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetCustomType = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + typeId?: Maybe; + typeKey?: Maybe; + type?: Maybe; + fields?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetDescription = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + description?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetKey = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + assetKey?: Maybe; + assetId: Scalars["String"]; +}; + +export type SetProductAssetSources = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + sources?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAssetTags = { + variantId?: Maybe; + sku?: Maybe; + catalog?: Maybe; + staged?: Maybe; + tags?: Maybe>; + assetKey?: Maybe; + assetId?: Maybe; +}; + +export type SetProductAttribute = { + variantId?: Maybe; + sku?: Maybe; + name: Scalars["String"]; + value?: Maybe; + staged?: Maybe; +}; + +export type SetProductAttributeInAllVariants = { + name: Scalars["String"]; + value?: Maybe; + staged?: Maybe; +}; + +export type SetProductCategoryOrderHint = { + categoryId: Scalars["String"]; + orderHint?: Maybe; + staged?: Maybe; +}; + +export type SetProductDescription = { + description?: Maybe>; + staged?: Maybe; +}; + +export type SetProductDiscountDescription = { + description?: Maybe>; +}; + +export type SetProductDiscountedPrice = { + priceId: Scalars["String"]; + discounted?: Maybe; + catalog?: Maybe; + staged?: Maybe; +}; + +export type SetProductDiscountKey = { + key?: Maybe; +}; + +export type SetProductDiscountValidFrom = { + validFrom?: Maybe; +}; + +export type SetProductDiscountValidFromAndUntil = { + validFrom?: Maybe; + validUntil?: Maybe; +}; + +export type SetProductDiscountValidUntil = { + validUntil?: Maybe; +}; + +export type SetProductImageLabel = { + variantId?: Maybe; + sku?: Maybe; + imageUrl: Scalars["String"]; + label?: Maybe; + staged?: Maybe; +}; + +export type SetProductKey = { + key?: Maybe; +}; + +export type SetProductMetaAttributes = { + metaDescription?: Maybe>; + metaKeywords?: Maybe>; + metaTitle?: Maybe>; + staged?: Maybe; +}; + +export type SetProductMetaDescription = { + metaDescription?: Maybe>; + staged?: Maybe; +}; + +export type SetProductMetaKeywords = { + metaKeywords?: Maybe>; + staged?: Maybe; +}; + +export type SetProductMetaTitle = { + metaTitle?: Maybe>; + staged?: Maybe; +}; + +export type SetProductPriceCustomField = { + priceId: Scalars["String"]; + catalog?: Maybe; + staged?: Maybe; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetProductPriceCustomType = { + priceId: Scalars["String"]; + catalog?: Maybe; + staged?: Maybe; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetProductPrices = { + variantId?: Maybe; + sku?: Maybe; + prices: Array; + catalog?: Maybe; + staged?: Maybe; +}; + +export type SetProductSku = { + variantId: Scalars["Int"]; + sku?: Maybe; + staged?: Maybe; +}; + +export type SetProductTaxCategory = { + taxCategory?: Maybe; +}; + +export type SetProductVariantKey = { + variantId?: Maybe; + sku?: Maybe; + key?: Maybe; + staged?: Maybe; +}; + +export type SetProjectSettingsExternalOAuth = { + externalOAuth?: Maybe; +}; + +export type SetProjectSettingsShippingRateInputType = { + shippingRateInputType?: Maybe; +}; + +export type SetSearchKeywords = { + searchKeywords: Array; + staged?: Maybe; +}; + +export type SetShippingMethodDescription = { + description?: Maybe; +}; + +export type SetShippingMethodKey = { + key?: Maybe; +}; + +export type SetShippingMethodPredicate = { + predicate?: Maybe; +}; + +export type SetShoppingListAnonymousId = { + anonymousId?: Maybe; +}; + +export type SetShoppingListCustomer = { + customer?: Maybe; +}; + +export type SetShoppingListCustomField = { + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetShoppingListCustomType = { + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetShoppingListDeleteDaysAfterLastModification = { + deleteDaysAfterLastModification?: Maybe; +}; + +export type SetShoppingListDescription = { + description?: Maybe>; +}; + +export type SetShoppingListKey = { + key?: Maybe; +}; + +export type SetShoppingListLineItemCustomField = { + lineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetShoppingListLineItemCustomType = { + lineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetShoppingListSlug = { + slug?: Maybe>; +}; + +export type SetShoppingListTextLineItemCustomField = { + textLineItemId: Scalars["String"]; + name: Scalars["String"]; + value?: Maybe; +}; + +export type SetShoppingListTextLineItemCustomType = { + textLineItemId: Scalars["String"]; + fields?: Maybe>; + type?: Maybe; + typeKey?: Maybe; + typeId?: Maybe; +}; + +export type SetShoppingListTextLineItemDescription = { + textLineItemId: Scalars["String"]; + description?: Maybe>; +}; + +export type SetStoreLanguages = { + languages?: Maybe>; +}; + +export type SetStoreName = { + name?: Maybe>; +}; + +export type SetTaxCategoryKey = { + key?: Maybe; +}; + +export type SetType = FieldType & { + __typename?: "SetType"; + elementType: FieldType; + name: Scalars["String"]; +}; + +export type SetZoneDescription = { + description?: Maybe; +}; + +export type SetZoneKey = { + key?: Maybe; +}; + +export enum ShipmentState { + Delayed = "Delayed", + Backorder = "Backorder", + Partial = "Partial", + Pending = "Pending", + Ready = "Ready", + Shipped = "Shipped" +} + +export type ShippingInfo = { + __typename?: "ShippingInfo"; + shippingMethodName: Scalars["String"]; + price: Money; + shippingRate: ShippingRate; + taxRate?: Maybe; + taxCategory?: Maybe; + deliveries: Array; + discountedPrice?: Maybe; + taxedPrice?: Maybe; + shippingMethodState: ShippingMethodState; + shippingMethod?: Maybe; + shippingMethodRef?: Maybe; +}; + +export type ShippingMethod = Versioned & { + __typename?: "ShippingMethod"; + id: Scalars["String"]; + version: Scalars["Long"]; + name: Scalars["String"]; + description?: Maybe; + zoneRates: Array; + isDefault: Scalars["Boolean"]; + predicate?: Maybe; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + key?: Maybe; + lastModifiedBy?: Maybe; + createdBy?: Maybe; + taxCategoryRef?: Maybe; + taxCategory?: Maybe; +}; + +export type ShippingMethodDraft = { + name: Scalars["String"]; + description?: Maybe; + taxCategory: ResourceIdentifierInput; + zoneRates?: Maybe>; + isDefault: Scalars["Boolean"]; + predicate?: Maybe; + key?: Maybe; +}; + +export type ShippingMethodQueryResult = { + __typename?: "ShippingMethodQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +/** A field to retrieve available shipping methods for a cart. */ +export type ShippingMethodsByCartInterface = { + shippingMethodsByCart: Array; +}; + +/** A field to retrieve available shipping methods for a cart. */ +export type ShippingMethodsByCartInterfaceShippingMethodsByCartArgs = { + id: Scalars["String"]; +}; + +export enum ShippingMethodState { + /** Either there is no predicate defined for the ShippingMethod or the given predicate matches the cart */ + MatchesCart = "MatchesCart", + /** The ShippingMethod predicate does not match the cart. Ordering this cart will + * fail with error ShippingMethodDoesNotMatchCart + */ + DoesNotMatchCart = "DoesNotMatchCart" +} + +export type ShippingMethodUpdateAction = { + addShippingRate?: Maybe; + addZone?: Maybe; + changeIsDefault?: Maybe; + changeName?: Maybe; + changeTaxCategory?: Maybe; + removeShippingRate?: Maybe; + removeZone?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; + setPredicate?: Maybe; +}; + +/** Shipping Rate */ +export type ShippingRate = { + __typename?: "ShippingRate"; + price: Money; + freeAbove?: Maybe; + isMatching?: Maybe; + tiers: Array; +}; + +export type ShippingRateCartClassificationPriceTier = ShippingRatePriceTier & { + __typename?: "ShippingRateCartClassificationPriceTier"; + value: Scalars["String"]; + price: Money; + isMatching?: Maybe; + type: Scalars["String"]; +}; + +export type ShippingRateCartScorePriceTier = ShippingRatePriceTier & { + __typename?: "ShippingRateCartScorePriceTier"; + score: Scalars["Int"]; + price?: Maybe; + priceFunction?: Maybe; + isMatching?: Maybe; + type: Scalars["String"]; +}; + +export type ShippingRateCartValuePriceTier = ShippingRatePriceTier & { + __typename?: "ShippingRateCartValuePriceTier"; + minimumCentAmount: Scalars["Int"]; + price: Money; + isMatching?: Maybe; + type: Scalars["String"]; +}; + +export type ShippingRateDraft = { + price: MoneyDraft; + freeAbove?: Maybe; + tiers?: Maybe>; +}; + +export type ShippingRateInput = { + type: Scalars["String"]; +}; + +export type ShippingRateInputDraft = { + Classification?: Maybe; + Score?: Maybe; +}; + +export type ShippingRateInputLocalizedEnumValue = { + __typename?: "ShippingRateInputLocalizedEnumValue"; + key: Scalars["String"]; + label?: Maybe; + labelAllLocales: Array; +}; + +export type ShippingRateInputLocalizedEnumValueLabelArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShippingRateInputType = { + type: Scalars["String"]; +}; + +export type ShippingRateInputTypeInput = { + CartValue?: Maybe; + CartClassification?: Maybe; + CartScore?: Maybe; +}; + +export type ShippingRatePriceTier = { + type: Scalars["String"]; +}; + +export type ShippingRatePriceTierCartClassificationDraft = { + value: Scalars["String"]; + price: MoneyDraft; +}; + +export type ShippingRatePriceTierCartScoreDraft = { + score: Scalars["Int"]; + price?: Maybe; + priceFunction?: Maybe; +}; + +export type ShippingRatePriceTierCartValueDraft = { + minimumCentAmount: Scalars["Int"]; + price: MoneyDraft; +}; + +export type ShippingRatePriceTierDraft = { + CartValue?: Maybe; + CartClassification?: Maybe; + CartScore?: Maybe; +}; + +export type ShippingTarget = CartDiscountTarget & { + __typename?: "ShippingTarget"; + type: Scalars["String"]; +}; + +export type ShippingTargetDraft = { + addressKey: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ShippingTargetDraftType = { + addressKey: Scalars["String"]; + quantity: Scalars["Long"]; +}; + +export type ShippingTargetInput = { + dummy?: Maybe; +}; + +export type ShoppingList = Versioned & { + __typename?: "ShoppingList"; + key?: Maybe; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales?: Maybe>; + customerRef?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + textLineItems: Array; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ShoppingListNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListDraft = { + name: Array; + description?: Maybe>; + lineItems?: Maybe>; + textLineItems?: Maybe>; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + key?: Maybe; + customer?: Maybe; + slug?: Maybe>; + anonymousId?: Maybe; +}; + +export type ShoppingListLineItem = { + __typename?: "ShoppingListLineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + variantId?: Maybe; + productTypeRef: Reference; + productType: ProductTypeDefinition; + quantity: Scalars["Int"]; + addedAt: Scalars["DateTime"]; + name?: Maybe; + nameAllLocales: Array; + deactivatedAt?: Maybe; + custom?: Maybe; + productSlug?: Maybe; + variant?: Maybe; +}; + +export type ShoppingListLineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListLineItemProductSlugArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type ShoppingListLineItemDraft = { + productId?: Maybe; + sku?: Maybe; + variantId?: Maybe; + quantity?: Maybe; + custom?: Maybe; + addedAt?: Maybe; +}; + +/** Fields to access shopping lists. Includes direct access to a single list and searching for shopping lists. */ +export type ShoppingListQueryInterface = { + shoppingList?: Maybe; + shoppingLists: ShoppingListQueryResult; +}; + +/** Fields to access shopping lists. Includes direct access to a single list and searching for shopping lists. */ +export type ShoppingListQueryInterfaceShoppingListArgs = { + id?: Maybe; + key?: Maybe; +}; + +/** Fields to access shopping lists. Includes direct access to a single list and searching for shopping lists. */ +export type ShoppingListQueryInterfaceShoppingListsArgs = { + where?: Maybe; + sort?: Maybe>; + limit?: Maybe; + offset?: Maybe; +}; + +export type ShoppingListQueryResult = { + __typename?: "ShoppingListQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ShoppingListUpdateAction = { + addLineItem?: Maybe; + addTextLineItem?: Maybe; + changeLineItemQuantity?: Maybe; + changeLineItemsOrder?: Maybe; + changeName?: Maybe; + changeTextLineItemName?: Maybe; + changeTextLineItemQuantity?: Maybe; + changeTextLineItemsOrder?: Maybe; + removeLineItem?: Maybe; + removeTextLineItem?: Maybe; + setAnonymousId?: Maybe; + setCustomField?: Maybe; + setCustomType?: Maybe; + setCustomer?: Maybe; + setDeleteDaysAfterLastModification?: Maybe< + SetShoppingListDeleteDaysAfterLastModification + >; + setDescription?: Maybe; + setKey?: Maybe; + setLineItemCustomField?: Maybe; + setLineItemCustomType?: Maybe; + setSlug?: Maybe; + setTextLineItemCustomField?: Maybe; + setTextLineItemCustomType?: Maybe; + setTextLineItemDescription?: Maybe; +}; + +export type SimpleAttributeTypeDraft = { + dummy?: Maybe; +}; + +/** Describes how this discount interacts with other discounts */ +export enum StackingMode { + /** Don’t apply any more matching discounts after this one. */ + StopAfterThisDiscount = "StopAfterThisDiscount", + /** Default. Continue applying other matching discounts after applying this one. */ + Stacking = "Stacking" +} + +/** [State](http://dev.commercetools.com/http-api-projects-states.html) */ +export type State = Versioned & { + __typename?: "State"; + id: Scalars["String"]; + version: Scalars["Long"]; + key?: Maybe; + type: StateType; + roles: Array; + name?: Maybe; + nameAllLocales?: Maybe>; + description?: Maybe; + descriptionAllLocales?: Maybe>; + builtIn: Scalars["Boolean"]; + transitionsRef?: Maybe>; + transitions?: Maybe>; + initial: Scalars["Boolean"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +/** [State](http://dev.commercetools.com/http-api-projects-states.html) */ +export type StateNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** [State](http://dev.commercetools.com/http-api-projects-states.html) */ +export type StateDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type StateQueryResult = { + __typename?: "StateQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export enum StateRole { + Return = "Return", + ReviewIncludedInStatistics = "ReviewIncludedInStatistics" +} + +export enum StateType { + OrderState = "OrderState", + ProductState = "ProductState", + ReviewState = "ReviewState", + PaymentState = "PaymentState", + LineItemState = "LineItemState" +} + +/** [BETA] Stores allow defining different contexts for a project. */ +export type Store = Versioned & { + __typename?: "Store"; + id: Scalars["String"]; + version: Scalars["Long"]; + key: Scalars["String"]; + name?: Maybe; + nameAllLocales?: Maybe>; + languages?: Maybe>; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + distributionChannels: Array; + supplyChannels: Array; +}; + +/** [BETA] Stores allow defining different contexts for a project. */ +export type StoreNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type StoreQueryResult = { + __typename?: "StoreQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type StoreUpdateAction = { + setLanguages?: Maybe; + setName?: Maybe; +}; + +export type StringAttribute = Attribute & { + __typename?: "StringAttribute"; + value: Scalars["String"]; + name: Scalars["String"]; +}; + +export type StringField = CustomField & { + __typename?: "StringField"; + value: Scalars["String"]; + name: Scalars["String"]; +}; + +export type StringType = FieldType & { + __typename?: "StringType"; + name: Scalars["String"]; +}; + +export type SubRate = { + __typename?: "SubRate"; + name: Scalars["String"]; + amount: Scalars["Float"]; +}; + +export type SubRateDraft = { + name: Scalars["String"]; + amount: Scalars["Float"]; +}; + +/** Stores information about order synchronization activities (like export or import). */ +// This file is generated do not change it. + +export type SyncInfo = { + __typename?: "SyncInfo"; + channelRef: Reference; + channel?: Maybe; + externalId?: Maybe; + syncedAt: Scalars["DateTime"]; +}; + +export enum TaxCalculationMode { + /** This calculation mode calculates the taxes on the unit price before multiplying with the quantity. + * E.g. `($1.08 * 1.19 = $1.2852 -> $1.29 rounded) * 3 = $3.87` + */ + UnitPriceLevel = "UnitPriceLevel", + /** Default. This calculation mode calculates the taxes after the unit price is multiplied with the quantity. + * E.g. `($1.08 * 3 = $3.24) * 1.19 = $3.8556 -> $3.86 rounded` + */ + LineItemLevel = "LineItemLevel" +} + +/** Tax Categories define how products are to be taxed in different countries. */ +export type TaxCategory = Versioned & { + __typename?: "TaxCategory"; + name: Scalars["String"]; + description?: Maybe; + rates: Array; + key?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type TaxCategoryAddTaxRate = { + taxRate: TaxRateDraft; +}; + +export type TaxCategoryChangeName = { + name: Scalars["String"]; +}; + +export type TaxCategoryDraft = { + name: Scalars["String"]; + description?: Maybe; + rates?: Maybe>; + key?: Maybe; +}; + +export type TaxCategoryQueryResult = { + __typename?: "TaxCategoryQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type TaxCategoryRemoveTaxRate = { + taxRateId: Scalars["String"]; +}; + +export type TaxCategoryReplaceTaxRate = { + taxRateId: Scalars["String"]; + taxRate: TaxRateDraft; +}; + +export type TaxCategorySetDescription = { + description?: Maybe; +}; + +export type TaxCategoryUpdateAction = { + changeName?: Maybe; + setDescription?: Maybe; + addTaxRate?: Maybe; + replaceTaxRate?: Maybe; + removeTaxRate?: Maybe; + setKey?: Maybe; +}; + +export type TaxedItemPrice = { + __typename?: "TaxedItemPrice"; + totalNet: Money; + totalGross: Money; +}; + +export type TaxedPrice = { + __typename?: "TaxedPrice"; + totalNet: Money; + totalGross: Money; + taxPortions: Array; +}; + +export enum TaxMode { + /** No taxes are added to the cart. */ + Disabled = "Disabled", + /** The tax amounts and the tax rates as well as the tax portions are set externally per ExternalTaxAmountDraft. + * A cart with this tax mode can only be ordered if the cart itself and all line items, all custom line items and + * the shipping method have an external tax amount and rate set + */ + ExternalAmount = "ExternalAmount", + /** The tax rates are set externally per ExternalTaxRateDraft. A cart with this tax mode can only be ordered if all + * line items, all custom line items and the shipping method have an external tax rate set. The totalNet and + * totalGross as well as the taxPortions fields are calculated by the platform according to the taxRoundingMode. + */ + External = "External", + /** The tax rates are selected by the platform from the TaxCategories based on the cart shipping address. + * The totalNet and totalGross as well as the taxPortions fields are calculated by the platform according to the + * taxRoundingMode. + */ + Platform = "Platform" +} + +/** Represents the portions that sum up to the totalGross field of a TaxedPrice. The portions are calculated + * from the TaxRates. If a tax rate has SubRates, they are used and can be identified by name. Tax portions + * from line items that have the same rate and name will be accumulated to the same tax portion. + */ +export type TaxPortion = { + __typename?: "TaxPortion"; + rate: Scalars["Float"]; + amount: Money; + name?: Maybe; +}; + +export type TaxPortionDraft = { + name?: Maybe; + rate: Scalars["Float"]; + amount: MoneyInput; +}; + +export type TaxRate = { + __typename?: "TaxRate"; + name: Scalars["String"]; + amount: Scalars["Float"]; + includedInPrice: Scalars["Boolean"]; + country: Scalars["Country"]; + state?: Maybe; + id?: Maybe; + subRates: Array; +}; + +export type TaxRateDraft = { + name: Scalars["String"]; + amount?: Maybe; + includedInPrice: Scalars["Boolean"]; + country: Scalars["Country"]; + state?: Maybe; + subRates?: Maybe>; +}; + +export type TextAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "TextAttributeDefinitionType"; + name: Scalars["String"]; +}; + +/** UI hint telling what kind of edit control should be displayed for a text attribute. */ +export enum TextInputHint { + MultiLine = "MultiLine", + SingleLine = "SingleLine" +} + +export type TextLineItem = { + __typename?: "TextLineItem"; + id: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + quantity: Scalars["Int"]; + custom?: Maybe; + addedAt: Scalars["DateTime"]; +}; + +export type TextLineItemNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type TextLineItemDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +export type TextLineItemDraft = { + name: Array; + description?: Maybe>; + quantity?: Maybe; + custom?: Maybe; + addedAt?: Maybe; +}; + +export type TimeAttribute = Attribute & { + __typename?: "TimeAttribute"; + value: Scalars["Time"]; + name: Scalars["String"]; +}; + +export type TimeAttributeDefinitionType = AttributeDefinitionType & { + __typename?: "TimeAttributeDefinitionType"; + name: Scalars["String"]; +}; + +export type TimeField = CustomField & { + __typename?: "TimeField"; + value: Scalars["Time"]; + name: Scalars["String"]; +}; + +export type TimeType = FieldType & { + __typename?: "TimeType"; + name: Scalars["String"]; +}; + +export type TrackingData = { + __typename?: "TrackingData"; + trackingId?: Maybe; + carrier?: Maybe; + provider?: Maybe; + providerTransaction?: Maybe; + isReturn: Scalars["Boolean"]; +}; + +export type TrackingDataDraftType = { + trackingId?: Maybe; + carrier?: Maybe; + provider?: Maybe; + providerTransaction?: Maybe; + isReturn?: Maybe; +}; + +export type Transaction = { + __typename?: "Transaction"; + id: Scalars["String"]; + timestamp?: Maybe; + type?: Maybe; + amount: Money; + interactionId?: Maybe; + state: TransactionState; +}; + +export enum TransactionState { + Failure = "Failure", + Success = "Success", + Pending = "Pending", + Initial = "Initial" +} + +export enum TransactionType { + Chargeback = "Chargeback", + Refund = "Refund", + Charge = "Charge", + CancelAuthorization = "CancelAuthorization", + Authorization = "Authorization" +} + +export type TransitionOrderCustomLineItemState = { + customLineItemId: Scalars["String"]; + quantity: Scalars["Long"]; + fromState: ResourceIdentifierInput; + toState: ResourceIdentifierInput; + actualTransitionDate?: Maybe; +}; + +export type TransitionOrderLineItemState = { + lineItemId: Scalars["String"]; + quantity: Scalars["Long"]; + fromState: ResourceIdentifierInput; + toState: ResourceIdentifierInput; + actualTransitionDate?: Maybe; +}; + +export type TransitionOrderState = { + state: ResourceIdentifierInput; + force?: Maybe; +}; + +export type TransitionProductState = { + state: ReferenceInput; + force?: Maybe; +}; + +export type Type = { + typeRef: Reference; + type?: Maybe; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinition = Versioned & { + __typename?: "TypeDefinition"; + key: Scalars["String"]; + name?: Maybe; + description?: Maybe; + nameAllLocales: Array; + descriptionAllLocales?: Maybe>; + resourceTypeIds: Array; + fieldDefinitions: Array; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinitionNameArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinitionDescriptionArgs = { + locale?: Maybe; + acceptLanguage?: Maybe>; +}; + +/** Types define the structure of custom fields which can be attached to different entities throughout the platform. */ +export type TypeDefinitionFieldDefinitionsArgs = { + includeNames?: Maybe>; + excludeNames?: Maybe>; +}; + +export type TypeDefinitionQueryResult = { + __typename?: "TypeDefinitionQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type UnpublishProduct = { + dummy?: Maybe; +}; + +export type UpdateCartItemShippingAddress = { + address: AddressInput; +}; + +export type UpdateOrderItemShippingAddress = { + address: AddressInput; +}; + +export type UpdateOrderSyncInfo = { + channel: ResourceIdentifierInput; + syncedAt?: Maybe; + externalId?: Maybe; +}; + +/** Versioned object have an ID and version and modification. Every update of this object changes it's version. */ +export type Versioned = { + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type WhitespaceSuggestTokenizerInput = { + dummy?: Maybe; +}; + +/** Zones allow defining ShippingRates for specific Locations. */ +export type Zone = Versioned & { + __typename?: "Zone"; + name: Scalars["String"]; + key?: Maybe; + description?: Maybe; + locations: Array; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; +}; + +export type ZoneLocation = { + country: Scalars["Country"]; + state?: Maybe; +}; + +export type ZoneQueryResult = { + __typename?: "ZoneQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; +}; + +export type ZoneRate = { + __typename?: "ZoneRate"; + shippingRates: Array; + zoneRef?: Maybe; + zone?: Maybe; +}; + +export type ZoneRateDraft = { + zone: ResourceIdentifierInput; + shippingRates?: Maybe>; +}; + +export type ZoneUpdateAction = { + addLocation?: Maybe; + changeName?: Maybe; + removeLocation?: Maybe; + setDescription?: Maybe; + setKey?: Maybe; +}; diff --git a/packages/commercetools/composables/src/types/index.ts b/packages/commercetools/composables/src/types/index.ts new file mode 100644 index 0000000000..f3b2ee7b21 --- /dev/null +++ b/packages/commercetools/composables/src/types/index.ts @@ -0,0 +1,53 @@ +import { + Filter, + ProductVariant, + Category, + ResetPasswordResponse, + CreatePasswordResetTokenResponse, + Store +} from '@vue-storefront/commercetools-api'; +import { FacetSearchResult } from '@vue-storefront/core'; +import { StoreQueryResult } from './GraphQL'; + +export type OrderSearchParams = { + id?: string; + page?: number; + perPage?: number; +}; + +export interface ProductsSearchParams { + perPage?: number; + page?: number; + sort?: any; + term?: any; + filters?: Record; + catId?: string | string[]; + skus?: string[]; + slug?: string; + id?: string; +} + +export interface FacetResultsData { + products: ProductVariant[]; + categories: Category[]; + facets: Record; + total: number; + perPageOptions: number[]; + itemsPerPage: number; +} + +export interface ForgotPasswordResult { + resetPasswordResult: CreatePasswordResetTokenResponse; + setNewPasswordResult: ResetPasswordResponse; +} + +export type SearchData = FacetSearchResult + +export interface StoresData extends StoreQueryResult { + _selectedStore: string; +} + +export interface StoreGetters { + getItems(stores: STORES, criteria?: CRITERIA): Store[]; + getSelected(stores: STORES): Store | undefined; +} diff --git a/packages/commercetools/composables/src/useBilling/index.ts b/packages/commercetools/composables/src/useBilling/index.ts new file mode 100644 index 0000000000..df8557739a --- /dev/null +++ b/packages/commercetools/composables/src/useBilling/index.ts @@ -0,0 +1,37 @@ +import { useBillingFactory, UseBillingParams, Context } from '@vue-storefront/core'; +import { useCart } from '../useCart'; +import { cartActions } from '@vue-storefront/commercetools-api'; +import { Address } from './../types/GraphQL'; + +const useBillingProviderFactoryParams: UseBillingParams = { + provide() { + return { + cart: useCart() + }; + }, + load: async (context: Context, { customQuery }) => { + if (!context.cart.cart?.value?.billingAddress) { + await context.cart.load({ customQuery }); + } + return context.cart.cart.value.billingAddress; + }, + save: async (context: Context, { billingDetails, customQuery }) => { + const cartResponse = await context.$ct.api.updateCart({ + id: context.cart.cart.value.id, + version: context.cart.cart.value.version, + actions: [ + cartActions.setBillingAddressAction(billingDetails) + ] + }, customQuery); + + context.cart.setCart(cartResponse.data.cart); + return context.cart.cart.value.billingAddress; + } +}; + +const useBilling = useBillingFactory(useBillingProviderFactoryParams); + +export { + useBilling, + useBillingProviderFactoryParams +}; diff --git a/packages/commercetools/composables/src/useCart/currentCart.ts b/packages/commercetools/composables/src/useCart/currentCart.ts new file mode 100644 index 0000000000..bc90de5112 --- /dev/null +++ b/packages/commercetools/composables/src/useCart/currentCart.ts @@ -0,0 +1,16 @@ +import { Context } from '@vue-storefront/core'; + +const loadCurrentCart = async (context: Context, customQueryFn = (user = null, cart = null) => ({ cart, user })) => { + const { user, cart } = customQueryFn(); + const { data: profileData } = await context.$ct.api.getMe({ customer: false }, user); + + if (profileData.me.activeCart) { + return profileData.me.activeCart; + } + + const { data } = await context.$ct.api.createCart({}, cart); + + return data.cart; +}; + +export default loadCurrentCart; diff --git a/packages/commercetools/composables/src/useCart/index.ts b/packages/commercetools/composables/src/useCart/index.ts new file mode 100644 index 0000000000..3a69a0ffdf --- /dev/null +++ b/packages/commercetools/composables/src/useCart/index.ts @@ -0,0 +1,99 @@ +import loadCurrentCart from './currentCart'; +import { ProductVariant, LineItem } from './../types/GraphQL'; +import { Cart, CartDetails } from '@vue-storefront/commercetools-api'; +import { useCartFactory, UseCartFactoryParams, Context } from '@vue-storefront/core'; + +const getCartItemByProduct = ({ currentCart, product }) => { + return currentCart.lineItems.find((item) => item.productId === product._id); +}; + +/** returns current cart or creates new one **/ +const getCurrentCartDetails = async (context: Context, currentCart, completeDetails?: COMPLETE_DETAILS): + Promise => { + const cart = currentCart || await loadCurrentCart(context); + + return completeDetails ? cart : { id: cart.id, version: cart.version }; +}; + +const useCartFactoryParams: UseCartFactoryParams = { + load: async (context: Context, { customQuery }) => { + const { $ct } = context; + if (!$ct.config.auth.onTokenRead()) return null; + + const isGuest = await $ct.api.isGuest(); + + if (isGuest) { + return null; + } + + const { data: profileData } = await context.$ct.api.getMe({ customer: false }, customQuery); + + return profileData.me.activeCart; + }, + addItem: async (context: Context, { currentCart, product, quantity, customQuery }) => { + const cartDetails = await getCurrentCartDetails(context, currentCart); + + const params: { + product: ProductVariant; + quantity: number; + supplyChannel?: string; + distributionChannel?: string; + } = { + product, + quantity + }; + + if (customQuery?.distributionChannel) params.distributionChannel = customQuery.distributionChannel; + if (customQuery?.supplyChannel) params.supplyChannel = customQuery.supplyChannel; + + const { data } = await context.$ct.api.addToCart(cartDetails, params, customQuery); + + return data.cart; + }, + removeItem: async (context: Context, { currentCart, product, customQuery }) => { + const cartDetails = await getCurrentCartDetails(context, currentCart); + + const { data } = await context.$ct.api.removeFromCart(cartDetails, product, customQuery); + return data.cart; + }, + updateItemQty: async (context: Context, { currentCart, product, quantity, customQuery }) => { + const cartDetails = await getCurrentCartDetails(context, currentCart); + + const { data } = await context.$ct.api.updateCartQuantity(cartDetails, { ...product, quantity }, customQuery); + return data.cart; + }, + clear: async (context: Context, { currentCart }) => { + const cartDetails = await getCurrentCartDetails(context, currentCart); + + const { data } = await context.$ct.api.deleteCart(cartDetails); + return data.cart; + }, + applyCoupon: async (context: Context, { currentCart, couponCode, customQuery }) => { + const cartDetails = await getCurrentCartDetails(context, currentCart); + + const { data } = await context.$ct.api.applyCartCoupon(cartDetails, couponCode, customQuery); + return { updatedCart: data.cart, updatedCoupon: couponCode }; + }, + removeCoupon: async (context: Context, { currentCart, couponCode, customQuery }) => { + const cartDetails = await getCurrentCartDetails(context, currentCart, true); + + const couponId = cartDetails.discountCodes.find((d) => d.discountCode.code === couponCode)?.discountCode?.id; + + if (!couponId) { + return { updatedCart: currentCart }; + } + + const { data } = await context.$ct.api.removeCartCoupon(cartDetails, { id: couponId, typeId: 'discount-code' }, customQuery); + return { updatedCart: data.cart }; + }, + isInCart: (context: Context, { currentCart, product }) => { + return Boolean(currentCart && getCartItemByProduct({ currentCart, product })); + } +}; + +const useCart = useCartFactory(useCartFactoryParams); + +export { + useCart, + useCartFactoryParams +}; diff --git a/packages/commercetools/composables/src/useCategory/index.ts b/packages/commercetools/composables/src/useCategory/index.ts new file mode 100644 index 0000000000..7229e7b489 --- /dev/null +++ b/packages/commercetools/composables/src/useCategory/index.ts @@ -0,0 +1,21 @@ +import { + Context, + UseCategory, + useCategoryFactory, + UseCategoryFactoryParams +} from '@vue-storefront/core'; +import { Category } from './../types/GraphQL'; + +const useCategoryFactoryParams: UseCategoryFactoryParams = { + categorySearch: async (context: Context, { customQuery, ...searchParams }) => { + const categoryResponse = await context.$ct.api.getCategory(searchParams, customQuery); + return categoryResponse.data.categories.results; + } +}; + +const useCategory: (id: string) => UseCategory = useCategoryFactory(useCategoryFactoryParams); + +export { + useCategory, + useCategoryFactoryParams +}; diff --git a/packages/commercetools/composables/src/useFacet/_utils.ts b/packages/commercetools/composables/src/useFacet/_utils.ts new file mode 100644 index 0000000000..6f47a75fff --- /dev/null +++ b/packages/commercetools/composables/src/useFacet/_utils.ts @@ -0,0 +1,61 @@ +import { SearchData } from './../types'; + +const buildBreadcrumbsList = (rootCat, bc) => { + const newBc = [...bc, { text: rootCat.name, link: rootCat.slug }]; + return rootCat.parent ? buildBreadcrumbsList(rootCat.parent, newBc) : newBc; +}; + +export const buildBreadcrumbs = (rootCat) => + buildBreadcrumbsList(rootCat, []) + .reverse() + .reduce((prev, curr, index) => ([ + ...prev, + { ...curr, link: `${prev[index - 1]?.link || '' }/${curr.link}` }]), + []); + +const filterFacets = criteria => f => criteria ? criteria.includes(f) : true; + +const createFacetsFromOptions = (facets, filters, filterKey) => { + const options = facets[filterKey]?.options || []; + const selectedList = filters && filters[filterKey] ? filters[filterKey] : []; + + return options + .map(({ label, value }) => ({ + type: 'attribute', + id: label, + attrName: filterKey, + value, + selected: selectedList.includes(value), + count: null + })); +}; + +export const reduceForFacets = (facets, filters) => (prev, curr) => ([ + ...prev, + ...createFacetsFromOptions(facets, filters, curr) +]); + +export const reduceForGroupedFacets = (facets, filters) => (prev, curr) => ([ + ...prev, + { + id: curr, + label: curr, + options: createFacetsFromOptions(facets, filters, curr), + count: null + } +]); + +export const buildFacets = (searchData: SearchData, reduceFn, criteria?: string[]) => { + if (!searchData.data) { + return []; + } + + const { + data: { facets }, + input: { filters } + } = searchData; + + return Object.keys(facets) + .filter(filterFacets(criteria)) + .reduce(reduceFn(facets, filters), []); +}; diff --git a/packages/commercetools/composables/src/useFacet/index.ts b/packages/commercetools/composables/src/useFacet/index.ts new file mode 100644 index 0000000000..46d6d9278c --- /dev/null +++ b/packages/commercetools/composables/src/useFacet/index.ts @@ -0,0 +1,51 @@ +import { useFacetFactory, FacetSearchResult, Context } from '@vue-storefront/core'; +import { AttributeType } from '@vue-storefront/commercetools-api'; +import { enhanceProduct, getFiltersFromProductsAttributes, getChannelId } from './../helpers/internals'; +import { ProductVariant } from './../types/GraphQL'; +import { FacetResultsData } from './../types'; + +// TODO: move to the config file +const ITEMS_PER_PAGE = [20, 40, 100]; + +const useFacetFactoryParams = { + search: async (context: Context, params: FacetSearchResult): Promise => { + const itemsPerPage = params.input.itemsPerPage; + + const categoryResponse = await context.$ct.api.getCategory({ slug: params.input.categorySlug }); + const categories = categoryResponse.data.categories.results; + const inputFilters = params.input.filters; + const filters = Object.keys(inputFilters).reduce((prev, curr) => ([ + ...prev, + ...inputFilters[curr].map(value => ({ type: AttributeType.STRING, name: curr, value })) + ]), []); + + const productResponse = await context.$ct.api.getProduct({ + catId: categories[0].id, + limit: itemsPerPage, + offset: (params.input.page - 1) * itemsPerPage, + filters, + channelId: getChannelId(context.$ct.config.store) + // TODO: https://github.com/DivanteLtd/vue-storefront/issues/4857 + // sort: params.sort + }); + const enhancedProductResponse = enhanceProduct(productResponse, context); + const products = (enhancedProductResponse.data as any)._variants as ProductVariant[]; + const facets = getFiltersFromProductsAttributes(products); + + return { + products, + categories, + facets, + total: productResponse.data.products.total, + perPageOptions: ITEMS_PER_PAGE, + itemsPerPage + }; + } +}; + +const useFacet = useFacetFactory(useFacetFactoryParams); + +export { + useFacet, + useFacetFactoryParams +}; diff --git a/packages/commercetools/composables/src/useForgotPassword/index.ts b/packages/commercetools/composables/src/useForgotPassword/index.ts new file mode 100644 index 0000000000..1b6c1515b0 --- /dev/null +++ b/packages/commercetools/composables/src/useForgotPassword/index.ts @@ -0,0 +1,37 @@ +import { Context, UseForgotPassword, useForgotPasswordFactory, UseForgotPasswordFactoryParams } from '@vue-storefront/core'; +import { ForgotPasswordResult } from '../types'; + +const useForgotPasswordFactoryParams: UseForgotPasswordFactoryParams = { + resetPassword: async (context: Context, { email, currentResult, customQuery }) => { + try { + const resetPasswordResult = await context.$ct.api.customerCreatePasswordResetToken(email, customQuery); + return { + ...currentResult, + resetPasswordResult + }; + } catch (err) { + err.message = err?.graphQLErrors?.[0]?.message || err.message; + throw err?.response?.data?.graphQLErrors?.[0] || err; + } + + }, + setNewPassword: async (context: Context, { tokenValue, newPassword, currentResult, customQuery }) => { + try { + const setNewPasswordResult = await context.$ct.api.customerResetPassword(tokenValue, newPassword, customQuery); + return { + ...currentResult, + setNewPasswordResult + }; + } catch (err) { + err.message = err?.graphQLErrors?.[0]?.message || err.message; + throw err?.response?.data?.graphQLErrors?.[0] || err; + } + } +}; + +const useForgotPassword: () => UseForgotPassword = useForgotPasswordFactory(useForgotPasswordFactoryParams); + +export { + useForgotPassword, + useForgotPasswordFactoryParams +}; diff --git a/packages/commercetools/composables/src/useMakeOrder/index.ts b/packages/commercetools/composables/src/useMakeOrder/index.ts new file mode 100644 index 0000000000..1495e500f5 --- /dev/null +++ b/packages/commercetools/composables/src/useMakeOrder/index.ts @@ -0,0 +1,24 @@ +import { useCart } from '../useCart'; +import { Order } from './../types/GraphQL'; +import { UseMakeOrder, useMakeOrderFactory, Context } from '@vue-storefront/core'; + +const useMakeOrderFactoryParams = { + provide() { + return { + cart: useCart() + }; + }, + + make: async (context: Context, { customQuery }): Promise => { + const { id, version } = context.cart.cart.value; + const response = await context.$ct.api.createMyOrderFromCart({ id, version }, customQuery); + return response.data.order; + } +}; + +const useMakeOrder: () => UseMakeOrder = useMakeOrderFactory(useMakeOrderFactoryParams); + +export { + useMakeOrder, + useMakeOrderFactoryParams +}; diff --git a/packages/commercetools/composables/src/useProduct/index.ts b/packages/commercetools/composables/src/useProduct/index.ts new file mode 100644 index 0000000000..008f27b64a --- /dev/null +++ b/packages/commercetools/composables/src/useProduct/index.ts @@ -0,0 +1,28 @@ +import { ProductsSearchParams } from '../types'; +import { ProductVariant } from './../types/GraphQL'; +import { enhanceProduct, mapPaginationParams, getChannelId } from './../helpers/internals'; +import { useProductFactory, UseProduct, Context } from '@vue-storefront/core'; + +const useProductFactoryParams = { + productsSearch: async (context: Context, { customQuery, ...searchParams }): Promise => { + + const apiSearchParams = { + ...searchParams, + ...mapPaginationParams(searchParams), + channelId: getChannelId(context.$ct.config.store) + }; + + const productResponse = await context.$ct.api.getProduct(apiSearchParams, customQuery); + const enhancedProductResponse = enhanceProduct(productResponse, context); + const products = (enhancedProductResponse.data as any)._variants; + + return products; + } +}; + +const useProduct: (cacheId: string) => UseProduct = useProductFactory(useProductFactoryParams); + +export { + useProduct, + useProductFactoryParams +}; diff --git a/packages/commercetools/composables/src/useReview/index.ts b/packages/commercetools/composables/src/useReview/index.ts new file mode 100644 index 0000000000..1bff2241de --- /dev/null +++ b/packages/commercetools/composables/src/useReview/index.ts @@ -0,0 +1,23 @@ +/* istanbul ignore file */ + +import { useReviewFactory, UseReview, UseReviewFactoryParams, Context } from '@vue-storefront/core'; + +const useReviewFactoryParams: UseReviewFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + searchReviews: async (context: Context, params) => { + console.log('Mocked: searchReviews'); + return {}; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addReview: async (context: Context, params) => { + console.log('Mocked: addReview'); + return {}; + } +}; + +const useReview: (cacheId: string) => UseReview = useReviewFactory(useReviewFactoryParams); + +export { + useReview, + useReviewFactoryParams +}; diff --git a/packages/commercetools/composables/src/useShipping/index.ts b/packages/commercetools/composables/src/useShipping/index.ts new file mode 100644 index 0000000000..5301cf3b9a --- /dev/null +++ b/packages/commercetools/composables/src/useShipping/index.ts @@ -0,0 +1,38 @@ +import { useShippingFactory, UseShippingParams, Context } from '@vue-storefront/core'; +import { useCart } from '../useCart'; +import { cartActions } from '@vue-storefront/commercetools-api'; +import { Address } from './../types/GraphQL'; + +const useShippingFactoryParams: UseShippingParams = { + provide() { + return { + cart: useCart() + }; + }, + load: async (context: Context, { customQuery }) => { + if (!context.cart.cart?.value?.shippingAddress) { + await context.cart.load({ customQuery }); + } + return context.cart.cart.value.shippingAddress; + }, + save: async (context: Context, { shippingDetails, customQuery }) => { + const cartResponse = await context.$ct.api.updateCart({ + id: context.cart.cart.value.id, + version: context.cart.cart.value.version, + actions: [ + cartActions.setShippingMethodAction(), + cartActions.setShippingAddressAction(shippingDetails) + ] + }, customQuery); + + context.cart.setCart(cartResponse.data.cart); + return context.cart.cart.value.shippingAddress; + } +}; + +const useShipping = useShippingFactory(useShippingFactoryParams); + +export { + useShipping, + useShippingFactoryParams +}; diff --git a/packages/commercetools/composables/src/useShippingProvider/index.ts b/packages/commercetools/composables/src/useShippingProvider/index.ts new file mode 100644 index 0000000000..5b063b794e --- /dev/null +++ b/packages/commercetools/composables/src/useShippingProvider/index.ts @@ -0,0 +1,47 @@ +import { useShippingProviderFactory, UseShippingProviderParams, Context } from '@vue-storefront/core'; +import { useCart } from '../useCart'; +import { ShippingInfo, ShippingMethod } from './../types/GraphQL'; +import { cartActions } from '@vue-storefront/commercetools-api'; + +interface ShippingProviderState { + response: ShippingInfo +} + +const useShippingProviderFactoryParams: UseShippingProviderParams = { + provide() { + return { + cart: useCart() + }; + }, + load: async (context: Context, { customQuery, state }) => { + if (!context.cart.cart?.value?.shippingInfo) { + await context.cart.load({ customQuery }); + } + return { + ...state.value, + response: context.cart.cart.value.shippingInfo + }; + }, + save: async (context: Context, { shippingMethod, customQuery, state }) => { + const cartResponse = await context.$ct.api.updateCart({ + id: context.cart.cart.value.id, + version: context.cart.cart.value.version, + actions: [ + cartActions.setShippingMethodAction(shippingMethod.id) + ] + }, customQuery); + context.cart.setCart(cartResponse.data.cart); + + return { + ...state.value, + response: context.cart.cart.value.shippingInfo + }; + } +}; + +const useShippingProvider = useShippingProviderFactory(useShippingProviderFactoryParams); + +export { + useShippingProvider, + useShippingProviderFactoryParams +}; diff --git a/packages/commercetools/composables/src/useStore/factoryParams.ts b/packages/commercetools/composables/src/useStore/factoryParams.ts new file mode 100644 index 0000000000..22fee3e1bd --- /dev/null +++ b/packages/commercetools/composables/src/useStore/factoryParams.ts @@ -0,0 +1,43 @@ +import { + Context, + UseStoreFactoryParams, + UseStoreFactoryLoadParamArguments, + UseStoreFactoryChangeParamArguments, CustomQuery +} from '@vue-storefront/core'; +import { StoresData } from '../types'; +import { ResourceIdentifierInput } from '../types/GraphQL'; + +export interface UseStoreFactoryChangeChannelParamArguments { + channel: Record; + customQuery?: CustomQuery; +} + +export interface CtUseStoreFactoryParams extends UseStoreFactoryParams { + changeChannel(context: Context, params: UseStoreFactoryChangeChannelParamArguments): Promise +} + +async function load (context: Context, params: UseStoreFactoryLoadParamArguments): Promise { + const { api, config } = context.$ct; + const { customQuery } = params; + + return { + ...await api.getStores({ customQuery }), + _selectedStore: config.store + }; +} + +async function change (context: Context, { store }: UseStoreFactoryChangeParamArguments) { + context.$ct.config.storeService.changeCurrentStore(`${store.key}`); + window.location.reload(); + return null as StoresData; +} + +async function changeChannel (context: Context, { channel }: UseStoreFactoryChangeChannelParamArguments) { + context.$ct.config.storeService.changeCurrentStore(`${(channel.distributtionChannel || 'null')}-${(channel.supplyChannel || 'null')}`); + window.location.reload(); + return null as StoresData; +} + +const factoryParams: CtUseStoreFactoryParams = { load, change, changeChannel }; + +export default factoryParams; diff --git a/packages/commercetools/composables/src/useStore/index.ts b/packages/commercetools/composables/src/useStore/index.ts new file mode 100644 index 0000000000..96732d7837 --- /dev/null +++ b/packages/commercetools/composables/src/useStore/index.ts @@ -0,0 +1,10 @@ +import { useStoreFactory } from '@vue-storefront/core'; +import useStoreFactoryParams from './factoryParams'; +import { StoresData } from '../types'; + +const useStore = useStoreFactory(useStoreFactoryParams); + +export { + useStore, + useStoreFactoryParams +}; diff --git a/packages/commercetools/composables/src/useUser/authenticate.ts b/packages/commercetools/composables/src/useUser/authenticate.ts new file mode 100644 index 0000000000..377e977d7d --- /dev/null +++ b/packages/commercetools/composables/src/useUser/authenticate.ts @@ -0,0 +1,15 @@ +import { Logger } from '@vue-storefront/core'; +import { CustomerSignMeInDraft, CustomerSignMeUpDraft } from '../types/GraphQL'; + +type UserData = CustomerSignMeUpDraft | CustomerSignMeInDraft; + +export const authenticate = async (userData: UserData, fn) => { + try { + const userResponse = await fn(userData); + return userResponse.data.user; + } catch (err) { + err.message = err?.graphQLErrors?.[0]?.message || err.message; + Logger.error('useUser.authenticate', err.message); + throw err?.response?.data?.graphQLErrors?.[0] || err; + } +}; diff --git a/packages/commercetools/composables/src/useUser/factoryParams.ts b/packages/commercetools/composables/src/useUser/factoryParams.ts new file mode 100644 index 0000000000..58a3306553 --- /dev/null +++ b/packages/commercetools/composables/src/useUser/factoryParams.ts @@ -0,0 +1,67 @@ +import { UseUserFactoryParams, Context, UseCart } from '@vue-storefront/core'; +import { Cart, Customer, LineItem, ProductVariant } from '../types/GraphQL'; +import { authenticate } from './authenticate'; +import { useCart } from '../useCart'; + +type UserContext = UseCart & Context; + +const load = async (context: Context, {customQuery}) => { + if (!context.$ct.config.auth.onTokenRead()) return null; + + const isGuest = await context.$ct.api.isGuest(); + + if (isGuest) { + return null; + } + + const profile = await context.$ct.api.getMe({ customer: true }, customQuery); + return profile.data.me.customer; +}; + +const getCurrentUser = async (context: Context, currentUser, customQuery) => { + if (!currentUser) { + return load(context, {customQuery}); + } + + return currentUser; +}; + +export const useUserFactoryParams: UseUserFactoryParams = { + provide() { + return useCart(); + }, + load, + logOut: async (context: UserContext) => { + await context.$ct.api.customerSignOut(); + await context.$ct.config?.auth?.onTokenRemove(); + + context.setCart(null); + }, + updateUser: async (context: UserContext, { currentUser, updatedUserData, customQuery }) => { + const loadedUser = await getCurrentUser(context, currentUser, customQuery); + const { user } = await context.$ct.api.customerUpdateMe(loadedUser, updatedUserData); + + return user; + }, + register: async (context: UserContext, { email, password, firstName, lastName }) => { + const { customer, cart } = await authenticate({email, password, firstName, lastName}, context.$ct.api.customerSignMeUp); + context.setCart(cart); + + return customer; + }, + logIn: async (context: UserContext, { username, password }) => { + const customerLogin = { email: username, password }; + const { customer, cart } = await authenticate(customerLogin, context.$ct.api.customerSignMeIn); + context.setCart(cart); + + return customer; + }, + changePassword: async function changePassword(context: UserContext, { currentUser, currentPassword, newPassword, customQuery }) { + const loadedUser = await getCurrentUser(context, currentUser, customQuery); + const userResponse = await context.$ct.api.customerChangeMyPassword(loadedUser.version, currentPassword, newPassword); + // we do need to re-authenticate user to acquire new token - otherwise all subsequent requests will fail as unauthorized + await this.logOut(context); + return await useUserFactoryParams.logIn(context, { username: userResponse.data.user.email, password: newPassword }); + } +}; + diff --git a/packages/commercetools/composables/src/useUser/index.ts b/packages/commercetools/composables/src/useUser/index.ts new file mode 100644 index 0000000000..2dcd2922d8 --- /dev/null +++ b/packages/commercetools/composables/src/useUser/index.ts @@ -0,0 +1,13 @@ +import { + Customer +} from '../types/GraphQL'; + +import { useUserFactoryParams } from './factoryParams'; +import { useUserFactory } from '@vue-storefront/core'; + +const useUser = useUserFactory(useUserFactoryParams); + +export { + useUser, + useUserFactoryParams +}; diff --git a/packages/commercetools/composables/src/useUserBilling/index.ts b/packages/commercetools/composables/src/useUserBilling/index.ts new file mode 100644 index 0000000000..ac2044ce96 --- /dev/null +++ b/packages/commercetools/composables/src/useUserBilling/index.ts @@ -0,0 +1,141 @@ +import { useUserBillingFactory, UseUserBillingFactoryParams, Context } from '@vue-storefront/core'; +import { makeId } from '../helpers/internals'; + +const addresses: any[] = [ + { + id: '_1231231253623423', + firstName: 'John', + lastName: 'Doe', + streetName: 'Warsawska', + streetNumber: '24', + apartment: '193A', + city: 'Palo Alto', + state: 'California', + postalCode: '26-620', + country: 'US', + phone: '+48560123456', + email: '', + company: null, + isDefault: true + }, + { + id: '_245463456456356', + firstName: 'Jonatan', + lastName: 'Doe', + streetName: 'Starachowicka', + streetNumber: '20', + apartment: '193A', + city: 'Las Vegas', + state: 'Nevada', + postalCode: '53-603', + country: 'US', + phone: '+48560123456', + email: '', + company: null, + isDefault: true + } +]; + +const billing = { + addresses +}; + +const disableOldDefault = () => { + const oldDefault = addresses.find(address => address.isDefault); + if (oldDefault) { + oldDefault.isDefault = false; + } +}; + +const sortDefaultAtTop = (a, b) => { + if (a.isDefault) { + return -1; + } else if (b.isDefault) { + return 1; + } + return 0; +}; + +const useUserBillingFactoryParams: UseUserBillingFactoryParams = { + addAddress: async (context: Context, params?) => { + console.log('Mocked: addAddress', params.address); + + const newAddress = { + ...params.address, + id: makeId() + }; + + if (params.address.isDefault) { + disableOldDefault(); + addresses.unshift(newAddress); + } else { + addresses.push(newAddress); + } + + return Promise.resolve(billing); + }, + + deleteAddress: async (context: Context, params?) => { + console.log('Mocked: deleteAddress', params); + + const indexToRemove = addresses.findIndex(address => address.id === params.address.id); + if (indexToRemove < 0) { + return Promise.reject('This address does not exist'); + } + + addresses.splice(indexToRemove, 1); + return Promise.resolve(billing); + }, + + updateAddress: async (context: Context, params?) => { + console.log('Mocked: updateAddress', params); + + const indexToUpdate = addresses.findIndex(address => address.id === params.address.id); + if (indexToUpdate < 0) { + return Promise.reject('This address does not exist'); + } + + const isNewDefault = params.address.isDefault && addresses[0].id !== params.address.id; + + if (isNewDefault) { + disableOldDefault(); + } + + addresses[indexToUpdate] = params.address; + + if (isNewDefault) { + addresses.sort(sortDefaultAtTop); + } + return Promise.resolve(billing); + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, params?) => { + console.log('Mocked: load'); + return Promise.resolve(billing); + }, + + setDefaultAddress: async (context: Context, params?) => { + console.log('Mocked: setDefaultAddress'); + const isDefault = id => addresses[0].id === id; + + if (!isDefault(params.address.id)) { + const indexToUpdate = addresses.findIndex(address => address.id === params.address.id); + if (indexToUpdate < 0) { + return Promise.reject('This address does not exist'); + } + disableOldDefault(); + addresses[indexToUpdate].isDefault = true; + addresses.sort(sortDefaultAtTop); + } + + return Promise.resolve(billing); + } +}; + +const useUserBilling = useUserBillingFactory(useUserBillingFactoryParams); + +export { + useUserBilling, + useUserBillingFactoryParams +}; diff --git a/packages/commercetools/composables/src/useUserOrder/index.ts b/packages/commercetools/composables/src/useUserOrder/index.ts new file mode 100644 index 0000000000..b54920dc0c --- /dev/null +++ b/packages/commercetools/composables/src/useUserOrder/index.ts @@ -0,0 +1,18 @@ +import { useUserOrderFactory, UseUserOrderFactoryParams, Context } from '@vue-storefront/core'; +import { OrderQueryResult } from '../types/GraphQL'; +import { OrderSearchParams } from '../types'; + +const useUserOrderFactoryParams: UseUserOrderFactoryParams = { + searchOrders: async (context: Context, { customQuery, ...searchParams } = {}): Promise => { + const result = await context.$ct.api.getOrders(searchParams, customQuery); + const orders = result.data?.me.orders || { results: [], total: 0, offset: 0, count: 0 }; + return orders; + } +}; + +const useUserOrder = useUserOrderFactory(useUserOrderFactoryParams); + +export { + useUserOrder, + useUserOrderFactoryParams +}; diff --git a/packages/commercetools/composables/src/useUserShipping/index.ts b/packages/commercetools/composables/src/useUserShipping/index.ts new file mode 100644 index 0000000000..a1163c92f8 --- /dev/null +++ b/packages/commercetools/composables/src/useUserShipping/index.ts @@ -0,0 +1,141 @@ +import { useUserShippingFactory, UseUserShippingFactoryParams, Context } from '@vue-storefront/core'; +import { makeId } from '../helpers/internals'; + +const addresses: any[] = [ + { + id: '_1231231253623423', + firstName: 'John', + lastName: 'Doe', + streetName: 'Warsawska', + streetNumber: '193A', + apartment: '193A', + city: 'Palo Alto', + state: 'California', + postalCode: '26-620', + country: 'US', + phone: '+48560123456', + email: '', + company: null, + isDefault: true + }, + { + id: '_245463456456356', + firstName: 'Jonatan', + lastName: 'Doe', + streetName: 'Starachowicka', + streetNumber: '193A', + apartment: '193A', + city: 'Las Vegas', + state: 'Nevada', + postalCode: '53-603', + country: 'US', + phone: '+48560123456', + email: '', + company: null, + isDefault: true + } +]; + +const shipping = { + addresses +}; + +const disableOldDefault = () => { + const oldDefault = addresses.find(address => address.isDefault); + if (oldDefault) { + oldDefault.isDefault = false; + } +}; + +const sortDefaultAtTop = (a, b) => { + if (a.isDefault) { + return -1; + } else if (b.isDefault) { + return 1; + } + return 0; +}; + +const useUserShippingFactoryParams: UseUserShippingFactoryParams = { + addAddress: async (context: Context, params?) => { + console.log('Mocked: addAddress', params.address); + + const newAddress = { + ...params.address, + id: makeId() + }; + + if (params.address.isDefault) { + disableOldDefault(); + addresses.unshift(newAddress); + } else { + addresses.push(newAddress); + } + + return Promise.resolve(shipping); + }, + + deleteAddress: async (context: Context, params?) => { + console.log('Mocked: deleteAddress', params); + + const indexToRemove = addresses.findIndex(address => address.id === params.address.id); + if (indexToRemove < 0) { + return Promise.reject('This address does not exist'); + } + + addresses.splice(indexToRemove, 1); + return Promise.resolve(shipping); + }, + + updateAddress: async (context: Context, params?) => { + console.log('Mocked: updateAddress', params); + + const indexToUpdate = addresses.findIndex(address => address.id === params.address.id); + if (indexToUpdate < 0) { + return Promise.reject('This address does not exist'); + } + + const isNewDefault = params.address.isDefault && addresses[0].id !== params.address.id; + + if (isNewDefault) { + disableOldDefault(); + } + + addresses[indexToUpdate] = params.address; + + if (isNewDefault) { + addresses.sort(sortDefaultAtTop); + } + return Promise.resolve(shipping); + }, + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context, params?) => { + console.log('Mocked: load'); + return Promise.resolve(shipping); + }, + + setDefaultAddress: async (context: Context, params?) => { + console.log('Mocked: setDefaultAddress'); + const isDefault = id => addresses[0].id === id; + + if (!isDefault(params.address.id)) { + const indexToUpdate = addresses.findIndex(address => address.id === params.address.id); + if (indexToUpdate < 0) { + return Promise.reject('This address does not exist'); + } + disableOldDefault(); + addresses[indexToUpdate].isDefault = true; + addresses.sort(sortDefaultAtTop); + } + + return Promise.resolve(shipping); + } +}; + +const useUserShipping = useUserShippingFactory(useUserShippingFactoryParams); + +export { + useUserShipping, + useUserShippingFactoryParams +}; diff --git a/packages/commercetools/composables/src/useWishlist/index.ts b/packages/commercetools/composables/src/useWishlist/index.ts new file mode 100644 index 0000000000..61daa1d8e0 --- /dev/null +++ b/packages/commercetools/composables/src/useWishlist/index.ts @@ -0,0 +1,39 @@ +/* istanbul ignore file */ + +import { useWishlistFactory, UseWishlistFactoryParams, Context } from '@vue-storefront/core'; +import { ProductVariant, LineItem } from './../types/GraphQL'; + +type Wishlist = any; + +// @todo: implement wishlist +// https://github.com/DivanteLtd/vue-storefront/issues/4420 + +const useWishlistFactoryParams: UseWishlistFactoryParams = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + load: async (context: Context) => { + return {}; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + addItem: async (context: Context, { currentWishlist, product }) => { + return {}; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + removeItem: async (context: Context, { currentWishlist, product }) => { + return {}; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clear: async (context: Context, { currentWishlist }) => { + return {}; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isInWishlist: (context: Context, { currentWishlist }) => { + return false; + } +}; + +const useWishlist = useWishlistFactory(useWishlistFactoryParams); + +export { + useWishlist, + useWishlistFactoryParams +}; diff --git a/packages/commercetools/composables/tsconfig.json b/packages/commercetools/composables/tsconfig.json new file mode 100644 index 0000000000..88cc1db112 --- /dev/null +++ b/packages/commercetools/composables/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "es5", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "lib": ["es6", "es7", "ES2017", "ES2018", "ES2019", "dom"], + "strict": false, + "preserveSymlinks": true, + "paths": { + "@vue-storefront/core": ["core/core/src"], + }, + }, + "exclude": ["node_modules", "**/*.spec.ts", "lib"], + "include": ["src"], + "references": [{ "path": "apollo-client" }] +} diff --git a/packages/commercetools/theme/.babelrc b/packages/commercetools/theme/.babelrc new file mode 100644 index 0000000000..84c2e570ec --- /dev/null +++ b/packages/commercetools/theme/.babelrc @@ -0,0 +1,16 @@ +{ + "env": { + "test": { + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ] + ] + } + } +} diff --git a/packages/commercetools/theme/.editorconfig b/packages/commercetools/theme/.editorconfig new file mode 100644 index 0000000000..5d12634847 --- /dev/null +++ b/packages/commercetools/theme/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/commercetools/theme/.gitignore b/packages/commercetools/theme/.gitignore new file mode 100644 index 0000000000..b747047256 --- /dev/null +++ b/packages/commercetools/theme/.gitignore @@ -0,0 +1,99 @@ +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# theme +_theme + +# Nuxt generate +dist + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# IDE / Editor +.idea + +# Service worker +sw.* + +# Mac OSX +.DS_Store + +# Vim swap files +*.swp + +version + +# e2e reports +tests/e2e/report.json +tests/e2e/report diff --git a/packages/commercetools/theme/README.md b/packages/commercetools/theme/README.md new file mode 100644 index 0000000000..8eb7d2563d --- /dev/null +++ b/packages/commercetools/theme/README.md @@ -0,0 +1,22 @@ +# theme + +> My awesome Vue Storefront project + +## Build Setup + +``` bash +# install dependencies +$ yarn install + +# serve with hot reload at localhost:3000 +$ yarn dev + +# build for production and launch server +$ yarn build +$ yarn start + +# generate static project +$ yarn generate +``` + +For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). diff --git a/packages/commercetools/theme/components/AppHeader.vue b/packages/commercetools/theme/components/AppHeader.vue new file mode 100644 index 0000000000..f16136981c --- /dev/null +++ b/packages/commercetools/theme/components/AppHeader.vue @@ -0,0 +1,247 @@ + + + + + diff --git a/packages/commercetools/theme/components/Checkout/CartPreview.vue b/packages/commercetools/theme/components/Checkout/CartPreview.vue new file mode 100644 index 0000000000..ac42630104 --- /dev/null +++ b/packages/commercetools/theme/components/Checkout/CartPreview.vue @@ -0,0 +1,205 @@ + + + + diff --git a/packages/commercetools/theme/components/Checkout/UserBillingAddresses.vue b/packages/commercetools/theme/components/Checkout/UserBillingAddresses.vue new file mode 100644 index 0000000000..5a722f14f9 --- /dev/null +++ b/packages/commercetools/theme/components/Checkout/UserBillingAddresses.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/packages/commercetools/theme/components/Checkout/UserShippingAddresses.vue b/packages/commercetools/theme/components/Checkout/UserShippingAddresses.vue new file mode 100644 index 0000000000..741ca7ef02 --- /dev/null +++ b/packages/commercetools/theme/components/Checkout/UserShippingAddresses.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/packages/commercetools/theme/components/Checkout/VsfPaymentProviderMock.vue b/packages/commercetools/theme/components/Checkout/VsfPaymentProviderMock.vue new file mode 100644 index 0000000000..b6e43fe7fe --- /dev/null +++ b/packages/commercetools/theme/components/Checkout/VsfPaymentProviderMock.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/packages/commercetools/theme/components/Checkout/VsfShippingProvider.vue b/packages/commercetools/theme/components/Checkout/VsfShippingProvider.vue new file mode 100644 index 0000000000..c904f711bc --- /dev/null +++ b/packages/commercetools/theme/components/Checkout/VsfShippingProvider.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/packages/commercetools/theme/components/MyAccount/BillingAddressForm.vue b/packages/commercetools/theme/components/MyAccount/BillingAddressForm.vue new file mode 100644 index 0000000000..00920dd95c --- /dev/null +++ b/packages/commercetools/theme/components/MyAccount/BillingAddressForm.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/packages/commercetools/theme/components/MyAccount/PasswordResetForm.vue b/packages/commercetools/theme/components/MyAccount/PasswordResetForm.vue new file mode 100644 index 0000000000..709cff876a --- /dev/null +++ b/packages/commercetools/theme/components/MyAccount/PasswordResetForm.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/packages/commercetools/theme/components/MyAccount/ProfileUpdateForm.vue b/packages/commercetools/theme/components/MyAccount/ProfileUpdateForm.vue new file mode 100644 index 0000000000..734c465608 --- /dev/null +++ b/packages/commercetools/theme/components/MyAccount/ProfileUpdateForm.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/packages/commercetools/theme/components/MyAccount/ShippingAddressForm.vue b/packages/commercetools/theme/components/MyAccount/ShippingAddressForm.vue new file mode 100644 index 0000000000..43568665bb --- /dev/null +++ b/packages/commercetools/theme/components/MyAccount/ShippingAddressForm.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/packages/commercetools/theme/components/README.md b/packages/commercetools/theme/components/README.md new file mode 100644 index 0000000000..ea0008a860 --- /dev/null +++ b/packages/commercetools/theme/components/README.md @@ -0,0 +1 @@ +Put here theme-specific components to override default ones \ No newline at end of file diff --git a/packages/commercetools/theme/components/StoreLocaleSelector.vue b/packages/commercetools/theme/components/StoreLocaleSelector.vue new file mode 100644 index 0000000000..4156842f54 --- /dev/null +++ b/packages/commercetools/theme/components/StoreLocaleSelector.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/packages/commercetools/theme/components/TopBar.vue b/packages/commercetools/theme/components/TopBar.vue new file mode 100644 index 0000000000..f29d44d358 --- /dev/null +++ b/packages/commercetools/theme/components/TopBar.vue @@ -0,0 +1,37 @@ + + + + diff --git a/packages/commercetools/theme/components/UserBillingAddress.vue b/packages/commercetools/theme/components/UserBillingAddress.vue new file mode 100644 index 0000000000..dfe63f4989 --- /dev/null +++ b/packages/commercetools/theme/components/UserBillingAddress.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/packages/commercetools/theme/components/UserShippingAddress.vue b/packages/commercetools/theme/components/UserShippingAddress.vue new file mode 100644 index 0000000000..7d3fe2ca04 --- /dev/null +++ b/packages/commercetools/theme/components/UserShippingAddress.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/commercetools/theme/composables/usePaymentProviderMock/index.ts b/packages/commercetools/theme/composables/usePaymentProviderMock/index.ts new file mode 100644 index 0000000000..47d83c91e8 --- /dev/null +++ b/packages/commercetools/theme/composables/usePaymentProviderMock/index.ts @@ -0,0 +1,9 @@ +import { sharedRef } from '@vue-storefront/core'; + +export const usePaymentProviderMock = () => { + const status = sharedRef(false, 'usePaymentProviderMock-status'); + + return { + status + }; +}; diff --git a/packages/commercetools/theme/composables/useUiHelpers/index.ts b/packages/commercetools/theme/composables/useUiHelpers/index.ts new file mode 100644 index 0000000000..a37b97cbd1 --- /dev/null +++ b/packages/commercetools/theme/composables/useUiHelpers/index.ts @@ -0,0 +1,116 @@ +import { getCurrentInstance } from '@vue/composition-api'; +import { Category } from '@vue-storefront/commercetools-api'; +import { AgnosticFacet } from '@vue-storefront/core'; + +const nonFilters = ['page', 'sort', 'phrase', 'itemsPerPage']; + +const getInstance = () => { + const vm = getCurrentInstance(); + return vm.$root as any; +}; + +const reduceFilters = (query) => (prev, curr) => { + const makeArray = Array.isArray(query[curr]) || nonFilters.includes(curr); + + return { + ...prev, + [curr]: makeArray ? query[curr] : [query[curr]] + }; +}; + +const getFiltersDataFromUrl = (context, onlyFilters) => { + const { query } = context.$router.history.current; + + return Object.keys(query) + .filter(f => onlyFilters ? !nonFilters.includes(f) : nonFilters.includes(f)) + .reduce(reduceFilters(query), {}); +}; + +const useUiHelpers = () => { + const instance = getInstance(); + + const getFacetsFromURL = () => { + const { query, params } = instance.$router.history.current; + const categorySlug = Object.keys(params).reduce((prev, curr) => params[curr] || prev, params.slug_1); + + return { + rootCatSlug: params.slug_1, + categorySlug, + page: parseInt(query.page, 10) || 1, + sort: query.sort || 'latest', + filters: getFiltersDataFromUrl(instance, true), + itemsPerPage: parseInt(query.itemsPerPage, 10) || 20, + phrase: query.phrase + }; + }; + + const getSearchTermFromUrl = () => { + const { query, params } = instance.$router.history.current; + // hardcoded categorySlug for search results + const categorySlug = 'women-clothing-jackets'; + + return { + rootCatSlug: params.slug_1, + categorySlug, + page: parseInt(query.page, 10) || 1, + sort: query.sort || 'latest', + filters: getFiltersDataFromUrl(instance, true), + itemsPerPage: parseInt(query.itemsPerPage, 10) || 20, + phrase: query.phrase + }; + }; + + const getCatLink = (category: Category): string => { + return `/c/${instance.$route.params.slug_1}/${category.slug}`; + }; + + const changeSorting = (sort: string) => { + const { query } = instance.$router.history.current; + instance.$router.push({ query: { ...query, sort } }); + }; + + const changeFilters = (filters: any) => { + instance.$router.push({ + query: { + ...getFiltersDataFromUrl(instance, false), + ...filters + } + }); + }; + + const changeItemsPerPage = (itemsPerPage: number) => { + instance.$router.push({ + query: { + ...getFiltersDataFromUrl(instance, false), + itemsPerPage + } + }); + }; + + const setTermForUrl = (term: string) => { + instance.$router.push({ + query: { + ...getFiltersDataFromUrl(instance, false), + phrase: term || undefined + } + }); + }; + + const isFacetColor = (facet: AgnosticFacet): boolean => facet.id === 'color'; + + const isFacetCheckbox = (): boolean => false; + + return { + getFacetsFromURL, + getCatLink, + changeSorting, + changeFilters, + changeItemsPerPage, + setTermForUrl, + isFacetColor, + isFacetCheckbox, + getSearchTermFromUrl + }; +}; + +export default useUiHelpers; diff --git a/packages/commercetools/theme/helpers/Checkout/getShippingMethodPrice.ts b/packages/commercetools/theme/helpers/Checkout/getShippingMethodPrice.ts new file mode 100644 index 0000000000..529d7751a1 --- /dev/null +++ b/packages/commercetools/theme/helpers/Checkout/getShippingMethodPrice.ts @@ -0,0 +1,9 @@ +import { ShippingMethod } from '@vue-storefront/commercetools-api'; + +export default (shippingMethod: ShippingMethod, total: number) => { + const centAmount = shippingMethod?.zoneRates[0].shippingRates[0].freeAbove?.centAmount; + if (centAmount && total >= (centAmount / 100)) { + return 0; + } + return shippingMethod.zoneRates[0].shippingRates[0].price.centAmount / 100; +}; diff --git a/packages/commercetools/theme/helpers/README.md b/packages/commercetools/theme/helpers/README.md new file mode 100644 index 0000000000..87300cbeba --- /dev/null +++ b/packages/commercetools/theme/helpers/README.md @@ -0,0 +1 @@ +Put here platform-specific, non-agnostic functions that overwrite default code. diff --git a/packages/commercetools/theme/helpers/category/getCategorySearchParameters.js b/packages/commercetools/theme/helpers/category/getCategorySearchParameters.js new file mode 100644 index 0000000000..35315adcb2 --- /dev/null +++ b/packages/commercetools/theme/helpers/category/getCategorySearchParameters.js @@ -0,0 +1,8 @@ +export const getCategorySearchParameters = (context) => { + const { params } = context.root.$route; + const lastSlug = Object.keys(params).reduce((prev, curr) => params[curr] || prev, params.slug_1); + + return { + slug: lastSlug + }; +}; diff --git a/packages/commercetools/theme/helpers/validators/phone.ts b/packages/commercetools/theme/helpers/validators/phone.ts new file mode 100644 index 0000000000..ba5a2422c5 --- /dev/null +++ b/packages/commercetools/theme/helpers/validators/phone.ts @@ -0,0 +1,10 @@ +import { extend } from 'vee-validate'; +import PhoneNumber from 'awesome-phonenumber'; + +extend('phone', { + message: 'This is not a valid phone number', + validate (value) { + const phone = new PhoneNumber(value); + return phone.isValid(); + } +}); diff --git a/packages/commercetools/theme/jest.config.js b/packages/commercetools/theme/jest.config.js new file mode 100644 index 0000000000..2af2d96e6b --- /dev/null +++ b/packages/commercetools/theme/jest.config.js @@ -0,0 +1,17 @@ +module.exports = { + moduleNameMapper: { + '^@/(.*)$': '/$1', + '^~/(.*)$': '/$1', + '^vue$': 'vue/dist/vue.common.js' + }, + moduleFileExtensions: ['js', 'vue', 'json'], + transform: { + '^.+\\.js$': 'babel-jest', + '.*\\.(vue)$': 'vue-jest' + }, + collectCoverage: true, + collectCoverageFrom: [ + '/components/**/*.vue', + '/pages/**/*.vue' + ] +}; diff --git a/packages/commercetools/theme/lang/.gitkeep b/packages/commercetools/theme/lang/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/commercetools/theme/lang/de.js b/packages/commercetools/theme/lang/de.js new file mode 100644 index 0000000000..8d83e80a01 --- /dev/null +++ b/packages/commercetools/theme/lang/de.js @@ -0,0 +1,155 @@ +/* eslint-disable */ + +export default { + 'Categories': 'Kategorien', + 'Filters': 'Filters', + 'Sort by': 'Sortieren nach', + 'Products found': 'Produkte gefunden', + 'About us': 'Über uns', + 'Who we are': 'Wer wir sind', + 'Quality in the details': 'Qualität im Detail', + 'Customer Reviews': 'Kundenbewertungen', + 'Departments': 'Abteilungen', + 'Women fashion': 'Damenmode', + 'Men fashion': 'Herrenmode', + 'Kidswear': 'Kinderkleidung', + 'Home': 'Zuhause', + 'Help': 'Hilfe', + 'Customer service': 'Kundendienst', + 'Size guide': 'Größentabelle', + 'Contact us': 'Kontaktiere uns', + 'Payment & Delivery': 'Zahlung & Lieferung', + 'Purchase terms': 'Kaufbedingungen', + 'Guarantee': 'Garantie', + 'Description': 'Beschreibung', + 'Read reviews': 'Bewertungen lesen', + 'Additional Information': 'Zusätzliche Information', + 'Save for later': 'Für später speichern', + 'Add to compare': 'Hinzufügen zum vergleichen', + 'Match it with': 'Kombiniere es mit', + 'Share your look': 'Teile deinen Look', + 'Product description': 'Das Karissa V-Neck Tee hat eine halb taillierte Form schmeichelhaft für jede Figur. Sie können mit ins Fitnessstudio gehen Vertrauen, während es Kurven umarmt und häufiges "Problem" verbirgt Bereiche. Finden Sie atemberaubende Cocktailkleider für Frauen und feiern Sie Kleider.', + 'Brand': 'Marke', + 'Instruction1': 'Um mich kümmern', + 'Instruction2': 'Nur hier für die Pflegehinweise?', + 'Instruction3': 'Ja, das haben wir uns gedacht', + 'Items': 'Gegenstände', + 'View': 'Ansicht', + 'Show on page': 'Auf Seite anzeigen', + 'Done': 'Fertig', + 'Clear all': 'Alles löschen', + 'Empty': 'Sieht so aus, als hätten Sie der Tasche noch keine Artikel hinzugefügt. Beginnen Sie mit dem Einkaufen, um es auszufüllen.', + 'Help & FAQs': 'Hilfe & FAQs', + 'Download': 'Laden Sie unsere Anwendung herunter', + 'Find out more': 'Finde mehr heraus', + 'Login': 'Anmeldung', + 'Forgotten password?': 'Passwort vergessen?', + 'No account': `Sie haben noch keinen Account?`, + 'Register today': 'Melde dich noch heute an', + 'Go to checkout': 'Zur Kasse gehen', + 'Go back shopping': 'Zurück einkaufen', + 'Personal details': 'Persönliche Daten', + 'Edit': 'Bearbeiten', + 'Shipping details': 'Versanddetails', + 'Billing address': 'Rechnungsadresse', + 'Same as shipping address': 'Wie Versandadresse', + 'Payment method': 'Zahlungsmethode', + 'Apply': 'Übernehmen', + 'Update password': 'Passwort aktualisieren', + 'Update personal data': 'Persönliche Daten aktualisieren', + 'Item': 'Artikel', + 'Go back': 'Go back', + 'Continue to shipping': 'Weiter zum Versand', + 'I agree to': 'Ich stimme zu', + 'Terms and conditions': 'Allgemeine Geschäftsbedingungen', + 'Pay for order': 'Für Bestellung bezahlen', + 'Log into your account': 'In dein Konto einloggen', + 'or fill the details below': 'oder füllen Sie die Details unten', + 'Enjoy your free account': 'Enjoy these perks with your free account!', + 'Continue to payment': 'Weiter zur Zahlung', + 'Order No.': 'Bestellnummer', + 'Successful placed order': 'Sie haben die Bestellung erfolgreich aufgegeben. Sie können den Status Ihres Bestellen Sie über unsere Lieferstatusfunktion. Sie erhalten eine Bestellung Bestätigungs-E-Mail mit Details Ihrer Bestellung und einem Link zum Verfolgen der Bestellung Fortschritt.', + 'Info after order': 'Sie können sich mit E-Mail und definiertem Passwort in Ihrem Konto anmelden vorhin. Überprüfen Sie in Ihrem Konto Ihre Profildaten Transaktionsverlauf, Abonnement für Newsletter bearbeiten.', + 'Allow order notifications': 'Bestellbenachrichtigungen zulassen', + 'Feedback': 'Ihr Feedback ist uns wichtig. Lassen Sie uns wissen, was wir verbessern können.', + 'Send my feedback': 'Senden Sie mein Feedback', + 'Go back to shop': 'Zurück zum Einkaufen', + 'Read all reviews': 'Alle Bewertungen lesen', + 'Color': 'Farbe', + 'Contact details updated': 'Halten Sie Ihre Adressen und Kontaktdaten auf dem neuesten Stand.', + 'Manage billing addresses': 'Alle gewünschten Rechnungsadressen verwalten (Arbeitsplatz, Privatadresse ...) Auf diese Weise müssen Sie die Rechnungsadresse nicht bei jeder Bestellung manuell eingeben.', + 'Change': 'Änderungsänderung', + 'Delete': 'Löschen', + 'Add new address': 'Neue Adresse hinzufügen', + 'Set up newsletter': 'Richten Sie Ihren Newsletter ein und wir senden Ihnen wöchentlich Informationen zu neuen Produkten und Trends aus den von Ihnen ausgewählten Bereichen', + 'Sections that interest you': 'Abschnitte, die Sie interessieren', + 'Save changes': 'Änderungen speichern', + 'Read and understand': 'Ich habe das gelesen und verstanden', + 'Privacy': 'Datenschutz', + 'Cookies Policy': 'Cookie-Richtlinie', + 'Commercial information': 'und erklären sich damit einverstanden, personalisierte Handelsinformationen vom Markennamen per E-Mail zu erhalten', + 'Feel free to edit': 'Fühlen Sie sich frei, Ihre unten stehenden Daten zu bearbeiten, damit Ihr Konto immer auf dem neuesten Stand ist', + 'Use your personal data': 'Bei Markennamen legen wir großen Wert auf Datenschutzfragen und verpflichten uns, die persönlichen Daten unserer Benutzer zu schützen. Erfahren Sie mehr darüber, wie wir Ihre persönlichen Daten pflegen und verwenden', + 'Privacy Policy': 'Datenschutzrichtlinie', + 'Change password your account': 'Wenn Sie das Passwort ändern möchten, um auf Ihr Konto zuzugreifen, geben Sie die folgenden Informationen ein', + 'Your current email address is': 'Ihre aktuelle E-Mail-Adresse lautet', + 'Product': 'Produkt', + 'Details and status orders': 'Überprüfen Sie die Details und den Status Ihrer Bestellungen im Online-Shop. Sie können Ihre Bestellung auch stornieren oder eine Rücksendung anfordern. ', + 'You currently have no orders': 'Sie haben derzeit keine Bestellungen', + 'Start shopping': 'Einkaufen starten', + 'Download': 'Herunterladen', + 'Download all': 'Alle herunterladen', + 'View details': 'Details anzeigen', + 'Manage shipping addresses': 'Alle gewünschten Versandadressen verwalten (Arbeitsplatz, Privatadresse ...) Auf diese Weise müssen Sie die Versandadresse nicht bei jeder Bestellung manuell eingeben.', + 'Quantity': 'Menge', + 'Price': 'Preis', + 'Back to homepage': 'Zurück zur Homepage', + 'Select shipping method': 'Versandart auswählen', + 'Review my order': 'Meine Bestellung überprüfen', + 'Select payment method': 'Zahlungsmethode auswählen', + 'Make an order': 'Bestellung aufgeben', + 'or': 'oder', + 'login in to your account': 'Anmelden bei Ihrem Konto', + 'Create an account': 'Konto erstellen', + 'Your bag is empty': 'Ihre Tasche ist leer', + 'Cancel': 'Abbrechen', + 'See all results': 'Alle Ergebnisse anzeigen', + 'You haven’t searched for items yet': 'Sie haben noch nicht nach Artikeln gesucht.', + 'Let’s start now – we’ll help you': 'Fangen wir jetzt an - wir helfen Ihnen.', + 'Search results': 'Suchergebnisse', + 'Product suggestions': 'Produktvorschläge', + 'Search for items': 'Nach Artikeln suchen', + 'Enter promo code': 'Geben Sie den Promo-Code ein', + 'Shipping method': 'Versandart', + 'Continue to billing': 'Weiter zur Abrechnung', + 'Payment methods': 'Zahlungsmethoden', + 'Shipping address': 'Lieferanschrift', + 'Subtotal': 'Zwischensumme', + 'Shipping': 'Versand', + 'Total price': 'Gesamtpreis', + 'Payment': 'Zahlung', + 'Order summary': 'Bestellübersicht', + 'Products': 'Produkte', + 'Total': 'Gesamt', + 'Reset Password': 'Passwort Zurücksetzen', + 'Save Password': 'Passwort Speichern', + 'Back to home': 'Zurück Zur Startseite', + 'Forgot Password': 'Wenn Sie Ihr Passwort vergessen haben, können Sie es zurücksetzen.', + 'Thank You Inbox': 'Wenn die Nachricht nicht in Ihrem Posteingang ankommt, versuchen Sie es mit einer anderen E-Mail-Adresse, mit der Sie sich möglicherweise registriert haben.', + 'Sign in': 'Einloggen', + 'Register': 'Registrieren', + 'Password Changed': 'Passwort erfolgreich geändert. Sie können nun zur Startseite zurückkehren und sich anmelden.', + 'Password': 'Passwort', + 'Repeat Password': 'Wiederhole das Passwort', + 'Forgot Password Modal Email': 'E-Mail, mit der Sie sich anmelden:', + forgotPasswordConfirmation: 'Vielen Dank! Wenn ein Konto mit der E-Mail-Adresse {0} registriert ist, finden Sie in Ihrem Posteingang eine Nachricht mit einem Link zum Zurücksetzen des Passworts.', + subscribeToNewsletterModalContent: + 'Wenn Sie sich für den Newsletter angemeldet haben, erhalten Sie spezielle Angebote und Nachrichten von VSF per E-Mail. Wir werden Ihre E-Mail zu keinem Zeitpunkt an Dritte verkaufen oder weitergeben. Bitte beachten Sie unsere {0}.', + 'Subscribe': 'Abonnieren', + 'Subscribe to newsletter': 'Anmeldung zum Newsletter', + 'Email address': 'E-Mail Adresse', + 'I confirm subscription': 'Ich bestätige das Abonnement', + 'You can unsubscribe at any time': 'Sie können sich jederzeit abmelden', + 'show more': 'mehr anzeigen', + 'hide': 'ausblenden' +}; diff --git a/packages/commercetools/theme/lang/en.js b/packages/commercetools/theme/lang/en.js new file mode 100644 index 0000000000..482677f5a8 --- /dev/null +++ b/packages/commercetools/theme/lang/en.js @@ -0,0 +1,155 @@ +/* eslint-disable */ + +export default { + 'Categories': 'Categories', + 'Filters': 'Filter', + 'Sort by': 'Sort by', + 'Products found': 'Products found', + 'About us': 'About us', + 'Who we are': 'Who we are', + 'Quality in the details': 'Quality in the details', + 'Customer Reviews': 'Customer Reviews', + 'Departments': 'Departments', + 'Women fashion': 'Women fashion', + 'Men fashion': 'Men fashion', + 'Kidswear': 'Kidswear', + 'Home': 'Home', + 'Help': 'Help', + 'Customer service': 'Customer service', + 'Size guide': 'Size guide', + 'Contact us': 'Contact us', + 'Payment & Delivery': 'Payment & Delivery', + 'Purchase terms': 'Purchase terms', + 'Guarantee': 'Guarantee', + 'Description': 'Description', + 'Read reviews': 'Read reviews', + 'Additional Information': 'Additional Information', + 'Save for later': 'Save for later', + 'Add to compare': 'Add to compare', + 'Match it with': 'Match it with', + 'Share your look': 'Share your look', + 'Product description': `The Karissa V-Neck Tee features a semi-fitted shape that's flattering for every figure. You can hit the gym with confidence while it hugs curves and hides common "problem" areas. Find stunning women's cocktail dresses and party dresses.`, + 'Brand': 'Brand', + 'Instruction1': 'Take care of me', + 'Instruction2': 'Just here for the care instructions?', + 'Instruction3': 'Yeah, we thought so', + 'Items': 'Items', + 'View': 'View', + 'Show on page': 'Show on page', + 'Done': 'Done', + 'Clear all': 'Clear all', + 'Empty': 'Looks like you haven’t added any items to the bag yet. Start shopping to fill it in.', + 'Help & FAQs': 'Help & FAQs', + 'Download': 'Download our application.', + 'Find out more': 'Find out more', + 'Login': 'Login', + 'Forgotten password?': 'Forgotten password?', + 'No account': `Don't have an account yet?`, + 'Register today': 'Register today', + 'Go to checkout': 'Go to checkout', + 'Go back shopping': 'Go back shopping', + 'Personal details': 'Personal details', + 'Edit': 'Edit', + 'Shipping details': 'Shipping details', + 'Billing address': 'Billing address', + 'Same as shipping address': 'Same as shipping address', + 'Payment method': 'Payment method', + 'Apply': 'Apply', + 'Update password': 'Update password', + 'Update personal data': 'Update personal data', + 'Item': 'Item', + 'Go back': 'Go back', + 'Continue to shipping': 'Continue to shipping', + 'I agree to': 'I agree to', + 'Terms and conditions': 'Terms and conditions', + 'Pay for order': 'Pay for order', + 'Log into your account': 'Log into your account', + 'or fill the details below': 'or fill the details below', + 'Enjoy your free account': 'Enjoy these perks with your free account!', + 'Continue to payment': 'Continue to payment', + 'Order No.': 'Order No.', + 'Successful placed order': 'You have successfully placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.', + 'Info after order': 'You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.', + 'Allow order notifications': 'Allow order notifications', + 'Feedback': 'Your feedback is important to us. Let us know what we could improve.', + 'Send my feedback': 'Send my feedback', + 'Go back to shop': 'Go back to shop', + 'Read all reviews': 'Read all reviews', + 'Color': 'Color', + 'Contact details updated': 'Keep your addresses and contact details updated.', + 'Manage billing addresses': 'Manage all the billing addresses you want (work place, home address...) This way you won"t have to enter the billing address manually with each order.', + 'Change': 'Change', + 'Delete': 'Delete', + 'Add new address': 'Add new address', + 'Set up newsletter': 'Set up your newsletter and we will send you information about new products and trends from the sections you selected every week.', + 'Sections that interest you': 'Sections that interest you', + 'Save changes': 'Save changes', + 'Read and understand': 'I have read and understand the', + 'Privacy': 'Privacy', + 'Cookies Policy': 'Cookies Policy', + 'Commercial information': 'and agree to receive personalized commercial information from Brand name by email', + 'Feel free to edit': 'Feel free to edit any of your details below so your account is always up to date', + 'Use your personal data': 'At Brand name, we attach great importance to privacy issues and are committed to protecting the personal data of our users. Learn more about how we care and use your personal data in the', + 'Privacy Policy': 'Privacy Policy', + 'Change password your account': 'If you want to change the password to access your account, enter the following information', + 'Your current email address is': 'Your current email address is', + 'Product': 'Product', + 'Details and status orders': 'Check the details and status of your orders in the online store. You can also cancel your order or request a return.', + 'You currently have no orders': 'You currently have no orders', + 'Start shopping': 'Start shopping', + 'Download': 'Download', + 'Download all': 'Download all', + 'View details': 'View details', + 'Manage shipping addresses': 'Manage all the shipping addresses you want (work place, home address...) This way you won"t have to enter the shipping address manually with each order.', + 'Quantity': 'Quantity', + 'Price': 'Price', + 'Back to homepage': 'Back to homepage', + 'Select shipping method': 'Select shipping method', + 'Review my order': 'Review my order', + 'Select payment method': 'Select payment method', + 'Make an order': 'Make an order', + 'or': 'or', + 'login in to your account': 'login in to your account', + 'Create an account': 'Create an account', + 'Your bag is empty': 'Your bag is empty', + 'Cancel': 'Cancel', + 'See all results': 'See all results', + 'You haven’t searched for items yet': 'You haven’t searched for items yet.', + 'Let’s start now – we’ll help you': 'Let’s start now – we’ll help you.', + 'Search results': 'Search results', + 'Product suggestions': 'Product suggestions', + 'Search for items': 'Search for items', + 'Enter promo code': 'Enter promo code', + 'Shipping method': 'Shipping method', + 'Continue to billing': 'Continue to billing', + 'Payment methods': 'Payment methods', + 'Shipping address': 'Shipping address', + 'Subtotal': 'Subtotal', + 'Shipping': 'Shipping', + 'Total price': 'Total price', + 'Payment': 'Payment', + 'Order summary': 'Order summary', + 'Products': 'Products', + 'Total': 'Total', + 'Reset Password': 'Reset Password', + 'Save Password': 'Save Password', + 'Back to home': 'Back to home', + 'Forgot Password': 'If you can’t remember your password, you can reset it.', + 'Thank You Inbox': 'If the message is not arriving in your inbox, try another email address you might’ve used to register.', + 'Sign in': 'Sign in', + 'Register': 'Register', + 'Password Changed': 'Password successfuly changed. You can now go back to homepage and sign in.', + 'Password': 'Password', + 'Repeat Password': 'Repeat Password', + 'Forgot Password Modal Email': 'Email you are using to sign in:', + forgotPasswordConfirmation: 'Thanks! If there is an account registered with the {0} email, you will find message with a password reset link in your inbox.', + subscribeToNewsletterModalContent: + 'After signing up for the newsletter, you will receive special offers and messages from VSF via email. We will not sell or distribute your email to any third party at any time. Please see our {0}.', + 'Subscribe': 'Subscribe', + 'Subscribe to newsletter': 'Subscribe to newsletter', + 'Email address': 'Email address', + 'I confirm subscription': 'I confirm subscription', + 'You can unsubscribe at any time': 'You can unsubscribe at any time', + 'show more': 'show more', + 'hide': 'hide' +}; diff --git a/packages/commercetools/theme/middleware.config.js b/packages/commercetools/theme/middleware.config.js new file mode 100644 index 0000000000..469d746a1d --- /dev/null +++ b/packages/commercetools/theme/middleware.config.js @@ -0,0 +1,22 @@ + +module.exports = { + integrations: { + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { + api: { + uri: 'https://api.commercetools.com/vsf-ct-dev/graphql', + authHost: 'https://auth.sphere.io', + projectKey: 'vsf-ct-dev', + clientId: 'kuFT95wdTP4uH_hVOKjqfGEo', + clientSecret: 'tklIDic86mgWrFy0oBHRQQmwX7ZC5wIP', + scopes: [ + 'manage_project:vsf-ct-dev' + ] + }, + currency: 'USD', + country: 'US' + } + } + } +}; diff --git a/packages/commercetools/theme/middleware/README.md b/packages/commercetools/theme/middleware/README.md new file mode 100644 index 0000000000..01595ded74 --- /dev/null +++ b/packages/commercetools/theme/middleware/README.md @@ -0,0 +1,8 @@ +# MIDDLEWARE + +**This directory is not required, you can delete it if you don't want to use it.** + +This directory contains your application middleware. +Middleware let you define custom functions that can be run before rendering either a page or a group of pages. + +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). diff --git a/packages/commercetools/theme/middleware/checkout.js b/packages/commercetools/theme/middleware/checkout.js new file mode 100644 index 0000000000..e05e11e15b --- /dev/null +++ b/packages/commercetools/theme/middleware/checkout.js @@ -0,0 +1,28 @@ +const canEnterPayment = cart => cart.shippingInfo && cart.shippingAddress; + +const canEnterReview = cart => Boolean(cart.billingAddress); + +export default async ({ app, $vsf }) => { + const currentPath = app.context.route.fullPath.split('/checkout/')[1]; + + if (!currentPath) return; + + const { data } = await $vsf.$ct.api.getMe(); + + if (!data || !data.me.activeCart) return; + const { activeCart } = data.me; + + switch (currentPath) { + case 'billing': + if (!canEnterPayment(activeCart)) { + app.context.redirect('/'); + } + break; + case 'payment': + if (!canEnterReview(activeCart)) { + app.context.redirect('/'); + } + break; + } +}; + diff --git a/packages/commercetools/theme/middleware/is-authenticated.js b/packages/commercetools/theme/middleware/is-authenticated.js new file mode 100644 index 0000000000..97323dc47f --- /dev/null +++ b/packages/commercetools/theme/middleware/is-authenticated.js @@ -0,0 +1,5 @@ +export default async ({ app, redirect }) => { + if (!app.$cookies.get('vsf-commercetools-token')?.scope?.includes('customer_id')) { + return redirect('/'); + } +}; diff --git a/packages/commercetools/theme/mockedSearchProducts.json b/packages/commercetools/theme/mockedSearchProducts.json new file mode 100644 index 0000000000..0ea8a4b327 --- /dev/null +++ b/packages/commercetools/theme/mockedSearchProducts.json @@ -0,0 +1,166 @@ +{ + "products": [ + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + }, + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + }, + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + }, + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + } + ], + "categories": [ + { + "label": "Men", + "slug": "men" + }, + { + "label": "Women", + "slug": "women" + }, + { + "label": "Sale", + "slug": "sale" + }, + { + "label": "New", + "slug": "new" + } + ] +} diff --git a/packages/commercetools/theme/nuxt.config.js b/packages/commercetools/theme/nuxt.config.js new file mode 100644 index 0000000000..3de2f7463f --- /dev/null +++ b/packages/commercetools/theme/nuxt.config.js @@ -0,0 +1,154 @@ +import webpack from 'webpack'; +import { VSF_LOCALE_COOKIE } from '@vue-storefront/core'; +import theme from './themeConfig'; + +export default { + mode: 'universal', + server: { + port: 3000, + host: '0.0.0.0' + }, + head: { + title: process.env.npm_package_name || '', + meta: [ + { charset: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } + ], + link: [ + { + rel: 'icon', + type: 'image/x-icon', + href: '/favicon.ico' + }, + { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossorigin: 'crossorigin' + }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css?family=Raleway:300,400,400i,500,600,700|Roboto:300,300i,400,400i,500,700&display=swap' + } + ], + script: [] + }, + loading: { color: '#fff' }, + router: { + middleware: ['checkout'], + scrollBehavior (_to, _from, savedPosition) { + if (savedPosition) { + return savedPosition; + } else { + return { x: 0, y: 0 }; + } + } + }, + buildModules: [ + // to core + '@nuxt/typescript-build', + '@nuxtjs/style-resources', + // to core soon + '@nuxtjs/pwa', + ['@vue-storefront/nuxt', { + coreDevelopment: true, + useRawSource: { + dev: [ + '@vue-storefront/commercetools', + '@vue-storefront/core' + ], + prod: [ + '@vue-storefront/commercetools', + '@vue-storefront/core' + ] + } + }], + // @core-development-only-start + ['@vue-storefront/nuxt-theme', { + generate: { + replace: { + apiClient: '@vue-storefront/commercetools-api', + composables: '@vue-storefront/commercetools' + } + } + }], + // @core-development-only-end + /* project-only-start + ['@vue-storefront/nuxt-theme'], + project-only-end */ + ['@vue-storefront/commercetools/nuxt', { + i18n: { useNuxtI18nConfig: true } + }] + ], + modules: [ + 'nuxt-i18n', + 'cookie-universal-nuxt', + 'vue-scrollto/nuxt', + '@vue-storefront/middleware/nuxt' + ], + i18n: { + currency: 'USD', + country: 'US', + countries: [ + { name: 'US', label: 'United States', states: ['California', 'Nevada'] }, + { name: 'AT', label: 'Austria' }, + { name: 'DE', label: 'Germany' }, + { name: 'NL', label: 'Netherlands' } + ], + currencies: [ + { name: 'EUR', label: 'Euro' }, + { name: 'USD', label: 'Dollar' } + ], + locales: [ + { code: 'en', label: 'English', file: 'en.js', iso: 'en' }, + { code: 'de', label: 'German', file: 'de.js', iso: 'de' } + ], + defaultLocale: 'en', + lazy: true, + seo: true, + langDir: 'lang/', + vueI18n: { + fallbackLocale: 'en', + numberFormats: { + en: { + currency: { + style: 'currency', currency: 'USD', currencyDisplay: 'symbol' + } + }, + de: { + currency: { + style: 'currency', currency: 'EUR', currencyDisplay: 'symbol' + } + } + } + }, + detectBrowserLanguage: { + cookieKey: VSF_LOCALE_COOKIE + } + }, + styleResources: { + scss: [require.resolve('@storefront-ui/shared/styles/_helpers.scss', { paths: [process.cwd()] })] + }, + publicRuntimeConfig: { + theme + }, + build: { + babel: { + plugins: [ + ['@babel/plugin-proposal-private-methods', { loose: true }] + ] + }, + transpile: [ + 'vee-validate/dist/rules' + ], + plugins: [ + new webpack.DefinePlugin({ + 'process.VERSION': JSON.stringify({ + // eslint-disable-next-line global-require + version: require('./package.json').version, + lastCommit: process.env.LAST_COMMIT || '' + }) + }) + ] + } +}; diff --git a/packages/commercetools/theme/package.json b/packages/commercetools/theme/package.json new file mode 100644 index 0000000000..53a5b37ac5 --- /dev/null +++ b/packages/commercetools/theme/package.json @@ -0,0 +1,47 @@ +{ + "name": "@vue-storefront/commercetools-theme", + "description": "My awesome Vue Storefront project", + "author": "Filip Rakowski", + "version": "1.3.1", + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "nuxt", + "build": "nuxt build -m", + "build:analyze": "nuxt build -a -m", + "start": "nuxt start", + "test": "jest", + "test:e2e": "cypress open --config-file tests/e2e/cypress.json", + "test:e2e:hl": "cypress run --headless --config-file tests/e2e/cypress.json", + "test:e2e:generate:report": "yarn -s mochawesome-merge \"tests/e2e/report/*.json\" > \"tests/e2e/report.json\" && yarn -s marge tests/e2e/report.json -o \"tests/e2e/report\"" + }, + "dependencies": { + "@nuxtjs/pwa": "^3.2.2", + "@storefront-ui/vue": "0.10.3", + "@vue-storefront/commercetools": "~1.3.1", + "@vue-storefront/middleware": "~2.4.1", + "@vue-storefront/nuxt": "~2.4.1", + "@vue-storefront/nuxt-theme": "~2.4.1", + "awesome-phonenumber": "^2.51.2", + "cookie-universal-nuxt": "^2.1.3", + "core-js": "^2.6.5", + "nuxt": "^2.13.3", + "nuxt-i18n": "^6.5.0", + "vee-validate": "^3.2.3", + "vue-scrollto": "^2.17.1" + }, + "devDependencies": { + "@nuxt/types": "^0.7.9", + "@vue/test-utils": "^1.0.0-beta.27", + "babel-jest": "^24.1.0", + "cypress": "^6.6.0", + "cypress-pipe": "^2.0.0", + "cypress-tags": "^0.0.20", + "jest": "^24.1.0", + "mochawesome": "^6.2.2", + "mochawesome-merge": "^4.2.0", + "mochawesome-report-generator": "^5.2.0", + "vue-jest": "^4.0.0-0" + } +} diff --git a/packages/commercetools/theme/pages/Checkout.vue b/packages/commercetools/theme/pages/Checkout.vue new file mode 100644 index 0000000000..378c1b2119 --- /dev/null +++ b/packages/commercetools/theme/pages/Checkout.vue @@ -0,0 +1,107 @@ + + + + diff --git a/packages/commercetools/theme/pages/Checkout/Billing.vue b/packages/commercetools/theme/pages/Checkout/Billing.vue new file mode 100644 index 0000000000..fce4d3160a --- /dev/null +++ b/packages/commercetools/theme/pages/Checkout/Billing.vue @@ -0,0 +1,504 @@ + + + + diff --git a/packages/commercetools/theme/pages/Checkout/Payment.vue b/packages/commercetools/theme/pages/Checkout/Payment.vue new file mode 100644 index 0000000000..7461497494 --- /dev/null +++ b/packages/commercetools/theme/pages/Checkout/Payment.vue @@ -0,0 +1,372 @@ + + + + + diff --git a/packages/commercetools/theme/pages/Checkout/Shipping.vue b/packages/commercetools/theme/pages/Checkout/Shipping.vue new file mode 100644 index 0000000000..5ad8393ce3 --- /dev/null +++ b/packages/commercetools/theme/pages/Checkout/Shipping.vue @@ -0,0 +1,518 @@ + + + + + diff --git a/packages/commercetools/theme/pages/Checkout/ThankYou.vue b/packages/commercetools/theme/pages/Checkout/ThankYou.vue new file mode 100644 index 0000000000..cd693ac6c3 --- /dev/null +++ b/packages/commercetools/theme/pages/Checkout/ThankYou.vue @@ -0,0 +1,261 @@ + + + + + diff --git a/packages/commercetools/theme/pages/MyAccount/MyProfile.vue b/packages/commercetools/theme/pages/MyAccount/MyProfile.vue new file mode 100644 index 0000000000..7ac22eb14e --- /dev/null +++ b/packages/commercetools/theme/pages/MyAccount/MyProfile.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/packages/commercetools/theme/pages/Product.vue b/packages/commercetools/theme/pages/Product.vue new file mode 100644 index 0000000000..b68e167d87 --- /dev/null +++ b/packages/commercetools/theme/pages/Product.vue @@ -0,0 +1,555 @@ + + + + diff --git a/packages/commercetools/theme/pages/README.md b/packages/commercetools/theme/pages/README.md new file mode 100644 index 0000000000..66a9efbe6a --- /dev/null +++ b/packages/commercetools/theme/pages/README.md @@ -0,0 +1 @@ +Put here theme-specific pages to override default ones \ No newline at end of file diff --git a/packages/commercetools/theme/shims-webpack.d.ts b/packages/commercetools/theme/shims-webpack.d.ts new file mode 100644 index 0000000000..499241bbef --- /dev/null +++ b/packages/commercetools/theme/shims-webpack.d.ts @@ -0,0 +1,16 @@ +declare module 'webpack-hot-middleware' { + const middleware: any; + export interface Options { + [proName: string]: any; + } + + export interface ClientOptions { + [proName: string]: any; + } + + export interface MiddlewareOptions { + [proName: string]: any; + } + + export default middleware; +} diff --git a/packages/commercetools/theme/static/README.md b/packages/commercetools/theme/static/README.md new file mode 100644 index 0000000000..cf004353ba --- /dev/null +++ b/packages/commercetools/theme/static/README.md @@ -0,0 +1,11 @@ +# STATIC + +**This directory is not required, you can delete it if you don't want to use it.** + +This directory contains your static files. +Each file inside this directory is mapped to `/`. +Thus you'd want to delete this README.md before deploying to production. + +Example: `/static/robots.txt` is mapped as `/robots.txt`. + +More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). diff --git a/packages/commercetools/theme/static/error/error.svg b/packages/commercetools/theme/static/error/error.svg new file mode 100644 index 0000000000..e2d0756788 --- /dev/null +++ b/packages/commercetools/theme/static/error/error.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/commercetools/theme/static/favicon.ico b/packages/commercetools/theme/static/favicon.ico new file mode 100644 index 0000000000..479229d87b Binary files /dev/null and b/packages/commercetools/theme/static/favicon.ico differ diff --git a/packages/commercetools/theme/static/homepage/apple.png b/packages/commercetools/theme/static/homepage/apple.png new file mode 100644 index 0000000000..3b67e06976 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/apple.png differ diff --git a/packages/commercetools/theme/static/homepage/bannerA.webp b/packages/commercetools/theme/static/homepage/bannerA.webp new file mode 100644 index 0000000000..4e3b12a328 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerA.webp differ diff --git a/packages/commercetools/theme/static/homepage/bannerB.webp b/packages/commercetools/theme/static/homepage/bannerB.webp new file mode 100644 index 0000000000..a0169a0908 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerB.webp differ diff --git a/packages/commercetools/theme/static/homepage/bannerC.webp b/packages/commercetools/theme/static/homepage/bannerC.webp new file mode 100644 index 0000000000..832dfcd18d Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerC.webp differ diff --git a/packages/commercetools/theme/static/homepage/bannerD.png b/packages/commercetools/theme/static/homepage/bannerD.png new file mode 100644 index 0000000000..244482e941 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerD.png differ diff --git a/packages/commercetools/theme/static/homepage/bannerE.webp b/packages/commercetools/theme/static/homepage/bannerE.webp new file mode 100644 index 0000000000..1181b8f0e6 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerE.webp differ diff --git a/packages/commercetools/theme/static/homepage/bannerF.webp b/packages/commercetools/theme/static/homepage/bannerF.webp new file mode 100644 index 0000000000..46c74549e4 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerF.webp differ diff --git a/packages/commercetools/theme/static/homepage/bannerG.webp b/packages/commercetools/theme/static/homepage/bannerG.webp new file mode 100644 index 0000000000..0a9bebf579 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerG.webp differ diff --git a/packages/commercetools/theme/static/homepage/bannerH.webp b/packages/commercetools/theme/static/homepage/bannerH.webp new file mode 100644 index 0000000000..7d01c20c76 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/bannerH.webp differ diff --git a/packages/commercetools/theme/static/homepage/google.png b/packages/commercetools/theme/static/homepage/google.png new file mode 100644 index 0000000000..6d5cd62679 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/google.png differ diff --git a/packages/commercetools/theme/static/homepage/imageAd.webp b/packages/commercetools/theme/static/homepage/imageAd.webp new file mode 100644 index 0000000000..1a79e56731 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageAd.webp differ diff --git a/packages/commercetools/theme/static/homepage/imageAm.webp b/packages/commercetools/theme/static/homepage/imageAm.webp new file mode 100644 index 0000000000..bb267f7944 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageAm.webp differ diff --git a/packages/commercetools/theme/static/homepage/imageBd.webp b/packages/commercetools/theme/static/homepage/imageBd.webp new file mode 100644 index 0000000000..1920527fa9 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageBd.webp differ diff --git a/packages/commercetools/theme/static/homepage/imageBm.webp b/packages/commercetools/theme/static/homepage/imageBm.webp new file mode 100644 index 0000000000..a7c98740c4 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageBm.webp differ diff --git a/packages/commercetools/theme/static/homepage/imageCd.webp b/packages/commercetools/theme/static/homepage/imageCd.webp new file mode 100644 index 0000000000..18f82af02a Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageCd.webp differ diff --git a/packages/commercetools/theme/static/homepage/imageCm.webp b/packages/commercetools/theme/static/homepage/imageCm.webp new file mode 100644 index 0000000000..811116d6e2 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageCm.webp differ diff --git a/packages/commercetools/theme/static/homepage/imageDd.webp b/packages/commercetools/theme/static/homepage/imageDd.webp new file mode 100644 index 0000000000..2e37f209f9 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageDd.webp differ diff --git a/packages/commercetools/theme/static/homepage/imageDm.webp b/packages/commercetools/theme/static/homepage/imageDm.webp new file mode 100644 index 0000000000..c8ab58c43d Binary files /dev/null and b/packages/commercetools/theme/static/homepage/imageDm.webp differ diff --git a/packages/commercetools/theme/static/homepage/newsletter.webp b/packages/commercetools/theme/static/homepage/newsletter.webp new file mode 100644 index 0000000000..b3592b87da Binary files /dev/null and b/packages/commercetools/theme/static/homepage/newsletter.webp differ diff --git a/packages/commercetools/theme/static/homepage/productA.webp b/packages/commercetools/theme/static/homepage/productA.webp new file mode 100644 index 0000000000..fb5141e301 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/productA.webp differ diff --git a/packages/commercetools/theme/static/homepage/productB.webp b/packages/commercetools/theme/static/homepage/productB.webp new file mode 100644 index 0000000000..b5d10b9dd8 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/productB.webp differ diff --git a/packages/commercetools/theme/static/homepage/productC.webp b/packages/commercetools/theme/static/homepage/productC.webp new file mode 100644 index 0000000000..a12f3f56d3 Binary files /dev/null and b/packages/commercetools/theme/static/homepage/productC.webp differ diff --git a/packages/commercetools/theme/static/icons/empty-cart.svg b/packages/commercetools/theme/static/icons/empty-cart.svg new file mode 100644 index 0000000000..83b07851a4 --- /dev/null +++ b/packages/commercetools/theme/static/icons/empty-cart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/commercetools/theme/static/icons/facebook.svg b/packages/commercetools/theme/static/icons/facebook.svg new file mode 100644 index 0000000000..8dcb72b3f9 --- /dev/null +++ b/packages/commercetools/theme/static/icons/facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/commercetools/theme/static/icons/google.svg b/packages/commercetools/theme/static/icons/google.svg new file mode 100644 index 0000000000..c3fe1f0035 --- /dev/null +++ b/packages/commercetools/theme/static/icons/google.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/commercetools/theme/static/icons/langs/de.webp b/packages/commercetools/theme/static/icons/langs/de.webp new file mode 100644 index 0000000000..fee9a24388 Binary files /dev/null and b/packages/commercetools/theme/static/icons/langs/de.webp differ diff --git a/packages/commercetools/theme/static/icons/langs/en.webp b/packages/commercetools/theme/static/icons/langs/en.webp new file mode 100644 index 0000000000..891a0e301b Binary files /dev/null and b/packages/commercetools/theme/static/icons/langs/en.webp differ diff --git a/packages/commercetools/theme/static/icons/logo.svg b/packages/commercetools/theme/static/icons/logo.svg new file mode 100644 index 0000000000..c9efd085f5 --- /dev/null +++ b/packages/commercetools/theme/static/icons/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/commercetools/theme/static/icons/pinterest.svg b/packages/commercetools/theme/static/icons/pinterest.svg new file mode 100644 index 0000000000..f0527a7a57 --- /dev/null +++ b/packages/commercetools/theme/static/icons/pinterest.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/commercetools/theme/static/icons/twitter.svg b/packages/commercetools/theme/static/icons/twitter.svg new file mode 100644 index 0000000000..338994fd10 --- /dev/null +++ b/packages/commercetools/theme/static/icons/twitter.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/commercetools/theme/static/icons/youtube.svg b/packages/commercetools/theme/static/icons/youtube.svg new file mode 100644 index 0000000000..795eaedbf8 --- /dev/null +++ b/packages/commercetools/theme/static/icons/youtube.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/commercetools/theme/static/productpage/productA.jpg b/packages/commercetools/theme/static/productpage/productA.jpg new file mode 100644 index 0000000000..866c1ffd66 Binary files /dev/null and b/packages/commercetools/theme/static/productpage/productA.jpg differ diff --git a/packages/commercetools/theme/static/productpage/productB.jpg b/packages/commercetools/theme/static/productpage/productB.jpg new file mode 100644 index 0000000000..c518dedc8f Binary files /dev/null and b/packages/commercetools/theme/static/productpage/productB.jpg differ diff --git a/packages/commercetools/theme/static/productpage/productM.jpg b/packages/commercetools/theme/static/productpage/productM.jpg new file mode 100644 index 0000000000..a1cb15a18c Binary files /dev/null and b/packages/commercetools/theme/static/productpage/productM.jpg differ diff --git a/packages/commercetools/theme/static/thank-you/bannerD.png b/packages/commercetools/theme/static/thank-you/bannerD.png new file mode 100644 index 0000000000..de97105a36 Binary files /dev/null and b/packages/commercetools/theme/static/thank-you/bannerD.png differ diff --git a/packages/commercetools/theme/static/thank-you/bannerM.png b/packages/commercetools/theme/static/thank-you/bannerM.png new file mode 100644 index 0000000000..72b909ca87 Binary files /dev/null and b/packages/commercetools/theme/static/thank-you/bannerM.png differ diff --git a/packages/commercetools/theme/tests/e2e/api/requests.ts b/packages/commercetools/theme/tests/e2e/api/requests.ts new file mode 100644 index 0000000000..7e6c415eab --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/api/requests.ts @@ -0,0 +1,206 @@ +import { Address, Customer, Product } from '../types/types'; + +export type CreateCartResponse = { + body: { + data: { + cart: { + id: string; + } + } + } +} + +export type CustomerSignMeInResponse = { + body: { + data: { + user: { + customer: { + firstName: string; + lastName: string; + email: string; + } + } + } + } +} + +export type GetMeResponse = { + body: { + data: { + me: { + customer: { + firstName: string; + lastName: string; + email: string; + } + } + } + } +} + +export type GetShippingMethodsResponse = { + body: { + data: { + shippingMethods: [{ + id: string; + name: string; + }] + } + } +} + +const requests = { + + addToCart(cartId: string, product: Product, quantity?: number): Cypress.Chainable { + const options = { + url: '/api/ct/addToCart', + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: [ + {id: cartId, version: 1}, + { + product: {id: product.id, sku: product.sku }, + quantity: quantity ?? 1 + }, + null + ] + }; + return cy.request(options); + }, + + createCart(): Cypress.Chainable { + const options = { + url: '/api/ct/createCart', + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: [{}, null] + }; + return cy.request(options); + }, + + customerSignMeIn(customer: Customer): Cypress.Chainable { + const options = { + url: '/api/ct/customerSignMeIn', + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: [ + { + email: customer.email, + password: customer.password + } + ] + }; + return cy.request(options); + }, + + customerSignMeUp(customer: Customer): Cypress.Chainable { + const options = { + url: '/api/ct/customerSignMeUp', + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: [ + { + email: customer.email, + password: customer.password, + firstName: customer.firstName, + lastName: customer.lastName + } + ] + }; + return cy.request(options); + }, + + getMe(customer?: boolean): Cypress.Chainable { + const options = { + url: '/api/ct/getMe', + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: [ + {customer: customer ?? false}, null + ] + }; + return cy.request(options); + }, + + getShippingMethods(cartId: string): Cypress.Chainable { + const options = { + url: '/api/ct/getShippingMethods', + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: [ + cartId + ] + }; + return cy.request(options); + }, + + updateCart(cartId: string, data?: { addresses?: { shipping?: Address, billing?: Address }, shippingMethodId?: string }): Cypress.Chainable { + const actions = []; + + if (data.addresses !== undefined) { + if (data.addresses.shipping !== undefined) actions.push({ + setShippingAddress: { + address: { + ...data.addresses.shipping + } + } + }); + + if (data.addresses.billing !== undefined) actions.push({ + setBillingAddress: { + address: { + ...data.addresses.billing + } + } + }); + } + + if (data.shippingMethodId !== undefined) actions.push({ + setShippingMethod: { + shippingMethod: { + id: data.shippingMethodId + } + } + }); + + const options = { + url: '/api/ct/updateCart', + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + }, + body: [ + { + id: cartId, + version: 1, + actions: [ + ...actions + ] + }, + null + ] + }; + return cy.request(options); + } +}; + +export default requests; diff --git a/packages/commercetools/theme/tests/e2e/cypress.json b/packages/commercetools/theme/tests/e2e/cypress.json new file mode 100644 index 0000000000..6db36da142 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/cypress.json @@ -0,0 +1,24 @@ +{ + "baseUrl": "http://localhost:3000", + "fixturesFolder": "tests/e2e/fixtures", + "integrationFolder": "tests/e2e/integration", + "pluginsFile": "tests/e2e/plugins/index.js", + "supportFile": "tests/e2e/support/index.js", + "viewportHeight": 1080, + "viewportWidth": 1920, + "pageLoadTimeout": 180000, + "screenshotOnRunFailure": true, + "screenshotsFolder": "tests/e2e/report/assets/screenshots", + "video": false, + "reporter": "../../../../node_modules/mochawesome", + "reporterOptions": { + "reportDir": "tests/e2e/report", + "reportFilename": "report", + "overwrite": false, + "html": false + }, + "retries": { + "runMode": 2, + "openMode": 0 + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-add-to-cart.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-add-to-cart.json new file mode 100644 index 0000000000..0a559d4d60 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-add-to-cart.json @@ -0,0 +1,21 @@ +{ + "Should successfully add product to cart - Category grid view": { + "product": { + "category": "women", + "name": "T-Shirt Moschino Cheap And Chic black" + } + }, + "Should successfully add product to cart - Category list view": { + "product": { + "category": "men", + "name": "Sweater Polo Ralph Lauren pink" + } + }, + "Should successfully add product to cart - Product details page": { + "product": { + "name": "Lace up shoes Tods dark blue", + "id": "eda439df-e655-493d-9fe9-b93c45144374", + "slug": "tods-laceupshoes-C20RE0U820-darkblue" + } + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-carts-merging.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-carts-merging.json new file mode 100644 index 0000000000..824bc06d38 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-carts-merging.json @@ -0,0 +1,109 @@ +{ + "Should merge guest cart with registered customer cart": { + "customer": { + "password": "Vu3S70r3fr0n7!" + }, + "products": [{ + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "quantity": 1, + "size": 34 + }, + { + "name": "Shirt Aspesi white M", + "id": 2, + "sku": "M0E20000000ED0X", + "quantity": 1, + "size": 36 + }, + { + "name": "Shirt ”David” MU light blue", + "id": 1, + "sku": "M0E20000000DL5W", + "quantity": 2, + "size": 36 + } + ], + "expectedCart": [{ + "name": "Shirt Aspesi white M", + "quantity": 1, + "size": 34, + "color": "white" + }, + { + "name": "Shirt Aspesi white M", + "quantity": 1, + "size": 36, + "color": "white" + }, + { + "name": "Shirt ”David” MU light blue", + "quantity": 2, + "size": 36, + "color": "blue" + } + ] + }, + + "Should merge guest cart with registered customer cart - products already in cart": { + "customer": { + "password": "Vu3S70r3fr0n7!" + }, + "products": { + "guest": [{ + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "quantity": 1, + "size": 34 + }, + { + "name": "Shirt Aspesi white M", + "id": 2, + "sku": "M0E20000000ED0X", + "quantity": 1, + "size": 36 + }, + { + "name": "Shirt ”David” MU light blue", + "id": 1, + "sku": "M0E20000000DL5W", + "quantity": 2, + "size": 36 + } + ], + "customer": [{ + "name": "Sweater Polo Ralph Lauren pink", + "id": 4, + "sku": "M0E20000000E2X0", + "quantity": 3, + "size": "M" + }] + }, + "expectedCart": [{ + "name": "Sweater Polo Ralph Lauren pink", + "quantity": 3, + "size": "M", + "color": "pink" + }, { + "name": "Shirt Aspesi white M", + "quantity": 1, + "size": 34, + "color": "white" + }, + { + "name": "Shirt Aspesi white M", + "quantity": 1, + "size": 36, + "color": "white" + }, + { + "name": "Shirt ”David” MU light blue", + "quantity": 2, + "size": 36, + "color": "blue" + } + ] + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-billing-validation.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-billing-validation.json new file mode 100644 index 0000000000..cd41eb7a59 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-billing-validation.json @@ -0,0 +1,413 @@ +{ + "Should successfully save address - guest customer": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US" + }, + "Should successfully save address - registered customer": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "firstName": "Jane", + "lastName": "Doe", + "password": "vuestorefront", + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US" + }, + "Should display an error - First Name empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "Please fill out this field." + }, + "Should display an error - Last Name empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "Please fill out this field." + }, + "Should display an error - Street Name empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "Please fill out this field." + }, + "Should display an error - Apartment empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "Please fill out this field." + }, + "Should display an error - City empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "Please fill out this field." + }, + "Should display an error - Postal Code empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "Please fill out this field." + }, + "Should display an error - Phone empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "Please fill out this field." + }, + "Should display an error - Country empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "This field is required" + }, + "Should display an error - State empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "errorMessage": "This field is required" + }, + "Should copy shipping address": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US" + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-order-summary.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-order-summary.json new file mode 100644 index 0000000000..11331c6758 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-order-summary.json @@ -0,0 +1,86 @@ +{ + "Should contain correct data in Order Summary": { + "products": [{ + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34, + "quantity": 3 + }, + { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0X", + "size": 36, + "quantity": 1 + }, + { + "name": "T-Shirt Moschino Cheap And Chic black", + "id": 1, + "sku": "M0E20000000DLPH", + "size": 34, + "quantity": 1 + } + ], + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + }, + "billing": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "US", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US", + "expectedCartSummary": { + "products": [{ + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34, + "color": "white", + "quantity": 3, + "amount": "$359.10" + }, + { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0X", + "size": 36, + "color": "white", + "quantity": 1, + "amount": "$166.25" + }, + { + "name": "T-Shirt Moschino Cheap And Chic black", + "id": 1, + "sku": "M0E20000000DLPH", + "size": 34, + "color": "black", + "quantity": 1, + "amount": "$372.50" + } + ], + "discountedPrice": "$1,037.50", + "specialPrice": "$897.85", + "totalPrice": "$907.85" + } + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-shipping-validation.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-shipping-validation.json new file mode 100644 index 0000000000..a419065a48 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-checkout-shipping-validation.json @@ -0,0 +1,259 @@ +{ + "Should successfully save address - guest customer": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US" + }, + "Should successfully save address - registered customer": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "firstName": "Jane", + "lastName": "Doe", + "password": "vuestorefront", + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "shippingMethod": "Express US" + }, + "Should display an error - First Name empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "errorMessage": "Please fill out this field." + }, + "Should display an error - Last Name empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "errorMessage": "Please fill out this field." + }, + "Should display an error - Street Name empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "errorMessage": "Please fill out this field." + }, + "Should display an error - Apartment empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "errorMessage": "Please fill out this field." + }, + "Should display an error - City empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "state": "California", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "errorMessage": "Please fill out this field." + }, + "Should display an error - Postal Code empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "phone": "+12345678910" + } + } + }, + "errorMessage": "Please fill out this field." + }, + "Should display an error - Phone empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90001" + } + } + }, + "errorMessage": "Please fill out this field." + }, + "Should display an error - Country empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "errorMessage": "This field is required" + }, + "Should display an error - State empty": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "size": 34 + }, + "customer": { + "address": { + "shipping": { + "firstName": "Jane", + "lastName": "Doe", + "streetName": "Vuestorefront Rd.", + "apartment": "13/37", + "city": "Los Angeles", + "country": "United States", + "postalCode": "90001", + "phone": "+12345678910" + } + } + }, + "errorMessage": "This field is required" + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-my-account.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-my-account.json new file mode 100644 index 0000000000..80af7a58b8 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-my-account.json @@ -0,0 +1,30 @@ +{ + "Should display customer's correct personal data": { + "customer": { + "firstName": "Jane", + "lastName": "Doe", + "password": "vuestorefront2" + } + }, + "Should update customer's personal data": { + "customer": { + "firstName": "Jo", + "lastName": "Do", + "password": "vuestorefront2" + }, + "updatedCustomer": { + "firstName": "Joe", + "lastName": "Doe" + } + }, + "Should update customer's password": { + "customer": { + "firstName": "Joe", + "lastName": "Doe", + "password": "vuestorefront2" + }, + "updatedCustomer": { + "password": "vuestorefront3" + } + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-place-order.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-place-order.json new file mode 100644 index 0000000000..43ea8553b9 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-place-order.json @@ -0,0 +1,61 @@ +{ + "Should successfully place an order as a guest": { + "customer": { + "address": { + "shipping": { + "firstName": "John", + "lastName": "Doe", + "streetName": "1 VueStorefront Rd.", + "apartment": "23", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90210", + "phone": "+12345678910" + }, + "billing": { + "firstName": "John", + "lastName": "Doe", + "streetName": "1 VueStorefront Rd.", + "apartment": "23", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90210", + "phone": "+12345678910" + } + } + } + }, + "Should successfully place an order as a registered customer": { + "customer": { + "firstName": "John", + "lastName": "Doe", + "password": "P@ssw0rd", + "address": { + "shipping": { + "firstName": "John", + "lastName": "Doe", + "streetName": "1 VueStorefront Rd.", + "apartment": "23", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90210", + "phone": "+12345678910" + }, + "billing": { + "firstName": "John", + "lastName": "Doe", + "streetName": "1 VueStorefront Rd.", + "apartment": "23", + "city": "Los Angeles", + "state": "California", + "country": "United States", + "postalCode": "90210", + "phone": "+12345678910" + } + } + } + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-product-page.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-product-page.json new file mode 100644 index 0000000000..1cce17b6d4 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-product-page.json @@ -0,0 +1,45 @@ +{ + "Should contain all size options": { + "product": { + "id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "slug": "aspesi-shirt-h805-white", + "attributes": { + "size": [ + "34", + "36", + "38", + "40", + "42", + "44", + "46", + "48", + "50", + "52", + "54", + "56", + "58", + "70" + ] + } + } + }, + "Should select correct size option": { + "product": { + "id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "slug": "aspesi-shirt-h805-white", + "attributes": { + "size": "58" + } + } + }, + "Should add correct variant to cart": { + "product": { + "id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "slug": "aspesi-shirt-h805-white", + "attributes": { + "size": "38", + "color": "white" + } + } + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-remove-from-cart.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-remove-from-cart.json new file mode 100644 index 0000000000..46c5b9b754 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-remove-from-cart.json @@ -0,0 +1,34 @@ +{ + "Should remove all products from cart": { + "product": { + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "quantity": 2, + "size": 34 + } + }, + "Should remove single product from cart": { + "products": [{ + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "quantity": 1, + "size": 34 + }, + { + "name": "Shirt ”David” MU light blue", + "id": 1, + "sku": "M0E20000000DL5W", + "quantity": 1, + "size": 36 + } + ], + "productToRemove": { + "name": "Shirt Aspesi white M" + }, + "expectedCart": [{ + "name": "Shirt ”David” MU light blue" + }] + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-update-cart.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-update-cart.json new file mode 100644 index 0000000000..f6e51a8c90 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-update-cart.json @@ -0,0 +1,60 @@ +{ + "Should increase product quantity": { + "products": [{ + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "quantity": 2, + "size": 34 + }, + { + "name": "Shirt ”David” MU light blue", + "id": 1, + "sku": "M0E20000000DL5W", + "quantity": 2, + "size": 36 + } + ], + "productToUpdate": { + "name": "Shirt ”David” MU light blue" + }, + "expectedCart": [{ + "name": "Shirt Aspesi white M", + "quantity": 2 + }, + { + "name": "Shirt ”David” MU light blue", + "quantity": 3 + } + ] + }, + "Should decrease product quantity": { + "products": [{ + "name": "Shirt Aspesi white M", + "id": 1, + "sku": "M0E20000000ED0W", + "quantity": 2, + "size": 34 + }, + { + "name": "Shirt ”David” MU light blue", + "id": 1, + "sku": "M0E20000000DL5W", + "quantity": 2, + "size": 36 + } + ], + "productToUpdate": { + "name": "Shirt ”David” MU light blue" + }, + "expectedCart": [{ + "name": "Shirt Aspesi white M", + "quantity": 2 + }, + { + "name": "Shirt ”David” MU light blue", + "quantity": 1 + } + ] + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-user-login.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-user-login.json new file mode 100644 index 0000000000..9eb9ea26a1 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-user-login.json @@ -0,0 +1,13 @@ +{ + "Should successfully login": { + "customer": { + "password": "Vu3S70r3fr0n7!" + } + }, + "Incorrect credentials - should display an error": { + "customer": { + "password": "Vu3S70r3fr0n7!" + }, + "errorMessage": "Account with the given credentials not found." + } +} diff --git a/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-user-registration.json b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-user-registration.json new file mode 100644 index 0000000000..d26a47955a --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/fixtures/test-data/e2e-user-registration.json @@ -0,0 +1,17 @@ +{ + "Should successfully register": { + "customer": { + "password": "Vu3S70r3fr0n7!", + "firstName": "Jane", + "lastName": "Doe" + } + }, + "Existing user - should display an error": { + "customer": { + "password": "Vu3S70r3fr0n7!", + "firstName": "Jane", + "lastName": "Doe" + }, + "errorMessage": "There is already an existing customer with the email" + } +} diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-add-to-cart.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-add-to-cart.spec.ts new file mode 100644 index 0000000000..0380ebfb24 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-add-to-cart.spec.ts @@ -0,0 +1,37 @@ +import page from '../pages/factory'; + +context('Add product to cart', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-add-to-cart').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + it(['regression'], 'Should successfully add product to cart - Category grid view', function() { + const data = this.fixtures.data[this.test.title]; + const category = page.category(data.product.category); + category.visit(); + category.addToCart(data.product.name); + category.header.openCart(); + page.components.cart.product(data.product.name).should('be.visible'); + }); + + it(['regression'], 'Should successfully add product to cart - Category list view', function() { + const data = this.fixtures.data[this.test.title]; + const category = page.category(data.product.category); + category.visit(); + category.changeView('list'); + category.addToCart(data.product.name); + category.header.openCart(); + page.components.cart.product(data.product.name).should('be.visible'); + }); + + it(['regression'], 'Should successfully add product to cart - Product details page', function() { + const data = this.fixtures.data[this.test.title]; + page.product(data.product.id, data.product.slug).visit(); + page.product().addToCartButton.click(); + page.product().header.openCart(); + page.components.cart.product(data.product.name).should('be.visible'); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-carts-merging.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-carts-merging.spec.ts new file mode 100644 index 0000000000..83feb40205 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-carts-merging.spec.ts @@ -0,0 +1,77 @@ +import requests, { CreateCartResponse } from '../api/requests'; +import page from '../pages/factory'; +import generator from '../utils/data-generator'; + +context(['regression'], 'Carts merging', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-carts-merging').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it('Should merge guest cart with registered customer cart', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + requests.getMe(); + requests.createCart().then((response: CreateCartResponse) => { + data.products.forEach(product => { + requests.addToCart(response.body.data.cart.id, product, product.quantity); + }); + }); + requests.customerSignMeUp(data.customer); + page.home.visit(); + page.home.header.openCart(); + page.components.cart.productName.each((name, index) => { + cy.wrap(name).should('contain', data.expectedCart[index].name); + }); + page.components.cart.quantity().each((input, index) => { + cy.wrap(input).should('have.value', data.expectedCart[index].quantity); + }); + page.components.cart.product().each((product, index) => { + page.components.cart.productSizeProperty(product).should('contain', data.expectedCart[index].size); + page.components.cart.productColorProperty(product).should('contain', data.expectedCart[index].color); + }); + + }); + it('Should merge guest cart with registered customer cart - products already in cart', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + requests.customerSignMeUp(data.customer); + requests.createCart().then((response: CreateCartResponse) => { + data.products.customer.forEach(product => { + requests.addToCart(response.body.data.cart.id, product, product.quantity); + }); + cy.clearCookies(); + }); + requests.createCart().then((response: CreateCartResponse) => { + data.products.guest.forEach(product => { + requests.addToCart(response.body.data.cart.id, product, product.quantity); + }); + }); + page.home.visit(); + page.home.header.openLoginModal(); + page.components.loginModal.loginToAccountButton.click(); + page.components.loginModal.fillForm(data.customer); + page.components.loginModal.loginBtn.click(); + page.home.header.openCart(); + page.components.cart.totalItems.should($ti => { + const totalItems: number = data.expectedCart.reduce((total, product) => { + return total + product.quantity; + }, 0); + expect($ti.text().trim()).to.be.equal(totalItems.toString()); + }); + page.components.cart.productName.each((name, index) => { + cy.wrap(name).should('contain', data.expectedCart[index].name); + }); + page.components.cart.quantity().each((input, index) => { + cy.wrap(input).should('have.value', data.expectedCart[index].quantity); + }); + page.components.cart.product().each((product, index) => { + page.components.cart.productSizeProperty(product).should('contain', data.expectedCart[index].size); + page.components.cart.productColorProperty(product).should('contain', data.expectedCart[index].color); + }); + }); + +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-billing-validation.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-billing-validation.spec.ts new file mode 100644 index 0000000000..0072e53205 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-billing-validation.spec.ts @@ -0,0 +1,123 @@ +import requests, { CreateCartResponse } from '../api/requests'; +import page from '../pages/factory'; +import generator from '../utils/data-generator'; + +context(['regression'], 'Checkout - Billing', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-checkout-billing-validation').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it('Should successfully save address - guest customer', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + requests.updateCart(response.body.data.cart.id, { addresses: { shipping: data.customer.address.shipping }}); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.contains(data.shippingMethod).click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.fillForm(data.customer); + page.checkout.billing.continueToPaymentButton.click(); + page.checkout.payment.heading.should('be.visible'); + }); + + it('Should successfully save address - registered customer', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + requests.customerSignMeUp(data.customer); + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + requests.updateCart(response.body.data.cart.id, { addresses: { shipping: data.customer.address.shipping }}); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.contains(data.shippingMethod).click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.addNewAddressButton.click(); + page.checkout.billing.fillForm(data.customer); + page.checkout.billing.continueToPaymentButton.click(); + page.checkout.payment.heading.should('be.visible'); + }); + + const requiredFields = [ + 'First Name', + 'Last Name', + 'Street Name', + 'Apartment', + 'City', + 'Postal Code', + 'Phone' + ]; + + requiredFields.forEach(requiredField => { + it(`Should display an error - ${requiredField} empty`, function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + requests.updateCart(response.body.data.cart.id, { addresses: { shipping: data.customer.address.shipping }}); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.contains(data.shippingMethod).click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.fillForm(data.customer); + page.checkout.billing.continueToPaymentButton.click(); + page.checkout.billing[Cypress._.camelCase(requiredField)].parent().within(() => { + cy.get('input').then(($input) => { + expect($input[0].validationMessage).to.be.eq(data.errorMessage); + }); + }); + }); + }); + + const requiredSelects = [ + 'Country', + 'State' + ]; + + requiredSelects.forEach(requiredSelect => { + it(`Should display an error - ${requiredSelect} empty`, function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + requests.updateCart(response.body.data.cart.id, { addresses: { shipping: data.customer.address.shipping }}); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.contains(data.shippingMethod).click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.fillForm(data.customer); + page.checkout.billing.continueToPaymentButton.click(); + page.checkout.billing[Cypress._.camelCase(requiredSelect)].parent().within(() => { + cy.contains(data.errorMessage).should('be.visible'); + }); + }); + }); + + it('Should copy shipping address', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + requests.updateCart(response.body.data.cart.id, { addresses: { shipping: data.customer.address.shipping }}); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.contains(data.shippingMethod).click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.copyAddressLabel.click(); + for (const field in data.customer.address.shipping) { + console.log(field); + page.checkout.billing[field].should('have.value', data.customer.address.shipping[field]); + } + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-order-summary.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-order-summary.spec.ts new file mode 100644 index 0000000000..58c8f28317 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-order-summary.spec.ts @@ -0,0 +1,62 @@ +import requests, { CreateCartResponse, GetShippingMethodsResponse } from '../api/requests'; +import page from '../pages/factory'; + +context([], 'Checkout - Order Summary', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-checkout-order-summary').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it('Should contain correct data in Order Summary', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + data.products.forEach((product) => { + requests.addToCart(response.body.data.cart.id, product, product.quantity); + }); + requests.updateCart(response.body.data.cart.id, { addresses: { shipping: data.customer.address.shipping }}); + requests.getShippingMethods(response.body.data.cart.id).then((res: GetShippingMethodsResponse) => { + const shippingMethod = res.body.data.shippingMethods.find((method) => { + return method.name === data.shippingMethod; + }); + requests.updateCart(response.body.data.cart.id, { shippingMethodId: shippingMethod.id }); + }); + requests.updateCart(response.body.data.cart.id, { addresses: { billing: data.customer.address.billing }}); + }); + page.checkout.payment.visit(); + page.checkout.payment.productRow.each((row, index) => { + cy.wrap(row).within(() => { + page.checkout.payment.productTitleSku.within(() => { + cy.contains(data.expectedCartSummary.products[index].name).should('be.visible'); + }); + page.checkout.payment.productTitleSku.within(() => { + cy.contains(data.expectedCartSummary.products[index].sku).should('be.visible'); + }); + page.checkout.payment.productAttributes.within(() => { + cy.contains(data.expectedCartSummary.products[index].size).should('be.visible'); + }); + page.checkout.payment.productAttributes.within(() => { + cy.contains(data.expectedCartSummary.products[index].color).should('be.visible'); + }); + page.checkout.payment.productQuantity.within(() => { + cy.contains(data.expectedCartSummary.products[index].quantity).should('be.visible'); + }); + page.checkout.payment.productPrice.within(() => { + cy.contains(data.expectedCartSummary.products[index].amount).should('be.visible'); + }); + }); + }); + page.checkout.payment.discountedPrice.within(() => { + cy.contains(data.expectedCartSummary.discountedPrice).should('be.visible'); + }); + page.checkout.payment.specialPrice.within(() => { + cy.contains(data.expectedCartSummary.specialPrice).should('be.visible'); + }); + page.checkout.payment.totalPrice.within(() => { + cy.contains(data.expectedCartSummary.totalPrice).should('be.visible'); + }); + }); + +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-shipping-validation.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-shipping-validation.spec.ts new file mode 100644 index 0000000000..de76f7cfc7 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-checkout-shipping-validation.spec.ts @@ -0,0 +1,89 @@ +import requests, { CreateCartResponse } from '../api/requests'; +import page from '../pages/factory'; +import generator from '../utils/data-generator'; + +context(['regression'], 'Checkout - Shipping', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-checkout-shipping-validation').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it('Should successfully save address - guest customer', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.fillForm(data.customer); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.contains(data.shippingMethod).click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + }); + + it('Should successfully save address - registered customer', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + requests.customerSignMeUp(data.customer); + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.addNewAddressButton.click(); + page.checkout.shipping.fillForm(data.customer); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.contains(data.shippingMethod).click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + }); + + const requiredFields = [ + 'First Name', + 'Last Name', + 'Street Name', + 'Apartment', + 'City', + 'Postal Code', + 'Phone' + ]; + + requiredFields.forEach(requiredField => { + it(`Should display an error - ${requiredField} empty`, function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.fillForm(data.customer); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping[Cypress._.camelCase(requiredField)].parent().within(() => { + cy.get('input').then(($input) => { + expect($input[0].validationMessage).to.be.eq(data.errorMessage); + }); + }); + }); + }); + + const requiredSelects = [ + 'Country', + 'State' + ]; + + requiredSelects.forEach(requiredSelect => { + it(`Should display an error - ${requiredSelect} empty`, function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product); + }); + page.checkout.shipping.visit(); + page.checkout.shipping.fillForm(data.customer); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping[Cypress._.camelCase(requiredSelect)].parent().within(() => { + cy.contains(data.errorMessage).should('be.visible'); + }); + }); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-my-account.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-my-account.spec.ts new file mode 100644 index 0000000000..cc538e5b72 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-my-account.spec.ts @@ -0,0 +1,80 @@ +import requests, { CustomerSignMeInResponse, GetMeResponse } from '../api/requests'; +import page from '../pages/factory'; +import { MyAccountTab } from '../pages/my-account'; +import generator from '../utils/data-generator'; +import intercept from '../utils/network'; + +context('My Account', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-my-account').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it(['regression'], 'Should redirect anonymous customer to home page', function () { + page.myAccount.myProfile.visit().url().should('eq', `${Cypress.config().baseUrl}${page.home.path}`); + }); + + it(['regression'], 'Should display customer\'s correct personal data', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + const getMeRequest = intercept.getMe(); + requests.customerSignMeUp(data.customer); + page.home.visit().then(() => { + cy.wait([getMeRequest, getMeRequest]); + }); + page.home.header.account.click(); + page.myAccount.myProfile.firstName.should('have.value', data.customer.firstName); + page.myAccount.myProfile.lastName.should('have.value', data.customer.lastName); + page.myAccount.myProfile.email.should('have.value', data.customer.email); + }); + + it(['regression'], 'Should update customer\'s personal data', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + const getMeRequest = intercept.getMe(); + requests.customerSignMeUp(data.customer).its('status').should('eq', 200); + page.home.visit().then(() => { + cy.wait([getMeRequest, getMeRequest]); + }); + page.home.header.account.click(); + data.updatedCustomer.email = generator.email; + page.myAccount.myProfile.firstName.clear().type(data.updatedCustomer.firstName); + page.myAccount.myProfile.lastName.clear().type(data.updatedCustomer.lastName); + page.myAccount.myProfile.email.clear().type(data.updatedCustomer.email); + const customerUpdateMeRequest = intercept.customerUpdateMe(); + page.myAccount.myProfile.updatePersonalDataButton.click().then(() => { + cy.wait(customerUpdateMeRequest); + }); + requests.getMe(true).should((response: GetMeResponse) => { + expect(response.body.data.me.customer.firstName).to.equal(data.updatedCustomer.firstName); + expect(response.body.data.me.customer.lastName).to.equal(data.updatedCustomer.lastName); + expect(response.body.data.me.customer.email).to.equal(data.updatedCustomer.email); + }); + }); + + it(['regression'], 'Should update customer\'s password', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email, data.updatedCustomer.email = data.customer.email; + const getMeRequest = intercept.getMe(); + requests.customerSignMeUp(data.customer).its('status').should('eq', 200); + page.home.visit().then(() => { + cy.wait([getMeRequest, getMeRequest]); + }); + page.home.header.account.click(); + page.myAccount.myProfile.switchTab(MyAccountTab.PasswordChange); + page.myAccount.myProfile.messageEmail.should('contain.text', data.customer.email); + page.myAccount.myProfile.currentPassword.type(data.customer.password); + page.myAccount.myProfile.newPassword.type(data.updatedCustomer.password); + page.myAccount.myProfile.repeatPassword.type(data.updatedCustomer.password); + const customerChangeMyPasswordRequest = intercept.customerChangeMyPassword(); + page.myAccount.myProfile.updatePasswordButton.click().then(() => { + cy.wait(customerChangeMyPasswordRequest); + }); + requests.customerSignMeIn(data.updatedCustomer).then((response: CustomerSignMeInResponse) => { + expect(response.body.data.user.customer.email).to.equal(data.updatedCustomer.email); + }); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-place-order.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-place-order.spec.ts new file mode 100644 index 0000000000..155f575cad --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-place-order.spec.ts @@ -0,0 +1,72 @@ +import page from '../pages/factory'; +import generator from '../utils/data-generator'; +import intercept from '../utils/network'; + +context('Order placement', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-place-order').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it(['happypath', 'regression'], 'Should successfully place an order as a guest', function() { + const data = this.fixtures.data[this.test.title]; + const getProductReq = intercept.getProduct(); + page.home.visit(); + page.home.header.categories.first().click(); + page.category().products.first().click().then(() => { + cy.wait([getProductReq, getProductReq]); + }); + page.product().addToCartButton.click(); + page.product().header.openCart(); + page.components.cart.goToCheckoutButton.click(); + page.checkout.shipping.heading.should('be.visible'); + page.checkout.shipping.fillForm(data.customer); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.first().click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.copyAddressLabel.click(); + page.checkout.billing.continueToPaymentButton.click(); + page.checkout.payment.paymentMethods.first().click(); + page.checkout.payment.terms.click(); + page.checkout.payment.makeAnOrderButton.click(); + page.checkout.thankyou.heading.should('be.visible'); + }); + + it(['happypath', 'regression'], 'Should successfully place an order as a registered customer', function() { + const data = this.fixtures.data[this.test.title]; + const getProductReq = intercept.getProduct(); + data.customer.email = generator.email; + page.home.visit(); + page.home.header.openLoginModal(); + page.components.loginModal.fillForm(data.customer); + page.components.loginModal.iWantToCreateAccountCheckbox.click(); + page.components.loginModal.submitButton.click(); + page.home.header.categories.first().click(); + page.category().products.first().click().then(() => { + cy.wait([getProductReq, getProductReq]); + }); + page.product().addToCartButton.click(); + page.product().header.openCart(); + page.components.cart.goToCheckoutButton.click(); + page.checkout.shipping.heading.should('be.visible'); + page.checkout.shipping.addresses.first().click(); + page.checkout.shipping.selectShippingButton.click(); + page.checkout.shipping.shippingMethods.first().click(); + page.checkout.shipping.continueToBillingButton.click(); + page.checkout.billing.heading.should('be.visible'); + page.checkout.billing.copyAddressLabel.click(); + page.checkout.billing.continueToPaymentButton.click(); + page.checkout.payment.paymentMethods.first().click(); + page.checkout.payment.terms.click(); + page.checkout.payment.makeAnOrderButton.click(); + page.checkout.thankyou.heading.should('be.visible'); + page.checkout.thankyou.orderNumber.then(($order) => { + page.myAccount.orderHistory.visit(); + page.myAccount.orderHistory.orderNumber.should('have.text', $order.text().substring(1)); + }); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-product-page.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-product-page.spec.ts new file mode 100644 index 0000000000..45c1205b34 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-product-page.spec.ts @@ -0,0 +1,47 @@ +import page from '../pages/factory'; +import intercept from '../utils/network'; + +context(['regression'], 'Product page', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-product-page').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + cy.clearLocalStorage(); + }); + + it('Should contain all size options', function () { + const data = this.fixtures.data[this.test.title]; + page.product(data.product.id, data.product.slug).visit(); + page.product().sizeOptions.then(options => { + const productSizes = [...options].map(option => option.value); + expect(productSizes).to.deep.eq(data.product.attributes.size); + }); + }); + + it('Should select correct size option', function () { + const data = this.fixtures.data[this.test.title]; + page.product(data.product.id, data.product.slug).visit(); + page.product().sizeSelect.select(data.product.attributes.size); + cy.url().should('contain', `size=${data.product.attributes.size}`); + page.product().sizeSelect.should('have.value', data.product.attributes.size); + }); + + it('Should add correct variant to cart', function() { + const data = this.fixtures.data[this.test.title]; + const getProductReq = intercept.getProduct(); + page.product(data.product.id, data.product.slug).visit(); + page.product().sizeSelect.select(data.product.attributes.size).then(() => { + cy.wait(getProductReq); + }); + page.product().addToCartButton.click(); + page.product().header.openCart(); + page.components.cart.productProperties.should('be.visible').then(() => { + page.components.cart.product().each((product) => { + page.components.cart.productSizeProperty(product).should('contain', data.product.attributes.size); + page.components.cart.productColorProperty(product).should('contain', data.product.attributes.color); + }); + }); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-remove-from-cart.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-remove-from-cart.spec.ts new file mode 100644 index 0000000000..c2a268c319 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-remove-from-cart.spec.ts @@ -0,0 +1,43 @@ +import requests, { CreateCartResponse } from '../api/requests'; +import page from '../pages/factory'; + +context('Remove from cart', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-remove-from-cart').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it('Should remove all products from cart', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + requests.addToCart(response.body.data.cart.id, data.product, data.product.quantity); + }); + page.home.visit(); + page.home.header.openCart(); + page.components.cart.product(data.product.name).should('be.visible'); + page.components.cart.removeProduct(data.product.name); + page.components.cart.product(data.product.name).should('not.exist'); + page.components.cart.yourCartIsEmptyHeading.should('be.visible'); + }); + + it('Should remove single product from cart', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + data.products.forEach(product => { + requests.addToCart(response.body.data.cart.id, product, product.quantity); + }); + }); + page.home.visit(); + page.home.header.openCart(); + page.components.cart.product(data.productToRemove.name).should('be.visible'); + page.components.cart.removeProduct(data.productToRemove.name); + page.components.cart.product(data.productToRemove.name).should('not.exist'); + data.expectedCart.forEach(product => { + page.components.cart.product(product.name).should('be.visible'); + }); + }); + +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-update-cart.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-update-cart.spec.ts new file mode 100644 index 0000000000..8d661d5cea --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-update-cart.spec.ts @@ -0,0 +1,57 @@ +import requests, { CreateCartResponse } from '../api/requests'; +import page from '../pages/factory'; +import intercept from '../utils/network'; + +context(['regression'], 'Update cart', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-update-cart').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it('Should increase product quantity', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + data.products.forEach(product => { + requests.addToCart(response.body.data.cart.id, product, product.quantity); + }); + }); + page.home.visit(); + page.home.header.openCart(); + page.components.cart.product(data.productToUpdate.name).should('be.visible'); + const updateCartRequest = intercept.updateCartQuantity(); + page.components.cart.increaseQtyButton(data.productToUpdate.name).click().then(() => { + cy.wait(updateCartRequest); + }); + page.components.cart.productName.each((name, index) => { + cy.wrap(name).should('contain', data.expectedCart[index].name); + }); + page.components.cart.quantity().each((input, index) => { + cy.wrap(input).should('have.value', data.expectedCart[index].quantity); + }); + }); + + it('Should decrease product quantity', function () { + const data = this.fixtures.data[this.test.title]; + requests.createCart().then((response: CreateCartResponse) => { + data.products.forEach(product => { + requests.addToCart(response.body.data.cart.id, product, product.quantity); + }); + }); + page.home.visit(); + page.home.header.openCart(); + page.components.cart.product(data.productToUpdate.name).should('be.visible'); + const updateCartRequest = intercept.updateCartQuantity(); + page.components.cart.decreaseQtyButton(data.productToUpdate.name).click().then(() => { + cy.wait(updateCartRequest); + }); + page.components.cart.productName.each((name, index) => { + cy.wrap(name).should('contain', data.expectedCart[index].name); + }); + page.components.cart.quantity().each((input, index) => { + cy.wrap(input).should('have.value', data.expectedCart[index].quantity); + }); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-user-login.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-user-login.spec.ts new file mode 100644 index 0000000000..f3dd6fa3c0 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-user-login.spec.ts @@ -0,0 +1,41 @@ +import requests from '../api/requests'; +import page from '../pages/factory'; +import generator from '../utils/data-generator'; + +context(['regression'], 'User login', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-user-login').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + cy.clearLocalStorage(); + }); + + it('Should successfully login', function() { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + requests.customerSignMeUp(data.customer).then(() => { + cy.clearCookies(); + }); + page.home.visit(); + page.home.header.openLoginModal(); + page.components.loginModal.loginToAccountButton.click(); + page.components.loginModal.fillForm(data.customer); + page.components.loginModal.loginBtn.click(); + page.components.loginModal.container.should('not.exist'); + page.home.header.account.click(); + page.myAccount.myProfile.heading.should('be.visible'); + }); + + it('Incorrect credentials - should display an error', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + page.home.visit(); + page.home.header.openLoginModal(); + page.components.loginModal.loginToAccountButton.click(); + page.components.loginModal.fillForm(data.customer); + page.components.loginModal.loginBtn.click(); + page.components.loginModal.container.contains(data.errorMessage).should('be.visible'); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/integration/e2e-user-registration.spec.ts b/packages/commercetools/theme/tests/e2e/integration/e2e-user-registration.spec.ts new file mode 100644 index 0000000000..d8e5eb6816 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/integration/e2e-user-registration.spec.ts @@ -0,0 +1,40 @@ +import page from '../pages/factory'; +import requests from '../api/requests'; +import generator from '../utils/data-generator'; + +context(['regression'], 'User registration', () => { + beforeEach(function () { + cy.fixture('test-data/e2e-user-registration').then((fixture) => { + this.fixtures = { + data: fixture + }; + }); + }); + + it('Should successfully register', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + page.home.visit(); + page.home.header.openLoginModal(); + page.components.loginModal.fillForm(data.customer); + page.components.loginModal.iWantToCreateAccountCheckbox.click(); + page.components.loginModal.submitButton.click(); + page.components.loginModal.container.should('not.exist'); + page.home.header.account.click(); + page.myAccount.myProfile.heading.should('be.visible'); + }); + + it('Existing user - should display an error', function () { + const data = this.fixtures.data[this.test.title]; + data.customer.email = generator.email; + requests.customerSignMeUp(data.customer).then(() => { + cy.clearCookies(); + }); + page.home.visit(); + page.home.header.openLoginModal(); + page.components.loginModal.fillForm(data.customer); + page.components.loginModal.iWantToCreateAccountCheckbox.click(); + page.components.loginModal.submitButton.click(); + page.components.loginModal.container.contains(`${data.errorMessage} '"${data.customer.email}"'`).should('be.visible'); + }); +}); diff --git a/packages/commercetools/theme/tests/e2e/pages/base.ts b/packages/commercetools/theme/tests/e2e/pages/base.ts new file mode 100644 index 0000000000..6d59d62acf --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/base.ts @@ -0,0 +1,17 @@ +import Header from './components/header'; + +export default class Base { + + get path(): string { + return '/'; + } + + get header() { + return Header; + } + + visit(): Cypress.Chainable { + return cy.visit(this.path); + } + +} diff --git a/packages/commercetools/theme/tests/e2e/pages/category.ts b/packages/commercetools/theme/tests/e2e/pages/category.ts new file mode 100644 index 0000000000..fb0d67887b --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/category.ts @@ -0,0 +1,68 @@ +import { el } from './utils/element'; +import Base from './base'; + +type View = 'tiles' | 'list' + +export class Category extends Base { + + private readonly _category: string; + private _view: View = 'tiles'; + + constructor(category?: string) { + super(); + if (category) this._category = category; + } + + set view(view: View) { + this._view = view; + } + + get view(): View { + return this._view; + } + + get category(): string { + return this._category; + } + + get path(): string { + return `/c/${this.category}`; + } + + get products(): Cypress.Chainable { + return el('category-product-card', 'a'); + } + + get selectors(): { [key: string]: { [key in View]: string } } { + return { + productCard: { + tiles: '.sf-product-card', + list: '.sf-product-card-horizontal' + }, + addToCardButton: { + tiles: '.sf-product-card__add-button', + list: '.sf-add-to-cart__button' + } + }; + } + + viewIcon(view: View): Cypress.Chainable { + return el(`${view}-icon`); + } + + changeView(view: View): Cypress.Chainable { + this.view = view; + return this.viewIcon(view).click(); + } + + product(name: string): Cypress.Chainable { + return this.products.contains(name); + } + + addToCart(name: string): Cypress.Chainable { + return this.product(name).parents(this.selectors.productCard[this.view]) + .find(this.selectors.addToCardButton[this.view]) + .click(); + } + +} diff --git a/packages/commercetools/theme/tests/e2e/pages/checkout.ts b/packages/commercetools/theme/tests/e2e/pages/checkout.ts new file mode 100644 index 0000000000..7549399b73 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/checkout.ts @@ -0,0 +1,200 @@ +import { Customer, Address } from '../types/types'; +import Base from './base'; +import { el } from './utils/element'; + +class Checkout extends Base { + + protected step = '' + + get addNewAddressButton(): Cypress.Chainable { + return el(`${this.step}-add-new-address`); + } + + get firstName(): Cypress.Chainable { + return el(`${this.step}-firstName`, 'input'); + } + + get lastName(): Cypress.Chainable { + return el(`${this.step}-lastName`, 'input'); + } + + get streetName(): Cypress.Chainable { + return el(`${this.step}-streetName`, 'input'); + } + + get apartment(): Cypress.Chainable { + return el(`${this.step}-apartment`, 'input'); + } + + get city(): Cypress.Chainable { + return el(`${this.step}-city`, 'input'); + } + + get state(): Cypress.Chainable { + return el(`${this.step}-state`, 'select'); + } + + get country(): Cypress.Chainable { + return el(`${this.step}-country`, 'select'); + } + + get postalCode(): Cypress.Chainable { + return el(`${this.step}-zipcode`, 'input'); + } + + get phone(): Cypress.Chainable { + return el(`${this.step}-phone`, 'input'); + } + + public fillForm(address: Address) { + if (address.firstName !== undefined) this.firstName.clear().type(address.firstName); + if (address.lastName !== undefined) this.lastName.clear().type(address.lastName); + if (address.streetName !== undefined) this.streetName.clear().type(address.streetName); + if (address.apartment !== undefined) { + this.apartment.parent().click(); + this.apartment.clear().type(address.apartment); + } + if (address.city !== undefined) this.city.clear().type(address.city); + if (address.country !== undefined) this.country.select(address.country); + if (address.state !== undefined) this.state.select(address.state); + if (address.postalCode !== undefined) this.postalCode.clear().type(address.postalCode); + if (address.phone !== undefined) this.phone.clear().type(address.phone); + } +} + +class Shipping extends Checkout { + + constructor() { + super(); + this.step = 'shipping'; + } + + get path(): string { + return '/checkout/shipping'; + } + + get addresses(): Cypress.Chainable { + return el('shipping-addresses', '.sf-radio label'); + } + + get continueToBillingButton(): Cypress.Chainable { + return el('continue-to-billing'); + } + + get heading(): Cypress.Chainable { + return el(`${this.step}-heading`); + } + + get selectShippingButton(): Cypress.Chainable { + return el('select-shipping'); + } + + get shippingMethods(): Cypress.Chainable { + return el('shipping-method-label'); + } + + public fillForm(customer: Customer) { + super.fillForm(customer.address.shipping); + } + +} + +class Billing extends Checkout { + + constructor() { + super(); + this.step = 'billing'; + } + + get path(): string { + return '/checkout/billing'; + } + + get continueToPaymentButton(): Cypress.Chainable { + return el('continue-to-payment'); + } + + get heading(): Cypress.Chainable { + return el(`${this.step}-heading`); + } + + get copyAddressLabel(): Cypress.Chainable { + return el('copy-address', 'label'); + } + + public fillForm(customer: Customer) { + super.fillForm(customer.address.billing); + } +} + +class Payment extends Base { + + get path(): string { + return '/checkout/payment'; + } + + get heading(): Cypress.Chainable { + return el('heading-payment'); + } + + get makeAnOrderButton(): Cypress.Chainable { + return el('make-an-order'); + } + + get paymentMethods(): Cypress.Chainable { + return el('payment-method'); + } + + get terms(): Cypress.Chainable { + return el('terms', 'label'); + } + + get productRow(): Cypress.Chainable { + return el('product-row'); + } + + get productTitleSku(): Cypress.Chainable { + return el('product-title-sku'); + } + + get productAttributes(): Cypress.Chainable { + return el('product-attributes'); + } + + get productQuantity(): Cypress.Chainable { + return el('product-quantity'); + } + + get productPrice(): Cypress.Chainable { + return el('product-price'); + } + + get discountedPrice(): Cypress.Chainable { + return cy.get('.discounted'); + } + + get specialPrice(): Cypress.Chainable { + return cy.get('.special-price'); + } + + get totalPrice(): Cypress.Chainable { + return cy.get('.property-total'); + } +} + +class ThankYou { + get heading(): Cypress.Chainable { + return el('thank-you-banner', 'h2'); + } + + get orderNumber(): Cypress.Chainable { + return el('order-number'); + } +} + +export { + Shipping, + Billing, + Payment, + ThankYou +}; diff --git a/packages/commercetools/theme/tests/e2e/pages/components/cart-sidebar.ts b/packages/commercetools/theme/tests/e2e/pages/components/cart-sidebar.ts new file mode 100644 index 0000000000..328439eb2d --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/components/cart-sidebar.ts @@ -0,0 +1,63 @@ +import { el } from '../utils/element'; + +class Cart { + + product(name?: string): Cypress.Chainable { + const product = el('collected-product'); + return name === undefined ? product : product.contains(name).parents('[data-e2e="collected-product"]'); + } + + get productName(): Cypress.Chainable { + return this.product().find('.sf-collected-product__title'); + } + + get goToCheckoutButton(): Cypress.Chainable { + return el('go-to-checkout-btn'); + } + + get productProperties(): Cypress.Chainable { + return this.product().find('.collected-product__properties'); + } + + get totalItems(): Cypress.Chainable { + return el('sidebar-cart', '.cart-summary .sf-property__value'); + } + + get yourCartIsEmptyHeading(): Cypress.Chainable { + return el('sidebar-cart', 'h2').contains('Your cart is empty'); + } + + propertyValue(collectedProduct: JQuery): Cypress.Chainable { + return cy.wrap(collectedProduct).find('.sf-property__value'); + } + + productSizeProperty(collectedProduct: JQuery): Cypress.Chainable { + return this.propertyValue(collectedProduct).eq(0); + } + + productColorProperty(collectedProduct: JQuery): Cypress.Chainable { + return this.propertyValue(collectedProduct).eq(1); + } + + removeProductButton(name?: string): Cypress.Chainable { + return this.product(name).find('.sf-collected-product__remove'); + } + + removeProduct(name?: string): Cypress.Chainable { + return this.removeProductButton(name).first().click(); + } + + increaseQtyButton(name?: string): Cypress.Chainable { + return this.product(name).find('button').contains('+'); + } + + decreaseQtyButton(name?: string): Cypress.Chainable { + return this.product(name).find('button').contains('−'); + } + + quantity(name?: string): Cypress.Chainable { + return this.product(name).find('input'); + } +} + +export default new Cart(); diff --git a/packages/commercetools/theme/tests/e2e/pages/components/header.ts b/packages/commercetools/theme/tests/e2e/pages/components/header.ts new file mode 100644 index 0000000000..d5e325822c --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/components/header.ts @@ -0,0 +1,39 @@ +import { el } from '../utils/element'; + +class Header { + + get account(): Cypress.Chainable { + return el('app-header-account'); + } + + get cart(): Cypress.Chainable { + return el('app-header-cart'); + } + + get categories(): Cypress.Chainable { + return cy.get('[data-e2e*="app-header"]'); + } + + get category() { + return { + women: () => el('app-header-url_women'), + men: () => el('app-header-url_men') + }; + } + + openCart(): Cypress.Chainable { + const click = $el => $el.click(); + return this.cart.pipe(click).should(() => { + expect(Cypress.$('[data-e2e="sidebar-cart"]')).to.exist; + }); + } + + openLoginModal(): Cypress.Chainable { + const click = $el => $el.click(); + return this.account.pipe(click).should(() => { + expect(Cypress.$('[data-e2e="login-modal"]')).to.exist; + }); + } +} + +export default new Header(); diff --git a/packages/commercetools/theme/tests/e2e/pages/components/login-modal.ts b/packages/commercetools/theme/tests/e2e/pages/components/login-modal.ts new file mode 100644 index 0000000000..83dc386231 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/components/login-modal.ts @@ -0,0 +1,51 @@ +import { Customer } from '../../types/types'; +import { el } from '../utils/element'; + +class LoginModal { + + get container(): Cypress.Chainable { + return el('login-modal', '.sf-modal__container'); + } + + get email(): Cypress.Chainable { + return el('login-modal-email'); + } + + get firstName(): Cypress.Chainable { + return el('login-modal-firstName'); + } + + get lastName(): Cypress.Chainable { + return el('login-modal-lastName'); + } + + get password(): Cypress.Chainable { + return el('login-modal-password'); + } + + get iWantToCreateAccountCheckbox(): Cypress.Chainable { + return el('login-modal-create-account'); + } + + get submitButton(): Cypress.Chainable { + return el('login-modal-submit'); + } + + get loginToAccountButton(): Cypress.Chainable { + return el('login-modal-login-to-your-account'); + } + + get loginBtn(): Cypress.Chainable { + return el('login-modal-submit'); + } + + fillForm(customer: Customer): void { + if (customer.email) this.email.type(customer.email); + if (customer.firstName) this.firstName.type(customer.firstName); + if (customer.lastName) this.lastName.type(customer.lastName); + if (customer.password) this.password.type(customer.password); + } + +} + +export default new LoginModal(); diff --git a/packages/commercetools/theme/tests/e2e/pages/factory.ts b/packages/commercetools/theme/tests/e2e/pages/factory.ts new file mode 100644 index 0000000000..a0ff7997d4 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/factory.ts @@ -0,0 +1,47 @@ +import { Billing, Payment, Shipping, ThankYou } from './checkout'; +import Cart from './components/cart-sidebar'; +import LoginModal from './components/login-modal'; +import Home from './home'; +import { MyProfile, OrderHistory } from './my-account'; +import { Product } from './product'; +import { Category } from './category'; + +const page = { + get checkout() { + return { + shipping: new Shipping(), + billing: new Billing(), + payment: new Payment(), + thankyou: new ThankYou() + }; + }, + + get components() { + return { + cart: Cart, + loginModal: LoginModal + }; + }, + + get home() { + return Home; + }, + + get myAccount() { + return { + myProfile: new MyProfile(), + orderHistory: new OrderHistory() + }; + }, + + category(category?: string) { + return new Category(category); + }, + + product(id?: string, slug?: string) { + return new Product(id, slug); + } + +}; + +export default page; diff --git a/packages/commercetools/theme/tests/e2e/pages/home.ts b/packages/commercetools/theme/tests/e2e/pages/home.ts new file mode 100644 index 0000000000..e67d6469de --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/home.ts @@ -0,0 +1,11 @@ +import Base from './base'; +import Header from './components/header'; + +class Home extends Base { + get header() { + return Header; + } + +} + +export default new Home(); diff --git a/packages/commercetools/theme/tests/e2e/pages/my-account.ts b/packages/commercetools/theme/tests/e2e/pages/my-account.ts new file mode 100644 index 0000000000..c01f0f7c22 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/my-account.ts @@ -0,0 +1,81 @@ +import Base from './base'; +import { contains, el } from './utils/element'; + +export enum MyAccountTab { + PersonalData = 'Personal data', + PasswordChange = 'Password change' +} + +class MyAccount extends Base { + + get path(): string { + return '/my-account'; + } + + get heading(): Cypress.Chainable { + return contains('my-account-content-pages', 'My Account'); + } +} + +class MyProfile extends MyAccount { + + get firstName(): Cypress.Chainable { + return el('myaccount-firstName').find('input'); + } + + get lastName(): Cypress.Chainable { + return el('myaccount-lastName').find('input'); + } + + get email(): Cypress.Chainable { + return el('myaccount-email').find('input'); + } + + get updatePersonalDataButton(): Cypress.Chainable { + return el('myaccount-update-personal-data-btn'); + } + + get messageEmail(): Cypress.Chainable { + return el('myaccount-message-email'); + } + + get currentPassword(): Cypress.Chainable { + return el('myaccount-current-password'); + } + + get newPassword(): Cypress.Chainable { + return el('myaccount-new-password'); + } + + get repeatPassword(): Cypress.Chainable { + return el('myaccount-repeat-password'); + } + + get updatePasswordButton(): Cypress.Chainable { + return el('myaccount-update-password-btn'); + } + + tab(tab: MyAccountTab): Cypress.Chainable { + return cy.contains('.sf-tabs__title', tab); + } + + switchTab(tab: MyAccountTab) { + const click = $tab => cy.wrap($tab).click(); + return this.tab(tab).pipe(click).should('have.class', 'is-active'); + } +} +class OrderHistory extends Base { + get path(): string { + return '/my-account/order-history'; + } + + get orderNumber(): Cypress.Chainable { + return el('order-number'); + } +} + +export { + MyAccount, + MyProfile, + OrderHistory +}; diff --git a/packages/commercetools/theme/tests/e2e/pages/product.ts b/packages/commercetools/theme/tests/e2e/pages/product.ts new file mode 100644 index 0000000000..0832ecaba9 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/product.ts @@ -0,0 +1,47 @@ +import Base from './base'; +import { el } from './utils/element'; + +export class Product extends Base { + + private _id: string; + private _slug: string; + + constructor(id?: string, slug?: string) { + super(); + if (id) this.id = id; + if (slug) this.slug = slug; + } + + get id(): string { + return this._id; + } + + set id(id: string) { + this._id = id; + } + + get slug(): string { + return this._slug; + } + + set slug(slug: string) { + this._slug = slug; + } + + get path() { + return `/p/${this.id}/${this.slug}`; + } + + get addToCartButton(): Cypress.Chainable { + return el('product_add-to-cart'); + } + + get sizeSelect(): Cypress.Chainable { + return el('size-select', 'select'); + } + + get sizeOptions(): Cypress.Chainable { + return el('size-select', 'select option'); + } + +} diff --git a/packages/commercetools/theme/tests/e2e/pages/utils/element.ts b/packages/commercetools/theme/tests/e2e/pages/utils/element.ts new file mode 100644 index 0000000000..7f15c3b7ee --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/pages/utils/element.ts @@ -0,0 +1,7 @@ +export function el(selector: string, children?: string): Cypress.Chainable { + return children ? cy.get(`[data-e2e="${selector}"] ${children}`) : cy.get(`[data-e2e="${selector}"]`); +} + +export function contains(selector: string, text: string): Cypress.Chainable { + return cy.contains(`[data-e2e="${selector}"]`, text); +} diff --git a/packages/commercetools/theme/tests/e2e/plugins/index.js b/packages/commercetools/theme/tests/e2e/plugins/index.js new file mode 100644 index 0000000000..70b6427fe6 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/plugins/index.js @@ -0,0 +1,25 @@ +// eslint-disable-next-line spaced-comment +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +const tagify = require('cypress-tags'); + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config + on('file:preprocessor', tagify(config)); +}; diff --git a/packages/commercetools/theme/tests/e2e/support/commands.js b/packages/commercetools/theme/tests/e2e/support/commands.js new file mode 100644 index 0000000000..9ce9016cc1 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/support/commands.js @@ -0,0 +1,26 @@ +/* eslint-disable no-undef */ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/packages/commercetools/theme/tests/e2e/support/index.d.ts b/packages/commercetools/theme/tests/e2e/support/index.d.ts new file mode 100644 index 0000000000..b34f8e01a9 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/support/index.d.ts @@ -0,0 +1,9 @@ +/* eslint-disable spaced-comment */ +/// +/// + +declare namespace Cypress { + interface Chainable { + fixtures?: any; + } +} diff --git a/packages/commercetools/theme/tests/e2e/support/index.js b/packages/commercetools/theme/tests/e2e/support/index.js new file mode 100644 index 0000000000..9500306864 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/support/index.js @@ -0,0 +1,35 @@ +/* eslint-disable no-undef */ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands.js'; +import 'cypress-pipe'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') + +import addContext from 'mochawesome/addContext'; + +Cypress.on('test:after:run', (test, runnable) => { + if (test.state === 'failed') { + const screenshot = `assets/screenshots/${Cypress.spec.name}/${runnable.parent.title} -- ${test.title} (failed).png`; + addContext({test}, { + title: 'Screenshot', + value: screenshot + }); + } +}); + diff --git a/packages/commercetools/theme/tests/e2e/types/types.ts b/packages/commercetools/theme/tests/e2e/types/types.ts new file mode 100644 index 0000000000..d66faa10a4 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/types/types.ts @@ -0,0 +1,27 @@ +export type Address = { + firstName?: string; + lastName?: string; + streetName?: string; + apartment?: string; + city?: string; + state?: string; + country?: string; + postalCode?: string; + phone?: string; +} + +export type Customer = { + firstName?: string; + lastName?: string; + email?: string; + password?: string; + address?: { + shipping: Address, + billing: Address + } +} + +export type Product = { + sku: string; + id: number; +} diff --git a/packages/commercetools/theme/tests/e2e/utils/data-generator.ts b/packages/commercetools/theme/tests/e2e/utils/data-generator.ts new file mode 100644 index 0000000000..db7335d4ed --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/utils/data-generator.ts @@ -0,0 +1,15 @@ +const generator = { + get email(): string { + return `cypress.${Date.now()}@vuestorefront.test`; + }, + + get uuid(): string { + return 'xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, () => { + const random = Math.floor(Math.random() * 16); + return random.toString(16); + }); + } + +}; + +export default generator; diff --git a/packages/commercetools/theme/tests/e2e/utils/network.ts b/packages/commercetools/theme/tests/e2e/utils/network.ts new file mode 100644 index 0000000000..22ea0c8183 --- /dev/null +++ b/packages/commercetools/theme/tests/e2e/utils/network.ts @@ -0,0 +1,32 @@ +import generator from '../utils/data-generator'; + +function _intercept(path: string, alias?: string) { + const as = alias ?? generator.uuid; + cy.intercept(path).as(as); + return `@${as}`; +} + +const intercept = { + + customerChangeMyPassword(as?: string): string { + return _intercept('/customerChangeMyPassword', as); + }, + + customerUpdateMe(as?: string): string { + return _intercept('/customerUpdateMe', as); + }, + + getMe(as?: string): string { + return _intercept('/getMe', as); + }, + + getProduct(as?: string): string { + return _intercept('/getProduct', as); + }, + + updateCartQuantity(as?: string): string { + return _intercept('/updateCartQuantity', as); + } +}; + +export default intercept; diff --git a/packages/commercetools/theme/themeConfig.js b/packages/commercetools/theme/themeConfig.js new file mode 100644 index 0000000000..84d19fbd69 --- /dev/null +++ b/packages/commercetools/theme/themeConfig.js @@ -0,0 +1,55 @@ +// default configuration for links and images +const defaultConfig = { + home: { + bannerA: { + link: '/', + image: { + mobile: '/homepage/bannerB.webp', + desktop: '/homepage/bannerF.webp' + } + }, + bannerB: { + link: '/', + image: '/homepage/bannerE.webp' + }, + bannerC: { + link: '/', + image: '/homepage/bannerC.webp' + }, + bannerD: { + link: '/', + image: '/homepage/bannerG.webp' + } + } +}; +// configuration used for links and images for demo purposes +const demoConfig = { + home: { + bannerA: { + link: '/c/women/women-clothing-skirts', + image: { + mobile: '/homepage/bannerB.webp', + desktop: '/homepage/bannerF.webp' + } + }, + bannerB: { + link: '/c/women/women-clothing-dresses', + image: '/homepage/bannerE.webp' + }, + bannerC: { + link: '/c/women/women-clothing-shirts', + image: '/homepage/bannerC.webp' + }, + bannerD: { + link: '/c/women/women-shoes-sandals', + image: '/homepage/bannerG.webp' + } + } +}; + +export default (() => { + if (process.env.IS_DEMO) { + return demoConfig; + } + return defaultConfig; +})(); diff --git a/packages/commercetools/theme/tsconfig.json b/packages/commercetools/theme/tsconfig.json new file mode 100644 index 0000000000..38cfd69cb1 --- /dev/null +++ b/packages/commercetools/theme/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "esnext", + "moduleResolution": "node", + "lib": [ + "esnext", + "esnext.asynciterable", + "dom" + ], + "esModuleInterop": true, + "allowJs": true, + "sourceMap": true, + "strict": false, + "noEmit": true, + "baseUrl": ".", + "paths": { + "~/*": [ + "./*" + ], + "@/*": [ + "./*" + ] + }, + "types": [ + "@types/node", + "@nuxt/types", + "nuxt-i18n", + "cypress" + ] + }, + "exclude": [ + "node_modules" + ], + "include": [ + "tests/e2e/**/*.ts", + "tests/e2e/plugins/index.js", + "tests/e2e/support/index.js" + ] +} diff --git a/packages/core/cache/.gitignore b/packages/core/cache/.gitignore new file mode 100644 index 0000000000..f6d2990017 --- /dev/null +++ b/packages/core/cache/.gitignore @@ -0,0 +1,4 @@ +lib +node_modules +coverage +yarn-error.log diff --git a/packages/core/cache/README.md b/packages/core/cache/README.md new file mode 100644 index 0000000000..3fca9d771f --- /dev/null +++ b/packages/core/cache/README.md @@ -0,0 +1 @@ +# VSF Next Cache \ No newline at end of file diff --git a/packages/core/cache/__tests__/defaultHandler.spec.ts b/packages/core/cache/__tests__/defaultHandler.spec.ts new file mode 100644 index 0000000000..8e0de5c9fe --- /dev/null +++ b/packages/core/cache/__tests__/defaultHandler.spec.ts @@ -0,0 +1,66 @@ +import defaultHandler from '../defaultHandler'; + +describe('defaultHandler', () => { + it('returns empty array if key is not configured', () => { + const result = defaultHandler({ + request: { + url: '/?tags=a,b,c' + }, + options: {} + }); + + expect(result).toEqual([]); + }); + + it('returns empty array if key was not provided in the request', () => { + const result = defaultHandler({ + request: { + url: '/?tags=a,b,c' + }, + options: { + key: 'key' + } + }); + + expect(result).toEqual([]); + }); + + it('returns empty array if tags were not provided in the request', () => { + const result = defaultHandler({ + request: { + url: '/?key=secret' + }, + options: { + key: 'secret' + } + }); + + expect(result).toEqual([]); + }); + + it('handles single tag', () => { + const result = defaultHandler({ + request: { + url: '/?key=secret&tags=a' + }, + options: { + key: 'secret' + } + }); + + expect(result).toEqual(['a']); + }); + + it('handles multiple tags', () => { + const result = defaultHandler({ + request: { + url: '/?key=secret&tags=a,b,c' + }, + options: { + key: 'secret' + } + }); + + expect(result).toEqual(['a', 'b', 'c']); + }); +}); diff --git a/packages/core/cache/__tests__/nuxtModule.spec.ts b/packages/core/cache/__tests__/nuxtModule.spec.ts new file mode 100644 index 0000000000..6b2e61e8aa --- /dev/null +++ b/packages/core/cache/__tests__/nuxtModule.spec.ts @@ -0,0 +1,80 @@ +import nuxtModule from '../nuxt'; +import path from 'path'; + +/** + * Variables + */ +const mockNuxt = { + nuxt: { + renderer: { + renderRoute: jest.fn() + } + }, + + addPlugin: jest.fn(), + + addServerMiddleware: jest.fn(({ handler }) => { + // Simulate request to invalidation endpoint + handler({}, { writeHead: jest.fn(), end: jest.fn() }); + }) +}; + +const mockInvoke = jest.fn(); +const mockInvalidate = jest.fn(); +const mockHandler = jest.fn(() => ['a', 'b', 'c', 'a', 'b', 'c']); + +/** + * Mocks + */ +jest.mock('@vue-storefront/core'); +jest.mock('path'); +jest.mock('../nuxt/helpers.js', () => ({ + requirePackage: jest.fn(() => ({})), + requireDriver: jest.fn(() => ({ + invoke: mockInvoke, + invalidate: mockInvalidate + })) +})); + +describe('nuxtModule', () => { + it('registers plugin and calls driver methods', () => { + const options = { + invalidation: { + endpoint: '/endpoint', + handlers: [ + mockHandler + ] + }, + driver: [ + '@scope/driver', + { + driverSettings: 'setting' + } + ] + }; + + // Run module + nuxtModule.call(mockNuxt, options); + + // Nuxt.js plugin should be called + expect(mockNuxt.addPlugin).toHaveBeenCalledWith({ + src: path.resolve(__dirname, '../nuxt/plugin.js'), + mode: 'server', + options + }); + + // Simulate route render + mockNuxt.nuxt.renderer.renderRoute('/', {}, jest.fn()); + + // Driver's "invoke" should be called + expect(mockInvoke).toBeCalled(); + + // Driver's "invalidate" should be called without duplicate tags + expect(mockInvalidate).toHaveBeenCalledWith( + expect.objectContaining({ tags: ['a', 'b', 'c'] }) + ); + + // Handler passed to options should be called + expect(mockHandler).toBeCalled(); + }); +}); diff --git a/packages/core/cache/__tests__/useCache.spec.ts b/packages/core/cache/__tests__/useCache.spec.ts new file mode 100644 index 0000000000..5c6507ce58 --- /dev/null +++ b/packages/core/cache/__tests__/useCache.spec.ts @@ -0,0 +1,110 @@ +import { CacheTagPrefix, useCache } from '../src'; +import { useContext } from '@nuxtjs/composition-api'; + +/** + * Mocks + */ +jest.mock('@nuxtjs/composition-api', () => ({ + useContext: jest.fn() +})); + +/** + * Variables + */ +const tags = [ + { + prefix: CacheTagPrefix.Product, + value: '1' + }, + { + prefix: CacheTagPrefix.Category, + value: '2' + }, + { + prefix: CacheTagPrefix.View, + value: '3' + } +]; + +/** + * Tests + */ +describe('useCache', () => { + beforeEach(() => jest.resetModules()); + + it('returns empty array by default', () => { + const useContextMock = useContext as jest.Mock; + useContextMock.mockImplementation(() => { + return { + req: { + $vsfCache: { + tagsSet: new Set() + } + } + }; + }); + const { getTags } = useCache(); + + expect(getTags()).toEqual([]); + }); + + it('can add tags', () => { + const useContextMock = useContext as jest.Mock; + useContextMock.mockImplementation(() => ({ + req: { + $vsfCache: { + tagsSet: new Set() + } + } + })); + const { addTags, getTags } = useCache(); + + addTags(tags); + + expect(getTags()).toEqual(tags); + }); + + it('can set / override tags', () => { + const useContextMock = useContext as jest.Mock; + useContextMock.mockImplementation(() => ({ + req: { + $vsfCache: { + tagsSet: new Set() + } + } + })); + const { addTags, setTags, getTags } = useCache(); + + addTags(tags); + setTags((currentTags) => currentTags.filter((tag) => tag.prefix === 'P')); + + expect(getTags()).toEqual([tags[0]]); + }); + + it('can clear tags', () => { + const useContextMock = useContext as jest.Mock; + useContextMock.mockImplementation(() => ({ + req: { + $vsfCache: { + tagsSet: new Set() + } + } + })); + const { addTags, clearTags, getTags } = useCache(); + + addTags(tags); + clearTags(); + + expect(getTags()).toEqual([]); + }); + + it('runs when req in context is undefined', () => { + const useContextMock = useContext as jest.Mock; + useContextMock.mockImplementation(() => ({ + req: undefined + })); + const { getTags } = useCache(); + + expect(getTags()).toEqual([]); + }); +}); diff --git a/packages/core/cache/api-extractor.json b/packages/core/cache/api-extractor.json new file mode 100644 index 0000000000..f12f30fd57 --- /dev/null +++ b/packages/core/cache/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../api-extractor.base.json", + "mainEntryPointFilePath": "./lib/index.d.ts", + "dtsRollup": { + "untrimmedFilePath": "./lib/.d.ts" + }, + "docModel": { + "apiJsonFilePath": "/core/docs/core/api-reference/.api.json" + } +} diff --git a/packages/core/cache/defaultHandler/index.js b/packages/core/cache/defaultHandler/index.js new file mode 100644 index 0000000000..a0a583b5ab --- /dev/null +++ b/packages/core/cache/defaultHandler/index.js @@ -0,0 +1,11 @@ +export default ({ request, options }) => { + // Remove leading slash and get URL params + const params = new URLSearchParams(request.url.replace(/^\//, '')); + const tags = params.get('tags') || ''; + + if (params.get('key') !== options.key || !tags) { + return []; + } + + return tags.split(','); +}; diff --git a/packages/core/cache/jest.config.js b/packages/core/cache/jest.config.js new file mode 100644 index 0000000000..2a889cfae5 --- /dev/null +++ b/packages/core/cache/jest.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('./../../jest.base.config'); + +module.exports = baseConfig; diff --git a/packages/core/cache/nuxt/helpers.js b/packages/core/cache/nuxt/helpers.js new file mode 100644 index 0000000000..0c7bb51768 --- /dev/null +++ b/packages/core/cache/nuxt/helpers.js @@ -0,0 +1,22 @@ +import { isAbsolute, resolve } from 'path'; + +/** + * Helper function that imports default handler by package name + */ +export function requirePackage (name) { + const path = isAbsolute(name) || name.startsWith('.') + ? resolve(process.cwd(), name) + : require.resolve(name, { paths: [process.cwd()] }); + + // eslint-disable-next-line global-require + return require(path).default; +} + +/** + * Loads driver using path provided in the configuration. + */ +export function requireDriver (driver) { + return Array.isArray(driver) + ? requirePackage(driver[0])(driver[1]) + : requirePackage(driver)(); +} diff --git a/packages/core/cache/nuxt/index.js b/packages/core/cache/nuxt/index.js new file mode 100644 index 0000000000..3cd663522b --- /dev/null +++ b/packages/core/cache/nuxt/index.js @@ -0,0 +1,107 @@ +import { resolve } from 'path'; +import { Logger } from '@vue-storefront/core'; +import { requirePackage, requireDriver } from './helpers'; + +/** + * Adds endpoint to invalidate cache + */ +function createInvalidationEndpoint (driver, options) { + if (!options || !options.endpoint || !options.handlers) { + return; + } + + const handler = async (request, response) => { + try { + // Resolve handlers paths + const tags = options.handlers + .map(handler => typeof handler === 'string' ? requirePackage(handler) : handler) + .reduce((tags, handler) => { + const newTags = handler({ request, response, options }); + return tags.concat(newTags); + }, []); + + // Call driver invalidator with all tags + await driver.invalidate({ + request, + response, + // Removes duplicates + tags: Array.from(new Set(tags)) + }); + + response.writeHead(200); + } catch (error) { + Logger.error('Cache driver thrown an error when invalidating cache! Operation skipped.'); + Logger.error(error); + + response.writeHead(500); + } + + response.end(); + }; + + this.addServerMiddleware({ + path: options.endpoint, + handler + }); +} + +/** + * Creates custom renderer to use cache as much as possible. + */ +function createRenderer (renderFn) { + const renderer = this.nuxt.renderer; + const renderRoute = renderer.renderRoute.bind(renderer); + + renderer.renderRoute = (route, context) => { + const render = () => renderRoute(route, context); + return renderFn(route, context, render); + }; +} + +/** + * Main VSF cache module. + */ +export default function cacheModule (options) { + Logger.info('Installed Vue Storefront Cache plugin'); + + // This part must be before the condition below + this.addPlugin({ + src: resolve(__dirname, './plugin.js'), + mode: 'server', + options + }); + + if (!this.nuxt || !this.nuxt.renderer) { + return; + } + + // Create cache driver + const driver = requireDriver(options.driver); + + // Create invalidation endpoint if necessary + createInvalidationEndpoint.call(this, driver, options.invalidation); + + // Create renderer + createRenderer.call(this, async (route, context, render) => { + const getTags = () => { + const tags = context.req.$vsfCache && context.req.$vsfCache.tagsSet + ? Array.from(context.req.$vsfCache.tagsSet) + : []; + + return tags.map(({ prefix, value }) => `${prefix}${value}`); + }; + + try { + return await driver.invoke({ + route, + context, + render, + getTags + }); + } catch (err) { + Logger.error('Cache driver thrown an error when fetching cache! Server will render fresh page.'); + Logger.error(err); + return render(); + } + }); +} diff --git a/packages/core/cache/nuxt/plugin.js b/packages/core/cache/nuxt/plugin.js new file mode 100644 index 0000000000..1b27d76fe7 --- /dev/null +++ b/packages/core/cache/nuxt/plugin.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +export default (ctx) => { + const options = <%= serialize(options) %>; + + ctx.req.$vsfCache = { + ...options, + tagsSet: new Set() + }; +} diff --git a/packages/core/cache/package.json b/packages/core/cache/package.json new file mode 100644 index 0000000000..37c5e6ad27 --- /dev/null +++ b/packages/core/cache/package.json @@ -0,0 +1,37 @@ +{ + "name": "@vue-storefront/cache", + "version": "2.4.1", + "license": "MIT", + "main": "lib/index.cjs.js", + "module": "lib/index.es.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "rimraf lib && rollup -c", + "dev": "rollup -c -w", + "test": "jest", + "prepublish": "yarn build" + }, + "dependencies": { + "@nuxtjs/composition-api": "0.17.0", + "@vue-storefront/core": "~2.4.1" + }, + "publishConfig": { + "access": "public" + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib/**/*", + "defaultHandler/**/*", + "nuxt/**/*" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/vuestorefront/vue-storefront.git" + }, + "bugs": { + "url": "https://github.com/vuestorefront/vue-storefront/issues" + }, + "homepage": "https://github.com/vuestorefront/vue-storefront#readme" +} diff --git a/packages/core/cache/rollup.config.js b/packages/core/cache/rollup.config.js new file mode 100644 index 0000000000..372f5d0220 --- /dev/null +++ b/packages/core/cache/rollup.config.js @@ -0,0 +1,34 @@ +import pkg from './package.json'; +import typescript from 'rollup-plugin-typescript2'; +import { terser } from 'rollup-plugin-terser'; + +export function generateBaseConfig(pkg) { + return { + input: 'src/index.ts', + output: [ + { + file: pkg.main, + format: 'cjs', + sourcemap: true + }, + { + file: pkg.module, + format: 'es', + sourcemap: true + } + ], + external: [ + ...Object.keys(pkg.dependencies || {}) + ], + plugins: [ + typescript({ + // eslint-disable-next-line global-require + typescript: require('typescript'), + objectHashIgnoreUnknownHack: true + }), + terser() + ] + }; +} + +export default generateBaseConfig(pkg); diff --git a/packages/core/cache/src/composables/useCache.ts b/packages/core/cache/src/composables/useCache.ts new file mode 100644 index 0000000000..5ab95bf3e5 --- /dev/null +++ b/packages/core/cache/src/composables/useCache.ts @@ -0,0 +1,33 @@ +import { CacheTag, UseCache, SetTagsFn } from '..'; +import { useContext } from '@nuxtjs/composition-api'; + +export const useCache = (): UseCache => { + const { req }: any = useContext(); + + if (!req) { + return { + addTags: () => {}, + clearTags: () => {}, + getTags: () => [], + setTags: () => {} + }; + } + + const $vsfCache = req.$vsfCache; + const addTags = (tags: CacheTag[]) => tags.forEach(tag => $vsfCache.tagsSet.add(tag)); + const clearTags = () => $vsfCache.tagsSet.clear(); + const getTags = (): CacheTag[] => Array.from($vsfCache.tagsSet); + const setTags = (fn: SetTagsFn) => { + const tagsSet = $vsfCache.tagsSet; + const newTags = fn(Array.from(tagsSet)); + tagsSet.clear(); + newTags.forEach(tag => tagsSet.add(tag)); + }; + + return { + addTags, + clearTags, + getTags, + setTags + }; +}; diff --git a/packages/core/cache/src/index.ts b/packages/core/cache/src/index.ts new file mode 100644 index 0000000000..d2d5b4c178 --- /dev/null +++ b/packages/core/cache/src/index.ts @@ -0,0 +1,41 @@ +/** + * Core Vue Storefront 2 library for enabling Server Side Rendering (SSR) cache. + * + * @remarks + * The `@vue-storefront/cache` library is a Nuxt.js module, that is core piece required + * to enable Server Side Rendering (SSR) cache in Vue Storefront 2. It uses drivers + * to integration with specific caching solutions. + * + * @packageDocumentation + */ + +export { useCache } from './composables/useCache'; + +export enum CacheTagPrefix { + Product = 'P', + Category = 'C', + Attribute = 'A', + Cart = 'B', + Filter = 'F', + Block = 'O', + View = 'V' +} + +export interface CacheTag { + prefix: CacheTagPrefix | string; + value: string; +} + +export type CacheDriver = (options: any) => { + invoke: () => Promise; + invalidate: () => Promise; +} + +export type SetTagsFn = (tags: CacheTag[]) => CacheTag[]; + +export interface UseCache { + addTags(tags: CacheTag[]): void; + clearTags(): void; + getTags(): CacheTag[]; + setTags(callback: SetTagsFn): void; +} diff --git a/packages/core/cache/tsconfig.json b/packages/core/cache/tsconfig.json new file mode 100644 index 0000000000..e99af1d9fc --- /dev/null +++ b/packages/core/cache/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "es5", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "lib": ["es6", "es7", "dom"], + "strict": false, + "allowJs": true + }, + "exclude": ["node_modules", "**/*.spec.ts"], + "include": ["src"], +} diff --git a/packages/core/cli/.gitignore b/packages/core/cli/.gitignore new file mode 100644 index 0000000000..9049842c5d --- /dev/null +++ b/packages/core/cli/.gitignore @@ -0,0 +1,5 @@ +coverage +lib +node_modules +templates +test diff --git a/packages/core/cli/.npmignore b/packages/core/cli/.npmignore new file mode 100644 index 0000000000..7cafbdb49d --- /dev/null +++ b/packages/core/cli/.npmignore @@ -0,0 +1,2 @@ +coverage +node_modules \ No newline at end of file diff --git a/packages/core/cli/README.md b/packages/core/cli/README.md new file mode 100644 index 0000000000..302b1d04ff --- /dev/null +++ b/packages/core/cli/README.md @@ -0,0 +1,44 @@ +# Vue Storefront CLI +## Commands +### Init new project +``` +yarn cli init +yarn cli init +``` + +## Create new commands +To create a new command you have to create Typescript file in `src/commands` e.g. `src/commands/my-command.ts`. Then inside you should export the default function that takes one argument - an array of strings. Check `src/commands/init.ts` if you need a working example. Signature: +```ts +(args: string[]): void +``` + +`args` is an array of arguments that begins from the first argument after the command. E.g. for `yarn cli my-command a b c` it would be `['a', 'b', 'c']`. +`src/index.ts` always tries to call exported function from `src/commands/.ts`. + +## Module installed globally via yarn does not work +If you have used `yarn global add @vue-storefront/cli@next` and everything went well. Then while using `vsf` command you are getting `Command 'vsf' not found, did you mean:` - below I will show you solution for this problem. + +It means you do not have path to yarn global binaries in your `$PATH` system variable. You can check that with this command: +```sh +echo $PATH | grep $(yarn global bin); +``` + +If there is no output - it means that you have to add output of `yarn global bin` command to your `$PATH` variable. + +If you want to make it work only in current terminal session - use: +```sh +export PATH="$(yarn global bin):$PATH"; +``` + +If you want to make it work permanently, you have to add it to your `~/.profile` or `~/.bashrc` file. More information click [here](https://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux-unix) + +## How to generate and publish your template of integration? + +If you want to generate a template of your integration for VSF Next, you need to follow those steps: + +1. Install VSF cli globally (`yarn global add @vue-storefront/cli`) +2. In your terminal `cd` to the folder with your integration theme (it's usually in `packages/theme`). Make sure `_theme` folder is generated inside of it (you can generate it with `yarn dev`) +3. Execute command `vsf generate-template ` (e.g.: `vsf generate-template commercetools`) +5. Create a new Github repository. +6. `cd` into `` and push it to the github repository. +7. Test with `cd && yarn && yarn dev` diff --git a/packages/core/cli/__tests__/commands/generate-template.spec.ts b/packages/core/cli/__tests__/commands/generate-template.spec.ts new file mode 100644 index 0000000000..cbd447299b --- /dev/null +++ b/packages/core/cli/__tests__/commands/generate-template.spec.ts @@ -0,0 +1,112 @@ +import generateTemplate from '@vue-storefront/cli/src/commands/generate-template'; +import log from '../../src/utils/log'; +import * as fs from 'fs'; +import { createTemplate } from '../../src/scripts/createTemplate/createTemplate'; +import { vsfTuConfig } from '../../src/utils/themeUtilsConfigTemplate'; + +jest.mock('fs', () => ({ + existsSync: jest.fn(), + unlinkSync: jest.fn(), + appendFile: jest.fn() +})); + +jest.mock('@vue-storefront/cli/src/utils/log', () => ({ + info: jest.fn(), + success: jest.fn(), + error: jest.fn() +})); + +jest.mock('path', () => ({ + resolve: jest.fn((path) => path), + join: jest.fn((path, file) => `${path}/${file}`) +})); + +jest.mock( + '@vue-storefront/cli/src/scripts/createTemplate/createTemplate', + () => ({ + createTemplate: jest.fn() + }) +); + +jest.mock('@vue-storefront/cli/src/utils/themeUtilsConfigTemplate', () => ({ + vsfTuConfig: jest.fn() +})); + +describe('[@core/cli/src/commands] generate template', () => { + it('should log error when no args provided', async () => { + await generateTemplate([]); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); + + it('should remove vsfTuConfig if exist', async () => { + const templateName = 'Test'; + const existSyncMock = fs.existsSync as jest.Mock; + existSyncMock.mockImplementation(() => true); + const testTargetPath = 'test_path'; + const spy = jest.spyOn(process, 'cwd'); + spy.mockReturnValue(testTargetPath); + + await generateTemplate([templateName]); + spy.mockRestore(); + + expect(fs.unlinkSync).toHaveBeenCalledWith(expect.any(String)); + }); + + it('should run createTemplate function with parameters', async () => { + const templateName = 'Test'; + const existSyncMock = fs.existsSync as jest.Mock; + existSyncMock.mockImplementation(() => false); + const appendFileMock = (fs.appendFile as unknown) as jest.Mock; + appendFileMock.mockImplementation((path, data, callback) => callback()); + const testTargetPath = 'test_path'; + const spy = jest.spyOn(process, 'cwd'); + spy.mockReturnValue(testTargetPath); + + await generateTemplate([templateName]); + spy.mockRestore(); + + expect(createTemplate).toHaveBeenCalledWith({ + vsfTuConfigFilePath: expect.any(String), + generatedTemplatePath: expect.any(String) + }); + }); + + it('should throw error on callback', async () => { + const templateName = 'Test'; + const existSyncMock = fs.existsSync as jest.Mock; + existSyncMock.mockImplementation(() => false); + const appendFileMock = (fs.appendFile as unknown) as jest.Mock; + appendFileMock.mockImplementation((path, data, callback) => + callback(new Error()) + ); + const testTargetPath = 'test_path'; + const spy = jest.spyOn(process, 'cwd'); + spy.mockReturnValue(testTargetPath); + + await generateTemplate([templateName]); + spy.mockRestore(); + + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); + + it('should run createTemplate function with provided path', async () => { + const templateName = 'Test'; + const templatePath = '/home/test'; + const existSyncMock = fs.existsSync as jest.Mock; + existSyncMock.mockImplementation(() => false); + const appendFileMock = (fs.appendFile as unknown) as jest.Mock; + appendFileMock.mockImplementation((path, data, callback) => callback()); + const testTargetPath = 'test_path'; + const spy = jest.spyOn(process, 'cwd'); + spy.mockReturnValue(testTargetPath); + + await generateTemplate([templateName, templatePath]); + spy.mockRestore(); + + expect(vsfTuConfig).toHaveBeenCalledWith({ + outputPathName: templateName.toLowerCase(), + themePath: templatePath, + _themePath: `${templatePath}/_theme` + }); + }); +}); diff --git a/packages/core/cli/__tests__/commands/init.spec.ts b/packages/core/cli/__tests__/commands/init.spec.ts new file mode 100644 index 0000000000..992eb43d00 --- /dev/null +++ b/packages/core/cli/__tests__/commands/init.spec.ts @@ -0,0 +1,115 @@ +import initCommand, { + CUSTOM_TEMPLATE +} from '@vue-storefront/cli/src/commands/init'; + +const chosenIntegration = 'my-super-new-backend-ecommerce-system'; +const resolvedPathWithProjectName = '/home/abc/my-project'; +const projectName = 'AwesomeShop'; +import inquirer from 'inquirer'; +import createProject from '@vue-storefront/cli/src/scripts/createProject'; + +jest.mock('inquirer', () => ({ + prompt: jest.fn(() => + Promise.resolve({ + chosenIntegration, + typedProjectName: projectName + }) + ) +})); +jest.mock('@vue-storefront/cli/src/scripts/createProject', () => + jest.fn((data) => data) +); +jest.mock('@vue-storefront/cli/src/utils/getIntegrations', () => ({ + __esModule: true, + default: { + 'my-super-new-backend-ecommerce-system': '', + 'some-other-integration': '', + 'and-other': '' + } +})); +jest.mock('isomorphic-git', () => ({ + clone: jest.fn() +})); +jest.mock('path', () => ({ + resolve: () => resolvedPathWithProjectName, + join: jest.fn(), + isAbsolute: jest.fn() +})); + +describe('Command: init ', () => { + it('calls inquirer.prompt for projectName if no ', async () => { + await initCommand([null]); + expect(inquirer.prompt).toHaveBeenCalledWith([ + expect.objectContaining({ + type: 'input', + name: 'typedProjectName', + message: 'What\'s your project name?' + }) + ]); + }); + + it('init with project name && choose integration && run createProject with proper arguments', async () => { + jest.clearAllMocks(); + const testTargetPath = 'test_path'; + const spy = jest.spyOn(process, 'cwd'); + spy.mockReturnValue(testTargetPath); + + await initCommand([projectName]); + spy.mockRestore(); + + expect(createProject).toHaveBeenCalledWith({ + projectName: projectName, + targetPath: testTargetPath, + repositoryLink: '' + }); + }); + + it('init with project name && choose custom integration && run createProject with proper arguments', async () => { + jest.clearAllMocks(); + const testTargetPath = 'test_path'; + const spy = jest.spyOn(process, 'cwd'); + spy.mockReturnValue(testTargetPath); + + inquirer.prompt.mockImplementation( + jest.fn(() => + Promise.resolve({ + chosenIntegration: CUSTOM_TEMPLATE, + otherIntegrationGitLink: 'http://test.com', + typedProjectName: projectName + }) + ) + ); + await initCommand([projectName]); + spy.mockRestore(); + + expect(createProject).toHaveBeenCalledWith({ + projectName: projectName, + targetPath: testTargetPath, + repositoryLink: 'http://test.com' + }); + }); + + it('proper validator', async () => { + let validatorForEmptyString = null; + let validatorForNotEmptyString = null; + + inquirer.prompt.mockImplementation( + jest.fn((arg) => { + if (arg[0].validate) { + validatorForEmptyString = arg[0].validate(''); + validatorForNotEmptyString = arg[0].validate('abc'); + } + + return Promise.resolve({ + chosenIntegration, + typedProjectName: projectName + }); + }) + ); + + await initCommand([null]); + + expect(typeof validatorForEmptyString).toBe('string'); + expect(validatorForNotEmptyString).toBe(true); + }); +}); diff --git a/packages/core/cli/__tests__/index.spec.ts b/packages/core/cli/__tests__/index.spec.ts new file mode 100644 index 0000000000..e33993e70f --- /dev/null +++ b/packages/core/cli/__tests__/index.spec.ts @@ -0,0 +1,30 @@ +import { cli } from '@vue-storefront/cli/src/index'; +import log from '@vue-storefront/cli/src/utils/log'; +import init from '../src/commands/init'; + +jest.mock('@vue-storefront/cli/src/utils/log', () => ({ + error: jest.fn() +})); +jest.mock('../src/commands/init.ts', () => ({ + default: jest.fn() +})); + +describe('CLI', () => { + it('stops if not command provided', () => { + cli(['', '', null]); + expect(log.error).toHaveBeenCalledWith('Provide command'); + }); + + it('stops if not existing command provided', () => { + cli(['', '', 'asdasdasd']); + expect(log.error).toHaveBeenCalledWith('Bad command'); + }); + + it('loads proper script and calls default function', () => { + const commandName = 'init'; + const commandArgs = [1, 51]; + + cli(['', '', commandName, ...commandArgs]); + expect((init as any).default).toHaveBeenCalledWith(commandArgs); + }); +}); diff --git a/packages/core/cli/__tests__/mock.ts b/packages/core/cli/__tests__/mock.ts new file mode 100644 index 0000000000..a17aed705e --- /dev/null +++ b/packages/core/cli/__tests__/mock.ts @@ -0,0 +1,4 @@ +export const integrationsMock: { [key: string]: string } = { + Commercetools: '', + Shopify: '' +}; diff --git a/packages/core/cli/__tests__/scripts/createProject/index.spec.ts b/packages/core/cli/__tests__/scripts/createProject/index.spec.ts new file mode 100644 index 0000000000..4143674bcd --- /dev/null +++ b/packages/core/cli/__tests__/scripts/createProject/index.spec.ts @@ -0,0 +1,51 @@ +import createProject from '@vue-storefront/cli/src/scripts/createProject'; +import log from '@vue-storefront/cli/src/utils/log'; +const git = require('isomorphic-git'); +const targetPath = 'vsf-new-project'; + +jest.mock('@vue-storefront/cli/src/utils/log', () => ({ + info: jest.fn(), + success: jest.fn(), + error: jest.fn() +})); + +jest.mock('path', () => ({ + join: jest.fn(() => targetPath), + isAbsolute: jest.fn(() => false) +})); + +jest.mock('isomorphic-git', () => ({ + clone: jest.fn() +})); +jest.mock('rimraf', () => ({ + sync: jest.fn() +})); + +describe('[vsf-next-cli] createProject', () => { + it('successful repository clone', async () => { + git.clone.mockImplementation(() => true); + await createProject({ + projectName: 'MyProject', + targetPath: __dirname, + repositoryLink: '' + }); + expect(log.success).toHaveBeenCalledWith(expect.any(String)); + }); + + it('fail repository clone', async () => { + git.clone.mockImplementation(() => { + throw new Error(); + }); + const testTargetPath = 'test_path'; + const spy = jest.spyOn(process, 'cwd'); + spy.mockReturnValue(testTargetPath); + + await createProject({ + projectName: 'MyProject', + targetPath: testTargetPath, + repositoryLink: '' + }); + spy.mockRestore(); + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); +}); diff --git a/packages/core/cli/__tests__/scripts/createProject/processMagicComments.spec.ts b/packages/core/cli/__tests__/scripts/createProject/processMagicComments.spec.ts new file mode 100644 index 0000000000..2bfcff37a3 --- /dev/null +++ b/packages/core/cli/__tests__/scripts/createProject/processMagicComments.spec.ts @@ -0,0 +1,225 @@ +import processMagicComments from '@vue-storefront/cli/src/scripts/createProject/processMagicComments'; + +const magicCommentsFile = ` +import webpack from 'webpack'; +import { config } from './plugins/commercetools-config.js'; + +const localeNames = config.locales.map(l => ({ code: l.name, file: 'abc.js', iso: l.name })); + +export default { + mode: 'universal', + server: { + port: 3000, + host: '0.0.0.0' + }, + head: { + title: process.env.npm_package_name || '', + meta: [ + { charset: 'utf-8' }, + { name: 'viewport', + content: 'width=device-width, initial-scale=1' }, + { hid: 'description', + name: 'description', + content: process.env.npm_package_description || '' } + ], + link: [ + { rel: 'icon', + type: 'image/x-icon', + href: '/favicon.ico' } + ] + }, + loading: { color: '#fff' }, + plugins: [ + './plugins/commercetools.js' + ], + router: { + middleware: ['commercetools', 'checkout'] + }, + buildModules: [ + // to core + '@nuxt/typescript-build', + ['@vue-storefront/nuxt', { + // @core-development-only-start + coreDevelopment: true, + // @core-development-only-end + useRawSource: { + dev: [ + '@vue-storefront/commercetools', + '@vue-storefront/core' + ], + prod: [ + '@vue-storefront/commercetools', + '@vue-storefront/core' + ] + } + }], + // @core-development-only-start + ['@vue-storefront/nuxt-theme', { + apiClient: '@vue-storefront/commercetools-api', + composables: '@vue-storefront/commercetools' + }] + // @core-development-only-end + ], + modules: [ + 'nuxt-i18n', + 'cookie-universal-nuxt', + 'vue-scrollto/nuxt' + ], + build: { + transpile: [ + 'vee-validate/dist/rules' + ], + plugins: [ + new webpack.DefinePlugin({ + 'process.VERSION': JSON.stringify({ + // eslint-disable-next-line global-require + version: require('./package.json').version, + lastCommit: process.env.LAST_COMMIT || '' + }) + }) + ] + }, + i18n: { + locales: localeNames, + defaultLocale: localeNames[0].code, + lazy: true, + seo: true, + langDir: 'lang/', + vueI18n: { + fallbackLocale: localeNames[0].code + }, + detectBrowserLanguage: { + cookieKey: config.cookies.localeCookieName, + alwaysRedirect: true + } + } +}; + +`; + +const projectOnlyCommmentsFile = ` +/* project-only-start + ['@vue-storefront/nuxt-theme'], + project-only-end */`; + +import { writeFileSync, readFileSync } from 'fs'; + +jest.mock('fs', () => ({ + readFileSync: jest.fn(() => magicCommentsFile), + writeFileSync: jest.fn() +})); + +describe('[vsf-next-cli] processMagicComments', () => { + it('removes magic comments from the file', async () => { + + const absoluteFilePath = 'nuxt.config.js'; + + // I removed magic comments in the const below + const expectedFileContent = ` +import webpack from 'webpack'; +import { config } from './plugins/commercetools-config.js'; + +const localeNames = config.locales.map(l => ({ code: l.name, file: 'abc.js', iso: l.name })); + +export default { + mode: 'universal', + server: { + port: 3000, + host: '0.0.0.0' + }, + head: { + title: process.env.npm_package_name || '', + meta: [ + { charset: 'utf-8' }, + { name: 'viewport', + content: 'width=device-width, initial-scale=1' }, + { hid: 'description', + name: 'description', + content: process.env.npm_package_description || '' } + ], + link: [ + { rel: 'icon', + type: 'image/x-icon', + href: '/favicon.ico' } + ] + }, + loading: { color: '#fff' }, + plugins: [ + './plugins/commercetools.js' + ], + router: { + middleware: ['commercetools', 'checkout'] + }, + buildModules: [ + // to core + '@nuxt/typescript-build', + ['@vue-storefront/nuxt', { + useRawSource: { + dev: [ + '@vue-storefront/commercetools', + '@vue-storefront/core' + ], + prod: [ + '@vue-storefront/commercetools', + '@vue-storefront/core' + ] + } + }], + ], + modules: [ + 'nuxt-i18n', + 'cookie-universal-nuxt', + 'vue-scrollto/nuxt' + ], + build: { + transpile: [ + 'vee-validate/dist/rules' + ], + plugins: [ + new webpack.DefinePlugin({ + 'process.VERSION': JSON.stringify({ + // eslint-disable-next-line global-require + version: require('./package.json').version, + lastCommit: process.env.LAST_COMMIT || '' + }) + }) + ] + }, + i18n: { + locales: localeNames, + defaultLocale: localeNames[0].code, + lazy: true, + seo: true, + langDir: 'lang/', + vueI18n: { + fallbackLocale: localeNames[0].code + }, + detectBrowserLanguage: { + cookieKey: config.cookies.localeCookieName, + alwaysRedirect: true + } + } +}; + +`; + await processMagicComments(absoluteFilePath); + expect(writeFileSync).toHaveBeenCalledWith(absoluteFilePath, expectedFileContent); + + }); + + it('uncomments parts inside "project only" comments', async () => { + + const absoluteFilePath = 'nuxt.config.js'; + (readFileSync as jest.Mock).mockClear(); + (readFileSync as jest.Mock).mockImplementation(() => projectOnlyCommmentsFile); + + // I removed magic comments in the const below + const expectedFileContent = ` + ['@vue-storefront/nuxt-theme'],`; + + await processMagicComments(absoluteFilePath); + + expect(writeFileSync).toHaveBeenCalledWith(absoluteFilePath, expectedFileContent); + + }); +}); diff --git a/packages/core/cli/__tests__/scripts/createProject/updatePackageJson.spec.ts b/packages/core/cli/__tests__/scripts/createProject/updatePackageJson.spec.ts new file mode 100644 index 0000000000..a1c8bca090 --- /dev/null +++ b/packages/core/cli/__tests__/scripts/createProject/updatePackageJson.spec.ts @@ -0,0 +1,33 @@ +import updatePackageJson from '@vue-storefront/cli/src/scripts/createProject/updatePackageJson'; + +const defaultFile = `{ + "someParam": "someValue", + "name": "@vue-storefront/about-you-theme", + "_toRem": "1", + "_toRem2": "2" +}`; + +const projectName = 'mySuperProject'; + +const expectedFile = `{ + "someParam": "someValue", + "name": "${projectName}" +}`; + +import { writeFileSync } from 'fs'; + +jest.mock('fs', () => ({ + readFileSync: jest.fn(() => defaultFile), + writeFileSync: jest.fn() +})); + +describe('[vsf-next-cli] updatePackageJson', () => { + it('updates name in the JSON file and removes keys that start with _', async () => { + + const absoluteFilePath = 'package.json'; + await updatePackageJson(absoluteFilePath, projectName); + expect(writeFileSync).toHaveBeenCalledWith(absoluteFilePath, expectedFile); + + }); + +}); diff --git a/packages/core/cli/__tests__/scripts/createTemplate/createTemplate.spec.ts b/packages/core/cli/__tests__/scripts/createTemplate/createTemplate.spec.ts new file mode 100644 index 0000000000..7f117301e2 --- /dev/null +++ b/packages/core/cli/__tests__/scripts/createTemplate/createTemplate.spec.ts @@ -0,0 +1,51 @@ +import { createTemplate } from '../../../src/scripts/createTemplate/createTemplate'; +import { processTemplate } from '../../../src/scripts/createTemplate/processTemplate'; +import log from '../../../src/utils/log'; +jest.mock('@vue-storefront/cli/src/utils/log', () => ({ + success: jest.fn((text: string) => text), + error: jest.fn((text: string) => text) +})); +jest.mock('../../../src/scripts/createTemplate/processTemplate', () => ({ + processTemplate: jest.fn() +})); + +const vsfTuConfigFilePathMock = '/test/config.ts'; +const generatedTemplatePathMock = '/generated_test'; +describe('[@core/cli] - create template', () => { + it('sets props correctly', async () => { + const processTemplateMock = processTemplate as jest.Mock; + processTemplateMock.mockImplementation((data) => Promise.resolve(data)); + await createTemplate({ + vsfTuConfigFilePath: vsfTuConfigFilePathMock, + generatedTemplatePath: generatedTemplatePathMock + }); + + expect(processTemplate).toHaveBeenCalledWith({ + vsfTuConfigFilePath: vsfTuConfigFilePathMock, + generatedTemplatePath: generatedTemplatePathMock + }); + }); + + it('Log successful generation', async () => { + const processTemplateMock = processTemplate as jest.Mock; + processTemplateMock.mockImplementation((data) => Promise.resolve(data)); + await createTemplate({ + vsfTuConfigFilePath: vsfTuConfigFilePathMock, + generatedTemplatePath: generatedTemplatePathMock + }); + + expect(log.success).toHaveBeenCalledWith(expect.any(String)); + }); + + it('should log error when processTemplate fail', async () => { + const processTemplateMock = processTemplate as jest.Mock; + processTemplateMock.mockImplementation(() => Promise.reject(new Error())); + + await createTemplate({ + vsfTuConfigFilePath: vsfTuConfigFilePathMock, + generatedTemplatePath: generatedTemplatePathMock + }); + + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); +}); diff --git a/packages/core/cli/__tests__/scripts/createTemplate/processMagicCommentsInNuxtConfig.spec.ts b/packages/core/cli/__tests__/scripts/createTemplate/processMagicCommentsInNuxtConfig.spec.ts new file mode 100644 index 0000000000..26f080ec06 --- /dev/null +++ b/packages/core/cli/__tests__/scripts/createTemplate/processMagicCommentsInNuxtConfig.spec.ts @@ -0,0 +1,49 @@ +import processMagicComments from '../../../src/scripts/createProject/processMagicComments'; +import { processMagicCommentsInNuxtConfig } from '../../../src/scripts/createTemplate/processMagicCommentsInNuxtConfig'; +import path from 'path'; +import log from '../../../src/utils/log'; +jest.mock('path', () => ({ + join: jest.fn((path, fileName) => `${path}/${fileName}`) +})); +jest.mock( + '@vue-storefront/cli/src/scripts/createProject/processMagicComments', + () => jest.fn() +); +jest.mock('@vue-storefront/cli/src/utils/log', () => ({ + __esModule: true, + default: { + error: jest.fn((text: string) => text) + } +})); + +describe('[@core/cli/scripts] process magic comments in nuxt config', () => { + it('should create absolute path to nuxt.config.js file', async () => { + const processMagicCommentsMock = processMagicComments as jest.Mock; + processMagicCommentsMock.mockImplementation(() => ({ + __esModule: true, + default: jest.fn(async (nuxtConfigPath) => nuxtConfigPath) + })); + const generatedTemplatePath = '/home/root/test'; + const nuxtConfigFile = path.join(generatedTemplatePath, 'nuxt.config.js'); + + await processMagicCommentsInNuxtConfig(generatedTemplatePath); + + expect(path.join).toHaveBeenCalledWith( + generatedTemplatePath, + 'nuxt.config.js' + ); + expect(processMagicComments).toHaveBeenCalledWith(nuxtConfigFile); + }); + + it('should log error when processMagicComments fail', async () => { + const processMagicCommentsMock = processMagicComments as jest.Mock; + processMagicCommentsMock.mockImplementation(() => { + throw new Error(); + }); + const generatedTemplatePath = '/home/root/test'; + + await processMagicCommentsInNuxtConfig(generatedTemplatePath); + + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); +}); diff --git a/packages/core/cli/__tests__/scripts/createTemplate/processTemplate.spec.ts b/packages/core/cli/__tests__/scripts/createTemplate/processTemplate.spec.ts new file mode 100644 index 0000000000..fae6b14a4a --- /dev/null +++ b/packages/core/cli/__tests__/scripts/createTemplate/processTemplate.spec.ts @@ -0,0 +1,84 @@ +import log from '@vue-storefront/cli/src/utils/log'; +import { processMagicCommentsInNuxtConfig } from '@vue-storefront/cli/src/scripts/createTemplate/processMagicCommentsInNuxtConfig'; +import { removeFolder } from '@vue-storefront/cli/src/utils/removeFolder'; +import { processTemplate } from '@vue-storefront/cli/src/scripts/createTemplate/processTemplate'; +const execa = require('execa'); +const fs = require('fs'); + +jest.mock('@vue-storefront/cli/src/utils/log', () => ({ + __esModule: true, + default: { + error: jest.fn((text: string) => text) + } +})); + +jest.mock( + '@vue-storefront/cli/src/scripts/createTemplate/processMagicCommentsInNuxtConfig', + () => ({ + processMagicCommentsInNuxtConfig: jest.fn() + }) +); + +jest.mock('fs', () => ({ + unlinkSync: jest.fn() +})); + +jest.mock('execa', () => jest.fn()); + +jest.mock('@vue-storefront/cli/src/utils/removeFolder', () => ({ + removeFolder: jest.fn() +})); + +describe('[@core/cli/scripts] Process Template', () => { + it('should invoke all functions and pass happy way', async () => { + const vsfTuConfigFilePath = '/home/root/test/nuxt.config.js'; + const generatedTemplatePath = '/home/root/test'; + + await processTemplate({ vsfTuConfigFilePath, generatedTemplatePath }); + + expect(fs.unlinkSync).toHaveBeenCalledWith(vsfTuConfigFilePath); + expect(execa).toHaveBeenCalledWith(expect.any(String)); + expect(processMagicCommentsInNuxtConfig).toHaveBeenCalledWith( + generatedTemplatePath + ); + }); + + it('should log when vsf-tu script fail', async () => { + const exacaMock = execa as jest.Mock; + exacaMock.mockImplementation(() => { + throw new Error(); + }); + const vsfTuConfigFilePath = '/home/root/test/nuxt.config.js'; + const generatedTemplatePath = '/home/root/test'; + + await processTemplate({ vsfTuConfigFilePath, generatedTemplatePath }); + + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); + + it('should log when removing config file fail', async () => { + const removeFile = fs.unlinkSync as jest.Mock; + removeFile.mockImplementation(() => { + throw new Error(); + }); + const vsfTuConfigFilePath = '/home/root/test/nuxt.config.js'; + const generatedTemplatePath = '/home/root/test'; + + await processTemplate({ vsfTuConfigFilePath, generatedTemplatePath }); + + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); + + it('should log when removeFolder function fail', async () => { + const removeFolderMock = removeFolder as jest.Mock; + removeFolderMock.mockImplementation(() => { + throw new Error(); + }); + const vsfTuConfigFilePath = '/home/root/test/nuxt.config.js'; + const generatedTemplatePath = '/home/root/test'; + + await processTemplate({ vsfTuConfigFilePath, generatedTemplatePath }); + + expect(log.error).toHaveBeenCalledWith(expect.any(String)); + }); +}); diff --git a/packages/core/cli/__tests__/utils/getIntegrations.spec.ts b/packages/core/cli/__tests__/utils/getIntegrations.spec.ts new file mode 100644 index 0000000000..f52d004da3 --- /dev/null +++ b/packages/core/cli/__tests__/utils/getIntegrations.spec.ts @@ -0,0 +1,7 @@ +import integrations from '@vue-storefront/cli/src/utils/getIntegrations'; + +describe('[@core/cli/src/utils] Integrations', () => { + it('integrations should be an object', () => { + expect(integrations).toEqual(expect.any(Object)); + }); +}); diff --git a/packages/core/cli/__tests__/utils/themeUtilsConfigTemplate.spec.ts b/packages/core/cli/__tests__/utils/themeUtilsConfigTemplate.spec.ts new file mode 100644 index 0000000000..4d4b1bb8d1 --- /dev/null +++ b/packages/core/cli/__tests__/utils/themeUtilsConfigTemplate.spec.ts @@ -0,0 +1,14 @@ +import { vsfTuConfig } from '../../src/utils/themeUtilsConfigTemplate'; + +describe('[@core/cli/src/utils] VSF theme utils config', () => { + it('should generate string for config file', () => { + const outputPathName = 'home/test'; + const themePath = 'home/theme'; + const _themePath = 'home/theme/_theme'; + const result = vsfTuConfig({ outputPathName, themePath, _themePath }); + + expect(result).toMatch( + /('home\/test'|'home\/theme'|'home\/theme\/_theme')/i + ); + }); +}); diff --git a/packages/core/cli/jest.config.js b/packages/core/cli/jest.config.js new file mode 100644 index 0000000000..2a889cfae5 --- /dev/null +++ b/packages/core/cli/jest.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('./../../jest.base.config'); + +module.exports = baseConfig; diff --git a/packages/core/cli/package.json b/packages/core/cli/package.json new file mode 100644 index 0000000000..16580b37ec --- /dev/null +++ b/packages/core/cli/package.json @@ -0,0 +1,26 @@ +{ + "name": "@vue-storefront/cli", + "version": "2.4.0", + "description": "", + "main": "./lib/index.js", + "bin": { + "vsf": "./lib/index.js" + }, + "scripts": { + "test": "cross-env APP_ENV=test jest --rootDir .", + "build": "rm -rf ./lib; tsc", + "cli": "ts-node ./src/index.ts", + "prepublish": "yarn build" + }, + "dependencies": { + "@vue-storefront/theme-utilities": "^0.1.3", + "chokidar": "^3.3.1", + "consola": "^2.10.1", + "execa": "^5.0.0", + "inquirer": "^7.3.3", + "isomorphic-git": "^1.9.2", + "rimraf": "^3.0.2" + }, + "author": "Filip Jędrasik", + "license": "MIT" +} diff --git a/packages/core/cli/src/commands/generate-template.ts b/packages/core/cli/src/commands/generate-template.ts new file mode 100644 index 0000000000..363fe895ed --- /dev/null +++ b/packages/core/cli/src/commands/generate-template.ts @@ -0,0 +1,49 @@ +import { vsfTuConfig } from '../utils/themeUtilsConfigTemplate'; +import { createTemplate } from '../scripts/createTemplate/createTemplate'; +import log from '../utils/log'; +const process = require('process'); +const path = require('path'); +const fs = require('fs'); + +export default async (args) => { + if (!args[0]) { + log.error('Error: No output folder provided'); + process.exitCode = 1; + return; + } + + const outputPathName: string = args[0].toLowerCase(); + const integrationThemePath: string | undefined = args[1] + ? path.resolve(args[1]) + : process.cwd(); + const vsfTuConfigFileName = 'theme-utils.config.js'; + const vsfTuConfigFilePath = path.join(process.cwd(), vsfTuConfigFileName); + const generatedTemplatePath = path.join(process.cwd(), outputPathName); + + const crateTemplateCallback = async (err) => { + try { + if (err) throw err; + await createTemplate({ vsfTuConfigFilePath, generatedTemplatePath }); + } catch (error) { + log.error('Error during VSF theme utils config file creation'); + } + }; + + const createVsfTuConfigFile = () => { + if (fs.existsSync(vsfTuConfigFilePath)) { + fs.unlinkSync(vsfTuConfigFilePath); + } + + fs.appendFile( + vsfTuConfigFilePath, + vsfTuConfig({ + outputPathName, + themePath: integrationThemePath, + _themePath: path.join(integrationThemePath, '_theme') + }), + async (err) => await crateTemplateCallback(err) + ); + }; + + createVsfTuConfigFile(); +}; diff --git a/packages/core/cli/src/commands/init.ts b/packages/core/cli/src/commands/init.ts new file mode 100644 index 0000000000..c11675f012 --- /dev/null +++ b/packages/core/cli/src/commands/init.ts @@ -0,0 +1,47 @@ +import integrations from '../utils/getIntegrations'; +import inquirer from 'inquirer'; +import createProject from '../scripts/createProject'; +import { customTemplateStrategy } from '../scripts/initStrategy/customTemplateStrategy'; +export const CUSTOM_TEMPLATE = 'Custom template from Github'; +export default async (args) => { + const cwd = process.cwd(); + const integrationsNames = Object.keys(integrations); + let projectName = args.join('-') || args[0]; + + if (!projectName) { + const { typedProjectName } = await inquirer.prompt([ + { + type: 'input', + name: 'typedProjectName', + message: 'What\'s your project name?', + validate(value) { + if (value.trim().length > 0) { + return true; + } + return 'Please provide longer name'; + } + } + ]); + projectName = typedProjectName.split(' ').join('-') || typedProjectName; + } + + const { chosenIntegration } = await inquirer.prompt([ + { + type: 'list', + name: 'chosenIntegration', + message: 'Choose integration', + choices: [...integrationsNames, CUSTOM_TEMPLATE] + } + ]); + + if (chosenIntegration !== CUSTOM_TEMPLATE) { + await createProject({ + projectName: projectName, + targetPath: cwd, + repositoryLink: integrations[chosenIntegration] + }); + return; + } + + await customTemplateStrategy({ projectName, targetPath: cwd }); +}; diff --git a/packages/core/cli/src/index.ts b/packages/core/cli/src/index.ts new file mode 100644 index 0000000000..312816569e --- /dev/null +++ b/packages/core/cli/src/index.ts @@ -0,0 +1,27 @@ +#!/usr/bin/env node +import log from './utils/log'; +import path from 'path'; + +export const cli = async (args) => { + const [command] = args.slice(2); + if (!command) { + log.error('Provide command'); + return; + } + + try { + // eslint-disable-next-line global-require + const commandFn = require(path.join(__dirname, `./commands/${command}.ts`)); + return commandFn.default(args.slice(3)); + } catch (err) { + try { + // eslint-disable-next-line global-require + const commandFn = require(path.join(__dirname, `./commands/${command}.js`)); + return commandFn.default(args.slice(3)); + } catch (err) { + log.error('Bad command'); + } + } +}; + +cli(process.argv); diff --git a/packages/core/cli/src/scripts/createProject/index.ts b/packages/core/cli/src/scripts/createProject/index.ts new file mode 100644 index 0000000000..c81bfc75dd --- /dev/null +++ b/packages/core/cli/src/scripts/createProject/index.ts @@ -0,0 +1,36 @@ +import fs from 'fs'; +import path from 'path'; +import git from 'isomorphic-git'; +import http from 'isomorphic-git/http/node'; +import log from '../../utils/log'; +import { removeFolder } from '../../utils/removeFolder'; +interface ICreateProjectProps { + projectName: string; + targetPath: string; + repositoryLink: string; +} + +async function createProject({ + projectName, + targetPath, + repositoryLink +}: ICreateProjectProps): Promise { + const templatePath = path.join(targetPath, projectName); + try { + await git.clone({ + fs, + http, + dir: templatePath, + url: repositoryLink, + singleBranch: true, + depth: 1 + }); + removeFolder(templatePath, '.git'); + log.success('Project template initialized successfully. '); + log.info('Check out https://docs.vuestorefront.io/v2'); + } catch (error) { + log.error('Unable to get integration template from git repository'); + } +} + +export default createProject; diff --git a/packages/core/cli/src/scripts/createProject/processMagicComments.ts b/packages/core/cli/src/scripts/createProject/processMagicComments.ts new file mode 100644 index 0000000000..282eec7fed --- /dev/null +++ b/packages/core/cli/src/scripts/createProject/processMagicComments.ts @@ -0,0 +1,29 @@ +import fs from 'fs'; + +const removeMagicComments = async (absoluteFilePath: string): Promise => { + const removeDevMagicComment = (source: string): string => source.replace(/\s+(\/\/ @core-development-only-start)(.*?)(\/\/ @core-development-only-end)/sg, ''); + const fileContent = fs.readFileSync(absoluteFilePath, { encoding: 'utf8' }); + + return fs.writeFileSync( + absoluteFilePath, + removeDevMagicComment(fileContent) + ); +}; + +const uncommentProjectOnly = async (absoluteFilePath: string): Promise => { + const uncommentProjectOnly = (source: string): string => source.replace( + /\s+(\/\* project-only-start)(.*?)\s+(project-only-end \*\/)/sg, + (_, commentStart, partToUncomment) => partToUncomment + ); + const fileContent = fs.readFileSync(absoluteFilePath, { encoding: 'utf8' }); + + return fs.writeFileSync( + absoluteFilePath, + uncommentProjectOnly(fileContent) + ); +}; + +export default async (nuxtConfigPath: string) => { + await removeMagicComments(nuxtConfigPath); + await uncommentProjectOnly(nuxtConfigPath); +}; diff --git a/packages/core/cli/src/scripts/createProject/updatePackageJson.ts b/packages/core/cli/src/scripts/createProject/updatePackageJson.ts new file mode 100644 index 0000000000..796abe04b5 --- /dev/null +++ b/packages/core/cli/src/scripts/createProject/updatePackageJson.ts @@ -0,0 +1,15 @@ +import fs from 'fs'; + +export default async (packageJsonPath: string, projectName: string) => { + const fileContent = JSON.parse(fs.readFileSync(packageJsonPath, { encoding: 'utf8' })); + fileContent.name = projectName; + for (const key of Object.keys(fileContent)) { + if (key.startsWith('_')) { + delete fileContent[key]; + } + } + return fs.writeFileSync( + packageJsonPath, + JSON.stringify(fileContent, null, ' ') + ); +}; diff --git a/packages/core/cli/src/scripts/createTemplate/createTemplate.ts b/packages/core/cli/src/scripts/createTemplate/createTemplate.ts new file mode 100644 index 0000000000..784639e9e9 --- /dev/null +++ b/packages/core/cli/src/scripts/createTemplate/createTemplate.ts @@ -0,0 +1,20 @@ +import { processTemplate } from './processTemplate'; +import log from '../../utils/log'; + +interface ICreateTemplateProps { + vsfTuConfigFilePath: string; + generatedTemplatePath: string; +} + +export const createTemplate = async ({ + vsfTuConfigFilePath, + generatedTemplatePath +}: ICreateTemplateProps) => { + try { + await processTemplate({ vsfTuConfigFilePath, generatedTemplatePath }); + log.success('Template generated'); + } catch (error) { + log.error('Template not generated'); + process.exitCode = 1; + } +}; diff --git a/packages/core/cli/src/scripts/createTemplate/processMagicCommentsInNuxtConfig.ts b/packages/core/cli/src/scripts/createTemplate/processMagicCommentsInNuxtConfig.ts new file mode 100644 index 0000000000..cafd6a81b9 --- /dev/null +++ b/packages/core/cli/src/scripts/createTemplate/processMagicCommentsInNuxtConfig.ts @@ -0,0 +1,16 @@ +import processMagicComments from '../createProject/processMagicComments'; +import log from '../../utils/log'; +const path = require('path'); + +export const processMagicCommentsInNuxtConfig = async ( + generatedTemplatePath: string +) => { + try { + await processMagicComments( + path.join(generatedTemplatePath, 'nuxt.config.js') + ); + } catch (error) { + log.error('No nuxt.config.js has been found in integration template'); + process.exitCode = 1; + } +}; diff --git a/packages/core/cli/src/scripts/createTemplate/processTemplate.ts b/packages/core/cli/src/scripts/createTemplate/processTemplate.ts new file mode 100644 index 0000000000..973fe3ce60 --- /dev/null +++ b/packages/core/cli/src/scripts/createTemplate/processTemplate.ts @@ -0,0 +1,37 @@ +import log from '../../utils/log'; +import { processMagicCommentsInNuxtConfig } from './processMagicCommentsInNuxtConfig'; +import { removeFolder } from '../../utils/removeFolder'; +const execa = require('execa'); +const fs = require('fs'); + +interface IProcessTemplateProps { + vsfTuConfigFilePath: string; + generatedTemplatePath: string; +} + +export const processTemplate = async ({ + vsfTuConfigFilePath, + generatedTemplatePath +}: IProcessTemplateProps) => { + try { + await execa('vsf-tu'); + } catch (error) { + log.error('Unprocessable template'); + process.exitCode = 1; + } + + try { + const removeVsfTuConfigFile = () => fs.unlinkSync(vsfTuConfigFilePath); + removeVsfTuConfigFile(); + } catch (error) { + log.error('Can\'t remove VSF-TU config file'); + } + + await processMagicCommentsInNuxtConfig(generatedTemplatePath); + + try { + removeFolder(generatedTemplatePath, '_theme'); + } catch (error) { + log.error('Fail to remove _theme folder'); + } +}; diff --git a/packages/core/cli/src/scripts/initStrategy/customTemplateStrategy.ts b/packages/core/cli/src/scripts/initStrategy/customTemplateStrategy.ts new file mode 100644 index 0000000000..0ffa392419 --- /dev/null +++ b/packages/core/cli/src/scripts/initStrategy/customTemplateStrategy.ts @@ -0,0 +1,34 @@ +import createProject from '../createProject'; +import inquirer from 'inquirer'; + +interface ICustomTemplateStrategyParams { + projectName: string; + targetPath: string; +} + +export async function customTemplateStrategy({ + projectName, + targetPath +}: ICustomTemplateStrategyParams) { + const { otherIntegrationGitLink } = await inquirer.prompt([ + { + type: 'input', + name: 'otherIntegrationGitLink', + message: 'Provide integration repository git link via https:', + validate(value) { + /* eslint-disable-next-line no-useless-escape*/ + const gitLinkRegex = /https?:(\/\/)?(.*?)(\.git)(\/?|\#[-\d\w._]+?)$/; + if (value.trim().length === 0 || !gitLinkRegex.test(value)) { + return 'Please provide git repository https link'; + } + return true; + } + } + ]); + + await createProject({ + projectName: projectName, + targetPath: targetPath, + repositoryLink: otherIntegrationGitLink + }); +} diff --git a/packages/core/cli/src/utils/getIntegrations.ts b/packages/core/cli/src/utils/getIntegrations.ts new file mode 100644 index 0000000000..5278aab881 --- /dev/null +++ b/packages/core/cli/src/utils/getIntegrations.ts @@ -0,0 +1,8 @@ +export default { + Commercetools: 'https://github.com/vuestorefront/template-commercetools.git', + 'Salesforce Commerce Cloud (beta)': 'https://github.com/ForkPoint/vsf-sfcc-template.git', + Shopify: 'https://github.com/vuestorefront/template-shopify.git', + 'Spryker (beta)': 'https://github.com/spryker/vsf-theme.git', + 'Magento 2 (beta)': 'https://github.com/vuestorefront/template-magento.git', + 'Vendure (beta)': 'https://github.com/vuestorefront/template-vendure.git' +}; diff --git a/packages/core/cli/src/utils/log.ts b/packages/core/cli/src/utils/log.ts new file mode 100644 index 0000000000..81f357b5a9 --- /dev/null +++ b/packages/core/cli/src/utils/log.ts @@ -0,0 +1,10 @@ +/* istanbul ignore file */ +import consola from 'consola'; +import chalk from 'chalk'; + +export default { + info: (message) => consola.info(chalk.bold('VSF'), message), + success: (message) => consola.success(chalk.bold('VSF'), message), + warning: (message) => consola.warn(chalk.bold('VSF'), message), + error: (message) => consola.error(chalk.bold('VSF'), message) +}; diff --git a/packages/core/cli/src/utils/removeFolder.ts b/packages/core/cli/src/utils/removeFolder.ts new file mode 100644 index 0000000000..f305497ef6 --- /dev/null +++ b/packages/core/cli/src/utils/removeFolder.ts @@ -0,0 +1,6 @@ +import rimraf from 'rimraf'; +const path = require('path'); + +export function removeFolder(parentPath, folderName) { + rimraf.sync(path.join(parentPath, folderName)); +} diff --git a/packages/core/cli/src/utils/themeUtilsConfigTemplate.ts b/packages/core/cli/src/utils/themeUtilsConfigTemplate.ts new file mode 100644 index 0000000000..73131065c2 --- /dev/null +++ b/packages/core/cli/src/utils/themeUtilsConfigTemplate.ts @@ -0,0 +1,26 @@ +interface IVsfTuConfigProps { + outputPathName: string; + themePath: string; + _themePath: string; +} +export const vsfTuConfig = ({ + outputPathName, + themePath, + _themePath +}: IVsfTuConfigProps) => `module.exports = { + copy: { + to: '${outputPathName}', + from: [ + { + path: '${_themePath}', + watch: false + }, + { + path: '${themePath}', + ignore: ['generate-template.ts', 'theme-utils.config.js'], + variables: {}, + watch: false + }, + ] + } +}`; diff --git a/packages/core/cli/tsconfig.json b/packages/core/cli/tsconfig.json new file mode 100644 index 0000000000..ab034b0e09 --- /dev/null +++ b/packages/core/cli/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "lib", + "lib": ["es6", "es7", "ES2017", "ES2018", "ES2019"], + "strict": false, + "preserveSymlinks": true, + "allowJs": true + }, + "exclude": ["node_modules", "**/*.spec.ts"], + "include": ["src"], +} diff --git a/packages/core/core/.gitignore b/packages/core/core/.gitignore new file mode 100644 index 0000000000..0754a9e575 --- /dev/null +++ b/packages/core/core/.gitignore @@ -0,0 +1,6 @@ +lib +node_modules +coverage +yarn-error.log +server +!src/server diff --git a/packages/core/core/__tests__/composables/useUiState.spec.ts b/packages/core/core/__tests__/composables/useUiState.spec.ts new file mode 100644 index 0000000000..746cde4a49 --- /dev/null +++ b/packages/core/core/__tests__/composables/useUiState.spec.ts @@ -0,0 +1,75 @@ +import { useUiState } from '../../../nuxt-theme-module/theme/composables'; + +const { + isCartSidebarOpen, + isWishlistSidebarOpen, + isLoginModalOpen, + isCategoryGridView, + isFilterSidebarOpen, + isMobileMenuOpen, + toggleCartSidebar, + toggleWishlistSidebar, + toggleLoginModal, + changeToCategoryListView, + changeToCategoryGridView, + toggleFilterSidebar, + toggleMobileMenu +} = useUiState(); + +describe('useUiState', () => { + it('Mobile Menu', () => { + const expectedIsMobileMenuOpen = !isMobileMenuOpen.value; + + toggleMobileMenu(); + + expect(expectedIsMobileMenuOpen).toBe(isMobileMenuOpen.value); + }); + + it('Cart Sidebar', () => { + const expectedIsCartSidebarOpen = !isCartSidebarOpen.value; + + toggleCartSidebar(); + + expect(expectedIsCartSidebarOpen).toBe(isCartSidebarOpen.value); + }); + + it('Wishlist Sidebar', () => { + const expectedIsWishlistSidebarOpen = !isWishlistSidebarOpen.value; + + toggleWishlistSidebar(); + + expect(expectedIsWishlistSidebarOpen).toBe(isWishlistSidebarOpen.value); + }); + + it('Login Modal', () => { + const expectedIsLoginModalOpen = !isLoginModalOpen.value; + + toggleLoginModal(); + + expect(expectedIsLoginModalOpen).toBe(isLoginModalOpen.value); + }); + + it('Grid View', () => { + const expectedIsCategoryGridView = isCategoryGridView.value; + + changeToCategoryGridView(); + + expect(expectedIsCategoryGridView).toBe(isCategoryGridView.value); + }); + + it('List View', () => { + const expectedIsCategoryGridView = !isCategoryGridView.value; + + changeToCategoryListView(); + + expect(expectedIsCategoryGridView).toBe(isCategoryGridView.value); + }); + + it('Filter Sidebar', () => { + const expectedIsFilterSidebarOpen = !isFilterSidebarOpen.value; + + toggleFilterSidebar(); + + expect(expectedIsFilterSidebarOpen).toBe(isFilterSidebarOpen.value); + }); +}); diff --git a/packages/core/core/__tests__/factories/_mountComposable.ts b/packages/core/core/__tests__/factories/_mountComposable.ts new file mode 100644 index 0000000000..609b030723 --- /dev/null +++ b/packages/core/core/__tests__/factories/_mountComposable.ts @@ -0,0 +1,17 @@ +/* eslint-disable */ + +import { shallowMount, VueClass } from '@vue/test-utils'; +import { createComponent } from '@vue/composition-api'; + +const mountComposable: any = (composableFn: Function) => { + const component = createComponent({ + template: '
my component
', + setup() { + return composableFn(); + } + }); + + return shallowMount(component as VueClass); +}; + +export default mountComposable; diff --git a/packages/core/core/__tests__/factories/apiClientFactory.spec.ts b/packages/core/core/__tests__/factories/apiClientFactory.spec.ts new file mode 100644 index 0000000000..60418d8d6d --- /dev/null +++ b/packages/core/core/__tests__/factories/apiClientFactory.spec.ts @@ -0,0 +1,102 @@ +import { apiClientFactory } from '../../src/factories/apiClientFactory'; +import { applyContextToApi } from '../../src/factories/apiClientFactory/context'; + +jest.mock('../../src/utils', () => ({ + integrationPluginFactory: jest.fn(), + Logger: { + debug: jest.fn() + } +})); + +describe('[CORE - factories] apiClientFactory', () => { + it('Should return passed config with overrides property', () => { + const params = { + onCreate: jest.fn((config) => ({ config })), + defaultSettings: { option: 'option' } + }; + + const { createApiClient } = apiClientFactory(params as any) as any; + + expect(createApiClient({}).settings).toEqual({}); + }); + + it('Should merge with default settings when setup is called', () => { + const params = { + onCreate: jest.fn((config) => ({ config })), + defaultSettings: { option: 'option' } + }; + + const { createApiClient} = apiClientFactory(params as any) as any; + + const { settings } = createApiClient({ newOption: 'newOption'}); + + expect(settings).toEqual({ + newOption: 'newOption' + }); + }); + + it('Should run onCreate when setup is invoked', () => { + const params = { + onCreate: jest.fn((config) => ({ config })), + defaultSettings: {} + }; + + const { createApiClient } = apiClientFactory(params as any); + + createApiClient({}); + + expect(params.onCreate).toHaveBeenCalled(); + }); + + it('Should run given extensions', () => { + const beforeCreate = jest.fn(a => a); + const afterCreate = jest.fn(a => a); + const extension = { + name: 'extTest', + hooks: () => ({ beforeCreate, afterCreate }) + }; + + const params = { + onCreate: jest.fn((config) => ({ config })), + defaultSettings: {}, + extensions: [extension] + }; + + const { createApiClient } = apiClientFactory(params as any); + const extensions = (createApiClient as any)._predefinedExtensions; + + createApiClient.bind({ middleware: { req: null, res: null, extensions } })({}); + + expect(beforeCreate).toHaveBeenCalled(); + expect(afterCreate).toHaveBeenCalled(); + }); + + it('applyContextToApi adds context as first argument to api functions', () => { + const api = { + firstFunc: jest.fn(), + secondFunc: jest.fn(), + thirdFunc: jest.fn() + }; + const context = { + extendQuery: jest.fn() + }; + + const apiWithContext: any = applyContextToApi(api, context); + + apiWithContext.firstFunc(); + apiWithContext.secondFunc('TEST'); + apiWithContext.thirdFunc('A', 'FEW', 'ARGS'); + + expect(api.firstFunc).toHaveBeenCalledWith( + expect.objectContaining({ extendQuery: expect.any(Function) }) + ); + expect(api.secondFunc).toHaveBeenCalledWith( + expect.objectContaining({ extendQuery: expect.any(Function) }), + 'TEST' + ); + expect(api.thirdFunc).toHaveBeenCalledWith( + expect.objectContaining({ extendQuery: expect.any(Function) }), + 'A', 'FEW', 'ARGS' + ); + }); +}); diff --git a/packages/core/core/__tests__/factories/proxyUtils.spec.ts b/packages/core/core/__tests__/factories/proxyUtils.spec.ts new file mode 100644 index 0000000000..0a78f9d38e --- /dev/null +++ b/packages/core/core/__tests__/factories/proxyUtils.spec.ts @@ -0,0 +1,81 @@ + +import * as utils from './../../src/utils/nuxt/_proxyUtils'; +import isHttps from 'is-https'; + +jest.mock('is-https'); + +describe('[CORE - factories] apiFactory/_proxyUtils', () => { + it('returns base url based on incomming headers', () => { + expect(utils.getBaseUrl(null)).toEqual('/api/') + + ;(isHttps as jest.Mock).mockReturnValue(true); + expect(utils.getBaseUrl({ headers: { host: 'some-domain' } } as any)).toEqual('https://some-domain/api/') + + ;(isHttps as jest.Mock).mockReturnValue(false); + expect(utils.getBaseUrl({ headers: { host: 'some-domain' } } as any)).toEqual('http://some-domain/api/') + + ;(isHttps as jest.Mock).mockReturnValue(true); + expect(utils.getBaseUrl({ headers: { host: 'some-domain', 'x-forwarded-host': 'forwarded-host' } } as any)).toEqual('https://forwarded-host/api/') + + ;(isHttps as jest.Mock).mockReturnValue(false); + expect(utils.getBaseUrl({ headers: { host: 'some-domain', 'x-forwarded-host': 'forwarded-host' } } as any)).toEqual('http://forwarded-host/api/'); + }); + + it('returns proxy for defined api', () => { + const givenApi = { + getProduct: jest.fn() + }; + + const client = { + post: jest.fn(() => ({ then: jest.fn() })) + }; + + const proxiedApi = utils.createProxiedApi({ givenApi, client, tag: 'ct' }); + + proxiedApi.getProduct({ product: 1 }); + proxiedApi.getCategory({ category: 1 }); + + expect(givenApi.getProduct).toBeCalled(); + expect(client.post).toBeCalledWith('/ct/getCategory', [{ category: 1 }]); + }); + + it('reads cookies from incomming request', () => { + expect(utils.getCookies(null)).toEqual(''); + expect(utils.getCookies({} as any)).toEqual(''); + expect(utils.getCookies({ req: { headers: {} } } as any)).toEqual(''); + expect(utils.getCookies({ req: { headers: { cookie: { someCookie: 1 } } } } as any)).toEqual({ someCookie: 1 }); + }); + + it('it cobines config with the current one', () => { + jest.spyOn(utils, 'getCookies').mockReturnValue(''); + jest.spyOn(utils, 'getBaseUrl').mockReturnValue('some-url'); + + expect(utils.getIntegrationConfig( + null, + { someGivenOption: 1 } + )).toEqual({ + axios: { + baseURL: 'some-url', + headers: {} + }, + someGivenOption: 1 + }); + }); + + it('it cobines config with the current one and adds a cookie', () => { + jest.spyOn(utils, 'getCookies').mockReturnValue('xxx'); + jest.spyOn(utils, 'getBaseUrl').mockReturnValue('some-url'); + + expect(utils.getIntegrationConfig( + null, + {} + )).toEqual({ + axios: { + baseURL: 'some-url', + headers: { + cookie: 'xxx' + } + } + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useBillingFactory.spec.ts b/packages/core/core/__tests__/factories/useBillingFactory.spec.ts new file mode 100644 index 0000000000..c2266de9fa --- /dev/null +++ b/packages/core/core/__tests__/factories/useBillingFactory.spec.ts @@ -0,0 +1,100 @@ +import { useBillingFactory } from '../../src/factories'; + +const factoryParams = { + load: jest.fn(() => null), + save: jest.fn() +}; + +const useBilling = useBillingFactory(factoryParams); +const useBillingMethods = useBilling(); + +describe('[CORE - factories] useBillingFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('initial setup', () => { + it('should have proper initial properties', () => { + const useBilling = useBillingFactory(factoryParams); + const { loading, billing, error } = useBilling(); + + expect(billing.value).toEqual(null); + expect(loading.value).toEqual(false); + expect(error.value).toMatchObject({}); + }); + + it('loading works during save execution', async () => { + const { loading } = useBillingMethods; + let resolver = null; + factoryParams.save.mockReturnValueOnce(new Promise((resolve) => { + resolver = resolve; + })); + useBillingMethods.save({ params: {}, billingDetails: {} }); + expect(loading.value).toBe(true); + resolver(); + setTimeout(()=>{ + expect(loading.value).toBe(false); + }, 0); + }); + + it('loading works during load execution', async () => { + const { loading } = useBillingMethods; + let resolver = null; + factoryParams.load.mockReturnValueOnce(new Promise((resolve) => { + resolver = resolve; + })); + useBillingMethods.load(); + expect(loading.value).toBe(true); + resolver(); + setTimeout(()=>{ + expect(loading.value).toBe(false); + }, 0); + }); + + it('save method execution clears error', async () => { + const { error } = useBillingMethods; + const errorMsg = 'errorMsg'; + factoryParams.save.mockReturnValueOnce(new Promise((_, reject) => { + reject(errorMsg); + })); + await useBillingMethods.save({ params: {}, billingDetails: {} }); + expect(error.value.save).toBe(errorMsg); + await useBillingMethods.save({ params: {}, billingDetails: {} }); + expect(error.value.save).toBe(null); + }); + + it('load method execution clears error', async () => { + const { error } = useBillingMethods; + const errorMsg = 'errorMsg'; + factoryParams.load.mockReturnValueOnce(new Promise((_, reject) => { + reject(errorMsg); + })); + await useBillingMethods.load(); + expect(error.value.load).toBe(errorMsg); + await useBillingMethods.load(); + expect(error.value.load).toBe(null); + }); + + it('load method sets billing info', async () => { + const billingInfo = { name: 'Test'}; + factoryParams.load.mockReturnValueOnce(billingInfo); + await useBillingMethods.load(); + expect(useBillingMethods.billing.value).toEqual(billingInfo); + }); + + it('load method supports custom query', async () => { + const billingInfo = { name: 'Test'}; + const customQuery = 123; + factoryParams.load.mockReturnValueOnce(billingInfo); + await useBillingMethods.load({ customQuery: customQuery as any }); + expect((factoryParams.load.mock.calls[0] as any)[0]).toMatchObject({ customQuery }); + expect(useBillingMethods.billing.value).toEqual(billingInfo); + }); + + it('save method sets billing info', async () => { + const billingInfo = { name: 'Test'}; + factoryParams.save.mockReturnValueOnce(billingInfo); + await useBillingMethods.save({ params: {}, billingDetails: {} }); + expect(useBillingMethods.billing.value).toEqual(billingInfo); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useCartFactory.spec.ts b/packages/core/core/__tests__/factories/useCartFactory.spec.ts new file mode 100644 index 0000000000..9f4f35f0b0 --- /dev/null +++ b/packages/core/core/__tests__/factories/useCartFactory.spec.ts @@ -0,0 +1,271 @@ +import { useCartFactory, UseCartFactoryParams } from '../../src/factories'; +import { UseCart } from '../../src/types'; +import { sharedRef } from './../../src/utils'; + +let useCart: () => UseCart; +let params: UseCartFactoryParams; + +function createComposable() { + params = { + load: jest.fn().mockResolvedValueOnce({ id: 'mocked_cart' }), + addItem: jest.fn().mockResolvedValueOnce({ id: 'mocked_added_cart' }), + removeItem: jest + .fn() + .mockResolvedValueOnce({ id: 'mocked_removed_cart' }), + updateItemQty: jest + .fn() + .mockResolvedValueOnce({ id: 'mocked_updated_quantity_cart' }), + clear: jest.fn().mockResolvedValueOnce({ id: 'mocked_cleared_cart' }), + applyCoupon: jest.fn().mockResolvedValueOnce({ + updatedCart: { id: 'mocked_apply_coupon_cart' }, + updatedCoupon: 'appliedCouponMock' + }), + removeCoupon: jest.fn().mockResolvedValueOnce({ + updatedCart: { id: 'mocked_removed_coupon_cart' } + }), + isInCart: jest.fn().mockReturnValueOnce(true) + }; + useCart = useCartFactory(params); +} + +const factoryParams = { + addItem: jest.fn(() => null), + removeItem: jest.fn(), + updateItemQty: jest.fn(), + load: jest.fn(), + clear: jest.fn(), + applyCoupon: jest.fn(), + removeCoupon: jest.fn(), + isInCart: jest.fn() +}; + +const useCartMock = useCartFactory(factoryParams); + +describe('[CORE - factories] useCartFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + createComposable(); + }); + + describe('initial setup', () => { + it('should have proper initial properties', async () => { + const { cart, loading } = useCart(); + + expect(cart.value).toEqual(null); + expect(loading.value).toEqual(false); + }); + + it('should not load cart if is provided during factory creation', () => { + createComposable(); + useCart(); + expect(params.load).not.toBeCalled(); + }); + it('set given cart', () => { + const { cart, setCart } = useCart(); + expect(cart.value).toEqual(null); + setCart({ cart: 'test' }); + expect(sharedRef).toHaveBeenCalled(); + }); + }); + + describe('computes', () => { + describe('isInCart', () => { + it('should invoke implemented isInCart method', () => { + const { isInCart } = useCart(); + const result = isInCart({ product: { id: 'productId' } }); + expect(result).toEqual(true); + expect(params.isInCart).toBeCalledWith({ + currentCart: null, + product: { id: 'productId' } + }); + }); + }); + }); + + describe('methods', () => { + describe('load', () => { + it('load the cart', async () => { + createComposable(); + + const { load, cart } = useCart(); + await load(); + await load(); + expect(params.load).toHaveBeenCalled(); + expect(cart.value).toEqual({ id: 'mocked_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.load.mockImplementationOnce(() => { + throw err; + }); + const { load, error } = useCartMock(); + + await load(); + + expect(error.value.load).toBe(err); + }); + }); + + describe('addItem', () => { + it('should invoke adding to cart', async () => { + const { addItem, cart } = useCart(); + await addItem({ product: { id: 'productId' }, quantity: 2}); + expect(params.addItem).toHaveBeenCalledWith({ + currentCart: null, + product: { id: 'productId' }, + quantity: 2 + }); + expect(cart.value).toEqual({ id: 'mocked_added_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.addItem.mockImplementationOnce(() => { + throw err; + }); + const { addItem, error } = useCartMock(); + + await addItem({ product: { id: 'productId' }, quantity: 1 }); + + expect(error.value.addItem).toBe(err); + }); + }); + + describe('removeItem', () => { + it('should invoke adding to cart', async () => { + const { removeItem, cart } = useCart(); + await removeItem({ product: { id: 'productId' }}); + expect(params.removeItem).toHaveBeenCalledWith({ + currentCart: null, + product: { id: 'productId' } + }); + expect(cart.value).toEqual({ id: 'mocked_removed_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.removeItem.mockImplementationOnce(() => { + throw err; + }); + const { removeItem, error } = useCartMock(); + + await removeItem({ product: { id: 'productId' } }); + + expect(error.value.removeItem).toBe(err); + }); + }); + + describe('updateItemQty', () => { + it('should not invoke quantity update if quantity is not provided', async () => { + const { updateItemQty } = useCart(); + await updateItemQty({ product: { id: 'productId' } }); + expect(params.updateItemQty).not.toBeCalled(); + }); + + it('should not invoke quantity update if quantity is lower than 1', async () => { + const { updateItemQty } = useCart(); + await updateItemQty({ product: { id: 'productId' }, quantity: 0 }); + expect(params.updateItemQty).not.toBeCalled(); + }); + + it('should invoke quantity update', async () => { + const { updateItemQty, cart } = useCart(); + await updateItemQty({ product: { id: 'productId' }, quantity: 2 }); + expect(params.updateItemQty).toHaveBeenCalledWith({ + currentCart: null, + product: { id: 'productId' }, + quantity: 2 + }); + expect(cart.value).toEqual({ id: 'mocked_updated_quantity_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.updateItemQty.mockImplementationOnce(() => { + throw err; + }); + const { updateItemQty, error } = useCartMock(); + + await updateItemQty({ product: { id: 'productId' }, quantity: 1 }); + + expect(error.value.updateItemQty).toBe(err); + }); + }); + + describe('clear', () => { + it('should invoke clear', async () => { + const { clear, cart } = useCart(); + await clear(); + expect(params.clear).toHaveBeenCalledWith({ currentCart: null }); + expect(cart.value).toEqual({ id: 'mocked_cleared_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.clear.mockImplementationOnce(() => { + throw err; + }); + const { clear, error } = useCartMock(); + + await clear(); + + expect(error.value.clear).toBe(err); + }); + }); + + describe('applyCoupon', () => { + it('should apply provided coupon', async () => { + const { applyCoupon, cart } = useCart(); + await applyCoupon({ couponCode: 'qwerty' }); + expect(params.applyCoupon).toHaveBeenCalledWith({ + currentCart: null, + couponCode: 'qwerty' + }); + expect(cart.value).toEqual({ id: 'mocked_apply_coupon_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.applyCoupon.mockImplementationOnce(() => { + throw err; + }); + const { applyCoupon, error } = useCartMock(); + + await applyCoupon({ couponCode: 'qwerty' }); + + expect(error.value.applyCoupon).toBe(err); + }); + }); + + describe('removeCoupon', () => { + it('should remove existing coupon', async () => { + const { removeCoupon, cart } = useCart(); + const couponCode = 'some-coupon-code-12321231'; + await removeCoupon({ couponCode }); + expect(params.removeCoupon).toHaveBeenCalledWith({ + currentCart: null, + couponCode + }); + expect(cart.value).toEqual({ id: 'mocked_removed_coupon_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.removeCoupon.mockImplementationOnce(() => { + throw err; + }); + const { removeCoupon, error } = useCartMock(); + const couponCode = 'some-coupon-code-12321231'; + + await removeCoupon({ couponCode }); + + expect(error.value.removeCoupon).toBe(err); + }); + + // TODO + // it('should not invoke removeCoupon method if coupon is not applied', async () => { + // }); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useCategoryFactory.spec.ts b/packages/core/core/__tests__/factories/useCategoryFactory.spec.ts new file mode 100644 index 0000000000..5cde925df1 --- /dev/null +++ b/packages/core/core/__tests__/factories/useCategoryFactory.spec.ts @@ -0,0 +1,60 @@ +import { useCategoryFactory, UseCategoryFactoryParams } from '../../src/factories'; +import { UseCategory } from '../../src/types'; + +let useCategory: (cacheId?: string) => UseCategory; +let params: UseCategoryFactoryParams; + +const factoryParams = { + categorySearch: jest.fn() +}; + +const useCategoryMock = useCategoryFactory(factoryParams); + +function createComposable() { + params = { + categorySearch: jest + .fn() + .mockResolvedValueOnce({ id: 'mocked_removed_cart' }) + }; + useCategory = useCategoryFactory(params); +} + +describe('[CORE - factories] useCategoryFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + createComposable(); + }); + + describe('initial setup', () => { + it('should have proper initial properties when no persisted state set', () => { + const { loading, categories } = useCategory(); + + expect(categories.value).toEqual([]); + expect(loading.value).toEqual(false); + }); + }); + + describe('methods', () => { + describe('search', () => { + it('should invoke search', async () => { + const { categories, search } = useCategory(); + expect(categories.value).toEqual([]); + await search({ someparam: 'qwerty' }); + expect(params.categorySearch).toBeCalledWith({ someparam: 'qwerty' }); + expect(categories.value).toEqual({ id: 'mocked_removed_cart' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.categorySearch.mockImplementationOnce(() => { + throw err; + }); + const { search, error } = useCategoryMock('a'); + + await search({ someparam: 'qwerty' }); + + expect(error.value.search).toBe(err); + }); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useContentFactory.spec.ts b/packages/core/core/__tests__/factories/useContentFactory.spec.ts new file mode 100644 index 0000000000..982c8a0025 --- /dev/null +++ b/packages/core/core/__tests__/factories/useContentFactory.spec.ts @@ -0,0 +1,95 @@ +import { + UseContentFactoryParams, + useContentFactory, + renderContentFactory, + RenderContentFactoryParams +} from '../../src/factories'; +import { UseContent } from '../../src/types'; +import { shallowMount } from '@vue/test-utils'; + +describe('[CORE - factories] useContentFactory', () => { + let params: UseContentFactoryParams; + let useContent: (cacheId: string) => UseContent; + const createContentFactoryMock = () => { + params = { + search: jest.fn().mockResolvedValueOnce({ id: 'test-id' }) + }; + useContent = useContentFactory(params); + }; + + const factoryParams = { + search: jest.fn() + }; + + const useContentMock = useContentFactory(factoryParams); + + beforeEach(() => { + jest.clearAllMocks(); + createContentFactoryMock(); + }); + + it('returns content initial values', () => { + const { loading, content, error } = useContent('test-id'); + + expect(content.value).toEqual([]); + expect(loading.value).toEqual(false); + expect(error.value).toEqual({ + search: null + }); + }); + + it('invokes content search', async () => { + const { search } = useContent('test-id'); + const searchParams = { contentId: 'test-id', contentUrl: 'test-url' }; + await search(searchParams); + + expect(params.search).toBeCalledWith(searchParams); + expect(params.search).toBeCalledTimes(1); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.search.mockImplementationOnce(() => { + throw err; + }); + const { search, error } = useContentMock('a'); + + await search({ someparam: 'qwerty' }); + + expect(error.value.search).toBe(err); + }); +}); + +describe('[CORE - factories] renderContentFactory', () => { + let renderContent: any; + let extractContent: any; + const renderContentFactoryMock = () => { + extractContent = (content) => content; + renderContent = renderContentFactory({ extractContent } as RenderContentFactoryParams); + }; + + beforeEach(() => { + jest.clearAllMocks(); + renderContentFactoryMock(); + }); + + it('renders content as a Vue component', () => { + const content = [{ + componentName: 'TestComponent', + props: { + title: 'test title' + } + }]; + const component = shallowMount(renderContent, { + propsData: { + content + }, + components: { + TestComponent: {} + } + }); + + expect(component).toMatchObject({ isFunctionalComponent: undefined }); + }); +}); + diff --git a/packages/core/core/__tests__/factories/useFacetFactory.spec.ts b/packages/core/core/__tests__/factories/useFacetFactory.spec.ts new file mode 100644 index 0000000000..c54dc20b0f --- /dev/null +++ b/packages/core/core/__tests__/factories/useFacetFactory.spec.ts @@ -0,0 +1,42 @@ +import { useFacetFactory } from '../../src/factories'; + +const factoryParams = { + search: jest.fn() +}; + +const useFacetMock = useFacetFactory(factoryParams); + +describe('[CORE - factories] useFacetFactory', () => { + it('creates properties', () => { + const factorySearch = () => jest.fn(); + + const useFacet = useFacetFactory({ search: factorySearch } as any); + const { result, loading } = useFacet(); + + expect(result.value).toEqual({ data: null, input: null }); + expect(loading.value).toEqual(false); + }); + + it('triggers search', () => { + const factorySearch = () => jest.fn(); + + const useFacet = useFacetFactory({ search: factorySearch } as any); + const { result, loading, search } = useFacet(); + + search({ param: 'test' }); + expect(result.value).toEqual({ data: null, input: { param: 'test' } }); + expect(loading.value).toEqual(true); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.search.mockImplementationOnce(() => { + throw err; + }); + const { search, error } = useFacetMock('a'); + + await search({ someparam: 'qwerty' }); + + expect(error.value.search).toBe(err); + }); +}); diff --git a/packages/core/core/__tests__/factories/useForgotPasswordFactory.spec.ts b/packages/core/core/__tests__/factories/useForgotPasswordFactory.spec.ts new file mode 100644 index 0000000000..f8887c3324 --- /dev/null +++ b/packages/core/core/__tests__/factories/useForgotPasswordFactory.spec.ts @@ -0,0 +1,68 @@ +import { useForgotPasswordFactory } from '../../src/factories'; + +const factoryParams = { + resetPassword: jest.fn(), + setNewPassword: jest.fn() +}; + +const useForgotPassword = useForgotPasswordFactory(factoryParams); +const useForgotPasswordMethods = useForgotPassword(); + +describe('[CORE - factories] useForgotPassword', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('initial setup', () => { + it('should have proper initial properties', () => { + const useForgotPassword = useForgotPasswordFactory(factoryParams); + const { result } = useForgotPassword(); + + expect(result.value).toEqual({ + resetPasswordResult: null, + setNewPasswordResult: null + }); + }); + }); + describe('methods', () => { + describe('reset', () => { + it('generates reset password token', async () => { + const mockedResetPasswordToken = '1234'; + factoryParams.resetPassword.mockReturnValueOnce(mockedResetPasswordToken); + await useForgotPasswordMethods.request({ email: 'john.doe@gmail.com' }); + expect(useForgotPasswordMethods.result.value).toEqual(mockedResetPasswordToken); + }); + + it('throws error', async () => { + const err = new Error('test-568-08989'); + factoryParams.resetPassword.mockImplementationOnce(() => { + throw err; + }); + await useForgotPasswordMethods.request('' as any); + await expect(useForgotPasswordMethods.error.value.request).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useForgotPasswordMethods.loading.value).toBe(false); + }); + }); + describe('change', () => { + it('sets new password', async () => { + const mockedResult = true; + factoryParams.setNewPassword.mockReturnValueOnce(mockedResult); + await useForgotPasswordMethods.setNew({ tokenValue: '1234', newPassword: '1234' }); + expect(useForgotPasswordMethods.result.value).toEqual(mockedResult); + }); + it('throws error', async () => { + const err = new Error('test-568-5687565'); + factoryParams.setNewPassword.mockImplementationOnce(() => { + throw err; + }); + await useForgotPasswordMethods.setNew('' as any); + expect(useForgotPasswordMethods.error.value.setNew).toBe(err); + }); + it('finally loading go to false', () => { + expect(useForgotPasswordMethods.loading.value).toBe(false); + }); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useMakeOrderFactory.spec.ts b/packages/core/core/__tests__/factories/useMakeOrderFactory.spec.ts new file mode 100644 index 0000000000..1fc987a8b9 --- /dev/null +++ b/packages/core/core/__tests__/factories/useMakeOrderFactory.spec.ts @@ -0,0 +1,41 @@ +import { useMakeOrderFactory } from '../../src'; + +const factoryParams = { + make: jest.fn() +}; + +const useMakeOrderMock = useMakeOrderFactory(factoryParams); + +describe('[CORE - factories] useMakeOrderFactory', () => { + it('creates properties', () => { + const factoryMakeOrder = () => jest.fn(); + + const useMakeOrder = useMakeOrderFactory({ make: factoryMakeOrder } as any); + const { order, loading } = useMakeOrder(); + + expect(order.value).toEqual(null); + expect(loading.value).toEqual(false); + }); + + it('triggers make', () => { + const factoryMakeOrder = () => jest.fn(); + const useMakeOrder = useMakeOrderFactory({ make: factoryMakeOrder } as any); + const { order, loading, make } = useMakeOrder(); + + make({ customQuery: null }); + expect(order.value).toEqual(null); + expect(loading.value).toEqual(true); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.make.mockImplementationOnce(() => { + throw err; + }); + const { make, error } = useMakeOrderMock(); + + await make({ customQuery: null }); + + expect(error.value.make).toBe(err); + }); +}); diff --git a/packages/core/core/__tests__/factories/useProductFactory.spec.ts b/packages/core/core/__tests__/factories/useProductFactory.spec.ts new file mode 100644 index 0000000000..3b65c6212f --- /dev/null +++ b/packages/core/core/__tests__/factories/useProductFactory.spec.ts @@ -0,0 +1,49 @@ +import { useProductFactory } from '../../src/factories'; +import { UseProduct } from '../../src/types'; + +const useProduct: (cacheId: string) => UseProduct = useProductFactory({ + productsSearch: (searchParams) => Promise.resolve([{ name: 'product ' + searchParams.slug }]) +}); + +const factoryParams = { + productsSearch: jest.fn() +}; + +const useProductMock = useProductFactory(factoryParams); + +describe('[CORE - factories] useProductFactory', () => { + it('creates properties', () => { + const { products, loading } = useProduct('test-product'); + + expect(products.value).toEqual([]); + expect(loading.value).toEqual(false); + }); + + it('returns product response', async () => { + const { search, products } = useProduct('test-use-product'); + + await search({ slug: 'product-slug' }); + + expect(products.value).toEqual([{name: 'product product-slug' }]); + }); + + it('returns product response with ssr', async () => { + const { search, products } = useProduct('test-use-product'); + + await search({ slug: 'product-slug' }); + + expect(products.value).toEqual([{name: 'product product-slug' }]); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.productsSearch.mockImplementationOnce(() => { + throw err; + }); + const { search, error } = useProductMock('a'); + + await search({ someparam: 'qwerty' }); + + expect(error.value.search).toBe(err); + }); +}); diff --git a/packages/core/core/__tests__/factories/useReviewFactory.spec.ts b/packages/core/core/__tests__/factories/useReviewFactory.spec.ts new file mode 100644 index 0000000000..37dc054d6d --- /dev/null +++ b/packages/core/core/__tests__/factories/useReviewFactory.spec.ts @@ -0,0 +1,129 @@ +import { useReviewFactory } from '../../src/factories'; +import { UseReview } from '../../src/types'; + +const searchReviewResponse = { + offset: 0, + limit: 5, + count: 1, + total: 1, + averageRating: 4, + ratingsDistribution: { + 1: 0, + 2: 0, + 3: 0, + 4: 1, + 5: 0 + }, + results: [ + { + id: '1', + version: 1, + createdAt: (new Date()).toDateString(), + lastModifiedAt: (new Date()).toDateString(), + authorName: 'Jane D.Smith', + text: 'I was looking for a bright light for the kitchen but wanted some item more modern than a strip light. this one is perfect, very bright and looks great. I can\'t comment on interlation as I had an electrition instal it. Would recommend', + rating: 4 + } + ] +}; + +const useReviews: (cacheId: string) => UseReview = useReviewFactory({ + searchReviews: jest.fn().mockResolvedValue(searchReviewResponse), + addReview: jest.fn().mockResolvedValue({ + offset: 0, + limit: 5, + count: 2, + total: 2, + averageRating: 4.5, + ratingsDistribution: { + 1: 0, + 2: 0, + 3: 0, + 4: 1, + 5: 1 + }, + results: [ + { + id: '1', + version: 1, + createdAt: (new Date()).toDateString(), + lastModifiedAt: (new Date()).toDateString(), + authorName: 'Jane D.Smith', + text: 'I was looking for a bright light for the kitchen but wanted some item more modern than a strip light. this one is perfect, very bright and looks great. I can\'t comment on interlation as I had an electrition instal it. Would recommend', + rating: 4 + }, + { + id: '2', + version: 1, + createdAt: (new Date()).toDateString(), + lastModifiedAt: (new Date()).toDateString(), + authorName: 'Mari', + text: 'Excellent light output from this led fitting. Relatively easy to fix to the ceiling,but having two people makes it easier, to complete the installation. Unable to comment on reliability at this time, but I am hopeful of years of use with good light levels. Excellent light output from this led fitting. Relatively easy to fix to the ceiling,', + rating: 5 + } + ] + }) +}); + +const useReviesError: (cacheId: string) => UseReview = useReviewFactory({ + searchReviews: jest.fn().mockImplementation(() => { + throw new Error('Couldn\'t retrieve reviews'); + }), + addReview: jest.fn().mockImplementation(() => { + throw new Error('Couldn\'t submit review'); + }) +}); + +describe('[CORE - factories] useReviews', () => { + it('returns proper initial values', () => { + const { reviews, loading, error } = useReviews('test-reviews'); + + expect(reviews.value).toEqual([]); + expect(loading.value).toEqual(false); + expect(error.value).toEqual({ + search: null, + addReview: null + }); + }); + + it('returns reviews response', async () => { + const { search, reviews, error } = useReviews('test-reviews'); + + await search({}); + + expect(reviews.value).toEqual(searchReviewResponse); + expect(error.value.search).toBe(null); + }); + + it('can submit new review', async () => { + const { search, addReview, reviews } = useReviews('test-reviews'); + + await search({}); + + expect(reviews.value.total).toEqual(1); + + await addReview({}); + + expect(reviews.value.total).toEqual(2); + }); + + it('returns error when search fails', async () => { + const { search, reviews, loading, error } = useReviesError('test-reviews'); + + await search({}); + + expect(reviews.value).toEqual([]); + expect(loading.value).toEqual(false); + expect(error.value.search.toString()).toEqual('Error: Couldn\'t retrieve reviews'); + }); + + it('returns error when submit fails', async () => { + const { addReview, reviews, loading, error } = useReviesError('test-reviews'); + + await addReview({}); + + expect(reviews.value).toEqual([]); + expect(loading.value).toEqual(false); + expect(error.value.addReview.toString()).toEqual('Error: Couldn\'t submit review'); + }); +}); diff --git a/packages/core/core/__tests__/factories/useShippingFactory.spec.ts b/packages/core/core/__tests__/factories/useShippingFactory.spec.ts new file mode 100644 index 0000000000..d4e168d004 --- /dev/null +++ b/packages/core/core/__tests__/factories/useShippingFactory.spec.ts @@ -0,0 +1,91 @@ +import { useShippingFactory } from '../../src/factories'; + +const factoryParams = { + load: jest.fn(() => null), + save: jest.fn() +}; + +const useShipping = useShippingFactory(factoryParams); +const useShippingMethods = useShipping(); + +describe('[CORE - factories] useShippingFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('initial setup', () => { + it('should have proper initial properties', () => { + const useShipping = useShippingFactory(factoryParams); + const { loading, shipping, error } = useShipping(); + + expect(shipping.value).toEqual(null); + expect(loading.value).toEqual(false); + expect(error.value).toMatchObject({}); + }); + + it('loading works during save execution', async () => { + const { loading } = useShippingMethods; + let resolver = null; + factoryParams.save.mockReturnValueOnce(new Promise((resolve) => { + resolver = resolve; + })); + useShippingMethods.save({ params: {}, shippingDetails: {} }); + expect(loading.value).toBe(true); + resolver(); + setTimeout(()=>{ + expect(loading.value).toBe(false); + }, 0); + }); + + it('loading works during load execution', async () => { + const { loading } = useShippingMethods; + let resolver = null; + factoryParams.load.mockReturnValueOnce(new Promise((resolve) => { + resolver = resolve; + })); + useShippingMethods.load(); + expect(loading.value).toBe(true); + resolver(); + setTimeout(()=>{ + expect(loading.value).toBe(false); + }, 0); + }); + + it('save method execution clears error', async () => { + const { error } = useShippingMethods; + const errorMsg = 'errorMsg'; + factoryParams.save.mockReturnValueOnce(new Promise((_, reject) => { + reject(errorMsg); + })); + await useShippingMethods.save({ params: {}, shippingDetails: {} }); + expect(error.value.save).toBe(errorMsg); + await useShippingMethods.save({ params: {}, shippingDetails: {} }); + expect(error.value.save).toBe(null); + }); + + it('load method execution clears error', async () => { + const { error } = useShippingMethods; + const errorMsg = 'errorMsg'; + factoryParams.load.mockReturnValueOnce(new Promise((_, reject) => { + reject(errorMsg); + })); + await useShippingMethods.load(); + expect(error.value.load).toBe(errorMsg); + await useShippingMethods.load(); + expect(error.value.load).toBe(null); + }); + + it('load method sets shipping info', async () => { + const shippingInfo = { name: 'Test'}; + factoryParams.load.mockReturnValueOnce(shippingInfo); + await useShippingMethods.load(); + expect(useShippingMethods.shipping.value).toEqual(shippingInfo); + }); + + it('save method sets shipping info', async () => { + const shippingInfo = { name: 'Test'}; + factoryParams.save.mockReturnValueOnce(shippingInfo); + await useShippingMethods.save({ params: {}, shippingDetails: {} }); + expect(useShippingMethods.shipping.value).toEqual(shippingInfo); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useShippingProviderFactory.spec.ts b/packages/core/core/__tests__/factories/useShippingProviderFactory.spec.ts new file mode 100644 index 0000000000..79554e33bd --- /dev/null +++ b/packages/core/core/__tests__/factories/useShippingProviderFactory.spec.ts @@ -0,0 +1,121 @@ +import { useShippingProviderFactory } from '../../src'; + +const factoryParams = { + load: jest.fn(() => null), + save: jest.fn() +}; + +describe('[CORE - factories] useShippingProviderFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should have proper initial properties', () => { + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { loading, state, error, setState } = useShippingProvider(); + + expect(state.value).toEqual(null); + expect(loading.value).toEqual(false); + expect(error.value).toMatchObject({}); + expect(setState).toBeDefined(); + }); + + it('loading works during save execution', async () => { + let resolver = null; + factoryParams.save.mockReturnValueOnce( + new Promise((resolve) => { + resolver = resolve; + }) + ); + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { loading, save } = useShippingProvider(); + + save({ shippingMethod: 'test', customQuery: null }); + expect(loading.value).toBe(true); + resolver(); + setTimeout(() => { + expect(loading.value).toBe(false); + }, 0); + }); + + it('loading works during save execution', async () => { + let resolver = null; + factoryParams.load.mockReturnValueOnce( + new Promise((resolve) => { + resolver = resolve; + }) + ); + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { loading, load } = useShippingProvider(); + + load({ customQuery: null }); + expect(loading.value).toBe(true); + resolver(); + setTimeout(() => { + expect(loading.value).toBe(false); + }, 0); + }); + + it('save method execution clears error', async () => { + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { error, save } = useShippingProvider(); + const errorMsg = 'errorMsg'; + factoryParams.save.mockReturnValueOnce( + new Promise((_, reject) => { + reject(errorMsg); + }) + ); + + await save({ shippingMethod: 'test', customQuery: null }); + expect(error.value.save).toBe(errorMsg); + await save({ shippingMethod: 'test', customQuery: null }); + expect(error.value.save).toBe(null); + }); + + it('load method execution clears error', async () => { + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { error, load } = useShippingProvider(); + const errorMsg = 'errorMsg'; + factoryParams.load.mockReturnValueOnce( + new Promise((_, reject) => { + reject(errorMsg); + }) + ); + + await load(); + expect(error.value.load).toBe(errorMsg); + await load(); + expect(error.value.load).toBe(null); + }); + + it('load method sets state value', async () => { + const shippingProviderMock = { test: 'Test provider' }; + factoryParams.load.mockReturnValueOnce(shippingProviderMock); + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { state, load } = useShippingProvider(); + + await load(); + + expect(state.value).toEqual(shippingProviderMock); + }); + + it('save method sets state', async () => { + const shippingProviderMock = { test: 'Test provider' }; + factoryParams.save.mockReturnValueOnce(shippingProviderMock); + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { state, save } = useShippingProvider(); + + await save({ shippingMethod: 'test', customQuery: null }); + + expect(state.value).toEqual(shippingProviderMock); + }); + + it('sets state manually', () => { + const shippingProviderMock = { test: 'Test provider' }; + const useShippingProvider = useShippingProviderFactory(factoryParams); + const { setState, state } = useShippingProvider(); + + setState(shippingProviderMock); + + expect(state.value).toEqual(shippingProviderMock); + }); +}); diff --git a/packages/core/core/__tests__/factories/useStoreFactory.spec.ts b/packages/core/core/__tests__/factories/useStoreFactory.spec.ts new file mode 100644 index 0000000000..aa4e15ffdf --- /dev/null +++ b/packages/core/core/__tests__/factories/useStoreFactory.spec.ts @@ -0,0 +1,98 @@ +import { useStoreFactory } from '../../src/factories'; +import { AgnosticStore } from '../../src/types'; + +const factoryParams = { + load: jest.fn(), + change: jest.fn() +}; + +const stores = {key: 'stores'}; +const currentStore = ({store: 1} as unknown) as AgnosticStore; +const store = ({store: 2} as unknown) as AgnosticStore; +const error = {key: 'errors'}; + +const useStore = useStoreFactory(factoryParams); +const useStoreMethods = useStore(); + +describe('[CORE - factories] useStoreFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('initial setup', () => { + + it('should have proper initial properties', () => { + const useStore = useStoreFactory(factoryParams); + const { response, loading, error } = useStore(); + + expect(response.value).toEqual(null); + expect(loading.value).toEqual(false); + expect(error.value).toEqual({load: null, change: null}); + }); + + }); + + describe('methods', () => { + + describe('load', () => { + + it('should return store data on success', async () => { + const store = {key: 'store'}; + factoryParams.load.mockResolvedValue(store); + await useStoreMethods.load(); + expect(useStoreMethods.response.value).toEqual(store); + }); + + it('should assign error on fail', async () => { + const error = {key: 'error'}; + factoryParams.load.mockRejectedValue(error); + await useStoreMethods.load(); + expect(useStoreMethods.error.value.load).toEqual(error); + }); + + it('should be called with correct arguments', async () => { + await useStoreMethods.load(); + expect(factoryParams.load).toHaveBeenNthCalledWith(1, { + customQuery: undefined + }); + + await useStoreMethods.load({customQuery: {key: 'customQuery'}}); + expect(factoryParams.load).toHaveBeenNthCalledWith(2, { + customQuery: {key: 'customQuery'} + }); + }); + + }); + + describe('change', () => { + + it('should return store data on success', async () => { + factoryParams.change.mockResolvedValue(stores); + await useStoreMethods.change({ currentStore, store }); + expect(useStoreMethods.response.value).toEqual(stores); + }); + + it('should assign error on fail', async () => { + factoryParams.change.mockRejectedValue(error); + await useStoreMethods.change({ currentStore, store }); + expect(useStoreMethods.error.value.change).toEqual(error); + }); + + it('should be called with correct arguments', async () => { + factoryParams.change.mockResolvedValue(stores); + + await useStoreMethods.change({currentStore, store}); + expect(factoryParams.change).toHaveBeenNthCalledWith(1, { + currentStore, store, customQuery: undefined + }); + + await useStoreMethods.change({currentStore, store, customQuery: {key: 'customQuery'}}); + expect(factoryParams.change).toHaveBeenNthCalledWith(2, { + currentStore, store, customQuery: {key: 'customQuery'} + }); + }); + + }); + + }); +}); diff --git a/packages/core/core/__tests__/factories/useUserBillingFactory.spec.ts b/packages/core/core/__tests__/factories/useUserBillingFactory.spec.ts new file mode 100644 index 0000000000..371d4de562 --- /dev/null +++ b/packages/core/core/__tests__/factories/useUserBillingFactory.spec.ts @@ -0,0 +1,222 @@ +import { useUserBillingFactory } from '../../src/factories'; + +const factoryParams = { + addAddress: jest.fn(() => null), + deleteAddress: jest.fn(), + updateAddress: jest.fn(), + load: jest.fn(), + setDefaultAddress: jest.fn() +}; + +const useUserBilling = useUserBillingFactory(factoryParams); +const useUserBillingMethods = useUserBilling(); + +describe('[CORE - factories] useUserBillingFactory', () => { + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should have proper initial properties', () => { + const useUserBilling = useUserBillingFactory(factoryParams); + const { + billing, + loading + } = useUserBilling(); + + expect(billing.value).toEqual({}); + expect(loading.value).toEqual(false); + }); + + describe('methods', () => { + describe('addAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.addAddress.mockReturnValueOnce(paramsToUpdate); + await useUserBillingMethods.addAddress({ address: paramsToUpdate }); + expect(useUserBillingMethods.billing.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('zxczxcx'); + factoryParams.addAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserBillingMethods.addAddress('' as any); + expect(useUserBillingMethods.error.value.addAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserBillingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserBillingMethods.addAddress({...params}); + expect(factoryParams.addAddress).toHaveBeenNthCalledWith(1, { + ...params, + billing: {}, + customQuery: undefined + }); + + await useUserBillingMethods.addAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.addAddress).toHaveBeenNthCalledWith(2, { + ...params, + billing: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + + describe('deleteAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.deleteAddress.mockReturnValueOnce(paramsToUpdate); + await useUserBillingMethods.deleteAddress({ address: paramsToUpdate }); + expect(useUserBillingMethods.billing.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('87878dfdf'); + factoryParams.deleteAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserBillingMethods.deleteAddress('' as any); + expect(useUserBillingMethods.error.value.deleteAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserBillingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserBillingMethods.deleteAddress({...params}); + expect(factoryParams.deleteAddress).toHaveBeenNthCalledWith(1, { + ...params, + billing: {}, + customQuery: undefined + }); + + await useUserBillingMethods.deleteAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.deleteAddress).toHaveBeenNthCalledWith(2, { + ...params, + billing: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + + describe('updateAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.updateAddress.mockReturnValueOnce(paramsToUpdate); + await useUserBillingMethods.updateAddress({ address: paramsToUpdate }); + expect(useUserBillingMethods.billing.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('23232323'); + factoryParams.updateAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserBillingMethods.updateAddress('' as any); + expect(useUserBillingMethods.error.value.updateAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserBillingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserBillingMethods.updateAddress({...params}); + expect(factoryParams.updateAddress).toHaveBeenNthCalledWith(1, { + ...params, + billing: {}, + customQuery: undefined + }); + + await useUserBillingMethods.updateAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.updateAddress).toHaveBeenNthCalledWith(2, { + ...params, + billing: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + + describe('load', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.load.mockReturnValueOnce(paramsToUpdate); + await useUserBillingMethods.load(); + expect(useUserBillingMethods.billing.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('cvcvc'); + factoryParams.load.mockImplementationOnce(() => { + throw err; + }); + await useUserBillingMethods.load(); + expect(useUserBillingMethods.error.value.load).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserBillingMethods.loading.value).toBe(false); + }); + }); + + describe('setDefaultAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.setDefaultAddress.mockReturnValueOnce(paramsToUpdate); + await useUserBillingMethods.setDefaultAddress({ address: paramsToUpdate }); + expect(useUserBillingMethods.billing.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('adsd'); + factoryParams.setDefaultAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserBillingMethods.setDefaultAddress('' as any); + expect(useUserBillingMethods.error.value.setDefaultAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserBillingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserBillingMethods.setDefaultAddress({...params}); + expect(factoryParams.setDefaultAddress).toHaveBeenNthCalledWith(1, { + ...params, + billing: {}, + customQuery: undefined + }); + + await useUserBillingMethods.setDefaultAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.setDefaultAddress).toHaveBeenNthCalledWith(2, { + ...params, + billing: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useUserFactory.spec.ts b/packages/core/core/__tests__/factories/useUserFactory.spec.ts new file mode 100644 index 0000000000..450adeafe0 --- /dev/null +++ b/packages/core/core/__tests__/factories/useUserFactory.spec.ts @@ -0,0 +1,271 @@ +import { useUserFactory } from '../../src/factories'; +import { sharedRef } from './../../src/utils'; + +const factoryParams = { + load: jest.fn(() => null), + logOut: jest.fn(), + updateUser: jest.fn(), + register: jest.fn(), + logIn: jest.fn(), + changePassword: jest.fn(), + refreshUser: jest.fn() +}; + +const useUser = useUserFactory(factoryParams); +const useUserMethods = useUser(); + +describe('[CORE - factories] useUserFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('initial setup', () => { + it('should have proper initial properties', () => { + const useUser = useUserFactory(factoryParams); + const { user, isAuthenticated } = useUser(); + + expect(user.value).toEqual(null); + expect(isAuthenticated.value).toEqual(false); + }); + + it('isAuthenticated returns true for logged in user', async () => { + const { isAuthenticated } = useUserMethods; + const userToLogin = { username: 'John', password: '123456'}; + factoryParams.logIn.mockReturnValueOnce( + userToLogin + ); + await useUserMethods.login({ user: userToLogin }); + expect(isAuthenticated.value).toBe(true); + }); + + it('set given user property', () => { + const { setUser } = useUser(); + setUser({ username: 'test' }); + expect(sharedRef).toHaveBeenCalled(); + }); + }); + describe('methods', () => { + describe('updateUser', () => { + it('return updated user data', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.updateUser.mockReturnValueOnce(paramsToUpdate); + await useUserMethods.updateUser({ user: paramsToUpdate }); + expect(useUserMethods.user.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('test-568-08989'); + factoryParams.updateUser.mockImplementationOnce(() => { + throw err; + }); + await useUserMethods.updateUser('' as any); + await expect(useUserMethods.error.value.updateUser).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserMethods.loading.value).toBe(false); + }); + it('called with correct arguments', async () => { + const params = { + user: {key: 'user'} + }; + + useUserMethods.setUser(params); + await useUserMethods.updateUser(params); + expect(factoryParams.updateUser).toHaveBeenNthCalledWith(1, { + currentUser: params, + updatedUserData: params.user, + customQuery: undefined + }); + + useUserMethods.setUser(params); + await useUserMethods.updateUser({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.updateUser).toHaveBeenNthCalledWith(2, { + currentUser: params, + updatedUserData: params.user, + customQuery: {key: 'customQuery'} + }); + }); + }); + describe('register', () => { + it('return registered user', async () => { + const userToRegister = { email: 'John', password: '123456', firstName: 'Diego', lastName: 'Ramirez'}; + factoryParams.register.mockReturnValueOnce(userToRegister); + await useUserMethods.register({ user: userToRegister }); + expect(useUserMethods.user.value).toEqual(userToRegister); + }); + it('throws error', async () => { + const err = new Error('test-568-5687565'); + factoryParams.register.mockImplementationOnce(() => { + throw err; + }); + await useUserMethods.register('' as any); + expect(useUserMethods.error.value.register).toBe(err); + }); + it('finally loading go to false', () => { + expect(useUserMethods.loading.value).toBe(false); + }); + it('called with correct arguments', async () => { + const params = { + user: { email: 'John', password: '123456', firstName: 'Diego', lastName: 'Ramirez'} + }; + + await useUserMethods.register(params); + expect(factoryParams.register).toHaveBeenNthCalledWith(1, { + ...params.user, + customQuery: undefined + }); + + await useUserMethods.register({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.register).toHaveBeenNthCalledWith(2, { + ...params.user, + customQuery: {key: 'customQuery'} + }); + }); + }); + describe('login', () => { + it('return logged user', async () => { + const userToLogin = { username: 'John', password: '123456'}; + factoryParams.logIn.mockReturnValueOnce( + userToLogin + ); + await useUserMethods.login({ user: userToLogin }); + expect(useUserMethods.user.value).toEqual(userToLogin); + }); + it('throws error', async () => { + const err = new Error('test-568-232332'); + factoryParams.logIn.mockImplementationOnce(() => { + throw err; + }); + await useUserMethods.login('' as any); + expect(useUserMethods.error.value.login).toBe(err); + }); + it('finally loading go to false', () => { + expect(useUserMethods.loading.value).toBe(false); + }); + it('called with correct arguments', async () => { + const params = { + user: { username: 'John', password: '123456'} + }; + + await useUserMethods.login(params); + expect(factoryParams.logIn).toHaveBeenNthCalledWith(1, { + ...params.user, + customQuery: undefined + }); + + await useUserMethods.login({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.logIn).toHaveBeenNthCalledWith(2, { + ...params.user, + customQuery: {key: 'customQuery'} + }); + }); + }); + describe('logout', () => { + it('return logout user', async () => { + factoryParams.logOut.mockReturnValueOnce(null); + await useUserMethods.logout(); + expect(factoryParams.logOut).toHaveBeenCalled(); + }); + it('throws error', async () => { + const err = new Error('test-value-568-232332'); + factoryParams.logOut.mockImplementationOnce(() => { + throw err; + }); + await useUserMethods.logout(); + expect(useUserMethods.error.value.logout).toBe(err); + }); + it('finally loading go to false', () => { + expect(useUserMethods.loading.value).toBe(false); + }); + it('called with correct arguments', async () => { + const params = { + user: { username: 'John', password: '123456'} + }; + + useUserMethods.setUser(params.user); + await useUserMethods.logout(); + expect(factoryParams.logOut).toHaveBeenNthCalledWith(1, { + currentUser: params.user + }); + }); + }); + describe('load', () => { + it('return loadedUser user', async () => { + const user = {firstName: 'John', lastName: 'Galt'}; + factoryParams.load.mockReturnValueOnce(user); + await useUserMethods.load(); + expect(factoryParams.load).toHaveBeenCalled(); + expect(useUserMethods.user.value).toEqual(user); + }); + it('throws error', async () => { + const err = new Error('test-value-568'); + factoryParams.load.mockImplementationOnce(() => { + throw err; + }); + await useUserMethods.load(); + expect(useUserMethods.error.value.load).toBe(err); + }); + it('finally loading go to false', () => { + expect(useUserMethods.loading.value).toBe(false); + }); + it('called with correct arguments', async () => { + await useUserMethods.load(); + expect(factoryParams.load).toHaveBeenNthCalledWith(1, { + customQuery: undefined + }); + + await useUserMethods.load({customQuery: {key: 'customQuery'}}); + expect(factoryParams.load).toHaveBeenNthCalledWith(2, { + customQuery: {key: 'customQuery'} + }); + }); + }); + describe('changePassword', () => { + it('return logout user', async () => { + const changePasswordData = {currentUser: {email: 'tonny@dot.com', password: '123456'}, currentPassword: '123456', newPassword: '654321'}; + factoryParams.changePassword.mockReturnValueOnce(changePasswordData); + await useUserMethods.changePassword({ current: changePasswordData.currentPassword, new: changePasswordData.newPassword }); + expect(useUserMethods.user.value).toEqual(changePasswordData); + }); + it('throws error', async () => { + const err = new Error('test-value'); + factoryParams.changePassword.mockImplementationOnce(() => { + throw err; + }); + await useUserMethods.changePassword({ current: null as any, new: null as any }); + expect(useUserMethods.error.value.changePassword).toBe(err); + }); + it('finally loading go to false', () => { + expect(useUserMethods.loading.value).toBe(false); + }); + it('called with correct arguments', async () => { + const params = { + current: '1', + new: '2' + }; + + const user = { + name: 'user' + }; + + useUserMethods.setUser(user); + await useUserMethods.changePassword(params); + expect(factoryParams.changePassword).toHaveBeenNthCalledWith(1, { + currentUser: user, + currentPassword: params.current, + newPassword: params.new, + customQuery: undefined + }); + + useUserMethods.setUser(user); + await useUserMethods.changePassword({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.changePassword).toHaveBeenNthCalledWith(2, { + currentUser: user, + currentPassword: params.current, + newPassword: params.new, + customQuery: {key: 'customQuery'} + }); + }); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useUserOrderFactory.spec.ts b/packages/core/core/__tests__/factories/useUserOrderFactory.spec.ts new file mode 100644 index 0000000000..060f0edb4e --- /dev/null +++ b/packages/core/core/__tests__/factories/useUserOrderFactory.spec.ts @@ -0,0 +1,56 @@ +import { UseUserOrder } from '../../src/types'; +import { UseUserOrderFactoryParams, useUserOrderFactory } from '../../src/factories'; +import { Ref } from '@vue/composition-api'; + +let useUserOrder: () => UseUserOrder>>, any>; +let params: UseUserOrderFactoryParams; + +const defaultOrdersValue = { + results: [], + total: 0 +}; + +function createComposable(): void { + params = { + searchOrders: jest.fn().mockResolvedValueOnce(['first', 'second']) + }; + useUserOrder = useUserOrderFactory(params); +} + +describe('[CORE - factories] useUserOrderFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + createComposable(); + }); + + describe('initial setup', () => { + it('should have proper initial props', () => { + const { loading, orders } = useUserOrder(); + expect(loading.value).toEqual(false); + expect(orders.value).toEqual(defaultOrdersValue); + }); + }); + + describe('methods', () => { + describe('search', () => { + it('should set search results', async () => { + const { search, orders } = useUserOrder(); + await search({}); + expect(orders.value).toEqual(['first', 'second']); + }); + + it('should disable loading flag on error', async () => { + const err = new Error('some-error'); + params.searchOrders = jest.fn().mockImplementationOnce(() => { + throw err; + }); + const { search, loading, orders, error } = useUserOrder(); + await search({}); + expect(error.value.search).toBe(err); + + expect(loading.value).toEqual(false); + expect(orders.value).toEqual(defaultOrdersValue); + }); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useUserShippingFactory.spec.ts b/packages/core/core/__tests__/factories/useUserShippingFactory.spec.ts new file mode 100644 index 0000000000..a3d991eaae --- /dev/null +++ b/packages/core/core/__tests__/factories/useUserShippingFactory.spec.ts @@ -0,0 +1,222 @@ +import { useUserShippingFactory } from '../../src/factories'; + +const factoryParams = { + addAddress: jest.fn(() => null), + deleteAddress: jest.fn(), + updateAddress: jest.fn(), + load: jest.fn(), + setDefaultAddress: jest.fn() +}; + +const useUserShipping = useUserShippingFactory(factoryParams); +const useUserShippingMethods = useUserShipping(); + +describe('[CORE - factories] useUserShippingFactory', () => { + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should have proper initial properties', () => { + const useUserShipping = useUserShippingFactory(factoryParams); + const { + shipping, + loading + } = useUserShipping(); + + expect(shipping.value).toEqual({}); + expect(loading.value).toEqual(false); + }); + + describe('methods', () => { + describe('addAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.addAddress.mockReturnValueOnce(paramsToUpdate); + await useUserShippingMethods.addAddress({ address: paramsToUpdate }); + expect(useUserShippingMethods.shipping.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('2323'); + factoryParams.addAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserShippingMethods.addAddress('' as any); + expect(useUserShippingMethods.error.value.addAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserShippingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserShippingMethods.addAddress({...params}); + expect(factoryParams.addAddress).toHaveBeenNthCalledWith(1, { + ...params, + shipping: {}, + customQuery: undefined + }); + + await useUserShippingMethods.addAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.addAddress).toHaveBeenNthCalledWith(2, { + ...params, + shipping: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + + describe('deleteAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.deleteAddress.mockReturnValueOnce(paramsToUpdate); + await useUserShippingMethods.deleteAddress({ address: paramsToUpdate }); + expect(useUserShippingMethods.shipping.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('2323'); + factoryParams.deleteAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserShippingMethods.deleteAddress('' as any); + expect(useUserShippingMethods.error.value.deleteAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserShippingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserShippingMethods.deleteAddress({...params}); + expect(factoryParams.deleteAddress).toHaveBeenNthCalledWith(1, { + ...params, + shipping: {}, + customQuery: undefined + }); + + await useUserShippingMethods.deleteAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.deleteAddress).toHaveBeenNthCalledWith(2, { + ...params, + shipping: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + + describe('updateAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.updateAddress.mockReturnValueOnce(paramsToUpdate); + await useUserShippingMethods.updateAddress({ address: paramsToUpdate }); + expect(useUserShippingMethods.shipping.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('2323'); + factoryParams.updateAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserShippingMethods.updateAddress('' as any); + expect(useUserShippingMethods.error.value.updateAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserShippingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserShippingMethods.updateAddress({...params}); + expect(factoryParams.updateAddress).toHaveBeenNthCalledWith(1, { + ...params, + shipping: {}, + customQuery: undefined + }); + + await useUserShippingMethods.updateAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.updateAddress).toHaveBeenNthCalledWith(2, { + ...params, + shipping: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + + describe('load', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.load.mockReturnValueOnce(paramsToUpdate); + await useUserShippingMethods.load(); + expect(useUserShippingMethods.shipping.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('2323'); + factoryParams.load.mockImplementationOnce(() => { + throw err; + }); + await useUserShippingMethods.load(); + expect(useUserShippingMethods.error.value.load).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserShippingMethods.loading.value).toBe(false); + }); + }); + + describe('setDefaultAddress', () => { + it('updates addresses', async () => { + const paramsToUpdate = { name: 'Test'}; + factoryParams.setDefaultAddress.mockReturnValueOnce(paramsToUpdate); + await useUserShippingMethods.setDefaultAddress({ address: paramsToUpdate }); + expect(useUserShippingMethods.shipping.value).toEqual(paramsToUpdate); + }); + + it('throws error', async () => { + const err = new Error('zxczxcx'); + factoryParams.setDefaultAddress.mockImplementationOnce(() => { + throw err; + }); + await useUserShippingMethods.setDefaultAddress('' as any); + expect(useUserShippingMethods.error.value.setDefaultAddress).toBe(err); + }); + + it('finally loading go to false', () => { + expect(useUserShippingMethods.loading.value).toBe(false); + }); + + it('called with correct arguments', async () => { + const params = { + address: {key: 'address'} + }; + + await useUserShippingMethods.setDefaultAddress({...params}); + expect(factoryParams.setDefaultAddress).toHaveBeenNthCalledWith(1, { + ...params, + shipping: {}, + customQuery: undefined + }); + + await useUserShippingMethods.setDefaultAddress({...params, customQuery: {key: 'customQuery'}}); + expect(factoryParams.setDefaultAddress).toHaveBeenNthCalledWith(2, { + ...params, + shipping: {}, + customQuery: {key: 'customQuery'} + }); + }); + }); + }); +}); diff --git a/packages/core/core/__tests__/factories/useWishlistFactory.spec.ts b/packages/core/core/__tests__/factories/useWishlistFactory.spec.ts new file mode 100644 index 0000000000..490a4b4312 --- /dev/null +++ b/packages/core/core/__tests__/factories/useWishlistFactory.spec.ts @@ -0,0 +1,170 @@ +import { useWishlistFactory, UseWishlistFactoryParams } from '../../src/factories'; +import { UseWishlist } from '../../src/types'; +import { sharedRef } from './../../src/utils'; + +let useWishlist: () => UseWishlist; +let params: UseWishlistFactoryParams; +const customQuery = undefined; + +function createComposable() { + params = { + load: jest.fn().mockResolvedValueOnce({ id: 'mocked_wishlist' }), + addItem: jest.fn().mockResolvedValueOnce({ id: 'mocked_added_wishlist' }), + isInWishlist: jest.fn().mockReturnValueOnce(true), + clear: jest.fn().mockResolvedValueOnce({ id: 'mocked_cleared_wishlist' }), + removeItem: jest + .fn() + .mockResolvedValueOnce({ id: 'mocked_removed_wishlist' }) + }; + useWishlist = useWishlistFactory(params); +} + +const factoryParams = { + addItem: jest.fn(() => null), + removeItem: jest.fn(), + load: jest.fn(), + clear: jest.fn(), + isInWishlist: jest.fn() +}; + +const useWishlistMock = useWishlistFactory(factoryParams); + +describe('[CORE - factories] useWishlistFactory', () => { + beforeEach(() => { + jest.clearAllMocks(); + createComposable(); + }); + + describe('initial setup', () => { + it('should have proper initial properties', async () => { + const { wishlist, loading } = useWishlist(); + + expect(wishlist.value).toEqual(null); + expect(loading.value).toEqual(false); + }); + + it('should not load wishlist if is provided during factory creation', () => { + createComposable(); + useWishlist(); + expect(params.load).not.toBeCalled(); + }); + it('set given wishlist', () => { + const { wishlist, setWishlist } = useWishlist(); + expect(wishlist.value).toEqual(null); + setWishlist({ wishlist: 'test' }); + expect(sharedRef).toHaveBeenCalled(); + }); + }); + + describe('computes', () => { + describe('isInWishlist', () => { + it('should invoke implemented isInWishlist method', () => { + const { isInWishlist } = useWishlist(); + const result = isInWishlist({ product: { id: 'productId' } }); + expect(result).toEqual(true); + expect(params.isInWishlist).toBeCalledWith({ + currentWishlist: null, + product: { id: 'productId' } + }); + }); + }); + }); + + describe('methods', () => { + describe('load', () => { + it('load the wishlist', async () => { + createComposable(); + + const { load, wishlist } = useWishlist(); + await load(); + expect(params.load).toHaveBeenCalledWith({ customQuery }); + expect(wishlist.value).toEqual({ id: 'mocked_wishlist' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.load.mockImplementationOnce(() => { + throw err; + }); + const { load, error } = useWishlistMock(); + + await load(); + + expect(error.value.load).toBe(err); + }); + }); + + describe('addItem', () => { + it('should invoke adding to wishlist', async () => { + const { addItem, wishlist } = useWishlist(); + await addItem({ product: { id: 'productId' } }); + expect(params.addItem).toHaveBeenCalledWith({ + currentWishlist: null, + product: { id: 'productId' } + }); + expect(wishlist.value).toEqual({ id: 'mocked_added_wishlist' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.addItem.mockImplementationOnce(() => { + throw err; + }); + const { addItem, error } = useWishlistMock(); + + await addItem({ + product: { id: 'productId' } + }); + + expect(error.value.addItem).toBe(err); + }); + }); + + describe('removeItem', () => { + it('should invoke adding to wishlist', async () => { + const { removeItem, wishlist } = useWishlist(); + await removeItem({ product: { id: 'productId' } }); + expect(params.removeItem).toHaveBeenCalledWith({ + currentWishlist: null, + product: { id: 'productId' } + }); + expect(wishlist.value).toEqual({ id: 'mocked_removed_wishlist' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.removeItem.mockImplementationOnce(() => { + throw err; + }); + const { removeItem, error } = useWishlistMock(); + + await removeItem({ + product: { id: 'productId' } + }); + + expect(error.value.removeItem).toBe(err); + }); + }); + + describe('clear', () => { + it('should invoke clear', async () => { + const { clear, wishlist } = useWishlist(); + await clear(); + expect(params.clear).toHaveBeenCalledWith({ currentWishlist: null }); + expect(wishlist.value).toEqual({ id: 'mocked_cleared_wishlist' }); + }); + + it('should set error if factory method throwed', async () => { + const err = new Error('zxczxcx'); + factoryParams.clear.mockImplementationOnce(() => { + throw err; + }); + const { clear, error } = useWishlistMock(); + + await clear(); + + expect(error.value.clear).toBe(err); + }); + }); + }); +}); diff --git a/packages/core/core/__tests__/setup.ts b/packages/core/core/__tests__/setup.ts new file mode 100644 index 0000000000..4b62c544c1 --- /dev/null +++ b/packages/core/core/__tests__/setup.ts @@ -0,0 +1,29 @@ +/* eslint-disable */ +require('jsdom-global')(); +import Vue from 'vue'; +import VueCompositionApi, { ref } from '@vue/composition-api'; + +Vue.config.productionTip = false; +Vue.config.devtools = false; + +Vue.use(VueCompositionApi); +jest.mock('lodash-es/merge', () => (arg1, arg2) => ({ ...arg1, ...arg2 })); + +jest.mock('../src/utils', () => ({ + Logger: { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {} + }, + mask: jest.fn((s) => s), + onSSR: jest.fn(fn => fn()), + sharedRef: jest.fn(ref), + vsfRef: jest.fn(ref), + generateContext: jest.fn(() => ({ context: null })), + configureFactoryParams: jest.fn((fParams) => fParams), + useVSFContext: jest.fn() +})); + +// @ts-ignore +global.__DEV__ = false; diff --git a/packages/core/core/__tests__/utils/configureSSR.spec.ts b/packages/core/core/__tests__/utils/configureSSR.spec.ts new file mode 100644 index 0000000000..a4a7a16013 --- /dev/null +++ b/packages/core/core/__tests__/utils/configureSSR.spec.ts @@ -0,0 +1,42 @@ + +import { vsfRef, onSSR, configureSSR } from '../../src/utils/ssr'; +import { ref, onServerPrefetch } from '@vue/composition-api'; + +describe('[CORE - utils] configureSSR', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns default implementation', () => { + expect(vsfRef).toEqual(ref); + expect(onSSR).toEqual(onServerPrefetch); + }); + + it('returns configured implementation', () => { + configureSSR({ + vsfRef: 'new-ref', + onSSR: 'new-on-ssr' + } as any); + + expect(vsfRef).toEqual('new-ref'); + expect(onSSR).toEqual('new-on-ssr'); + }); + + it('returns configured implementation for onSSR', () => { + configureSSR({ + onSSR: 'new-on-ssr-2' + } as any); + + expect(vsfRef).toEqual('new-ref'); + expect(onSSR).toEqual('new-on-ssr-2'); + }); + + it('returns configured implementation for vsfRef', () => { + configureSSR({ + vsfRef: 'new-ref-2' + } as any); + + expect(vsfRef).toEqual('new-ref-2'); + expect(onSSR).toEqual('new-on-ssr-2'); + }); +}); diff --git a/packages/core/core/__tests__/utils/context.spec.ts b/packages/core/core/__tests__/utils/context.spec.ts new file mode 100644 index 0000000000..dbbea7fe00 --- /dev/null +++ b/packages/core/core/__tests__/utils/context.spec.ts @@ -0,0 +1,80 @@ +import { + generateContext, + useVSFContext, + configureContext +} from '../../src/utils/context'; + +import { Context } from '../../src/types'; + +describe('context', () => { + it('useVSFContext returns {} by default', () => { + expect(useVSFContext()).toEqual({}); + }); + + it('is possible to set useVSFContext with configureContext', () => { + const myFn = jest.fn((): Context => ({})); + configureContext({ + useVSFContext: myFn + }); + + expect(myFn).toBe(useVSFContext); + }); + + it('configureContext uses fallback to current useVSFContext if not provided', () => { + const myFn = jest.fn((): Context => ({})); + + configureContext({ + useVSFContext: myFn + }); + configureContext({ + useVSFContext: null + }); + + expect(myFn).toBe(useVSFContext); + }); + + it('generateContext returns useVSFContext().$vsf if setup not provided', () => { + const myFn = jest.fn( + (): Context => ({ + $vsf: 12345 + }) + ); + configureContext({ + useVSFContext: myFn + }); + + const generatedContext = generateContext({}); + + expect(generatedContext).toBe(12345); + }); + + it('generateContext adds value returned by factoryParams.setup() to generated context', () => { + const vsfObject = { + a: 1 + }; + const factoryParams = { + provide() { + return { + b: 2, + c: 3 + }; + } + }; + const myFn = jest.fn( + (): Context => ({ + $vsf: vsfObject + }) + ); + configureContext({ + useVSFContext: myFn + }); + + const generatedContext = generateContext(factoryParams); + + expect(generatedContext).toEqual({ + a: 1, + b: 2, + c: 3 + }); + }); +}); diff --git a/packages/core/core/__tests__/utils/factoryParams.spec.ts b/packages/core/core/__tests__/utils/factoryParams.spec.ts new file mode 100644 index 0000000000..1102990a54 --- /dev/null +++ b/packages/core/core/__tests__/utils/factoryParams.spec.ts @@ -0,0 +1,37 @@ +import { configureFactoryParams } from '../../src/utils/factoryParams'; + +describe('context', () => { + it('create factory params', async () => { + const factoryParams = { + provide: () => ({ b: 2, c: 3 }), + testFn1: () => ({ b: 4, c: 5 }), + testFn2: () => ({ b: 6, c: 7 }), + api: { + platformFn1: (context, params) => params.name + } + }; + + const mainRef = { value: '' }; + const loading = { value: '' }; + const error = { value: '' }; + + const params = configureFactoryParams( + factoryParams, + { mainRef, alias: 'currentTest', loading, error } + ); + + expect(params).toEqual({ + provide: expect.any(Function), + testFn1: expect.any(Function), + testFn2: expect.any(Function), + api: { + platformFn1: expect.any(Function) + } + }); + + expect(params.testFn1()).toEqual({ b: 4, c: 5 }); + expect(params.testFn2()).toEqual({ b: 6, c: 7 }); + await params.api.platformFn1({ name: 'test-param' }); + expect(mainRef.value).toEqual('test-param'); + }); +}); diff --git a/packages/core/core/__tests__/utils/loggerFactory.spec.ts b/packages/core/core/__tests__/utils/loggerFactory.spec.ts new file mode 100644 index 0000000000..5c60602ce5 --- /dev/null +++ b/packages/core/core/__tests__/utils/loggerFactory.spec.ts @@ -0,0 +1,75 @@ + +import { registerLogger, Logger } from '../../src/utils/logger'; + +const testLogger = { + debug: () => 'debug', + info: () => 'info', + warn: () => 'warn', + error: () => 'error' +}; + +describe('[CORE - utils] registerLogger', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('registers logger with info verbosity', () => { + registerLogger(testLogger, 'info'); + + expect(Logger.info()).toEqual('info'); + expect(Logger.warn()).toEqual('warn'); + expect(Logger.error()).toEqual('error'); + expect(Logger.debug()).toEqual(undefined); + }); + + it('registers logger with debug verbosity', () => { + registerLogger(testLogger, 'debug'); + + expect(Logger.info()).toEqual('info'); + expect(Logger.warn()).toEqual('warn'); + expect(Logger.error()).toEqual('error'); + expect(Logger.debug()).toEqual('debug'); + }); + + it('registers logger with warn verbosity', () => { + registerLogger(testLogger, 'warn'); + + expect(Logger.info()).toEqual(undefined); + expect(Logger.warn()).toEqual('warn'); + expect(Logger.error()).toEqual('error'); + expect(Logger.debug()).toEqual(undefined); + }); + + it('registers logger with error verbosity', () => { + registerLogger(testLogger, 'error'); + + expect(Logger.info()).toEqual(undefined); + expect(Logger.warn()).toEqual(undefined); + expect(Logger.error()).toEqual('error'); + expect(Logger.debug()).toEqual(undefined); + }); + + it('registers logger with none verbosity', () => { + registerLogger(testLogger, 'none'); + + expect(Logger.info()).toEqual(undefined); + expect(Logger.warn()).toEqual(undefined); + expect(Logger.error()).toEqual(undefined); + expect(Logger.debug()).toEqual(undefined); + }); + + it('registers custom logger', () => { + const logger = (verbosity) => ({ + debug: () => verbosity + '-debug', + info: () => verbosity + '-info', + warn: () => verbosity + '-warn', + error: () => verbosity + '-error' + }); + registerLogger(logger, 'custom-verbose'); + + expect(Logger.info()).toEqual('custom-verbose-info'); + expect(Logger.warn()).toEqual('custom-verbose-warn'); + expect(Logger.error()).toEqual('custom-verbose-error'); + expect(Logger.debug()).toEqual('custom-verbose-debug'); + }); +}); diff --git a/packages/core/core/__tests__/utils/mask.spec.ts b/packages/core/core/__tests__/utils/mask.spec.ts new file mode 100644 index 0000000000..5227dfaa98 --- /dev/null +++ b/packages/core/core/__tests__/utils/mask.spec.ts @@ -0,0 +1,34 @@ + +import mask from '../../src/utils/logger/mask'; + +describe('[CORE - utils] mask', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('mask string', () => { + expect(mask('test string')).toEqual('t***g'); + }); + + it('mask array', () => { + expect(mask([])).toEqual('***'); + }); + + it('mask object', () => { + const obj = { + key1: 'test', + key2: 'lorem ipsum', + key3: { + key1: 'test' + }, + key4: '1234' + }; + + expect(mask(obj)).toEqual({ + key1: 't***t', + key2: 'l***m', + key3: '***', + key4: '1***4' + }); + }); +}); diff --git a/packages/core/core/__tests__/utils/nuxt/createExtendContext.spec.ts b/packages/core/core/__tests__/utils/nuxt/createExtendContext.spec.ts new file mode 100644 index 0000000000..1f46e7f97a --- /dev/null +++ b/packages/core/core/__tests__/utils/nuxt/createExtendContext.spec.ts @@ -0,0 +1,77 @@ +import { createExtendIntegrationInCtx } from '../../../src/utils/nuxt/context'; + +describe('createExtendIntegrationInCtx', () => { + it('extendContent injects vsf if not injected', () => { + const tag = 'myIntegration'; + const nuxtCtx = { + $vsf: {} + }; + const inject = jest.fn(); + + const extendContext = createExtendIntegrationInCtx({ + tag, + nuxtCtx, + inject + }); + + try { + extendContext({}); + } catch (err) { + console.log('Prevent running rest of the function with exception'); + } + + expect(inject).toHaveBeenCalledWith('vsf', expect.objectContaining({ + [`$${tag}`]: expect.any(Object) + })); + }); + + it('extendContent adds properties from props to the nuxtCtx.$vsf.$${tag}', () => { + const tag = 'myIntegration'; + const nuxtCtx = { + $vsf: { + $myIntegration: { + client: 'client', + config: 'config' + } + } + }; + const inject = jest.fn(); + + const extendContext = createExtendIntegrationInCtx({ + tag, + nuxtCtx, + inject + }); + + extendContext({ + testFieldToAdd: 15 + }); + + expect((nuxtCtx.$vsf.$myIntegration as any).testFieldToAdd).toBe(15); + }); + + it('extendContent extends api with response from applyContextToApi', () => { + const tag = 'myIntegration'; + const nuxtCtx = { + $vsf: { + $myIntegration: { + client: 'client', + config: 'config' + } + } + }; + const inject = jest.fn(); + + const extendContext = createExtendIntegrationInCtx({ + tag, + nuxtCtx, + inject + }); + + extendContext({ + testFieldToAdd: 15 + }); + + expect((nuxtCtx.$vsf.$myIntegration as any).testFieldToAdd).toBe(15); + }); +}); diff --git a/packages/core/core/__tests__/utils/nuxt/createInjectInContext.spec.ts b/packages/core/core/__tests__/utils/nuxt/createInjectInContext.spec.ts new file mode 100644 index 0000000000..a9c7290ca7 --- /dev/null +++ b/packages/core/core/__tests__/utils/nuxt/createInjectInContext.spec.ts @@ -0,0 +1,42 @@ +import { createAddIntegrationToCtx } from '../../../src/utils/nuxt/context'; +jest.mock('../../../src/utils/context', () => ({ + applyContextToApi: jest.fn() +})); + +describe('createAddIntegrationToCtx', () => { + it('injectInContext uses inject if nuxtCtx.$vsf does not exist', () => { + const tag = 'myIntegration'; + const nuxtCtx = {}; + const inject = jest.fn(); + + const injectInContext = createAddIntegrationToCtx({ + tag, + nuxtCtx, + inject + }); + + injectInContext('props'); + + expect(inject).toHaveBeenCalledWith('vsf', expect.objectContaining({ + [`$${tag}`]: 'props' + })); + }); + + it('injectInContext adds property if property does not exist in nuxtCtx.$vsf', () => { + const tag = 'myIntegration'; + const nuxtCtx = { + $vsf: {} + }; + const inject = jest.fn(); + + const injectInContext = createAddIntegrationToCtx({ + tag, + nuxtCtx, + inject + }); + + injectInContext('props'); + + expect(nuxtCtx.$vsf[`$${tag}`]).toBe('props'); + }); +}); diff --git a/packages/core/core/__tests__/utils/shared.spec.ts b/packages/core/core/__tests__/utils/shared.spec.ts new file mode 100644 index 0000000000..e64bc22011 --- /dev/null +++ b/packages/core/core/__tests__/utils/shared.spec.ts @@ -0,0 +1,52 @@ + +import { sharedRef } from '../../src/utils/shared'; +import { vsfRef, useVSFContext } from '../../src/utils'; + +describe('[CORE - utils] shared', () => { + beforeEach(() => { + jest.clearAllMocks(); + const $sharedRefsMap = new Map(); + (useVSFContext as any).mockImplementation(() => ({ $sharedRefsMap })); + }); + + it('returns same instance', () => { + const someRef1 = sharedRef('test', 'test-key'); + const someRef2 = sharedRef('test', 'test-key'); + someRef1.value = 'test-update'; + + expect(someRef1).toEqual(someRef2); + expect(vsfRef).toBeCalledWith('test', 'test-key'); + }); + + it('different instances are not equal', () => { + const someRef1 = sharedRef('test', 'test-key1'); + const someRef2 = sharedRef('test', 'test-key2'); + someRef1.value = 'test-update'; + + expect(someRef1).not.toEqual(someRef2); + expect(vsfRef).toBeCalledWith('test', 'test-key1'); + expect(vsfRef).toBeCalledWith('test', 'test-key2'); + + }); + + it('get shared ref', () => { + const someRef1 = sharedRef('test', 'test-key3'); + const someRef2 = sharedRef('test-key3'); + someRef1.value = 'test-update-3'; + + expect(someRef1).toEqual(someRef2); + expect(vsfRef).toBeCalledWith('test', 'test-key3'); + expect(vsfRef).toBeCalledWith('test', 'test-key3'); + }); + + it('assign a value when ref does not exist', () => { + const someRef1 = sharedRef('no-exist-key'); + someRef1.value = 'no-exist-update'; + + expect(someRef1.value).toEqual('no-exist-update'); + expect(vsfRef).toBeCalledWith(null, 'no-exist-key'); + + const someRef2 = sharedRef('no-exist-key'); + expect(someRef1).toEqual(someRef2); + }); +}); diff --git a/packages/core/core/__tests__/utils/wrap.spec.ts b/packages/core/core/__tests__/utils/wrap.spec.ts new file mode 100644 index 0000000000..815999e2ea --- /dev/null +++ b/packages/core/core/__tests__/utils/wrap.spec.ts @@ -0,0 +1,15 @@ +import { ref } from '@vue/composition-api'; +import wrap from '../../src/utils/wrap'; + +describe('[CORE utils] wrap', () => { + it('should return ref when passed ref', () => { + const element = ref('test-value'); + const unwrappedValue = wrap(element); + expect(unwrappedValue.value).toBe('test-value'); + }); + it('should return ref when passed value', () => { + const element = 'test-value'; + const unwrappedValue = wrap(element); + expect(unwrappedValue.value).toBe('test-value'); + }); +}); diff --git a/packages/core/core/api-extractor.json b/packages/core/core/api-extractor.json new file mode 100644 index 0000000000..3bafd4baa2 --- /dev/null +++ b/packages/core/core/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../api-extractor.base.json", + "mainEntryPointFilePath": "./lib/src/index.d.ts", + "dtsRollup": { + "untrimmedFilePath": "./lib/src/.d.ts" + }, + "docModel": { + "apiJsonFilePath": "/core/docs/core/api-reference/.api.json" + } +} \ No newline at end of file diff --git a/packages/core/core/jest.config.js b/packages/core/core/jest.config.js new file mode 100644 index 0000000000..721a788cd9 --- /dev/null +++ b/packages/core/core/jest.config.js @@ -0,0 +1,9 @@ +const baseConfig = require('./../../jest.base.config'); + +module.exports = { + ...baseConfig, + collectCoverage: true, + setupFilesAfterEnv: [ + './__tests__/setup.ts' + ] +}; diff --git a/packages/core/core/package.json b/packages/core/core/package.json new file mode 100644 index 0000000000..20eecb59a5 --- /dev/null +++ b/packages/core/core/package.json @@ -0,0 +1,38 @@ +{ + "name": "@vue-storefront/core", + "version": "2.4.1", + "sideEffects": false, + "main": "lib/index.cjs.js", + "module": "lib/index.es.js", + "mainServer": "server/index.js", + "types": "lib/src/index.d.ts", + "scripts": { + "build": "rimraf lib && rollup -c rollup.config.js", + "dev": "rimraf lib && rollup -c rollup.config.js -w", + "test": "jest", + "prepublish": "yarn build" + }, + "dependencies": { + "@vue/composition-api": "1.0.0-beta.21", + "axios": "0.21.1", + "express": "^4.17.1", + "is-https": "^3.0.2", + "lodash-es": "^4.17.15", + "vue": "^2.6.11" + }, + "devDependencies": { + "@nuxt/types": "^2.15.7", + "@types/express": "^4.11.1", + "@vue/test-utils": "^1.0.0-beta.30", + "jsdom": "^16.6.0", + "jsdom-global": "^3.0.2", + "vue-template-compiler": "^2.6.x" + }, + "files": [ + "lib/**/*", + "server/**/*" + ], + "publishConfig": { + "access": "public" + } +} diff --git a/packages/core/core/rollup.config.js b/packages/core/core/rollup.config.js new file mode 100644 index 0000000000..2f4aaa64ab --- /dev/null +++ b/packages/core/core/rollup.config.js @@ -0,0 +1,38 @@ +import pkg from './package.json'; +import typescript from 'rollup-plugin-typescript2'; +import replace from '@rollup/plugin-replace'; + +export function generateBaseConfig(pkg) { + return { + input: 'src/index.ts', + output: [ + { + file: pkg.main, + format: 'cjs', + sourcemap: true + }, + { + file: pkg.module, + format: 'es', + sourcemap: true + } + ], + external: [ + ...Object.keys(pkg.dependencies || {}) + ], + plugins: [ + typescript({ + // eslint-disable-next-line global-require + typescript: require('typescript') + }), + replace({ + __DEV__: process.env.NODE_ENV === 'development', + delimiters: ['', ''] + }) + ] + }; +} + +const baseConfig = generateBaseConfig(pkg); + +export default baseConfig; diff --git a/packages/core/core/src/factories/apiClientFactory/context.ts b/packages/core/core/src/factories/apiClientFactory/context.ts new file mode 100644 index 0000000000..c7b48c2aa0 --- /dev/null +++ b/packages/core/core/src/factories/apiClientFactory/context.ts @@ -0,0 +1,52 @@ +import { ApiClientMethod } from './../../types'; + +interface ApplyingContextHooks { + before: ({ callName, args }) => any[]; + after: ({ callName, args, response }) => any; +} + +const nopBefore = ({ args }) => args; +const nopAfter = ({ response }) => response; + +const createExtendQuery = (context) => (customQuery, defaults) => { + const customQueries = context.customQueries || {}; + const queryArgs = customQuery || {}; + const metadata = (customQuery && customQuery.metadata) || {}; + + return Object.entries(defaults) + .reduce((prev, [queryName, initialArgs]: any) => { + const queryFn = customQueries[queryArgs[queryName]] || (() => initialArgs); + + return { + ...prev, + [queryName]: queryFn({ ...initialArgs, metadata }) + }; + }, {}); +}; + +const applyContextToApi = ( + api: Record, + context: any, + + /** + * By default we use NOP function for returning the same parameters as they come. + * It's useful in extensions, when someone don't want to inject into changing arguments or the response, + * in that case, we use default function, to handle that scenario - NOP + */ + hooks: ApplyingContextHooks = { before: nopBefore, after: nopAfter } +) => + Object.entries(api) + .reduce((prev, [callName, fn]: any) => ({ + ...prev, + [callName]: async (...args) => { + const extendQuery = createExtendQuery(context); + const transformedArgs = hooks.before({ callName, args }); + const apiClientContext = { ...context, extendQuery }; + const response = await fn(apiClientContext, ...transformedArgs); + const transformedResponse = hooks.after({ callName, args, response }); + + return transformedResponse; + } + }), {}); + +export { applyContextToApi }; diff --git a/packages/core/core/src/factories/apiClientFactory/index.ts b/packages/core/core/src/factories/apiClientFactory/index.ts new file mode 100644 index 0000000000..d92be6891a --- /dev/null +++ b/packages/core/core/src/factories/apiClientFactory/index.ts @@ -0,0 +1,61 @@ +import { + ApiClientFactoryParams, + ApiClientConfig, + ApiInstance, + ApiClientFactory, + ApiClientExtension +} from './../../types'; +import { Logger } from './../../utils'; +import { applyContextToApi } from './context'; + +const isFn = (x) => typeof x === 'function'; + +const apiClientFactory = (factoryParams: ApiClientFactoryParams): ApiClientFactory => { + function createApiClient (config: any, customApi: any = {}): ApiInstance { + const rawExtensions: ApiClientExtension[] = this?.middleware?.extensions || []; + const lifecycles = Object.values(rawExtensions) + .filter(ext => isFn(ext.hooks)) + .map(({ hooks }) => hooks(this?.middleware?.req, this?.middleware?.res)); + const extendedApis = Object.keys(rawExtensions) + .reduce((prev, curr) => ({ ...prev, ...rawExtensions[curr].extendApiMethods }), customApi); + + const _config = lifecycles + .filter(ext => isFn(ext.beforeCreate)) + .reduce((prev, curr) => curr.beforeCreate({ configuration: prev }), config); + + const settings = factoryParams.onCreate ? factoryParams.onCreate(_config) : { config, client: config.client }; + + Logger.debug('apiClientFactory.create', settings); + + settings.config = lifecycles + .filter(ext => isFn(ext.afterCreate)) + .reduce((prev, curr) => curr.afterCreate({ configuration: prev }), settings.config); + + const extensionHooks = { + before: (params) => lifecycles + .filter(e => isFn(e.beforeCall)) + .reduce((args, e) => e.beforeCall({ ...params, configuration: settings.config, args}), params.args), + after: (params) => lifecycles + .filter(e => isFn(e.afterCall)) + .reduce((response, e) => e.afterCall({ ...params, configuration: settings.config, response }), params.response) + }; + + const api = applyContextToApi( + { ...factoryParams.api, ...extendedApis }, + { ...settings, ...this?.middleware || {} }, + extensionHooks + ); + + return { + api, + client: settings.client, + settings: settings.config + }; + } + + (createApiClient as any)._predefinedExtensions = factoryParams.extensions || []; + + return { createApiClient }; +}; + +export { apiClientFactory }; diff --git a/packages/core/core/src/factories/index.ts b/packages/core/core/src/factories/index.ts new file mode 100644 index 0000000000..1dfe09c667 --- /dev/null +++ b/packages/core/core/src/factories/index.ts @@ -0,0 +1,19 @@ +export * from './apiClientFactory'; +export * from './useBillingFactory'; +export * from './useCartFactory'; +export * from './useCategoryFactory'; +export * from './useContentFactory'; +export * from './useFacetFactory'; +export * from './useMakeOrderFactory'; +export * from './useProductFactory'; +export * from './useReviewFactory'; +export * from './useShippingFactory'; +export * from './useUserBillingFactory'; +export * from './useUserFactory'; +export * from './useUserOrderFactory'; +export * from './useUserShippingFactory'; +export * from './useWishlistFactory'; +export * from './useShippingProviderFactory'; +export * from './useForgotPasswordFactory'; +export * from './useStoreFactory'; +export * from './useSearchFactory'; diff --git a/packages/core/core/src/factories/useBillingFactory.ts b/packages/core/core/src/factories/useBillingFactory.ts new file mode 100644 index 0000000000..ce45a3ec4d --- /dev/null +++ b/packages/core/core/src/factories/useBillingFactory.ts @@ -0,0 +1,67 @@ +import { UseBilling, Context, FactoryParams, UseBillingErrors, CustomQuery, PlatformApi } from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseBillingParams extends FactoryParams { + load: (context: Context, params: { customQuery?: CustomQuery }) => Promise; + save: (context: Context, params: { params: BILLING_PARAMS; billingDetails: BILLING; customQuery?: CustomQuery }) => Promise; +} + +export const useBillingFactory = ( + factoryParams: UseBillingParams +) => { + return function useBilling (): UseBilling { + const loading: Ref = sharedRef(false, 'useBilling-loading'); + const billing: Ref = sharedRef(null, 'useBilling-billing'); + const error: Ref = sharedRef({ + load: null, + save: null + }, 'useBilling-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: billing, alias: 'currentBilling', loading, error } + ); + + const load = async ({ customQuery = null } = {}) => { + Logger.debug('useBilling.load'); + + try { + loading.value = true; + const billingInfo = await _factoryParams.load({ customQuery }); + error.value.load = null; + billing.value = billingInfo; + } catch (err) { + error.value.load = err; + Logger.error('useBilling/load', err); + } finally { + loading.value = false; + } + }; + + const save = async (saveParams) => { + Logger.debug('useBilling.save'); + + try { + loading.value = true; + const billingInfo = await _factoryParams.save(saveParams); + error.value.save = null; + billing.value = billingInfo; + } catch (err) { + error.value.save = err; + Logger.error('useBilling/save', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + billing: computed(() => billing.value), + loading: computed(() => loading.value), + error: computed(() => error.value), + load, + save + }; + }; +}; diff --git a/packages/core/core/src/factories/useCartFactory.ts b/packages/core/core/src/factories/useCartFactory.ts new file mode 100644 index 0000000000..189fdfe3b0 --- /dev/null +++ b/packages/core/core/src/factories/useCartFactory.ts @@ -0,0 +1,226 @@ +import { CustomQuery, UseCart, Context, FactoryParams, UseCartErrors, PlatformApi } from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseCartFactoryParams extends FactoryParams { + load: (context: Context, params: { customQuery?: any }) => Promise; + addItem: ( + context: Context, + params: { + currentCart: CART; + product: PRODUCT; + quantity: any; + customQuery?: CustomQuery; + } + ) => Promise; + removeItem: (context: Context, params: { currentCart: CART; product: CART_ITEM; customQuery?: CustomQuery }) => Promise; + updateItemQty: ( + context: Context, + params: { currentCart: CART; product: CART_ITEM; quantity: number; customQuery?: CustomQuery } + ) => Promise; + clear: (context: Context, params: { currentCart: CART }) => Promise; + applyCoupon: (context: Context, params: { currentCart: CART; couponCode: string; customQuery?: CustomQuery }) => Promise<{ updatedCart: CART }>; + removeCoupon: ( + context: Context, + params: { currentCart: CART; couponCode: string; customQuery?: CustomQuery } + ) => Promise<{ updatedCart: CART }>; + isInCart: (context: Context, params: { currentCart: CART; product: PRODUCT }) => boolean; +} + +export const useCartFactory = ( + factoryParams: UseCartFactoryParams +) => { + return function useCart (): UseCart { + const loading: Ref = sharedRef(false, 'useCart-loading'); + const cart: Ref = sharedRef(null, 'useCart-cart'); + const error: Ref = sharedRef({ + addItem: null, + removeItem: null, + updateItemQty: null, + load: null, + clear: null, + applyCoupon: null, + removeCoupon: null + }, 'useCart-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: cart, alias: 'currentCart', loading, error } + ); + + const setCart = (newCart: CART) => { + cart.value = newCart; + Logger.debug('useCartFactory.setCart', newCart); + }; + + const addItem = async ({ product, quantity, customQuery }) => { + Logger.debug('useCart.addItem', { product, quantity }); + + try { + loading.value = true; + const updatedCart = await _factoryParams.addItem({ + currentCart: cart.value, + product, + quantity, + customQuery + }); + error.value.addItem = null; + cart.value = updatedCart; + } catch (err) { + error.value.addItem = err; + Logger.error('useCart/addItem', err); + } finally { + loading.value = false; + } + }; + + const removeItem = async ({ product, customQuery }) => { + Logger.debug('useCart.removeItem', { product }); + + try { + loading.value = true; + const updatedCart = await _factoryParams.removeItem({ + currentCart: cart.value, + product, + customQuery + }); + error.value.removeItem = null; + cart.value = updatedCart; + } catch (err) { + error.value.removeItem = err; + Logger.error('useCart/removeItem', err); + } finally { + loading.value = false; + } + }; + + const updateItemQty = async ({ product, quantity, customQuery }) => { + Logger.debug('useCart.updateItemQty', { product, quantity }); + + if (quantity && quantity > 0) { + try { + loading.value = true; + const updatedCart = await _factoryParams.updateItemQty({ + currentCart: cart.value, + product, + quantity, + customQuery + }); + error.value.updateItemQty = null; + cart.value = updatedCart; + } catch (err) { + error.value.updateItemQty = err; + Logger.error('useCart/updateItemQty', err); + } finally { + loading.value = false; + } + } + }; + + const load = async ({ customQuery } = { customQuery: undefined }) => { + Logger.debug('useCart.load'); + + if (cart.value) { + + /** + * Triggering change for hydration purpose, + * temporary issue related with cpapi plugin + */ + loading.value = false; + error.value.load = null; + cart.value = { ...cart.value }; + return; + } + try { + loading.value = true; + cart.value = await _factoryParams.load({ customQuery }); + error.value.load = null; + } catch (err) { + error.value.load = err; + Logger.error('useCart/load', err); + } finally { + loading.value = false; + } + }; + + const clear = async () => { + Logger.debug('useCart.clear'); + + try { + loading.value = true; + const updatedCart = await _factoryParams.clear({ currentCart: cart.value }); + error.value.clear = null; + cart.value = updatedCart; + } catch (err) { + error.value.clear = err; + Logger.error('useCart/clear', err); + } finally { + loading.value = false; + } + }; + + const isInCart = ({ product }) => { + return _factoryParams.isInCart({ + currentCart: cart.value, + product + }); + }; + + const applyCoupon = async ({ couponCode, customQuery }) => { + Logger.debug('useCart.applyCoupon'); + + try { + loading.value = true; + const { updatedCart } = await _factoryParams.applyCoupon({ + currentCart: cart.value, + couponCode, + customQuery + }); + error.value.applyCoupon = null; + cart.value = updatedCart; + } catch (err) { + error.value.applyCoupon = err; + Logger.error('useCart/applyCoupon', err); + } finally { + loading.value = false; + } + }; + + const removeCoupon = async ({ couponCode, customQuery }) => { + Logger.debug('useCart.removeCoupon'); + + try { + loading.value = true; + const { updatedCart } = await _factoryParams.removeCoupon({ + currentCart: cart.value, + couponCode, + customQuery + }); + error.value.removeCoupon = null; + cart.value = updatedCart; + loading.value = false; + } catch (err) { + error.value.removeCoupon = err; + Logger.error('useCart/removeCoupon', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + setCart, + cart: computed(() => cart.value), + isInCart, + addItem, + load, + removeItem, + clear, + updateItemQty, + applyCoupon, + removeCoupon, + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +}; diff --git a/packages/core/core/src/factories/useCategoryFactory.ts b/packages/core/core/src/factories/useCategoryFactory.ts new file mode 100644 index 0000000000..a7e09ab903 --- /dev/null +++ b/packages/core/core/src/factories/useCategoryFactory.ts @@ -0,0 +1,51 @@ +import { CustomQuery, UseCategory, Context, FactoryParams, UseCategoryErrors, PlatformApi } from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseCategoryFactoryParams< + CATEGORY, + CATEGORY_SEARCH_PARAMS, + API extends PlatformApi = any +> extends FactoryParams { + categorySearch: (context: Context, params: CATEGORY_SEARCH_PARAMS & { customQuery?: CustomQuery }) => Promise; +} + +export function useCategoryFactory( + factoryParams: UseCategoryFactoryParams +) { + return function useCategory(id: string): UseCategory { + const categories: Ref = sharedRef([], `useCategory-categories-${id}`); + const loading = sharedRef(false, `useCategory-loading-${id}`); + const error: Ref = sharedRef({ + search: null + }, `useCategory-error-${id}`); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: categories, alias: 'currentCategories', loading, error } + ); + + const search = async (searchParams) => { + Logger.debug(`useCategory/${id}/search`, searchParams); + + try { + loading.value = true; + categories.value = await _factoryParams.categorySearch(searchParams); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error(`useCategory/${id}/search`, err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + search, + loading: computed(() => loading.value), + categories: computed(() => categories.value), + error: computed(() => error.value) + }; + }; +} diff --git a/packages/core/core/src/factories/useContentFactory.ts b/packages/core/core/src/factories/useContentFactory.ts new file mode 100644 index 0000000000..8b15772f92 --- /dev/null +++ b/packages/core/core/src/factories/useContentFactory.ts @@ -0,0 +1,80 @@ +import { Ref, computed } from '@vue/composition-api'; +import { RenderComponent, UseContent, Context, FactoryParams, UseContentErrors, PlatformApi } from '../types'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; +import { PropOptions, VNode } from 'vue'; + +export interface UseContentFactoryParams< + CONTENT, + CONTENT_SEARCH_PARAMS, + API extends PlatformApi = any +> extends FactoryParams { + search: (context: Context, params: CONTENT_SEARCH_PARAMS) => Promise; +} + +export function useContentFactory( + factoryParams: UseContentFactoryParams +) { + return function useContent(id: string): UseContent { + const content: Ref = sharedRef([], `useContent-content-${id}`); + const loading: Ref = sharedRef(false, `useContent-loading-${id}`); + const error: Ref = sharedRef({ + search: null + }, `useContent-error-${id}`); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: content, alias: 'currentContent', loading, error } + ); + + const search = async(params: CONTENT_SEARCH_PARAMS): Promise => { + Logger.debug(`useContent/${id}/search`, params); + + try { + loading.value = true; + content.value = await _factoryParams.search(params); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error(`useContent/${id}/search`, err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + search, + content: computed(() => content.value), + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +} + +export declare type RenderContentFactoryParams = { + extractContent: (content) => CONTENT; +}; + +export function renderContentFactory( + factoryParams: RenderContentFactoryParams +) { + return { + render: function render(createElement) { + const components: VNode[] = []; + // eslint-disable-next-line + const self = this; + const content = self.content; + const resolvedContent: RenderComponent[] = factoryParams.extractContent(content); + resolvedContent.map(function component(component: RenderComponent) { + const { componentName, props } = component; + components.push(createElement(componentName, { attrs: { name: componentName }, props }, self.$slots.default)); + }); + return createElement('div', components); + }, + props: { + content: { + type: [Array, Object] + } as PropOptions<[] | any> + } + }; +} diff --git a/packages/core/core/src/factories/useFacetFactory.ts b/packages/core/core/src/factories/useFacetFactory.ts new file mode 100644 index 0000000000..3cc378b6fa --- /dev/null +++ b/packages/core/core/src/factories/useFacetFactory.ts @@ -0,0 +1,47 @@ +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, vsfRef, Logger, configureFactoryParams } from '../utils'; +import { UseFacet, FacetSearchResult, AgnosticFacetSearchParams, Context, FactoryParams, UseFacetErrors } from '../types'; + +export interface UseFacetFactoryParams extends FactoryParams { + search: (context: Context, params?: FacetSearchResult) => Promise; +} + +const useFacetFactory = (factoryParams: UseFacetFactoryParams) => { + + const useFacet = (id?: string): UseFacet => { + const ssrKey = id || 'useFacet'; + const loading: Ref = vsfRef(false, `${ssrKey}-loading`); + const result: Ref> = vsfRef({ data: null, input: null }, `${ssrKey}-facets`); + const _factoryParams = configureFactoryParams(factoryParams); + const error: Ref = sharedRef({ + search: null + }, `useFacet-error-${id}`); + + const search = async (params?: AgnosticFacetSearchParams) => { + Logger.debug(`useFacet/${ssrKey}/search`, params); + + result.value.input = params; + try { + loading.value = true; + result.value.data = await _factoryParams.search(result.value); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error(`useFacet/${ssrKey}/search`, err); + } finally { + loading.value = false; + } + }; + + return { + result: computed(() => result.value), + loading: computed(() => loading.value), + error: computed(() => error.value), + search + }; + }; + + return useFacet; +}; + +export { useFacetFactory }; diff --git a/packages/core/core/src/factories/useForgotPasswordFactory.ts b/packages/core/core/src/factories/useForgotPasswordFactory.ts new file mode 100644 index 0000000000..34c065d319 --- /dev/null +++ b/packages/core/core/src/factories/useForgotPasswordFactory.ts @@ -0,0 +1,72 @@ +import { CustomQuery, Context, FactoryParams, UseForgotPasswordErrors, UseForgotPassword } from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +interface SetNewPasswordParams { + tokenValue: string; + newPassword: string; +} + +interface ResetPasswordParams { + email: string; +} + +export interface UseForgotPasswordFactoryParams extends FactoryParams { + resetPassword: (context: Context, params: ResetPasswordParams & { currentResult: RESULT, customQuery?: CustomQuery }) => Promise; + setNewPassword: (context: Context, params: SetNewPasswordParams & { currentResult: RESULT, customQuery?: CustomQuery }) => Promise; +} + +export function useForgotPasswordFactory( + factoryParams: UseForgotPasswordFactoryParams +) { + return function useForgotPassword(): UseForgotPassword { + const result: Ref = sharedRef({ + resetPasswordResult: null, + setNewPasswordResult: null + }, 'useForgotPassword-result'); + const loading = sharedRef(false, 'useProduct-loading'); + const _factoryParams = configureFactoryParams(factoryParams); + const error: Ref = sharedRef({ + request: null, + setNew: null + }, 'useForgotPassword-error'); + + const request = async (resetPasswordParams: ResetPasswordParams) => { + Logger.debug('useForgotPassword/request', resetPasswordParams.email); + + try { + loading.value = true; + result.value = await _factoryParams.resetPassword({ currentResult: result.value, ...resetPasswordParams }); + error.value.request = null; + } catch (err) { + error.value.request = err; + Logger.error('useForgotPassword/request', err); + } finally { + loading.value = false; + } + }; + + const setNew = async (setNewPasswordParams: SetNewPasswordParams) => { + Logger.debug('useForgotPassword/setNew', setNewPasswordParams); + + try { + loading.value = true; + result.value = await _factoryParams.setNewPassword({ currentResult: result.value, ...setNewPasswordParams }); + error.value.setNew = null; + } catch (err) { + error.value.setNew = err; + Logger.error('useForgotPassword/setNew', err); + } finally { + loading.value = false; + } + }; + + return { + request, + setNew, + result: computed(() => result.value), + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +} diff --git a/packages/core/core/src/factories/useMakeOrderFactory.ts b/packages/core/core/src/factories/useMakeOrderFactory.ts new file mode 100644 index 0000000000..40f125bff6 --- /dev/null +++ b/packages/core/core/src/factories/useMakeOrderFactory.ts @@ -0,0 +1,48 @@ +import { computed, Ref } from '@vue/composition-api'; +import { CustomQuery, Context, FactoryParams, UseMakeOrder, UseMakeOrderErrors, PlatformApi } from '../types'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseMakeOrderFactoryParams extends FactoryParams { + make: (context: Context, params: { customQuery?: CustomQuery }) => Promise; +} + +export const useMakeOrderFactory = ( + factoryParams: UseMakeOrderFactoryParams +) => { + return function useMakeOrder(): UseMakeOrder { + const order: Ref = sharedRef(null, 'useMakeOrder-order'); + const loading: Ref = sharedRef(false, 'useMakeOrder-loading'); + const error: Ref = sharedRef({ + make: null + }, 'useMakeOrder-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: order, alias: 'currentOrder', loading, error } + ); + + const make = async (params = { customQuery: null }) => { + Logger.debug('useMakeOrder.make'); + + try { + loading.value = true; + const createdOrder = await _factoryParams.make(params); + error.value.make = null; + order.value = createdOrder; + } catch (err) { + error.value.make = err; + Logger.error('useMakeOrder.make', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + order, + make, + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +}; diff --git a/packages/core/core/src/factories/useProductFactory.ts b/packages/core/core/src/factories/useProductFactory.ts new file mode 100644 index 0000000000..5041e0a1c9 --- /dev/null +++ b/packages/core/core/src/factories/useProductFactory.ts @@ -0,0 +1,57 @@ +import { + CustomQuery, + ProductsSearchParams, + UseProduct, + Context, + FactoryParams, + UseProductErrors, + PlatformApi +} from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; +export interface UseProductFactoryParams< +PRODUCTS, +PRODUCT_SEARCH_PARAMS extends ProductsSearchParams, +API extends PlatformApi = any +> extends FactoryParams { + productsSearch: (context: Context, params: PRODUCT_SEARCH_PARAMS & { customQuery?: CustomQuery }) => Promise; +} + +export function useProductFactory( + factoryParams: UseProductFactoryParams +) { + return function useProduct(id: string): UseProduct { + const products: Ref = sharedRef([], `useProduct-products-${id}`); + const loading = sharedRef(false, `useProduct-loading-${id}`); + const error: Ref = sharedRef({ + search: null + }, `useProduct-error-${id}`); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: products, alias: 'currentProducts', loading, error } + ); + + const search = async (searchParams) => { + Logger.debug(`useProduct/${id}/search`, searchParams); + + try { + loading.value = true; + products.value = await _factoryParams.productsSearch(searchParams); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error(`useProduct/${id}/search`, err); + } finally { + loading.value = false; + } + }; + + return { + search, + products: computed(() => products.value), + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +} diff --git a/packages/core/core/src/factories/useReviewFactory.ts b/packages/core/core/src/factories/useReviewFactory.ts new file mode 100644 index 0000000000..e0c4690e50 --- /dev/null +++ b/packages/core/core/src/factories/useReviewFactory.ts @@ -0,0 +1,70 @@ +import { Ref, computed } from '@vue/composition-api'; +import { CustomQuery, UseReview, Context, FactoryParams, UseReviewErrors, PlatformApi } from '../types'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseReviewFactoryParams< + REVIEW, + REVIEWS_SEARCH_PARAMS, + REVIEW_ADD_PARAMS, + API extends PlatformApi = any +> extends FactoryParams { + searchReviews: (context: Context, params: REVIEWS_SEARCH_PARAMS & { customQuery?: CustomQuery }) => Promise; + addReview: (context: Context, params: REVIEW_ADD_PARAMS & { customQuery?: CustomQuery }) => Promise; +} + +export function useReviewFactory( + factoryParams: UseReviewFactoryParams +) { + return function useReview(id: string): UseReview { + const reviews: Ref = sharedRef([], `useReviews-reviews-${id}`); + const loading: Ref = sharedRef(false, `useReviews-loading-${id}`); + const error: Ref = sharedRef({ + search: null, + addReview: null + }, `useReviews-error-${id}`); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: reviews, alias: 'currentReviews', loading, error } + ); + + const search = async (searchParams): Promise => { + Logger.debug(`useReview/${id}/search`, searchParams); + + try { + loading.value = true; + reviews.value = await _factoryParams.searchReviews(searchParams); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error(`useReview/${id}/search`, err); + } finally { + loading.value = false; + } + }; + + const addReview = async (params): Promise => { + Logger.debug(`useReview/${id}/addReview`, params); + + try { + loading.value = true; + reviews.value = await _factoryParams.addReview(params); + error.value.addReview = null; + } catch (err) { + error.value.addReview = err; + Logger.error(`useReview/${id}/addReview`, err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + search, + addReview, + reviews: computed(() => reviews.value), + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +} diff --git a/packages/core/core/src/factories/useSearchFactory.ts b/packages/core/core/src/factories/useSearchFactory.ts new file mode 100644 index 0000000000..d60811b6ea --- /dev/null +++ b/packages/core/core/src/factories/useSearchFactory.ts @@ -0,0 +1,42 @@ +import { CustomQuery, UseSearch, Context, FactoryParams, UseSearchErrors } from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseSearchFactoryParams extends FactoryParams { + search: (context: Context, params: SEARCH_PARAMS & { customQuery?: CustomQuery }) => Promise; +} + +export function useSearchFactory( + factoryParams: UseSearchFactoryParams +) { + return function useSearch(id: string): UseSearch { + const result: Ref = sharedRef([], `useSearch-products-${id}`); + const loading = sharedRef(false, `useSearch-loading-${id}`); + const _factoryParams = configureFactoryParams(factoryParams); + const error: Ref = sharedRef({ + search: null + }, `useSearch-error-${id}`); + + const search = async (searchParams) => { + Logger.debug(`useSearch/${id}/search`, searchParams); + + try { + loading.value = true; + result.value = await _factoryParams.search(searchParams); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error(`useSearch/${id}/search`, err); + } finally { + loading.value = false; + } + }; + + return { + search, + result: computed(() => result.value), + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +} diff --git a/packages/core/core/src/factories/useShippingFactory.ts b/packages/core/core/src/factories/useShippingFactory.ts new file mode 100644 index 0000000000..01bf3ac58c --- /dev/null +++ b/packages/core/core/src/factories/useShippingFactory.ts @@ -0,0 +1,71 @@ +import { UseShipping, Context, FactoryParams, UseShippingErrors, CustomQuery, PlatformApi } from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseShippingParams< + SHIPPING, + SHIPPING_PARAMS, + API extends PlatformApi = any +> extends FactoryParams { + load: (context: Context, params: { customQuery?: CustomQuery }) => Promise; + save: (context: Context, params: { params: SHIPPING_PARAMS; shippingDetails: SHIPPING; customQuery?: CustomQuery }) => Promise; +} + +export const useShippingFactory = ( + factoryParams: UseShippingParams +) => { + return function useShipping (): UseShipping { + const loading: Ref = sharedRef(false, 'useShipping-loading'); + const shipping: Ref = sharedRef(null, 'useShipping-shipping'); + const error: Ref = sharedRef({ + load: null, + save: null + }, 'useShipping-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: shipping, alias: 'currentShipping', loading, error } + ); + + const load = async ({ customQuery = null } = {}) => { + Logger.debug('useShipping.load'); + + try { + loading.value = true; + const shippingInfo = await _factoryParams.load({ customQuery }); + error.value.load = null; + shipping.value = shippingInfo; + } catch (err) { + error.value.load = err; + Logger.error('useShipping/load', err); + } finally { + loading.value = false; + } + }; + + const save = async (saveParams) => { + Logger.debug('useShipping.save'); + + try { + loading.value = true; + const shippingInfo = await _factoryParams.save(saveParams); + error.value.save = null; + shipping.value = shippingInfo; + } catch (err) { + error.value.save = err; + Logger.error('useShipping/save', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + shipping: computed(() => shipping.value), + loading: computed(() => loading.value), + error: computed(() => error.value), + load, + save + }; + }; +}; diff --git a/packages/core/core/src/factories/useShippingProviderFactory.ts b/packages/core/core/src/factories/useShippingProviderFactory.ts new file mode 100644 index 0000000000..be00601637 --- /dev/null +++ b/packages/core/core/src/factories/useShippingProviderFactory.ts @@ -0,0 +1,78 @@ +import { + UseShippingProvider, + Context, + FactoryParams, + UseShippingProviderErrors, + CustomQuery, + PlatformApi +} from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseShippingProviderParams extends FactoryParams { + load: (context: Context, params: { state: Ref, customQuery?: CustomQuery }) => Promise; + save: (context: Context, params: { state: Ref, shippingMethod: SHIPPING_METHOD, customQuery?: CustomQuery }) => Promise; +} + +export const useShippingProviderFactory = ( + factoryParams: UseShippingProviderParams +) => { + return function useShippingProvider (): UseShippingProvider { + const loading: Ref = sharedRef(false, 'useShippingProvider-loading'); + const state: Ref = sharedRef(null, 'useShippingProvider-response'); + const error: Ref = sharedRef({ + load: null, + save: null + }, 'useShippingProvider-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: state, alias: 'currentState', loading, error } + ); + + const setState = (newState: STATE) => { + state.value = newState; + Logger.debug('useShippingProvider.setState', newState); + }; + + const save = async ({ shippingMethod, customQuery = null }) => { + Logger.debug('useShippingProvider.save'); + + try { + loading.value = true; + state.value = await _factoryParams.save({ shippingMethod, customQuery, state }); + error.value.save = null; + } catch (err) { + error.value.save = err; + Logger.error('useShippingProvider/save', err); + } finally { + loading.value = false; + } + }; + + const load = async ({ customQuery = null } = {}) => { + Logger.debug('useShippingProvider.load'); + + try { + loading.value = true; + state.value = await _factoryParams.load({ customQuery, state }); + error.value.load = null; + } catch (err) { + error.value.load = err; + Logger.error('useShippingProvider/load', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + state, + loading: computed(() => loading.value), + error: computed(() => error.value), + load, + save, + setState + }; + }; +}; diff --git a/packages/core/core/src/factories/useStoreFactory.ts b/packages/core/core/src/factories/useStoreFactory.ts new file mode 100644 index 0000000000..57869d0049 --- /dev/null +++ b/packages/core/core/src/factories/useStoreFactory.ts @@ -0,0 +1,67 @@ +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, configureFactoryParams, Logger } from '../utils'; +import { UseStoreFactoryParams, UseStore, UseStoreErrors } from '../types'; + +export function useStoreFactory ( + factoryParams: UseStoreFactoryParams +): UseStore { + + return function useStore () { + + /* @private */ + const _factoryParams = configureFactoryParams(factoryParams); + + /* @readonly */ + const response: Ref = sharedRef(null, 'useStore-response'); + const loading: Ref = sharedRef(false, 'useStore-loading'); + const error: Ref = sharedRef({ load: null, change: null }, 'useStore-error'); + + /* @public */ + async function load (params): Promise { + Logger.debug('useStoreFactory.load', params); + + error.value.load = null; + + try { + loading.value = true; + const { customQuery } = Object(params); + response.value = await _factoryParams.load({ + customQuery + }); + } catch (err) { + error.value.load = err; + } finally { + loading.value = false; + } + } + + async function change (params): Promise { + Logger.debug('useStoreFactory.change', params); + + error.value.change = null; + + try { + loading.value = true; + const { customQuery, currentStore, store } = Object(params); + response.value = await _factoryParams.change({ + currentStore, + store, + customQuery + }); + } catch (err) { + error.value.change = err; + } finally { + loading.value = false; + } + } + + /* @interface */ + return { + load, + change, + response: computed(() => response.value), + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +} diff --git a/packages/core/core/src/factories/useUserBillingFactory.ts b/packages/core/core/src/factories/useUserBillingFactory.ts new file mode 100644 index 0000000000..7baf348b5e --- /dev/null +++ b/packages/core/core/src/factories/useUserBillingFactory.ts @@ -0,0 +1,181 @@ +import { Ref, unref, computed } from '@vue/composition-api'; +import { + UseUserBilling, + Context, + FactoryParams, + UseUserBillingErrors, + CustomQuery, + PlatformApi +} from '../types'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseUserBillingFactoryParams< + USER_BILLING, + USER_BILLING_ITEM, + API extends PlatformApi = any +> extends FactoryParams { + addAddress: ( + context: Context, + params: { + address: Readonly; + billing: Readonly; + customQuery?: CustomQuery; + }) => Promise; + deleteAddress: ( + context: Context, + params: { + address: Readonly; + billing: Readonly; + customQuery?: CustomQuery; + }) => Promise; + updateAddress: ( + context: Context, + params: { + address: Readonly; + billing: Readonly; + customQuery?: CustomQuery; + }) => Promise; + load: ( + context: Context, + params: { + billing: Readonly; + }) => Promise; + setDefaultAddress: ( + context: Context, + params: { + address: Readonly; + billing: Readonly; + customQuery?: CustomQuery; + }) => Promise; +} + +export const useUserBillingFactory = ( + factoryParams: UseUserBillingFactoryParams +) => { + + const useUserBilling = (): UseUserBilling => { + const loading: Ref = sharedRef(false, 'useUserBilling-loading'); + const billing: Ref = sharedRef({}, 'useUserBilling-billing'); + const error: Ref = sharedRef({ + addAddress: null, + deleteAddress: null, + updateAddress: null, + load: null, + setDefaultAddress: null + }, 'useUserBilling-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: billing, alias: 'currentBilling', loading, error } + ); + + const readonlyBilling: Readonly = unref(billing); + + const addAddress = async ({ address, customQuery }) => { + Logger.debug('useUserBilling.addAddress', address); + + try { + loading.value = true; + billing.value = await _factoryParams.addAddress({ + address, + billing: readonlyBilling, + customQuery + }); + error.value.addAddress = null; + } catch (err) { + error.value.addAddress = err; + Logger.error('useUserBilling/addAddress', err); + } finally { + loading.value = false; + } + }; + + const deleteAddress = async ({ address, customQuery }) => { + Logger.debug('useUserBilling.deleteAddress', address); + + try { + loading.value = true; + billing.value = await _factoryParams.deleteAddress({ + address, + billing: readonlyBilling, + customQuery + }); + error.value.deleteAddress = null; + } catch (err) { + error.value.deleteAddress = err; + Logger.error('useUserBilling/deleteAddress', err); + } finally { + loading.value = false; + } + }; + + const updateAddress = async ({ address, customQuery }) => { + Logger.debug('useUserBilling.updateAddress', address); + + try { + loading.value = true; + billing.value = await _factoryParams.updateAddress({ + address, + billing: readonlyBilling, + customQuery + }); + error.value.updateAddress = null; + } catch (err) { + error.value.updateAddress = err; + Logger.error('useUserBilling/updateAddress', err); + } finally { + loading.value = false; + } + }; + + const load = async () => { + Logger.debug('useUserBilling.load'); + + try { + loading.value = true; + billing.value = await _factoryParams.load({ + billing: readonlyBilling + }); + error.value.load = null; + } catch (err) { + error.value.load = err; + Logger.error('useUserBilling/load', err); + } finally { + loading.value = false; + } + }; + + const setDefaultAddress = async ({ address, customQuery }) => { + Logger.debug('useUserBilling.setDefaultAddress'); + + try { + loading.value = true; + billing.value = await _factoryParams.setDefaultAddress({ + address, + billing: readonlyBilling, + customQuery + }); + error.value.setDefaultAddress = null; + } catch (err) { + error.value.setDefaultAddress = err; + Logger.error('useUserBilling/setDefaultAddress', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + billing: computed(() => billing.value), + loading: computed(() => loading.value), + error: computed(() => error.value), + addAddress, + deleteAddress, + updateAddress, + load, + setDefaultAddress + }; + }; + + return useUserBilling; +}; diff --git a/packages/core/core/src/factories/useUserFactory.ts b/packages/core/core/src/factories/useUserFactory.ts new file mode 100644 index 0000000000..cc5f57d34e --- /dev/null +++ b/packages/core/core/src/factories/useUserFactory.ts @@ -0,0 +1,170 @@ +import { Ref, computed } from '@vue/composition-api'; +import { UseUser, Context, FactoryParams, UseUserErrors, CustomQuery, PlatformApi } from '../types'; +import { sharedRef, Logger, mask, configureFactoryParams } from '../utils'; + +export interface UseUserFactoryParams< + USER, + UPDATE_USER_PARAMS, + REGISTER_USER_PARAMS, + API extends PlatformApi = any +> extends FactoryParams { + load: (context: Context, params?: { customQuery: CustomQuery }) => Promise; + logOut: (context: Context, params: {currentUser: USER}) => Promise; + updateUser: (context: Context, params: {currentUser: USER; updatedUserData: UPDATE_USER_PARAMS; customQuery?: CustomQuery}) => Promise; + register: (context: Context, params: REGISTER_USER_PARAMS & {customQuery?: CustomQuery}) => Promise; + logIn: (context: Context, params: { username: string; password: string; customQuery?: CustomQuery }) => Promise; + changePassword: (context: Context, params: {currentUser: USER; currentPassword: string; newPassword: string; customQuery?: CustomQuery}) => Promise; +} + +export const useUserFactory = < +USER, +UPDATE_USER_PARAMS, +REGISTER_USER_PARAMS extends { email: string; password: string }, +API extends PlatformApi = any +>( + factoryParams: UseUserFactoryParams + ) => { + return function useUser (): UseUser { + const errorsFactory = (): UseUserErrors => ({ + updateUser: null, + register: null, + login: null, + logout: null, + changePassword: null, + load: null + }); + + const user: Ref = sharedRef(null, 'useUser-user'); + const loading: Ref = sharedRef(false, 'useUser-loading'); + const isAuthenticated = computed(() => Boolean(user.value)); + const error: Ref = sharedRef(errorsFactory(), 'useUser-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: user, alias: 'currentUser', loading, error } + ); + + const setUser = (newUser: USER) => { + user.value = newUser; + Logger.debug('useUserFactory.setUser', newUser); + }; + + const resetErrorValue = () => { + error.value = errorsFactory(); + }; + + const updateUser = async ({ user: providedUser, customQuery }) => { + Logger.debug('useUserFactory.updateUser', providedUser); + resetErrorValue(); + + try { + loading.value = true; + user.value = await _factoryParams.updateUser({currentUser: user.value, updatedUserData: providedUser, customQuery}); + error.value.updateUser = null; + } catch (err) { + error.value.updateUser = err; + Logger.error('useUser/updateUser', err); + } finally { + loading.value = false; + } + }; + + const register = async ({ user: providedUser, customQuery }) => { + Logger.debug('useUserFactory.register', providedUser); + resetErrorValue(); + + try { + loading.value = true; + user.value = await _factoryParams.register({...providedUser, customQuery}); + error.value.register = null; + } catch (err) { + error.value.register = err; + Logger.error('useUser/register', err); + } finally { + loading.value = false; + } + }; + + const login = async ({ user: providedUser, customQuery }) => { + Logger.debug('useUserFactory.login', providedUser); + resetErrorValue(); + + try { + loading.value = true; + user.value = await _factoryParams.logIn({...providedUser, customQuery}); + error.value.login = null; + } catch (err) { + error.value.login = err; + Logger.error('useUser/login', err); + } finally { + loading.value = false; + } + }; + + const logout = async () => { + Logger.debug('useUserFactory.logout'); + resetErrorValue(); + + try { + await _factoryParams.logOut({ currentUser: user.value }); + error.value.logout = null; + user.value = null; + } catch (err) { + error.value.logout = err; + Logger.error('useUser/logout', err); + } + }; + + const changePassword = async (params) => { + Logger.debug('useUserFactory.changePassword', { currentPassword: mask(params.current), newPassword: mask(params.new) }); + resetErrorValue(); + + try { + loading.value = true; + user.value = await _factoryParams.changePassword({ + currentUser: user.value, + currentPassword: params.current, + newPassword: params.new, + customQuery: params.customQuery + }); + error.value.changePassword = null; + } catch (err) { + error.value.changePassword = err; + Logger.error('useUser/changePassword', err); + } finally { + loading.value = false; + } + }; + + const load = async ({customQuery} = {customQuery: undefined}) => { + Logger.debug('useUserFactory.load'); + resetErrorValue(); + + try { + loading.value = true; + user.value = await _factoryParams.load({customQuery}); + error.value.load = null; + } catch (err) { + error.value.load = err; + Logger.error('useUser/load', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + setUser, + user: computed(() => user.value), + updateUser, + register, + login, + logout, + isAuthenticated, + changePassword, + load, + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +}; diff --git a/packages/core/core/src/factories/useUserOrderFactory.ts b/packages/core/core/src/factories/useUserOrderFactory.ts new file mode 100644 index 0000000000..a004883bb4 --- /dev/null +++ b/packages/core/core/src/factories/useUserOrderFactory.ts @@ -0,0 +1,54 @@ +import { Ref, computed } from '@vue/composition-api'; +import { CustomQuery, UseUserOrder, Context, FactoryParams, UseUserOrderErrors, PlatformApi } from '../types'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseUserOrderFactoryParams< + ORDERS, + ORDER_SEARCH_PARAMS, + API extends PlatformApi = any +> extends FactoryParams { + searchOrders: (context: Context, params: ORDER_SEARCH_PARAMS & { customQuery?: CustomQuery }) => Promise; +} + +export function useUserOrderFactory< + ORDERS, + ORDER_SEARCH_PARAMS, + API extends PlatformApi = any +>(factoryParams: UseUserOrderFactoryParams) { + return function useUserOrder(): UseUserOrder { + const orders: Ref = sharedRef({ + results: [], + total: 0 + }, 'useUserOrder-orders'); + const loading: Ref = sharedRef(false, 'useUserOrder-loading'); + const error: Ref = sharedRef({}, 'useUserOrder-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: orders, alias: 'currentOrders', loading, error } + ); + + const search = async (searchParams): Promise => { + Logger.debug('useUserOrder.search', searchParams); + + try { + loading.value = true; + orders.value = await _factoryParams.searchOrders(searchParams); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error('useUserOrder/search', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + orders: computed(() => orders.value), + search, + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; +} diff --git a/packages/core/core/src/factories/useUserShippingFactory.ts b/packages/core/core/src/factories/useUserShippingFactory.ts new file mode 100644 index 0000000000..472f956f32 --- /dev/null +++ b/packages/core/core/src/factories/useUserShippingFactory.ts @@ -0,0 +1,180 @@ +import { Ref, unref, computed } from '@vue/composition-api'; +import { + UseUserShipping, + Context, + FactoryParams, + UseUserShippingErrors, + CustomQuery, + PlatformApi +} from '../types'; +import { sharedRef, Logger, mask, configureFactoryParams } from '../utils'; + +export interface UseUserShippingFactoryParams< + USER_SHIPPING, + USER_SHIPPING_ITEM, + API extends PlatformApi = any +> extends FactoryParams { + addAddress: ( + context: Context, + params: { + address: Readonly; + shipping: Readonly; + customQuery?: CustomQuery; + }) => Promise; + deleteAddress: ( + context: Context, + params: { + address: Readonly; + shipping: Readonly; + customQuery?: CustomQuery; + }) => Promise; + updateAddress: ( + context: Context, + params: { + address: Readonly; + shipping: Readonly; + customQuery?: CustomQuery; + }) => Promise; + load: ( + context: Context, + params: { + shipping: Readonly; + }) => Promise; + setDefaultAddress: ( + context: Context, + params: { + address: Readonly; + shipping: Readonly; + customQuery?: CustomQuery; + }) => Promise; +} + +export const useUserShippingFactory = ( + factoryParams: UseUserShippingFactoryParams +) => { + + const useUserShipping = (): UseUserShipping => { + const loading: Ref = sharedRef(false, 'useUserShipping-loading'); + const shipping: Ref = sharedRef({}, 'useUserShipping-shipping'); + const readonlyShipping: Readonly = unref(shipping); + const error: Ref = sharedRef({ + addAddress: null, + deleteAddress: null, + updateAddress: null, + load: null, + setDefaultAddress: null + }, 'useUserShipping-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: shipping, alias: 'currentShipping', loading, error } + ); + + const addAddress = async ({ address, customQuery }) => { + Logger.debug('useUserShipping.addAddress', mask(address)); + + try { + loading.value = true; + shipping.value = await _factoryParams.addAddress({ + address, + shipping: readonlyShipping, + customQuery + }); + error.value.addAddress = null; + } catch (err) { + error.value.addAddress = err; + Logger.error('useUserShipping/addAddress', err); + } finally { + loading.value = false; + } + }; + + const deleteAddress = async ({ address, customQuery }) => { + Logger.debug('useUserShipping.deleteAddress', address); + + try { + loading.value = true; + shipping.value = await _factoryParams.deleteAddress({ + address, + shipping: readonlyShipping, + customQuery + }); + error.value.deleteAddress = null; + } catch (err) { + error.value.deleteAddress = err; + Logger.error('useUserShipping/deleteAddress', err); + } finally { + loading.value = false; + } + }; + + const updateAddress = async ({ address, customQuery }) => { + Logger.debug('useUserShipping.updateAddress', address); + + try { + loading.value = true; + shipping.value = await _factoryParams.updateAddress({ + address, + shipping: readonlyShipping, + customQuery + }); + error.value.updateAddress = null; + } catch (err) { + error.value.updateAddress = err; + Logger.error('useUserShipping/updateAddress', err); + } finally { + loading.value = false; + } + }; + + const load = async () => { + Logger.debug('useUserShipping.load'); + + try { + loading.value = true; + shipping.value = await _factoryParams.load({ + shipping: readonlyShipping + }); + error.value.load = null; + } catch (err) { + error.value.load = err; + Logger.error('useUserShipping/load', err); + } finally { + loading.value = false; + } + }; + + const setDefaultAddress = async ({ address, customQuery }) => { + Logger.debug('useUserShipping.setDefaultAddress', address); + + try { + loading.value = true; + shipping.value = await _factoryParams.setDefaultAddress({ + address, + shipping: readonlyShipping, + customQuery + }); + error.value.setDefaultAddress = null; + } catch (err) { + error.value.setDefaultAddress = err; + Logger.error('useUserShipping/setDefaultAddress', err); + } finally { + loading.value = false; + } + }; + + return { + api: _factoryParams.api, + shipping: computed(() => shipping.value), + loading: computed(() => loading.value), + error: computed(() => error.value), + addAddress, + deleteAddress, + updateAddress, + load, + setDefaultAddress + }; + }; + + return useUserShipping; +}; diff --git a/packages/core/core/src/factories/useWishlistFactory.ts b/packages/core/core/src/factories/useWishlistFactory.ts new file mode 100644 index 0000000000..75dacaa880 --- /dev/null +++ b/packages/core/core/src/factories/useWishlistFactory.ts @@ -0,0 +1,152 @@ +import { UseWishlist, CustomQuery, Context, FactoryParams, UseWishlistErrors, PlatformApi } from '../types'; +import { Ref, computed } from '@vue/composition-api'; +import { sharedRef, Logger, configureFactoryParams } from '../utils'; + +export interface UseWishlistFactoryParams< + WISHLIST, + WISHLIST_ITEM, + PRODUCT, + API extends PlatformApi = any +> extends FactoryParams { + load: (context: Context, params: { customQuery?: CustomQuery }) => Promise; + addItem: ( + context: Context, + params: { + currentWishlist: WISHLIST; + product: PRODUCT; + customQuery?: CustomQuery; + }) => Promise; + removeItem: ( + context: Context, + params: { + currentWishlist: WISHLIST; + product: WISHLIST_ITEM; + customQuery?: CustomQuery; + }) => Promise; + clear: (context: Context, params: { currentWishlist: WISHLIST }) => Promise; + isInWishlist: (context: Context, params: { currentWishlist: WISHLIST; product: PRODUCT }) => boolean; +} + +export const useWishlistFactory = ( + factoryParams: UseWishlistFactoryParams +) => { + const useWishlist = (): UseWishlist => { + const loading: Ref = sharedRef(false, 'useWishlist-loading'); + const wishlist: Ref = sharedRef(null, 'useWishlist-wishlist'); + const error: Ref = sharedRef({ + addItem: null, + removeItem: null, + load: null, + clear: null + }, 'useWishlist-error'); + + const _factoryParams = configureFactoryParams( + factoryParams, + { mainRef: wishlist, alias: 'currentWishlist', loading, error } + ); + + const setWishlist = (newWishlist: WISHLIST) => { + wishlist.value = newWishlist; + Logger.debug('useWishlistFactory.setWishlist', newWishlist); + }; + + const addItem = async ({ product, customQuery }) => { + Logger.debug('useWishlist.addItem', product); + + try { + loading.value = true; + const updatedWishlist = await _factoryParams.addItem({ + currentWishlist: wishlist.value, + product, + customQuery + }); + error.value.addItem = null; + wishlist.value = updatedWishlist; + } catch (err) { + error.value.addItem = err; + Logger.error('useWishlist/addItem', err); + } finally { + loading.value = false; + } + }; + + const removeItem = async ({ product, customQuery }) => { + Logger.debug('useWishlist.removeItem', product); + + try { + loading.value = true; + const updatedWishlist = await _factoryParams.removeItem({ + currentWishlist: wishlist.value, + product, + customQuery + }); + error.value.removeItem = null; + wishlist.value = updatedWishlist; + } catch (err) { + error.value.removeItem = err; + Logger.error('useWishlist/removeItem', err); + } finally { + loading.value = false; + } + }; + + const load = async ({ customQuery } = { customQuery: undefined }) => { + Logger.debug('useWishlist.load'); + if (wishlist.value) return; + + try { + loading.value = true; + wishlist.value = await _factoryParams.load({ customQuery }); + error.value.load = null; + } catch (err) { + error.value.load = err; + Logger.error('useWishlist/load', err); + } finally { + loading.value = false; + } + }; + + const clear = async () => { + Logger.debug('useWishlist.clear'); + + try { + loading.value = true; + const updatedWishlist = await _factoryParams.clear({ + currentWishlist: wishlist.value + }); + error.value.clear = null; + wishlist.value = updatedWishlist; + } catch (err) { + error.value.clear = err; + Logger.error('useWishlist/clear', err); + } finally { + loading.value = false; + } + }; + + const isInWishlist = ({ product }) => { + Logger.debug('useWishlist.isInWishlist', product); + + return _factoryParams.isInWishlist({ + currentWishlist: wishlist.value, + product + }); + }; + + return { + api: _factoryParams.api, + wishlist: computed(() => wishlist.value), + isInWishlist, + addItem, + load, + removeItem, + clear, + setWishlist, + loading: computed(() => loading.value), + error: computed(() => error.value) + }; + }; + + return useWishlist; +}; + diff --git a/packages/core/core/src/index.ts b/packages/core/core/src/index.ts new file mode 100644 index 0000000000..3720a4b3c6 --- /dev/null +++ b/packages/core/core/src/index.ts @@ -0,0 +1,31 @@ +/** + * Core Vue Storefront 2 library. + * + * @remarks + * The `@vue-storefront/core` library is a core of the whole Vue Storefront 2 application. + * It defines common interfaces for all eCommerce integrations, factories for creating + * composables, logger, SSR helpers and more. + * + * @packageDocumentation + */ + +/* istanbul ignore file */ +export * from './utils'; +export * from './factories'; +export * from './types'; + +declare global { + interface Window { $vuestorefront: any } +} + +if (typeof window !== 'undefined') { + window.$vuestorefront = window.$vuestorefront || { integrations: [] }; +} + +export function track(id: string): void { + if (typeof window !== 'undefined') { + if (window.$vuestorefront) { + window.$vuestorefront.integrations.push(id); + } + } +} diff --git a/packages/core/core/src/types.ts b/packages/core/core/src/types.ts new file mode 100644 index 0000000000..1f97835d76 --- /dev/null +++ b/packages/core/core/src/types.ts @@ -0,0 +1,912 @@ +/* istanbul ignore file */ + +import { Ref } from '@vue/composition-api'; +import type { Request, Response } from 'express'; + +/** + * Default name of the cookie storing active localization code + */ +export const VSF_LOCALE_COOKIE = 'vsf-locale'; + +/** + * Default name of the cookie storing active currency code + */ +export const VSF_CURRENCY_COOKIE = 'vsf-currency'; + +/** + * Default name of the cookie storing active country code + */ +export const VSF_COUNTRY_COOKIE = 'vsf-country'; + +/** + * Default name of the cookie storing active store code + */ +export const VSF_STORE_COOKIE = 'vsf-store'; + +export type ComputedProperty = Readonly>>; + +export type CustomQuery = Record + +export type ComposableFunctionArgs = T & { customQuery?: CustomQuery } + +export interface ProductsSearchParams { + perPage?: number; + page?: number; + sort?: any; + term?: any; + filters?: any; + [x: string]: any; +} +export interface UseProductErrors { + search: Error; +} + +export interface UseSearchErrors { + search: Error; +} + +export interface IntegrationContext { + client: CLIENT; + config: CONFIG; + api: API; + [x: string]: any; +} + +export interface Context { + [x: string]: IntegrationContext | any; +} + +export type PlatformApi = { + [functionName: string]: (context: Context, ...args: any[]) => Promise +} + +export type ContextedPlatformApi = { + [P in keyof T]: T[P] extends (context: Context, ...arg: infer X) => Promise + ? (...arg: X) => Promise + : never +} + +export interface Composable { + api?: ContextedPlatformApi +} + +export interface UseProduct< + PRODUCTS, + PRODUCT_SEARCH_PARAMS, + API extends PlatformApi = any +> extends Composable { + products: ComputedProperty; + loading: ComputedProperty; + error: ComputedProperty; + search(params: ComposableFunctionArgs): Promise; + [x: string]: any; +} + +export interface UseForgotPasswordErrors { + request: Error; + setNew: Error; +} + +export interface UseForgotPassword { + result: ComputedProperty; + loading: ComputedProperty; + error: ComputedProperty; + setNew(params: ComposableFunctionArgs<{ tokenValue: string, newPassword: string }>): Promise; + request(params: ComposableFunctionArgs<{ email: string }>): Promise; +} + +export interface UseSearch { + result: ComputedProperty; + loading: ComputedProperty; + error: ComputedProperty; + search(params: ComposableFunctionArgs): Promise; +} + +export interface UseUserRegisterParams { + email: string; + password: string; + firstName?: string; + lastName?: string; + [x: string]: any; +} + +export interface UseUserLoginParams { + username: string; + password: string; + [x: string]: any; +} +export interface UseUserErrors { + updateUser: Error; + register: Error; + login: Error; + logout: Error; + changePassword: Error; + load: Error; +} + +export interface UseUser +< + USER, + UPDATE_USER_PARAMS, + API extends PlatformApi = any +> extends Composable { + user: ComputedProperty; + setUser: (user: USER) => void; + updateUser: (params: { user: UPDATE_USER_PARAMS; customQuery?: CustomQuery }) => Promise; + register: (params: { user: UseUserRegisterParams; customQuery?: CustomQuery }) => Promise; + login: (params: { user: UseUserLoginParams; customQuery?: CustomQuery }) => Promise; + logout: (params?: {customQuery: CustomQuery}) => Promise; + changePassword: (params: { current: string; new: string, customQuery?: CustomQuery }) => Promise; + load: (params?: {customQuery: CustomQuery}) => Promise; + isAuthenticated: Ref; + loading: ComputedProperty; + error: ComputedProperty; +} + +export interface UseUserOrderSearchParams { + id?: any; + page?: number; + perPage?: number; + [x: string]: any; +} +export interface UseUserOrderErrors { + search: Error; +} +export interface UseUserOrder< + ORDERS, + ORDER_SEARCH_PARAMS, + API extends PlatformApi = any +> extends Composable { + orders: ComputedProperty; + search(params: ComposableFunctionArgs): Promise; + loading: ComputedProperty; + error: ComputedProperty; +} + +export interface UseUserAddress extends Composable { + addresses: ComputedProperty; + totalAddresses: ComputedProperty; + addAddress: (address: ADDRESS) => Promise; + deleteAddress: (address: ADDRESS) => Promise; + updateAddress: (address: ADDRESS) => Promise; + searchAddresses: (params?: { [x: string]: any }) => Promise; + loading: ComputedProperty; +} +export interface UseUserShippingErrors { + addAddress: Error; + deleteAddress: Error; + updateAddress: Error; + load: Error; + setDefaultAddress: Error; +} +export interface UseUserShipping< +USER_SHIPPING, +USER_SHIPPING_ITEM, +API extends PlatformApi = any +> extends Composable { + shipping: ComputedProperty; + addAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery }) => Promise; + deleteAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery }) => Promise; + updateAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery}) => Promise; + load: () => Promise; + setDefaultAddress: (params: { address: USER_SHIPPING_ITEM, customQuery?: CustomQuery }) => Promise; + loading: ComputedProperty; + error: ComputedProperty; +} + +export interface UserShippingGetters { + getAddresses: (shipping: USER_SHIPPING, criteria?: Record) => USER_SHIPPING_ITEM[]; + getDefault: (shipping: USER_SHIPPING) => USER_SHIPPING_ITEM; + getTotal: (shipping: USER_SHIPPING) => number; + getPostCode: (address: USER_SHIPPING_ITEM) => string; + getStreetName: (address: USER_SHIPPING_ITEM) => string; + getStreetNumber: (address: USER_SHIPPING_ITEM) => string | number; + getCity: (address: USER_SHIPPING_ITEM) => string; + getFirstName: (address: USER_SHIPPING_ITEM) => string; + getLastName: (address: USER_SHIPPING_ITEM) => string; + getCountry: (address: USER_SHIPPING_ITEM) => string; + getPhone: (address: USER_SHIPPING_ITEM) => string; + getEmail: (address: USER_SHIPPING_ITEM) => string; + getProvince: (address: USER_SHIPPING_ITEM) => string; + getCompanyName: (address: USER_SHIPPING_ITEM) => string; + getTaxNumber: (address: USER_SHIPPING_ITEM) => string; + getId: (address: USER_SHIPPING_ITEM) => string | number; + getApartmentNumber: (address: USER_SHIPPING_ITEM) => string | number; + isDefault: (address: USER_SHIPPING_ITEM) => boolean; +} + +export interface UseUserBillingErrors { + addAddress: Error; + deleteAddress: Error; + updateAddress: Error; + load: Error; + setDefaultAddress: Error; +} +export interface UseUserBilling< + USER_BILLING, + USER_BILLING_ITEM, + API extends PlatformApi = any +> extends Composable { + billing: ComputedProperty; + addAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise; + deleteAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise; + updateAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise; + load: () => Promise; + setDefaultAddress: (params: { address: USER_BILLING_ITEM, customQuery?: CustomQuery }) => Promise; + loading: ComputedProperty; + error: ComputedProperty; +} + +export interface UserBillingGetters { + getAddresses: (billing: USER_BILLING, criteria?: Record) => USER_BILLING_ITEM[]; + getDefault: (billing: USER_BILLING) => USER_BILLING_ITEM; + getTotal: (billing: USER_BILLING) => number; + getPostCode: (address: USER_BILLING_ITEM) => string; + getStreetName: (address: USER_BILLING_ITEM) => string; + getStreetNumber: (address: USER_BILLING_ITEM) => string | number; + getCity: (address: USER_BILLING_ITEM) => string; + getFirstName: (address: USER_BILLING_ITEM) => string; + getLastName: (address: USER_BILLING_ITEM) => string; + getCountry: (address: USER_BILLING_ITEM) => string; + getPhone: (address: USER_BILLING_ITEM) => string; + getEmail: (address: USER_BILLING_ITEM) => string; + getProvince: (address: USER_BILLING_ITEM) => string; + getCompanyName: (address: USER_BILLING_ITEM) => string; + getTaxNumber: (address: USER_BILLING_ITEM) => string; + getId: (address: USER_BILLING_ITEM) => string; + getApartmentNumber: (address: USER_BILLING_ITEM) => string | number; + isDefault: (address: USER_BILLING_ITEM) => boolean; +} + +export interface UseCategoryErrors { + search: Error; +} +export interface UseCategory< + CATEGORY, + CATEGORY_SEARCH_PARAMS, + API extends PlatformApi = any +> extends Composable { + categories: ComputedProperty; + search(params: ComposableFunctionArgs): Promise; + loading: ComputedProperty; + error: ComputedProperty; +} + +export interface UseCartErrors { + addItem: Error; + removeItem: Error; + updateItemQty: Error; + load: Error; + clear: Error; + applyCoupon: Error; + removeCoupon: Error; +} +export interface UseCart +< + CART, + CART_ITEM, + PRODUCT, + API extends PlatformApi = any +> extends Composable { + cart: ComputedProperty; + setCart(cart: CART): void; + addItem(params: { product: PRODUCT; quantity: number; customQuery?: CustomQuery }): Promise; + isInCart: ({ product: PRODUCT }) => boolean; + removeItem(params: { product: CART_ITEM; customQuery?: CustomQuery }): Promise; + updateItemQty(params: { product: CART_ITEM; quantity?: number; customQuery?: CustomQuery }): Promise; + clear(): Promise; + applyCoupon(params: { couponCode: string; customQuery?: CustomQuery }): Promise; + removeCoupon(params: { couponCode: string; customQuery?: CustomQuery }): Promise; + load(): Promise; + load(params: { customQuery?: CustomQuery }): Promise; + error: ComputedProperty; + loading: ComputedProperty; +} +export interface UseWishlistErrors { + addItem: Error; + removeItem: Error; + load: Error; + clear: Error; +} +export interface UseWishlist +< + WISHLIST, + WISHLIST_ITEM, + PRODUCT, + API extends PlatformApi = any +> extends Composable { + wishlist: ComputedProperty; + loading: ComputedProperty; + addItem(params: { product: PRODUCT; customQuery?: CustomQuery }): Promise; + removeItem(params: { product: WISHLIST_ITEM; customQuery?: CustomQuery }): Promise; + load(): Promise; + load(params: { customQuery?: CustomQuery }): Promise; + clear(): Promise; + setWishlist: (wishlist: WISHLIST) => void; + isInWishlist({ product: PRODUCT }): boolean; + error: ComputedProperty; +} + +export interface UseCompare { + compare: ComputedProperty; + addToCompare: (product: PRODUCT) => Promise; + removeFromCompare: (product: PRODUCT) => Promise; + clearCompare: () => Promise; + loading: ComputedProperty; +} + +export interface UseMakeOrderErrors { + make: Error; +} + +export interface UseMakeOrder extends Composable { + order: Ref; + make(params: { customQuery?: CustomQuery }): Promise; + error: ComputedProperty; + loading: ComputedProperty; +} + +export interface UseCheckout +< + PAYMENT_METHODS, + SHIPPING_METHODS, + PERSONAL_DETAILS, + SHIPPING_DETAILS, + BILLING_DETAILS, + CHOOSEN_PAYMENT_METHOD, + CHOOSEN_SHIPPING_METHOD, + PLACE_ORDER, + API extends PlatformApi = any +> extends Composable { + paymentMethods: Ref; + shippingMethods: Ref; + personalDetails: PERSONAL_DETAILS; + shippingDetails: SHIPPING_DETAILS; + billingDetails: BILLING_DETAILS; + chosenPaymentMethod: CHOOSEN_PAYMENT_METHOD; + chosenShippingMethod: CHOOSEN_SHIPPING_METHOD; + placeOrder: PLACE_ORDER; + loading: ComputedProperty; +} +export interface UseReviewErrors { + search: Error; + addReview: Error; +} +export interface UseReview< +REVIEW, +REVIEWS_SEARCH_PARAMS, +REVIEW_ADD_PARAMS, +API extends PlatformApi = any +> extends Composable { + search(params: ComposableFunctionArgs): Promise; + addReview(params: ComposableFunctionArgs): Promise; + error: ComputedProperty; + reviews: ComputedProperty; + loading: ComputedProperty; + [x: string]: any; +} + +export interface UseShippingErrors { + load: Error; + save: Error; +} +export interface UseShipping< +SHIPPING, +SHIPPING_PARAMS, +API extends PlatformApi = any +> extends Composable { + error: ComputedProperty; + loading: ComputedProperty; + shipping: ComputedProperty; + load(): Promise; + load(params: { customQuery?: CustomQuery }): Promise; + save: (params: { params: SHIPPING_PARAMS; shippingDetails: SHIPPING; customQuery?: CustomQuery }) => Promise; +} +export interface UseShippingProviderErrors { + load: Error; + save: Error; +} +export interface UseShippingProvider< +STATE, +SHIPPING_METHOD, +API extends PlatformApi = any +> extends Composable { + error: ComputedProperty; + loading: ComputedProperty; + state: ComputedProperty; + setState(state: STATE): void; + load(): Promise; + load(params: { customQuery?: CustomQuery }): Promise; + save(params: { shippingMethod: SHIPPING_METHOD, customQuery?: CustomQuery }): Promise; +} + +export interface UseBillingErrors { + load: Error; + save: Error; +} + +export interface UseBilling< + BILLING, + BILLING_PARAMS, + API extends PlatformApi = any +> extends Composable { + error: ComputedProperty; + loading: ComputedProperty; + billing: ComputedProperty; + load(): Promise; + load(params: { customQuery?: CustomQuery }): Promise; + save: (params: { params: BILLING_PARAMS; billingDetails: BILLING; customQuery?: CustomQuery }) => Promise; +} +export interface UseFacetErrors { + search: Error; +} + +export interface AgnosticFacetSearchParams { + categorySlug?: string; + rootCatSlug?: string; + term?: string; + page?: number; + itemsPerPage?: number; + sort?: string; + filters?: Record; + metadata?: any; + [x: string]: any; +} + +export interface FacetSearchResult { + data: S; + input: AgnosticFacetSearchParams; +} +export interface UseFacet { + result: ComputedProperty>; + loading: ComputedProperty; + search: (params?: AgnosticFacetSearchParams) => Promise; + error: ComputedProperty; +} +export interface UseContentErrors { + search: Error; +} +export interface UseContent< + CONTENT, + CONTENT_SEARCH_PARAMS, + API extends PlatformApi = any +> extends Composable { + search: (params: CONTENT_SEARCH_PARAMS) => Promise; + content: ComputedProperty; + loading: ComputedProperty; + error: ComputedProperty; +} + +export interface RenderComponent { + componentName: string; + props?: any; +} + +export interface AgnosticPrice { + regular: number | null; + special?: number | null; +} + +export interface AgnosticMediaGalleryItem { + small: string; + normal: string; + big: string; +} + +export interface AgnosticAttribute { + name?: string; + value: string | Record; + label: string; +} + +export interface AgnosticBreadcrumb { + text: string; + link: string; +} + +export interface AgnosticTotals { + total: number; + subtotal: number; + special?: number; + [x: string]: unknown; +} + +export interface AgnosticCoupon { + id: string; + name: string; + code: string; + value: number; +} + +export interface AgnosticDiscount { + id: string; + name: string; + description: string; + value: number; + code?: string; +} + +export interface AgnosticCategoryTree { + label: string; + slug?: string; + items: AgnosticCategoryTree[]; + isCurrent: boolean; + count?: number; + [x: string]: unknown; +} + +export interface AgnosticFilter { + id: string; + label: string; + values: { + id: string; + isSlected?: boolean; + count?: number; + label: string; + value: string; + }[] +} + +export interface AgnosticProductReview { + id: string; + author: string; + date: Date; + message: string | null; + rating: number | null; +} + +export interface AgnosticLocale { + code: string; + label: string; + [x: string]: unknown; +} + +export interface AgnosticCountry { + code: string; + label: string; + [x: string]: unknown; +} + +export interface AgnosticCurrency { + code: string; + label: string; + prefixSign: boolean; + sign: string; + [x: string]: unknown; +} + +export interface AgnosticSortByOption { + label: string; + value: string; + [x: string]: unknown; +} + +export interface AgnosticRateCount { + rate: number; + count: number; +} + +// TODO - remove this interface +export enum AgnosticOrderStatus { + Open = 'Open', + Pending = 'Pending', + Confirmed = 'Confirmed', + Shipped = 'Shipped', + Complete = 'Complete', + Cancelled = 'Cancelled', + Refunded = 'Refunded' +} + +export interface AgnosticFacet { + type: string; + id: string; + value: any; + attrName?: string; + count?: number; + selected?: boolean; + metadata?: any; +} + +export interface AgnosticGroupedFacet { + id: string; + label: string; + count?: number; + options: AgnosticFacet[]; +} + +export interface AgnosticSort { + options: AgnosticFacet[]; + selected: string; +} + +export interface AgnosticPagination { + currentPage: number; + totalPages: number; + totalItems: number; + itemsPerPage: number; + pageOptions: number[]; +} + +export interface AgnosticAddress { + addressLine1: string; + addressLine2: string; + [x: string]: unknown; +} + +export interface AgnosticGeoLocation { + type: string; + coordinates?: unknown; + [x: string]: unknown; +} + +export interface AgnosticStore { + name: string; + id: string; + description?: string; + locales?: AgnosticLocale[]; + currencies?: AgnosticCurrency[] + address?: AgnosticAddress; + geoLocation?: AgnosticGeoLocation; + [x: string]: unknown; +} + +export interface ProductGetters { + getName: (product: PRODUCT) => string; + getSlug: (product: PRODUCT) => string; + getPrice: (product: PRODUCT) => AgnosticPrice; + getGallery: (product: PRODUCT) => AgnosticMediaGalleryItem[]; + getCoverImage: (product: PRODUCT) => string; + getFiltered: (products: PRODUCT[], filters?: PRODUCT_FILTER) => PRODUCT[]; + getAttributes: (products: PRODUCT[] | PRODUCT, filters?: Array) => Record; + getDescription: (product: PRODUCT) => string; + getCategoryIds: (product: PRODUCT) => string[]; + getId: (product: PRODUCT) => string; + getFormattedPrice: (price: number) => string; + getTotalReviews: (product: PRODUCT) => number; + getAverageRating: (product: PRODUCT) => number; + getBreadcrumbs?: (product: PRODUCT) => AgnosticBreadcrumb[]; + [getterName: string]: any; +} + +export interface CartGetters { + getItems: (cart: CART) => CART_ITEM[]; + getItemName: (cartItem: CART_ITEM) => string; + getItemImage: (cartItem: CART_ITEM) => string; + getItemPrice: (cartItem: CART_ITEM) => AgnosticPrice; + getItemQty: (cartItem: CART_ITEM) => number; + getItemAttributes: (cartItem: CART_ITEM, filters?: Array) => Record; + getItemSku: (cartItem: CART_ITEM) => string; + getTotals: (cart: CART) => AgnosticTotals; + getShippingPrice: (cart: CART) => number; + getTotalItems: (cart: CART) => number; + getFormattedPrice: (price: number) => string; + // @deprecated - use getDiscounts instead + getCoupons: (cart: CART) => AgnosticCoupon[]; + getDiscounts: (cart: CART) => AgnosticDiscount[]; + [getterName: string]: (element: any, options?: any) => unknown; +} + +export interface WishlistGetters { + getItems: (wishlist: WISHLIST) => WISHLIST_ITEM[]; + getItemName: (wishlistItem: WISHLIST_ITEM) => string; + getItemImage: (wishlistItem: WISHLIST_ITEM) => string; + getItemPrice: (wishlistItem: WISHLIST_ITEM) => AgnosticPrice; + getItemAttributes: (wishlistItem: WISHLIST_ITEM, filters?: Array) => Record; + getItemSku: (wishlistItem: WISHLIST_ITEM) => string; + getTotals: (wishlist: WISHLIST) => AgnosticTotals; + getTotalItems: (wishlist: WISHLIST) => number; + getFormattedPrice: (price: number) => string; + [getterName: string]: (element: any, options?: any) => unknown; +} + +export interface CategoryGetters { + getTree: (category: CATEGORY) => AgnosticCategoryTree | null; + getBreadcrumbs?: (category: CATEGORY) => AgnosticBreadcrumb[]; + [getterName: string]: any; +} + +export interface UserGetters { + getFirstName: (customer: USER) => string; + getLastName: (customer: USER) => string; + getFullName: (customer: USER) => string; + getEmailAddress: (customer: USER) => string; + [getterName: string]: (element: any, options?: any) => unknown; +} + +export interface UserOrderGetters { + getDate: (order: ORDER) => string; + getId: (order: ORDER) => string; + getStatus: (order: ORDER) => string; + getPrice: (order: ORDER) => number; + getItems: (order: ORDER) => ORDER_ITEM[]; + getItemSku: (item: ORDER_ITEM) => string; + getItemName: (item: ORDER_ITEM) => string; + getItemQty: (item: ORDER_ITEM) => number; + getItemPrice: (item: ORDER_ITEM) => number; + getFormattedPrice: (price: number) => string; + getOrdersTotal: (orders: { + offset: number; + count: number; + total: number; + results: Array; + }) => number; + [getterName: string]: (element: any, options?: any) => unknown; +} + +export interface ReviewGetters { + getItems: (review: REVIEW) => REVIEW_ITEM[]; + getReviewId: (item: REVIEW_ITEM) => string; + getReviewAuthor: (item: REVIEW_ITEM) => string; + getReviewMessage: (item: REVIEW_ITEM) => string; + getReviewRating: (item: REVIEW_ITEM) => number; + getReviewDate: (item: REVIEW_ITEM) => string; + getTotalReviews: (review: REVIEW) => number; + getAverageRating: (review: REVIEW) => number; + getRatesCount: (review: REVIEW) => AgnosticRateCount[]; + getReviewsPage: (review: REVIEW) => number; +} + +export interface FacetsGetters { + getAll: (searchData: FacetSearchResult, criteria?: CRITERIA) => AgnosticFacet[]; + getGrouped: (searchData: FacetSearchResult, criteria?: CRITERIA) => AgnosticGroupedFacet[]; + getCategoryTree: (searchData: FacetSearchResult) => AgnosticCategoryTree; + getSortOptions: (searchData: FacetSearchResult) => AgnosticSort; + getProducts: (searchData: FacetSearchResult) => RESULTS; + getPagination: (searchData: FacetSearchResult) => AgnosticPagination; + getBreadcrumbs: (searchData: FacetSearchResult) => AgnosticBreadcrumb[]; + [getterName: string]: (element: any, options?: any) => unknown; +} + +export interface ForgotPasswordGetters { + getResetPasswordToken: (result: FORGOT_PASSWORD_RESULT) => string + isPasswordChanged: (result: FORGOT_PASSWORD_RESULT) => boolean +} + +export interface UseSearchGetters { + getItems: (result: RESULT) => ITEM[]; + getCategoryTree: (result: RESULT) => AgnosticCategoryTree; + getPagination: (result: RESULT) => AgnosticPagination; + getItemPrice: (item: ITEM) => AgnosticPrice; + getSortOptions: (result: RESULT) => AgnosticSort; + getBreadcrumbs: (result: RESULT) => AgnosticBreadcrumb[]; + getItemImages: (item: ITEM) => AgnosticMediaGalleryItem[] + getFilters: (result: RESULT) => AgnosticFilter[]; + getItemName: (item: ITEM) => string; + getItemId: (item: ITEM) => string; + getItemSlug: (item: ITEM) => string; +} + +export interface VSFLogger { + debug(message?: any, ...args: any): void; + info(message?: any, ...args: any): void; + warn(message?: any, ...args: any): void; + error(message?: any, ...args: any): void; +} + +export interface FactoryParams { + provide?: (context: Context) => any; + api?: Partial; +} + +export interface HookParams { + configuration: C; +} + +export interface CallHookParams extends HookParams { + callName: string; +} + +export type BeforeCallArgs = any; +export type AfterCallArgs = any; + +export interface BeforeCallParams< C> extends CallHookParams { + args: BeforeCallArgs; +} + +export interface AfterCallParams extends CallHookParams { + response: AfterCallArgs; +} + +export interface ApiClientExtensionHooks { + beforeCreate?: (params: HookParams) => C; + afterCreate?: (params: HookParams) => C; + beforeCall?: (params: BeforeCallParams) => BeforeCallArgs; + afterCall?: (params: AfterCallParams) => AfterCallArgs; +} + +export type CustomQueryFn = (query: any, variables: T) => { + query?: any; + variables?: T; + metadata: any; +}; + +export type ApiClientMethod = (...args: any) => Promise + +export interface ApiClientExtension { + name: string; + extendApiMethods?: Record; + extendApp?: ({ app: Express, configuration: any }) => void; + hooks?: (req: Request, res: Response) => ApiClientExtensionHooks; +} + +export interface Integration { + location: string; + configuration: any; + extensions?: (extensions: ApiClientExtension[]) => ApiClientExtension[]; + customQueries?: Record; +} + +export type IntegrationsSection = Record + +export interface MiddlewareConfig { + integrations: Record; +} + +export interface ApiClientFactoryParams { + api: F; + isProxy?: boolean; + onCreate: (config: T, headers?: Record) => { config: T; client: any }; + extensions?: ApiClientExtension[]; +} + +export interface ApiInstance { + api: any; + client: any; + settings: any; +} + +export type CreateApiProxyFn = (givenConfig: any, customApi?: any) => ApiInstance; +export type CreateApiClientFn = (givenConfig: any, customApi?: any) => ApiInstance; + +export interface ApiClientFactory { + createApiClient: CreateApiClientFn; +} + +export interface ApiClientConfig { + [x: string]: any; + client?: any; + extensions?: ApiClientExtension[]; +} + +export type ApiClientMethods = { + [K in keyof T]: + T[K] extends (...args: any) => any ? + (...args: [...Parameters, CustomQuery?]) => ReturnType : + T[K] +} + +export interface UseStoreErrors { + load: Error | null; + change: Error | null; +} + +export interface UseStoreFactoryChangeParamArguments { + currentStore: AgnosticStore; + store: AgnosticStore; + customQuery?: CustomQuery; +} + +export interface UseStoreFactoryLoadParamArguments { + customQuery: CustomQuery; +} + +export interface UseStoreFactoryParams extends FactoryParams { + load(context: Context, params: UseStoreFactoryLoadParamArguments): Promise + change(context: Context, params: UseStoreFactoryChangeParamArguments): Promise +} +export interface UseStoreInterface { + change(params: UseStoreFactoryChangeParamArguments): Promise; + load(params?: UseStoreFactoryLoadParamArguments): Promise; + loading: ComputedProperty; + response: ComputedProperty; + error: ComputedProperty; +} + +export interface UseStore { + (): UseStoreInterface; +} + +export interface UseStoreGetters { + getItems(stores: STORES, criteria?: CRITERIA): AgnosticStore[]; + getSelected(stores: STORES): AgnosticStore | undefined +} diff --git a/packages/core/core/src/utils/context/index.ts b/packages/core/core/src/utils/context/index.ts new file mode 100644 index 0000000000..6d6e261c97 --- /dev/null +++ b/packages/core/core/src/utils/context/index.ts @@ -0,0 +1,26 @@ +import { Context } from './../../types'; +interface ContextConfiguration { + useVSFContext: () => Context; +} + +let useVSFContext = () => ({}) as Context; + +const configureContext = (config: ContextConfiguration) => { + useVSFContext = config.useVSFContext || useVSFContext; +}; + +const generateContext = (factoryParams) => { + const vsfContext = useVSFContext(); + + if (factoryParams.provide) { + return { ...vsfContext.$vsf, ...factoryParams.provide(vsfContext.$vsf) }; + } + + return vsfContext.$vsf; +}; + +export { + generateContext, + useVSFContext, + configureContext +}; diff --git a/packages/core/core/src/utils/factoryParams/commonMethods.ts b/packages/core/core/src/utils/factoryParams/commonMethods.ts new file mode 100644 index 0000000000..3941df7e64 --- /dev/null +++ b/packages/core/core/src/utils/factoryParams/commonMethods.ts @@ -0,0 +1,18 @@ +const createFactoryParamsMethod = (fn, fnName, context) => (argObj) => { + if (fnName === 'provide') { + return fn(context); + } + + return fn(context, argObj); +}; + +const createFactoryParamsReducer = (context) => (prev, [fnName, fn]: any) => ({ + ...prev, + [fnName]: createFactoryParamsMethod(fn, fnName, context) +}); + +const createCommonMethods = (factoryParams, context) => + Object.entries(factoryParams) + .reduce(createFactoryParamsReducer(context), {}); + +export { createCommonMethods }; diff --git a/packages/core/core/src/utils/factoryParams/index.ts b/packages/core/core/src/utils/factoryParams/index.ts new file mode 100644 index 0000000000..9752e737dd --- /dev/null +++ b/packages/core/core/src/utils/factoryParams/index.ts @@ -0,0 +1,15 @@ +import { generateContext } from './../context'; +import { createCommonMethods } from './commonMethods'; +import { createPlatformMethods } from './platformMethods'; + +const configureFactoryParams = (factoryParams, refs = null): any => { + const context = generateContext(factoryParams); + const { api, ...methods } = factoryParams; + + const commonMethods = createCommonMethods(methods, context); + const platformMethods = refs ? createPlatformMethods(api || {}, context, refs) : {}; + + return { ...commonMethods, api: platformMethods }; +}; + +export { configureFactoryParams }; diff --git a/packages/core/core/src/utils/factoryParams/platformMethods.ts b/packages/core/core/src/utils/factoryParams/platformMethods.ts new file mode 100644 index 0000000000..cabb6fea80 --- /dev/null +++ b/packages/core/core/src/utils/factoryParams/platformMethods.ts @@ -0,0 +1,28 @@ +import { Logger } from './../logger'; + +const createPlatformMethod = (context, refs, functionObject) => async (params) => { + const { mainRef, loading, error, alias } = refs; + try { + loading.value = true; + mainRef.value = await functionObject.fn( + context, + { ...params, [alias]: mainRef.value } + ); + loading.value = false; + } catch (err) { + error.value[functionObject.fnName] = err; + Logger.error(`api.${functionObject.fnName}`, err); + } finally { + loading.value = false; + } +}; + +const createFactoryParamsReducer = (context, refs) => (prev, [fnName, fn]: any) => ({ + ...prev, + [fnName]: createPlatformMethod(context, refs, { fnName, fn }) +}); + +const createPlatformMethods = (apiSection, context, refs) => + Object.entries(apiSection).reduce(createFactoryParamsReducer(context, refs), {}); + +export { createPlatformMethods }; diff --git a/packages/core/core/src/utils/index.ts b/packages/core/core/src/utils/index.ts new file mode 100644 index 0000000000..4b96640086 --- /dev/null +++ b/packages/core/core/src/utils/index.ts @@ -0,0 +1,26 @@ +/* istanbul ignore file */ + +import { onSSR, vsfRef, configureSSR } from './ssr'; +import { sharedRef } from './shared'; +import wrap from './wrap'; +import { Logger, registerLogger } from './logger'; +import mask from './logger/mask'; +import { useVSFContext, configureContext, generateContext } from './context'; +import { integrationPlugin } from './nuxt'; +import { configureFactoryParams } from './factoryParams'; + +export { + wrap, + onSSR, + vsfRef, + configureSSR, + sharedRef, + Logger, + registerLogger, + mask, + configureContext, + useVSFContext, + configureFactoryParams, + generateContext, + integrationPlugin +}; diff --git a/packages/core/core/src/utils/logger/defaultLogger.ts b/packages/core/core/src/utils/logger/defaultLogger.ts new file mode 100644 index 0000000000..d315e9e889 --- /dev/null +++ b/packages/core/core/src/utils/logger/defaultLogger.ts @@ -0,0 +1,13 @@ +/* istanbul ignore file */ + +import { makeMethod } from './makeMethod'; +import { LogName } from './types'; + +const defaultLogger = { + debug: makeMethod(LogName.Debug, console.debug)(), + info: makeMethod(LogName.Info, console.info)(), + warn: makeMethod(LogName.Warn, console.warn)(), + error: makeMethod(LogName.Error, console.error)() +}; + +export default defaultLogger; diff --git a/packages/core/core/src/utils/logger/index.ts b/packages/core/core/src/utils/logger/index.ts new file mode 100644 index 0000000000..3e6fb9f5ae --- /dev/null +++ b/packages/core/core/src/utils/logger/index.ts @@ -0,0 +1,76 @@ +import { VSFLogger } from './../../types'; +import defaultLogger from './defaultLogger'; + +const defaultModes = { + // Test + test: 'none', + + // Development + dev: 'warn', + development: 'warn', + + // Production + prod: 'error', + production: 'error', + + // Fallback + default: 'warn' +}; + +let Logger: VSFLogger = defaultLogger; + +type LoggerImplementation = VSFLogger | ((verbosity: string) => VSFLogger); + +const registerLogger = (loggerImplementation: LoggerImplementation, verbosity: string) => { + if (typeof loggerImplementation === 'function') { + Logger = loggerImplementation(verbosity); + return; + } + + switch (verbosity) { + case 'info': + Logger = { + ...defaultLogger, + ...loggerImplementation, + debug: () => {} + }; + break; + case 'warn': + Logger = { + ...defaultLogger, + ...loggerImplementation, + info: () => {}, + debug: () => {} + }; + break; + case 'error': + Logger = { + ...defaultLogger, + ...loggerImplementation, + info: () => {}, + warn: () => {}, + debug: () => {} + }; + break; + case 'none': + Logger = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {} + }; + break; + default: + Logger = { + ...defaultLogger, + ...loggerImplementation + }; + } +}; + +registerLogger(defaultLogger, defaultModes[process.env.NODE_ENV] || defaultModes.default); + +export { + Logger, + registerLogger +}; diff --git a/packages/core/core/src/utils/logger/makeMessageStyle.ts b/packages/core/core/src/utils/logger/makeMessageStyle.ts new file mode 100644 index 0000000000..a2275dc6bc --- /dev/null +++ b/packages/core/core/src/utils/logger/makeMessageStyle.ts @@ -0,0 +1,33 @@ +import { LogLevelStyle, LogName } from './types'; +import { mountLog } from './utils'; + +export function makeMessageStyle(logEnum: LogName) { + switch (logEnum) { + case LogName.Error: + return mountLog( + '[VSF][error]', + LogLevelStyle.Error + ); + case LogName.Info: + return mountLog( + '[VSF][info]', + LogLevelStyle.Info + ); + case LogName.Warn: + return mountLog( + '[VSF][warn]', + LogLevelStyle.Warn + ); + case LogName.Debug: + return mountLog( + '[VSF][debug]', + LogLevelStyle.Log + ); + case LogName.None: + default: + return mountLog( + '[VSF]', + LogLevelStyle.Log + ); + } +} diff --git a/packages/core/core/src/utils/logger/makeMethod.ts b/packages/core/core/src/utils/logger/makeMethod.ts new file mode 100644 index 0000000000..09fcd29b81 --- /dev/null +++ b/packages/core/core/src/utils/logger/makeMethod.ts @@ -0,0 +1,11 @@ +import { LogName } from './types'; +import { makeMessageStyle } from './makeMessageStyle'; +// eslint-disable-next-line @typescript-eslint/ban-types +export function makeMethod(logEnum: LogName, fn: Function) { + return () => { + return Function.prototype.bind.apply(fn, [ + console, + ...makeMessageStyle(logEnum) + ]); + }; +} diff --git a/packages/core/core/src/utils/logger/mask.ts b/packages/core/core/src/utils/logger/mask.ts new file mode 100644 index 0000000000..8058d0284a --- /dev/null +++ b/packages/core/core/src/utils/logger/mask.ts @@ -0,0 +1,22 @@ +const maskString = (el: string) => `${el.charAt(0)}***${el.slice(-1)}`; + +const maskAny = (el: any) => { + if (typeof el === 'string') { + return maskString(el); + } + + return '***'; +}; + +const mask = (el: any): any => { + if (typeof el === 'object' && !Array.isArray(el)) { + return Object.keys(el).reduce((prev, key) => ({ + ...prev, + [key]: maskAny(el[key]) + }), {}); + } + + return maskAny(el); +}; + +export default mask; diff --git a/packages/core/core/src/utils/logger/types.ts b/packages/core/core/src/utils/logger/types.ts new file mode 100644 index 0000000000..62904175b1 --- /dev/null +++ b/packages/core/core/src/utils/logger/types.ts @@ -0,0 +1,14 @@ +export enum LogName { + Error = 'error', + Info = 'info', + Debug = 'debug', + None = 'none', + Warn = 'warn', +} + +export const LogLevelStyle = { + Log: 'background:#5ece7b; padding: 2px; border-radius: 0 2px 2px 0; color: #fff;', + Info: 'background:#0468DB; padding: 2px; border-radius: 0 2px 2px 0; color: #fff;', + Warn: 'background:#ecc713; padding: 2px; border-radius: 0 2px 2px 0; color: #000;', + Error: 'background:#d12727; padding: 2px; border-radius: 0 2px 2px 0; color: #fff' +}; diff --git a/packages/core/core/src/utils/logger/utils.ts b/packages/core/core/src/utils/logger/utils.ts new file mode 100644 index 0000000000..1d0eb02ae1 --- /dev/null +++ b/packages/core/core/src/utils/logger/utils.ts @@ -0,0 +1,33 @@ +export type LogMessage = string[] | Error | Record | string | boolean; + +export const getMessage = (message: LogMessage): string | undefined => { + if (Array.isArray(message)) return message.join(' | '); + if (message instanceof Error) return message.message; + if (typeof message === 'object') return JSON.stringify(message, null, 1); + + const returnMessage = message as string || ''; + + return `${returnMessage}`; +}; + +export const detectNode: boolean = Object.prototype + .toString + .call(typeof process !== 'undefined' + ? process + : 0) === '[object process]' || + process.env.APPLICATION_ENV === 'production'; + +export const mountLog = ( + name: string, + style: string +) => { + if (detectNode) { + return [`${name}: `]; + } + + return [ + `%c${name}%c:`, + style, + 'background: transparent;' + ]; +}; diff --git a/packages/core/core/src/utils/nuxt/_proxyUtils.ts b/packages/core/core/src/utils/nuxt/_proxyUtils.ts new file mode 100644 index 0000000000..69fb6d9d96 --- /dev/null +++ b/packages/core/core/src/utils/nuxt/_proxyUtils.ts @@ -0,0 +1,50 @@ +import { IncomingMessage } from 'http'; +import { Context as NuxtContext } from '@nuxt/types'; +import merge from 'lodash-es/merge'; +import { ApiClientMethod } from './../../types'; + +interface CreateProxiedApiParams { + givenApi: Record; + client: any; + tag: string; +} + +export const getBaseUrl = (req: IncomingMessage) => { + if (!req) return '/api/'; + const { headers } = req; + const isHttps = require('is-https')(req); + const scheme = isHttps ? 'https' : 'http'; + const host = headers['x-forwarded-host'] || headers.host; + + return `${scheme}://${host}/api/`; +}; + +export const createProxiedApi = ({ givenApi, client, tag }: CreateProxiedApiParams) => new Proxy(givenApi, { + get: (target, prop, receiver) => { + + const functionName = String(prop); + if (Reflect.has(target, functionName)) { + return Reflect.get(target, prop, receiver); + } + + return async (...args) => client + .post(`/${tag}/${functionName}`, args) + .then(r => r.data); + } +}); + +export const getCookies = (context: NuxtContext) => context?.req?.headers?.cookie ?? ''; + +export const getIntegrationConfig = (context: NuxtContext, configuration: any) => { + const cookie = getCookies(context); + const initialConfig = merge({ + axios: { + baseURL: getBaseUrl(context?.req), + headers: { + ...(cookie ? { cookie } : {}) + } + } + }, configuration); + + return initialConfig; +}; diff --git a/packages/core/core/src/utils/nuxt/context.ts b/packages/core/core/src/utils/nuxt/context.ts new file mode 100644 index 0000000000..1df5e69599 --- /dev/null +++ b/packages/core/core/src/utils/nuxt/context.ts @@ -0,0 +1,30 @@ +/** + * It extends given integartion, defined by `tag` in the context. + */ +export const createExtendIntegrationInCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties) => { + const integrationKey = '$' + tag; + + if (!nuxtCtx.$vsf || !nuxtCtx.$vsf[integrationKey]) { + inject('vsf', { [integrationKey]: {} }); + } + + Object.keys(integrationProperties) + .filter(k => !['api', 'client', 'config'].includes(k)) + .forEach(key => { + nuxtCtx.$vsf[integrationKey][key] = integrationProperties[key]; + }); +}; + +/** + * It creates a function that adds an integration to the context under the given name, defined by `tag`. + */ +export const createAddIntegrationToCtx = ({ tag, nuxtCtx, inject }) => (integrationProperties) => { + const integrationKey = '$' + tag; + + if (nuxtCtx.$vsf && !nuxtCtx.$vsf[integrationKey]) { + nuxtCtx.$vsf[integrationKey] = integrationProperties; + return; + } + + inject('vsf', { [integrationKey]: integrationProperties }); +}; diff --git a/packages/core/core/src/utils/nuxt/index.ts b/packages/core/core/src/utils/nuxt/index.ts new file mode 100644 index 0000000000..d335108037 --- /dev/null +++ b/packages/core/core/src/utils/nuxt/index.ts @@ -0,0 +1,32 @@ +import { createExtendIntegrationInCtx, createAddIntegrationToCtx } from './context'; +import { getIntegrationConfig, createProxiedApi } from './_proxyUtils'; +import { Context as NuxtContext, Plugin as NuxtPlugin } from '@nuxt/types'; +import axios from 'axios'; + +type InjectFn = (key: string, value: any) => void; +export type IntegrationPlugin = (pluginFn: NuxtPlugin) => NuxtPlugin + +export const integrationPlugin = (pluginFn: NuxtPlugin) => (nuxtCtx: NuxtContext, inject: InjectFn) => { + const configure = (tag, configuration) => { + const injectInContext = createAddIntegrationToCtx({ tag, nuxtCtx, inject }); + const config = getIntegrationConfig(nuxtCtx, configuration); + const { middlewareUrl } = (nuxtCtx as any).$config; + + if (middlewareUrl) { + config.axios.baseURL = middlewareUrl; + } + + const client = axios.create(config.axios); + const api = createProxiedApi({ givenApi: configuration.api || {}, client, tag }); + + injectInContext({ api, client, config }); + }; + + const extend = (tag, integrationProperties) => { + createExtendIntegrationInCtx({ tag, nuxtCtx, inject })(integrationProperties); + }; + + const integration = { configure, extend }; + + pluginFn({ ...nuxtCtx, integration } as NuxtContext, inject); +}; diff --git a/packages/core/core/src/utils/shared/index.ts b/packages/core/core/src/utils/shared/index.ts new file mode 100644 index 0000000000..de87561a20 --- /dev/null +++ b/packages/core/core/src/utils/shared/index.ts @@ -0,0 +1,26 @@ + +import { Ref } from '@vue/composition-api'; +import { vsfRef, useVSFContext } from '../../utils'; + +function sharedRef(value: T, key: string): Ref; +function sharedRef(key: string, _?): Ref; + +function sharedRef(value: T, key: string): Ref { + const { $sharedRefsMap } = useVSFContext() as any; + const givenKey = key || value; + + if ($sharedRefsMap.has(givenKey)) { + return $sharedRefsMap.get(givenKey); + } + + const newRef = vsfRef( + key ? value : null, + givenKey as string + ); + + $sharedRefsMap.set(givenKey, newRef); + + return newRef; +} + +export { sharedRef }; diff --git a/packages/core/core/src/utils/ssr/index.ts b/packages/core/core/src/utils/ssr/index.ts new file mode 100644 index 0000000000..423cd05818 --- /dev/null +++ b/packages/core/core/src/utils/ssr/index.ts @@ -0,0 +1,22 @@ +import { onServerPrefetch, ref, Ref } from '@vue/composition-api'; + +type VsfRef = (data?: T, key?: string) => Ref; + +interface SSRConfiguration { + onSSR: (fn: () => void) => void; + vsfRef: VsfRef; +} + +let onSSR = onServerPrefetch; +let vsfRef: VsfRef = ref; + +const configureSSR = (config: SSRConfiguration) => { + onSSR = config.onSSR || onSSR; + vsfRef = config.vsfRef || vsfRef as any; +}; + +export { + onSSR, + vsfRef, + configureSSR +}; diff --git a/packages/core/core/src/utils/wrap/index.ts b/packages/core/core/src/utils/wrap/index.ts new file mode 100644 index 0000000000..35f30f4dc4 --- /dev/null +++ b/packages/core/core/src/utils/wrap/index.ts @@ -0,0 +1,5 @@ +import { isRef, ref, Ref, UnwrapRef } from '@vue/composition-api'; + +export default function wrap(element: Ref> | T): Ref> { + return isRef(element) ? element : ref(element as T); +} diff --git a/packages/core/core/tsconfig.json b/packages/core/core/tsconfig.json new file mode 100644 index 0000000000..5f31c268ff --- /dev/null +++ b/packages/core/core/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "es5", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "lib": ["es6", "es7", "dom"], + "strict": false, + }, + "exclude": ["node_modules", "**/*.spec.ts"] +} diff --git a/packages/core/docs/.gitignore b/packages/core/docs/.gitignore new file mode 100644 index 0000000000..216c6ca155 --- /dev/null +++ b/packages/core/docs/.gitignore @@ -0,0 +1,5 @@ +.vuepress/dist +api.json +.vuepress/public/commercetools +commercetools/api-reference +core/api-reference diff --git a/packages/core/docs/.vuepress/components/CommerceIntegrationLink.vue b/packages/core/docs/.vuepress/components/CommerceIntegrationLink.vue new file mode 100644 index 0000000000..7993eff21f --- /dev/null +++ b/packages/core/docs/.vuepress/components/CommerceIntegrationLink.vue @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/components/CommerceIntegrationLinks.vue b/packages/core/docs/.vuepress/components/CommerceIntegrationLinks.vue new file mode 100644 index 0000000000..00f5be9a97 --- /dev/null +++ b/packages/core/docs/.vuepress/components/CommerceIntegrationLinks.vue @@ -0,0 +1,48 @@ + + + diff --git a/packages/core/docs/.vuepress/components/IncludeContent.vue b/packages/core/docs/.vuepress/components/IncludeContent.vue new file mode 100644 index 0000000000..d10961da54 --- /dev/null +++ b/packages/core/docs/.vuepress/components/IncludeContent.vue @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/components/IntegrationList.vue b/packages/core/docs/.vuepress/components/IntegrationList.vue new file mode 100644 index 0000000000..f1ebbdc2ed --- /dev/null +++ b/packages/core/docs/.vuepress/components/IntegrationList.vue @@ -0,0 +1,137 @@ + + + + + + + diff --git a/packages/core/docs/.vuepress/components/IntegrationTile.vue b/packages/core/docs/.vuepress/components/IntegrationTile.vue new file mode 100644 index 0000000000..7990b03305 --- /dev/null +++ b/packages/core/docs/.vuepress/components/IntegrationTile.vue @@ -0,0 +1,248 @@ + + + + + + diff --git a/packages/core/docs/.vuepress/components/PersonTile.vue b/packages/core/docs/.vuepress/components/PersonTile.vue new file mode 100644 index 0000000000..809e421c26 --- /dev/null +++ b/packages/core/docs/.vuepress/components/PersonTile.vue @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/components/Theme.vue b/packages/core/docs/.vuepress/components/Theme.vue new file mode 100644 index 0000000000..6bdc2a78a5 --- /dev/null +++ b/packages/core/docs/.vuepress/components/Theme.vue @@ -0,0 +1,31 @@ + diff --git a/packages/core/docs/.vuepress/config.js b/packages/core/docs/.vuepress/config.js new file mode 100644 index 0000000000..6ed8a556ce --- /dev/null +++ b/packages/core/docs/.vuepress/config.js @@ -0,0 +1,281 @@ +const { STATUSES, AVAILABILITY, CATEGORIES, INTEGRATIONS } = require('./integrations'); +const GTM_TAG = 'GTM-WMDC3CP'; + +module.exports = { + /** + * Ref:https://v1.vuepress.vuejs.org/config/#title + */ + title: 'Vue Storefront 2', + + /** + * Ref:https://v1.vuepress.vuejs.org/config/#description + */ + description: 'Vue Storefront 2 documentation', + + /** + * Ref: https://v1.vuepress.vuejs.org/config/#base + */ + base: '/v2/', + + /** + * Ref:https://v1.vuepress.vuejs.org/config/#head + */ + head: [ + ['link', { rel: 'icon', href: '/favicon.png' }], + + // HubSpot + ['script', { async: true, defer: true, src: 'https://js.hs-scripts.com/8443671.js', id: 'hs-script-loader' }], + + // Google Tag Manager + ['script', {}, [` + (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer','${GTM_TAG}'); + `]], + ], + + /** + * Ref:https://v1.vuepress.vuejs.org/config/#configurewebpack + */ + configureWebpack: (config) => { + config.module.rules = config.module.rules.map((rule) => ({ + ...rule, + use: + rule.use && + rule.use.map((useRule) => ({ + ...useRule, + options: + useRule.loader === 'url-loader' + ? /** + Hack for loading images properly. + ref: https://github.com/vuejs/vue-loader/issues/1612#issuecomment-559366730 + */ + { ...useRule.options, esModule: false } + : useRule.options + })) + })); + }, + + /** + * Ref:https://v1.vuepress.vuejs.org/plugin/ + */ + plugins: [ + '@vuepress/plugin-back-to-top', + [ + '@vuepress/plugin-medium-zoom', + { + // This selector excludes images from the "Integrations" page + selector: 'main :not(a):not(.tile) > img' + } + ], + '@vuepress/active-header-links', + '@vuepress/search' + ], + + /** + * Ref:https://v1.vuepress.vuejs.org/theme/default-theme-config.html + */ + themeConfig: { + GTM_TAG, + STATUSES, + AVAILABILITY, + CATEGORIES, + INTEGRATIONS, + repo: 'https://github.com/vuestorefront/vue-storefront/', + editLinks: true, + docsDir: 'packages/core/docs', + docsBranch: 'release/next', + editLinkText: 'Edit this page', + logo: 'https://camo.githubusercontent.com/48c886ac0703e3a46bc0ec963e20f126337229fc/68747470733a2f2f643968687267346d6e767a6f772e636c6f756466726f6e742e6e65742f7777772e76756573746f726566726f6e742e696f2f32383062313964302d6c6f676f2d76735f3062793032633062793032633030303030302e6a7067', + nav: [ + { text: 'Integrations', link: '/integrations/' }, + { text: 'Migration guide', link: '/migrate/' }, + { text: 'Demo', link: 'https://demo-ct.vuestorefront.io' }, + { text: 'Roadmap', link: 'https://www.notion.so/vuestorefront/Vue-Storefront-2-Next-High-level-Roadmap-201cf06abb314b84ad01b7b8463c0437' } + ], + sidebar: { + '/migrate/': [ + { + title: 'Migration guide 2.4.0', + children: [ + ['/migrate/2.4.0/overview', 'Overview'], + ['/migrate/2.4.0/integrators', 'Integrators'], + ['/migrate/2.4.0/commercetools', 'commercetools'] + ] + }, + { + title: 'Migration guide 2.3.0', + children: [ + ['/migrate/2.3.0/overview', 'Overview'], + ['/migrate/2.3.0/integrators', 'Integrators'], + ['/migrate/2.3.0/commercetools', 'commercetools'] + ] + }, + { + title: 'Migration guide 2.3.0-rc.3', + children: [ + ['/migrate/2.3.0-rc.3/overview', 'Overview'], + ['/migrate/2.3.0-rc.3/integrators', 'Integrators'], + ['/migrate/2.3.0-rc.3/commercetools', 'commercetools'] + ] + }, + { + title: 'Migration guide 2.3.0-rc.2', + children: [ + ['/migrate/2.3.0-rc.2/overview', 'Overview'], + ['/migrate/2.3.0-rc.2/integrators', 'Integrators'], + ['/migrate/2.3.0-rc.2/commercetools', 'commercetools'] + ] + }, + { + title: 'Migration guide 2.2.0', + children: [ + ['/migrate/2.2.0/overview', 'Overview'], + ['/migrate/2.2.0/integrators', 'Integrators'], + ['/migrate/2.2.0/projects', 'Projects'] + ] + }, + { + title: 'Migration guide 2.1.0-rc.1', + children: [ + ['/migrate/2.1.0-rc.1/overview', 'Overview'], + ['/migrate/2.1.0-rc.1/integrators', 'Integrators'], + ['/migrate/2.1.0-rc.1/projects', 'Projects'] + ] + } + ], + '/commercetools/': [ + { + title: 'Essentials', + collapsable: false, + children: [ + ['/commercetools/', 'Introduction'], + ['/commercetools/getting-started', 'Getting started'], + ['/commercetools/configuration', 'Configuration'], + ['/commercetools/authorization-strategy', 'Authorization'], + ['/enterprise/feature-list', 'Feature list'], + ['/commercetools/maintainers', 'Maintainers and support'], + ['/commercetools/changelog', 'Changelog'] + ] + }, + { + title: 'Composables', + collapsable: false, + children: [ + ['/commercetools/composables/use-billing', 'useBilling'], + ['/commercetools/composables/use-cart', 'useCart'], + ['/commercetools/composables/use-category', 'useCategory'], + ['/commercetools/composables/use-facet', 'useFacet'], + ['/commercetools/composables/use-forgot-password', 'useForgotPassword'], + ['/commercetools/composables/use-make-order', 'useMakeOrder'], + ['/commercetools/composables/use-product', 'useProduct'], + ['/commercetools/composables/use-review', 'useReview'], + ['/commercetools/composables/use-shipping-provider', 'useShippingProvider'], + ['/commercetools/composables/use-shipping', 'useShipping'], + ['/commercetools/composables/use-store', 'useStore'], + ['/commercetools/composables/use-user-billing', 'useUserBilling'], + ['/commercetools/composables/use-user-order', 'useUserOrder'], + ['/commercetools/composables/use-user-shipping', 'useUserShipping'], + ['/commercetools/composables/use-user', 'useUser'], + ['/commercetools/composables/use-wishlist', 'useWishlist'] + ] + }, + { + title: 'API Reference', + collapsable: false, + children: [ + ['/commercetools/api-reference/', 'API Reference'] + ] + }, + { + title: 'Extensions', + collapsable: false, + children: [ + ['/commercetools/extensions/user-groups', 'User groups'] + ] + }, + { + title: 'Theme', + collapsable: false, + children: [ + ['/commercetools/auth-middleware', 'Auth Middleware'] + ] + } + ], + '/': [ + { + title: 'Getting started', + collapsable: false, + children: [ + ['/', 'Introduction'], + ['/general/where-to-start', 'Where to start?'], + ['/general/installation', 'Installation'], + ['/general/key-concepts', 'Key concepts'], + ['/general/enterprise', 'Enterprise'], + ['/general/support', 'Support'] + ] + }, + { + title: 'Guides', + collapsable: false, + children: [ + ['/guide/theme', 'Theme'], + ['/guide/configuration', 'Configuration'], + ['/guide/composition-api', 'Composition API'], + ['/guide/composables', 'Composables'], + ['/guide/error-handling', 'Error Handling'], + ['/guide/getters', 'Getters'], + ['/guide/product-catalog', 'Product Catalog'], + ['/guide/authentication', 'Authentication'], + ['/guide/user-profile', 'User profile'], + ['/guide/cart-and-wishlist', 'Cart and wishlist'], + ['/guide/checkout', 'Checkout'] + ] + }, + { + title: 'Advanced', + collapsable: false, + children: [ + ['/advanced/architecture', 'Architecture'], + ['/advanced/context', 'Application Context'], + ['/advanced/calling-platform-api', 'Calling Platform API'], + ['/advanced/creating-custom-composable', 'Creating custom composable'], + ['/advanced/extending-graphql-queries', 'Extending GraphQL queries'], + ['/advanced/server-middleware', 'Server Middleware'], + ['/advanced/internationalization', 'Internationalization'], + ['/advanced/performance', 'Performance'], + ['/advanced/ssr-cache', 'SSR Cache'], + ['/advanced/logging', 'Logging'], + ['/core/api-reference/', 'API Reference'] + ] + }, + { + title: 'Building integration', + collapsable: true, + children: [ + ['/integrate/boilerplate', 'Using the boilerplate'], + ['/integrate/integration-guide', 'eCommerce'], + ['/integrate/cms', 'CMS'], + ['/integrate/cache-driver', 'Cache driver'], + ['/integrate/checklist', 'Integration Checklist'], + ['/integrate/good-practices', 'Good Practices'] + ] + }, + { + title: 'Contributing', + collapsable: true, + children: [ + ['/contributing/', 'Contributing'], + ['/contributing/api-design-philosophy', 'Rules and conventions'], + ['/contributing/creating-changelog', 'Creating changelog'], + ['/contributing/themes', 'Working with themes'], + ['/contributing/server-side-rendering', 'Server-side rendering'], + ['/contributing/changelog', 'Core Changelog'] + ] + } + ] + } + } +}; diff --git a/packages/core/docs/.vuepress/enhanceApp.js b/packages/core/docs/.vuepress/enhanceApp.js new file mode 100644 index 0000000000..ebcdd79b63 --- /dev/null +++ b/packages/core/docs/.vuepress/enhanceApp.js @@ -0,0 +1,14 @@ +/** + * Client app enhancement file. + * + * https://v1.vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements + */ + + export default ({ + Vue, // the version of Vue being used in the VuePress app + options, // the options for the root Vue instance + router, // the router instance for the app + siteData // site metadata +}) => { + // ...apply enhancements for the site. +} diff --git a/packages/core/docs/.vuepress/integrations.js b/packages/core/docs/.vuepress/integrations.js new file mode 100644 index 0000000000..5687e1df5c --- /dev/null +++ b/packages/core/docs/.vuepress/integrations.js @@ -0,0 +1,357 @@ +const STATUSES = { + WIP: 'In progress', + ALPHA: 'Alpha', + BETA: 'Beta', + STABLE: 'Stable' +}; + +const AVAILABILITY = { + ENTERPRISE: 'Enterprise', + OPEN_SOURCE: 'Open Source' +}; + +const CATEGORIES = { + ANALYTICS: 'Analytics', + AUTH: 'Authentication', + CACHE: 'Cache', + CMS: 'CMS', + PAYMENT: 'Payment', + REVIEWS: 'Reviews', + SEARCH: 'Search' +}; + +const INTEGRATIONS = { + eCommerce: [ + { + name: 'commercetools', + link: '/v2/commercetools', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed627806456312e2aa80da9_1.commercetools_primary-logo_horizontal_RGB.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ] + }, + { + name: 'Magento', + link: 'https://docs.vuestorefront.io/magento', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed6279b8b992c23fd014f3b_Magento.svg', + status: STATUSES.BETA, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'Ecritel', link: 'https://www.ecritel.com/' }, + { name: 'Leonex', link: 'https://www.leonex.de/' } + ] + }, + { + name: 'Salesforce Commerce Cloud', + link: 'https://docs.vuestorefront.io/sfcc', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed627707df0e6b720941b44_saleforce-commerce-cloud-logo%201.svg', + status: STATUSES.BETA, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ] + }, + { + name: 'SAP Commerce Cloud', + link: '', + image: '/v2/integrations-logos/sap.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ] + }, + { + name: 'Spryker', + link: 'https://docs.vuestorefront.io/spryker', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed62750bc49f087fcfc9fd1_spryker_logo.svg', + status: STATUSES.BETA, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'Spryker', link: 'https://spryker.com/en/' }, + ] + }, + { + name: 'Shopify', + link: '/v2/shopify', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed627658b992c5c98014d1c_Shopify_logo_2018%201.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'Aureate Labs', link: 'https://aureatelabs.com/' }, + ] + }, + { + name: 'Virto Commerce', + link: '', + image: 'https://tadviser.ru/images/3/3d/Virto_Commerce_logo.png', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [] + }, + { + name: 'BigCommerce', + link: '', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/6023aa663109a7a8f995f095_BigCommerce-logo-dark.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [] + }, + { + name: 'AboutYou', + link: '', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/60c62f5a2d9aaf32e2f47a20_logo-commercesuite-vertical-default.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [] + }, + { + name: 'Sylius', + link: '', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed6275b7df0e61000941a54_sylius_logo.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'BitBag', link: 'https://bitbag.io/' }, + ] + }, + { + name: 'WooCommerce', + link: '', + image: '/v2/integrations-logos/woocommerce.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [] + }, + { + name: 'OpenCart', + link: '', + image: '/v2/integrations-logos/opencart.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [] + }, + { + name: 'Vendure', + link: '', + image: '/v2/integrations-logos/vendure.png', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'Jakub Andrzejewski', link: 'https://www.linkedin.com/in/jakub-andrzejewski/' }, + ] + }, + { + name: 'Odoo', + link: '', + image: '/v2/integrations-logos/odoo.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'OdooGap', link: 'https://www.odoogap.com/' } + ] + } + ], + other: [ + { + name: 'Storyblok', + link: 'https://docs.vuestorefront.io/storyblok', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed6227b6170c9985e071dc3_storyblok.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.CMS ], + compatibility: [] + }, + { + name: 'Amplience', + link: 'https://docs.vuestorefront.io/amplience', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed62250f722b86be30b5353_amplieance_logo%201.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.CMS ], + compatibility: [] + }, + { + name: 'Contentstack', + link: 'https://docs.vuestorefront.io/contentstack', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed621fa05af1d6c645af2ae_contentstack-logo-color%201.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.CMS ], + compatibility: [] + }, + { + name: 'Contentful', + link: 'https://docs.vuestorefront.io/contentful', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed622293c358c6d1e975830_contentful_logo%201.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.CMS ], + compatibility: [] + }, + { + name: 'Bazaarvoice', + link: './bazaarvoice.html', + image: 'https://upload.wikimedia.org/wikipedia/en/6/6a/Bazaarvoice_logo.jpg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.REVIEWS ], + compatibility: [] + }, + { + name: 'Redis', + link: './redis-cache.html', + image: 'https://upload.wikimedia.org/wikipedia/commons/6/6b/Redis_Logo.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.CACHE ], + compatibility: [] + }, + { + name: 'LexasCMS', + link: 'https://github.com/LexasCMS/vsf-next-lexascms', + image: 'https://uploads-ssl.webflow.com/5e7cf661c23ac9df156d9c3d/600968c141eb1b7f86436e77_lexascms-logo.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.OPEN_SOURCE, + maintainedBy: [ + { name: 'LexasCMS', link: 'https://www.lexascms.com/' }, + ], + categories: [ CATEGORIES.CMS ], + compatibility: [] + }, + { + name: 'Checkout.com', + link: 'https://www.npmjs.com/package/@vue-storefront/checkout-com', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/602123835f640234fea926d5_checkout-logo.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.PAYMENT ], + compatibility: [] + }, + { + name: 'Adyen', + link: './adyen.html', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed621cc6456318210a7d22b_Adyen_Corporate_Logo.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.PAYMENT ], + compatibility: [ 'commercetools' ] + }, + { + name: 'Auth0', + link: 'https://docs.vuestorefront.io/auth0', + image: '/v2/integrations-logos/auth0.svg', + status: STATUSES.STABLE, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.AUTH ], + compatibility: [ 'commercetools' ] + }, + { + name: 'Bloomreach', + link: '', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/609a7a69e04df518abce7c13_bloomreach-logo-horizontal.png', + status: STATUSES.WIP, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.CMS ], + compatibility: [] + }, + { + name: 'Magnolia', + link: '', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/60c349629772875c3f75ec50_Magnolia-CMS-logo.svg', + status: STATUSES.WIP, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.CMS ], + compatibility: [] + }, + { + name: 'Recurly', + link: '', + image: '/v2/integrations-logos/Recurly.png', + status: STATUSES.WIP, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.PAYMENT ], + compatibility: [] + }, + { + name: 'Algolia', + link: 'https://docs.vuestorefront.io/algolia', + image: 'https://uploads-ssl.webflow.com/5e90e5cd5f86784ad554a3c2/5ed620cebc49f091f5fc7571_logo-algolia-nebula-blue-full.svg', + status: STATUSES.BETA, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.SEARCH ], + compatibility: [ 'commercetools' ] + }, + + { + name: 'Constructor.io', + link: '', + image: '/v2/integrations-logos/constructor-io.svg', + status: STATUSES.BETA, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [ + { name: 'Vue Storefront', link: 'https://vuestorefront.io/' }, + ], + categories: [ CATEGORIES.SEARCH ], + compatibility: [] + }, + { + name: 'Adobe Experience Manager', + link: '', + image: 'https://www.rackspace.com/sites/default/files/styles/rxt_image/public/2020-11/AEM.png', + status: STATUSES.WIP, + availability: AVAILABILITY.ENTERPRISE, + maintainedBy: [], + categories: [ CATEGORIES.CMS, CATEGORIES.ANALYTICS ], + compatibility: [] + } + ] +}; + +module.exports = { + STATUSES, + AVAILABILITY, + CATEGORIES, + INTEGRATIONS +}; \ No newline at end of file diff --git a/packages/core/docs/.vuepress/public/favicon.png b/packages/core/docs/.vuepress/public/favicon.png new file mode 100644 index 0000000000..8cb26d52e4 Binary files /dev/null and b/packages/core/docs/.vuepress/public/favicon.png differ diff --git a/packages/core/docs/.vuepress/public/integrations-logos/Recurly.png b/packages/core/docs/.vuepress/public/integrations-logos/Recurly.png new file mode 100644 index 0000000000..97ebc7f721 Binary files /dev/null and b/packages/core/docs/.vuepress/public/integrations-logos/Recurly.png differ diff --git a/packages/core/docs/.vuepress/public/integrations-logos/auth0.svg b/packages/core/docs/.vuepress/public/integrations-logos/auth0.svg new file mode 100644 index 0000000000..2766105944 --- /dev/null +++ b/packages/core/docs/.vuepress/public/integrations-logos/auth0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/public/integrations-logos/constructor-io.svg b/packages/core/docs/.vuepress/public/integrations-logos/constructor-io.svg new file mode 100644 index 0000000000..4bdd3398e8 --- /dev/null +++ b/packages/core/docs/.vuepress/public/integrations-logos/constructor-io.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/public/integrations-logos/odoo.svg b/packages/core/docs/.vuepress/public/integrations-logos/odoo.svg new file mode 100644 index 0000000000..e7b82f7b9b --- /dev/null +++ b/packages/core/docs/.vuepress/public/integrations-logos/odoo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/public/integrations-logos/opencart.svg b/packages/core/docs/.vuepress/public/integrations-logos/opencart.svg new file mode 100644 index 0000000000..cf05d79ebd --- /dev/null +++ b/packages/core/docs/.vuepress/public/integrations-logos/opencart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/public/integrations-logos/sap.svg b/packages/core/docs/.vuepress/public/integrations-logos/sap.svg new file mode 100644 index 0000000000..36c74abebb --- /dev/null +++ b/packages/core/docs/.vuepress/public/integrations-logos/sap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/core/docs/.vuepress/public/integrations-logos/vendure.png b/packages/core/docs/.vuepress/public/integrations-logos/vendure.png new file mode 100644 index 0000000000..76f79d1455 Binary files /dev/null and b/packages/core/docs/.vuepress/public/integrations-logos/vendure.png differ diff --git a/packages/core/docs/.vuepress/public/integrations-logos/woocommerce.svg b/packages/core/docs/.vuepress/public/integrations-logos/woocommerce.svg new file mode 100644 index 0000000000..17f67f7eb1 --- /dev/null +++ b/packages/core/docs/.vuepress/public/integrations-logos/woocommerce.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/.vuepress/public/vuelogo.jpg b/packages/core/docs/.vuepress/public/vuelogo.jpg similarity index 100% rename from docs/.vuepress/public/vuelogo.jpg rename to packages/core/docs/.vuepress/public/vuelogo.jpg diff --git a/packages/core/docs/.vuepress/styles/index.styl b/packages/core/docs/.vuepress/styles/index.styl new file mode 100644 index 0000000000..fad3ec455f --- /dev/null +++ b/packages/core/docs/.vuepress/styles/index.styl @@ -0,0 +1,27 @@ + +.site-name { + display: none !important; +} + +.badge { + margin-top: 4px; + background-color: #22c34b; +} + +.badge.info { + background-color: #22c34b !important; +} + +.multiselect__tag { + background: #22c34b !important; +} + +.custom-block.tip { + border-color: #22c34b; + background-color: #f0f7f2; +} + +a code { + color: $accentColor !important; + font-weight: bold; +} diff --git a/packages/core/docs/.vuepress/styles/palette.styl b/packages/core/docs/.vuepress/styles/palette.styl new file mode 100644 index 0000000000..9305fc9aa4 --- /dev/null +++ b/packages/core/docs/.vuepress/styles/palette.styl @@ -0,0 +1,10 @@ +/** + * Custom palette here. + * + * ref:https://v1.vuepress.vuejs.org/zh/config/#palette-styl + */ + +$accentColor = #22c34b +$textColor = #2c3e50 +$borderColor = #eaecef +$codeBgColor = #282c34 diff --git a/docs/.vuepress/theme/index.js b/packages/core/docs/.vuepress/theme/index.js similarity index 100% rename from docs/.vuepress/theme/index.js rename to packages/core/docs/.vuepress/theme/index.js diff --git a/docs/.vuepress/theme/layouts/Layout.vue b/packages/core/docs/.vuepress/theme/layouts/Layout.vue similarity index 99% rename from docs/.vuepress/theme/layouts/Layout.vue rename to packages/core/docs/.vuepress/theme/layouts/Layout.vue index 9babad3a83..ef47d49f32 100644 --- a/docs/.vuepress/theme/layouts/Layout.vue +++ b/packages/core/docs/.vuepress/theme/layouts/Layout.vue @@ -60,19 +60,23 @@ import Navbar from '@theme/components/Navbar.vue' import Page from '@theme/components/Page.vue' import Sidebar from '@theme/components/Sidebar.vue' import { resolveSidebarItems } from '@vuepress/theme-default/util' + export default { name: 'Layout', + components: { Home, Page, Sidebar, Navbar }, + data () { return { isSidebarOpen: false } }, + computed: { shouldShowNavbar () { const { themeConfig } = this.$site @@ -90,6 +94,7 @@ export default { || this.$themeLocaleConfig.nav ) }, + shouldShowSidebar () { const { frontmatter } = this.$page return ( @@ -98,6 +103,7 @@ export default { && this.sidebarItems.length ) }, + sidebarItems () { return resolveSidebarItems( this.$page, @@ -106,6 +112,7 @@ export default { this.$localePath ) }, + pageClasses () { const userPageClass = this.$page.frontmatter.pageClass return [ @@ -118,16 +125,19 @@ export default { ] } }, + mounted () { this.$router.afterEach(() => { this.isSidebarOpen = false }) }, + methods: { toggleSidebar (to) { this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen this.$emit('toggle-sidebar', this.isSidebarOpen) }, + // side swipe onTouchStart (e) { this.touchStart = { @@ -135,6 +145,7 @@ export default { y: e.changedTouches[0].clientY } }, + onTouchEnd (e) { const dx = e.changedTouches[0].clientX - this.touchStart.x const dy = e.changedTouches[0].clientY - this.touchStart.y diff --git a/packages/core/docs/Dockerfile b/packages/core/docs/Dockerfile new file mode 100644 index 0000000000..f0cab02fda --- /dev/null +++ b/packages/core/docs/Dockerfile @@ -0,0 +1,16 @@ +FROM node:12 AS build + +WORKDIR /var/www + +COPY . . + +RUN yarn install \ + && yarn build:ct:tools + +RUN cd packages/core/docs \ + && yarn install \ + && yarn build + +FROM nginx + +COPY --from=build /var/www/packages/core/docs/.vuepress/dist /usr/share/nginx/html/v2 \ No newline at end of file diff --git a/packages/core/docs/README.md b/packages/core/docs/README.md new file mode 100644 index 0000000000..124047b358 --- /dev/null +++ b/packages/core/docs/README.md @@ -0,0 +1,97 @@ +# Introduction + +[[toc]] + +## What is Vue Storefront? + +_Vue Storefront_ is a ___platform-agnostic e-commerce PWA frontend framework___ that can work with any e-commerce backend API. Additionally, thanks to _low coupling and high cohesion_, it can connect to other services, giving you the freedom to work with the technologies you know and love, be it CMS, ERP, PIM, or anything else. + +
+ +
+ +That's a mouthful, so let's break it down: + - __platform-agnostic__ - we made it possible to work with any platform and service you already use, as long as it has an API like REST or GraphQL. + - __e-commerce__ - today's shops are much more than just products and carts. That's why we made it easy to integrate other types of services, such as helper service for ERP, versatile search features for PIM, portable checkout for 3rd party payment kiosk, and more. + - __PWA__ - it's the technology of the future, designed to give the best performance on any device, with native-like features to satisfy your customer's needs. + - __frontend framework__ - _Vue Storefront_ is a set of modular features, glued together using _interfaces_ and _factories_ and powered by [Nuxt.js](https://nuxtjs.org/). + +## Problems Vue Storefront solves + +The main purpose of any software is to solve problems and Vue Storefront is no different. We're doing our best to find common issues in the eCommerce space and find viable and scalable solutions. Below you can find just a few. + +### Long time to market + +Headless eCommerce frontends are complex and developing them can take a lot of time. + +**Solution** + +With Vue Storefront you're getting a performant frontend connected to headless e-commerce, CMS, and other third-party platforms of your choice, along with hundreds of ready-to-use Vue Storefront and Nuxt.js modules for all common functionalities. Thanks to them, you will save hundreds (or even thousands) of working hours, so you can focus on creating value for your product while leaving the heavy lifting to us! + +### Slow, unresponsive online shop + +By some estimates, up to 1% of users will leave your website for every 100ms of delay in page load time. No matter how great your products are, a slow and unresponsive shop will make your conversion significantly lower. + +**Solution** + +We solved these issues by: +- using modern technologies for small bundle sizes and performance; +- using code splitting, lazy loading, and lazy hydration to load only what's needed at the moment; +- caching the resources, so the already visited pages are loaded instantly; +- preloading resources that might be needed in the future; +- hosting and executing as much as possible on the server, so the part served to the users is much lighter and faster compared to traditional SPA; + +### Unwieldy architectural decisions + +It can be incredibly hard to add or remove simple features when the code doesn't follow industry standard patterns and conventions. Even a simple bugfix or security update can be a very time-consuming task. + +**Solution** + +We are promoting good architectural decisions by providing an opinionated way of building eCommerce frontends based on years of experience. Whatever issues you could run into, we made sure that our modular and flexible architecture will handle them. + +### Painful or impossible migrations + +It can be frustrating when the technology you choose turned out to not fit your business needs, be it feature- or cost-wise. It can be even worse if you can't change it at all or the cost outweighs the benefits. + +**Solution** + +From the very beginning, Vue Storefront was designed to be backend-agnostic. This means that all eCommerce backends are integrated on the frontend under common interfaces and can be replaced without having to rewrite the frontend from scratch. Most technologies are completely different on the backend but are very similar from the frontend perspective, so we made abstractions that will make your migrations painless. + +### Lack of platform-specific competencies + +So your Magento department is not doing well, but the commercetools one is growing like crazy? If only you could move developers from one department to another... + +**Solution** + +With Vue Storefront you can! We have common interfaces for all integrations of the same type (eCommerce, CMS, Loyalty, etc.), so once a developer learns Vue Storefront they can be confident with any tech stack that works with it. + +### Lack of flexibility + +Do you recall the frustration when it wasn't possible to implement your dream design or feature within your backend platform, or adding a single modal window took few days? + + +**Solution** + +You will forget about these issues with Vue Storefront! For the best experience, we divided the system into small and modular chunks. All parts are individual `npm` packages, so switching from one version to another should be as easy as any package installation. +Vue Storefront was built on the firm ground of _microservice_ architecture. Each of these packages is independent and optional, so you decide how much of the framework you want to utilize. Moreover **there are absolutely no limitations in terms of UI customization**. Your theme is just a regular Nuxt.js project, which you can customize to any degree. + +## eCommerce Integrations + +Vue Storefront is a frontend framework, so it needs an e-commerce backend. You can see the list of supported e-commerce platforms on the [integrations page](./integrations). + +## Tech stack + +The speed and flexibility of Vue Storefront wouldn't be possible without the great technologies that power it: + +- [Vue.js](https://vuejs.org/v2/guide/) +- [Nuxt.js](https://nuxtjs.org/guide) +- [SCSS](https://sass-lang.com/) +- [Storefront UI](https://www.storefrontui.io/) (optional) +- [TypeScript](https://www.typescriptlang.org/docs/home) (optional) +- [Cypress](https://www.cypress.io/) (optional) + +## What's next? + +If you're already convinced to use Vue Storefront, check the [Installation guide](./general/installation.html). + +If you want to learn more, check the [Key concepts](./general/key-concepts.html) behind Vue Storefront. diff --git a/packages/core/docs/advanced/architecture.md b/packages/core/docs/advanced/architecture.md new file mode 100644 index 0000000000..9d4f973911 --- /dev/null +++ b/packages/core/docs/advanced/architecture.md @@ -0,0 +1,10 @@ +# Architecture + +::: tip Don't get scared! +This diagram shows a lot of useful information but it's not mandatory to understand all these relations to confidently work with Vue Storefront. +::: + + +
+ vue storefront cli +
\ No newline at end of file diff --git a/packages/core/docs/advanced/calling-platform-api.md b/packages/core/docs/advanced/calling-platform-api.md new file mode 100644 index 0000000000..32bab919c4 --- /dev/null +++ b/packages/core/docs/advanced/calling-platform-api.md @@ -0,0 +1,25 @@ +# Calling integration APIs + +## How do integrations work? + +In Vue Storefront, single integration has its own sort of software development kit (SDK) library. This library is named API-client and contains a set of functions. Each function is dedicated to one action, endpoint or a feature eg. `getProduct`, `loadCart` `addToCart`. For example, when you call `getProduct`, it will request the integration API and return its response in the original format of a given platform. + +## Accessing integration methods on the frontend + +To access API-client functions, you can use the composable function `useVSFContext`: + +```ts +// each platform has different tag to access its methods, eg $spryker, $storyblok etc. +const { $ct } = useVSFContext(); + +$ct.api.getProduct({ id: 1 }) +``` + +In the example above we access the API-client for `commercetools` and call `getProduct` function. Each integration has a dedicated tag name - for more information see [context docs](/advanced/context.html). + + + +## Extending Integrations + +Sometimes, it's necessary to override the original behavior for either API-client or even an entire request that comes from an external platform. +The Vue Storefront also provides a possibility to do this by using [middleware extensions](/advanced/server-middleware.html) diff --git a/packages/core/docs/advanced/context.md b/packages/core/docs/advanced/context.md new file mode 100644 index 0000000000..c9d2319002 --- /dev/null +++ b/packages/core/docs/advanced/context.md @@ -0,0 +1,67 @@ +# Context API + +The application context is essential when it comes to sharing something across the app. A runtime config, current connection to the API, API tokens, user session, and everything else that's related to the current request should be stored within the context. + +The common solution that may come to your mind in such a case is using one global object to store everything you need in the app. However, by doing this you would be sharing data not only over the app but also across all of the incoming requests. That would cause lots of issues and your app won't be able to handle ordinary traffic. + +## Context data structure + +In Vue Storefront, each integration has a common structure of the context object. A root of the context starts with the `$vsf` key. Everything that's under this key is the integration keys which are storing the data for corresponding integration using the specific, predefined format. + +```js +$vsf { + $ct: { + api: {}, + client: {}, + config: {} + } + ... +} +``` + +- `$vsf` - a general key that keeps Vue Storefront context +- `$ct` - an integration key +- `api` - integration API functions +- `client` - integration API client/connection +- `config` - integration configuration +- others - you can define custom context fields by yourself + +## Context composable + +To use the context or access your integration API, you can leverage a dedicated composable `useVSFContext`. It returns integration keys of all of the integrations you have registered in the Vue Storefront (prefixed by `$` sign). + +```js +const { $ct, $other } = useVSFContext(); + +$ct.api.getProduct({ id: 1 }); +$other.client.get('/othet-integration'); +``` + +## Context plugin + +If for some reason you don't want to use integration Nuxt modules, you have to configure the integration by yourself. For that purpose, each integration exposes an integration plugin: + +```js +// plugins/integration.js +import { integrationPlugin } from '@vue-storefront/commercetools'; + +export default integrationPlugin(({ app, integration }) => { + const settings = { api: '/graphql', user: 'root' }; + + integration.configure({ ...settings }); +}); +``` + +Each integration has a predefined set of API functions, that sometimes you may want to override. A `configure` function gives you that ability as well. When you pass your new API function, or use a name of the existing one, the Vue Storefront will automatically apply it to the app. + +```js +// plugins/integration.js +import { integrationPlugin } from '@vue-storefront/commercetools'; +import { getMe } from '@vue-storefeont/your-integration'; + +export default integrationPlugin(({ app, integration }) => { + const settings = { api: '/graphql', user: 'root' }; + + integration.configure({ ...settings }, { getMe }); +}); +``` diff --git a/packages/core/docs/advanced/creating-custom-composable.md b/packages/core/docs/advanced/creating-custom-composable.md new file mode 100644 index 0000000000..4f81fd6753 --- /dev/null +++ b/packages/core/docs/advanced/creating-custom-composable.md @@ -0,0 +1,86 @@ +# Creating custom composable + +You can create your own composable if our implementation and interfaces don't cover your use case. However, there are few things you need to consider and handle, like context and reactivity. + +Before implementing, we recommend getting familiar with our [composables factories](https://github.com/vuestorefront/vue-storefront/tree/next/packages/core/core/src/factories). This will help you understand how they work internally. + +## Naming convention + +While not mandatory, we recommend that you follow the industry convention of naming composables using camelCase and prefixing them with the `use` keyword. + +## Structure + +We recommend following our convention of returning raw data object (`result` in the example below) as well as `loading` and `error` properties: + +- Raw data object should contain a response from the API later passed to getters to extract data. + +- `loading` property should indicate if any of the composable methods is waiting for the data from the API. + +- `error` property should be an object with keys matching the names of the composable methods. Its values indicate if any of the composable methods threw an error. + +:::tip Why `loading` and `error` are `computed` properties? +You might have noticed that `loading` and `error` are wrapped in the `computed` function in the composable factories. +This prevents external modifications that could result in a state mismatch and hard-to-track errors. +::: + +## Shared composable + +In most cases, your composable is bound to a specific entity and isn't reusable across the components or pages. +For example, you might have a product with a given ID. If you load it using `useProduct`, this instance won't be helpful if you open a page with a different product. + +However, some composables are reusable and shared. Two such examples are `useUser` and `useCart`, which will load the same data, regardless of the component or page they are called from. + +**Composables specific to a single entity should accept an `id` parameter and use it in names passed to `sharedRef`.** + +## Example + +The example below shows how to implement composable bound to a single entity. If your composable is shared, you can remove the `id` parameter and its uses. + +:::warning Only call `sharedRef` inside composable +Be sure to create variables using `sharedRef` **inside** a wrapper function. Otherwise, you might have issues with reactivity or even leak state between the requests. +::: + +```typescript +import { computed } from '@vue/composition-api'; +import { sharedRef, useVSFContext, Logger } from '@vue-storefront/core'; + +export const useCustom = (id: string) => { + // Loads context used to call API endpoint + const context = useVSFContext(); + + // Shared ref holding the response from the API + const result = sharedRef(null, `useCustom-${id}`); + + // Shared ref indicating whether any method is waiting for the data from the API + const loading = sharedRef(false, `useCustom-loading-${id}`); + + // Shared ref holding errors from the methods + const error = sharedRef({ + search: null + }, `useCustom-error-${id}`); + + // Method to call an API endpoint and update `result`, `loading` and `error` properties + const search = async (params) => { + Logger.debug(`useCustom/${id}/search`, params); + + try { + loading.value = true; + // Change "yourIntegration" to the name of the integration + result.value = await context.$yourIntegration.api.searchCustom(params); + error.value.search = null; + } catch (err) { + error.value.search = err; + Logger.error(`useCustom/${id}/search`, err); + } finally { + loading.value = false; + } + }; + + return { + search, + result: computed(() => result.value), + loading: computed(() => loading.value), + error: computed(() => error.value) + }; +}; +``` diff --git a/packages/core/docs/advanced/extending-graphql-queries.md b/packages/core/docs/advanced/extending-graphql-queries.md new file mode 100644 index 0000000000..f478607553 --- /dev/null +++ b/packages/core/docs/advanced/extending-graphql-queries.md @@ -0,0 +1,122 @@ +# Extending GraphQL queries + +:::danger Don't forget to reload the application +The application does not reload automatically after saving the changes in Server Middleware. Due to this, you have to restart the application manually. We are working on enabling Hot Reloading in future updates. +::: + +If your integration uses GraphQL API, you may want to change the default query or mutation that is being sent. That's quite a common case for fetching additional or custom fields. Vue Storefront provides the mechanism for this called "custom queries". + +Since the communication with the API goes through our middleware, all queries also are defined there. To customize or even entirely override the original (default) queries you need to follow two steps. + +Firstly, you need to pass a `customQuery` parameter to the method that triggers the call to the API. It's an object where the keys are the name of the default queries and values are the name of the custom query that overrides them. Additionally, there is a special parameter called `metadata` which allows you to optionally pass additional parameters to your custom query that will be accessible in the custom query function. + +```ts +const { search } = useProduct(); + +search({ + customQuery: { + products: 'my-products-query', + metadata: { size: 'xl' } + } +}); +``` + +In the example above, we change the `products` query with our custom query named `my-products-query`. Additionally, the `metadata` field holds additional parameters about the product we seek for. As a second step, we need to define `my-products-query` query. + +Each custom query lives in the `middleware.config.js`, so it's the place where we should define `my-products-query`. + +Custom query functions have the arguments: + +- the default query (`query`), +- default variables (`variables`) passed to the query, +- additional parameters passed from the front-end (`metadata`). + +This function must always return an object with `query` and `variables` keys, while in the body you can do anything you want with those parameters - you can modify them or change to the new ones. + +Every custom query is registered in the `middleware.config.js` file: + +```js +// middleware.config.js + +module.exports = { + integrations: { + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { /* ... */ }, + customQueries: { + 'my-products-query': ({ query, variables, metadata }) => { + + variables.locale = 'en' + variables.size = metadata.size + + return { query, variables } + } + } + } + } +}; +``` + +In the example above we only modified some `variables` that are passed to the custom query. However, we can also change the default GraphQL query: + +```js +// middleware.config.js + +module.exports = { + integrations: { + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { /* ... */ }, + customQueries: { + 'my-products-query': ({ variables }) => { + query: ` + query products($where: String) { + products(where: $where) { /* ... */ } + } + `, + variables, + } + } + } + } +}; +``` + +## Keeping the configuration tidy + +Your configuration file can quickly become a mess if you override a lot of queries. We recommend extracting them into a separate folder to not overload `middleware.config.js` with GraphQL queries. +You can create a new file (or files) that exports the queries. Then, you can import them in the `middleware.config.js`: + +```js +// customQueries/index.js + +module.exports = { + 'my-products-query': ({ variables }) => { + query: ` + query products($where: String) { + products(where: $where) { /* ... */ } + } + `, + variables, + }, + ... // other custom queries +}; +``` + +Now let's import it in `middleware.config.js`: + +```js +// middleware.config.js + +const customQueries = require('./customQueries'); + +module.exports = { + integrations: { + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { /* ... */ }, + customQueries, + } + } +}; +``` diff --git a/packages/core/docs/advanced/internationalization.md b/packages/core/docs/advanced/internationalization.md new file mode 100644 index 0000000000..816742e98d --- /dev/null +++ b/packages/core/docs/advanced/internationalization.md @@ -0,0 +1,86 @@ +# Internationalization + +If you're building a shop for an international brand you want it to be translated to different languages and using different currencies. In this document, you will learn how we're approaching internationalization in Vue Storefront and how to configure your application to use it. + +::: warning i18n is not multi-tenancy! +This document explains only how to make a single shop instance available for multiple countries. If you need to build a system for multiple tenants we suggest creating an instance of Vue Storefront for each tenant and sharing common resources through an NPM package. +::: + +## How it works by default? + +By default, we are using [`nuxt-i18n`](https://i18n.nuxtjs.org/) module for handling both translations and currencies. It is preinstalled in the default theme and configured for English and German translations out of the box. + +In the theme `nuxt-i18n` is using `$t('key')` to translate strings and `$n(number)` to add the currency sign. You can find the translation keys in the `lang` directory of your project and configuration for currencies in `nuxt.config.js`. + +::: tip +Even though the module is included in the default theme, it's not required and [you can always get rid of it.](#configuring-modules-separately) and handle i18n differently. +::: + +In order to provide a unified way of configuring i18n across the application for different modules and integrations, we have introduced the field `i18n` in each module's configuration. It has the same format as `nuxt-i18n` options. You can add any configuration there and it will be propagated to all other Vue Storefront modules. + +All Vue Storefront integrations have `useNuxtI18nModule` property set to `true`. It means that they will use the same configuration as you provided for `nuxt-i18n` in `i18n` field of your `nuxt.config.js` + +```js +// nuxt.config.js +modules: [ + ['@vue-storefront/{INTEGRATION}/nuxt', { + api: { + // api client configuration + }, + i18n: { + useNuxtI18nModule: true + } + }] +], +i18n: { + locales: [ + { + code: 'en', + label: 'English', + file: 'en.js', + iso: 'en' + }, + { + code: 'de', + label: 'German', + file: 'de.js', + iso: 'de' + } + ], + defaultLocale: 'en' +} + +``` + +## Configuring modules separately + +You always can provide your own i18n configuration just for a specific module by setting `useNuxtI18nModule` to false. + +```js +[ + '@vue-storefront/{INTEGRATION}/nuxt', + { + api: { + // api client configuration + }, + i18n: { + useNuxtI18nModule: false, + locales: [ + { + code: 'en', + label: 'English', + file: 'en.js', + iso: 'en', + }, + { + code: 'de', + label: 'German', + file: 'de.js', + iso: 'de', + }, + ], + defaultLocale: 'en', + }, + }, +]; +``` diff --git a/packages/core/docs/advanced/logging.md b/packages/core/docs/advanced/logging.md new file mode 100644 index 0000000000..b7f7e019a2 --- /dev/null +++ b/packages/core/docs/advanced/logging.md @@ -0,0 +1,100 @@ +# Logging + +In Vue Storefront we're providing all the debugging information, warnings, and errors from composables out of the box so you won't miss anything that happens inside your application. In this document, you will learn how to use our logger in your application and how to connect it with external services. + +## Using logger in your app + +To make use of the Vue Storefront logger, simply import it from the core and use one of 4 available types of messages + +```js +import { Logger } from '@vue-storefront/core'; + +Logger.error('error message'); +Logger.info('info message'); +Logger.warn('warn message'); +Logger.debug('debug message'); +``` + +## Changing verbosity + +Configuration of logger happens through the `logger` property of `@vue-storefront/nuxt` module. + +You can set the `verbosity` level which tells the app what do you want to log and what communications you want to ignore. By default, we have the following verbosity levels: + +- `debug` - log everything, including debug calls, information, warnings, and errors (all of the logger functions can be called), +- `info` - log information, warnings, and errors (debug function calling is skipped), +- `warn` - log warnings and errors (debug and info functions are skipped), +- `error` - log only errors (debug, warn and info functions are skipped), +- `none` - don't log anything. + +```js +[ + '@vue-storefront/nuxt', + { + logger: { + verbosity: 'error' + } + } +]; +``` + +If not explicitly changed, logging level depends on the current environment variable `NODE_ENV`: + +- `development` or `dev` defaults to `warn`, +- `production` or `prod` defaults to `error`, +- `test` defaults to `none`, +- any other defaults to `warn` + +## Writing custom loggers + +By default, we're printing all the events happening in the app in the console but you can easily write a new logger and use a third-party library (like [consola](https://github.com/nuxt-contrib/consola)) or pass the logs to the external service like [Sentry](https://sentry.io/welcome/) + +To override the default logger, pass a function to the `logger.customLogger` property of `@vue-storefront/nuxt` module. This function returns the logger object and as an argument, you have access to the `verbosity` level: + +```js +[ + '@vue-storefront/nuxt', + { + logger: { + verbosity: 'error', + customLogger: (verbosity) => { + console.log('Current verbosity level is:', verbosity); + return { + debug: (message: any, ...args) => + console.debug('[VSF][debug]', message, ...args), + info: (message: any, ...args) => + console.info('[VSF][info]', message, ...args), + warn: (message: any, ...args) => + console.warn('[VSF][warn]', message, ...args), + error: (message: any, ...args) => + console.error('[VSF][error]', message, ...args) + }; + } + } + } +]; +``` + +::: details Configuring logger outside Vue Storefront Nuxt module +If for some reason you can't configure logger through `@vue-storefront/nuxt` module you can explicitly use `registerLogger` function: + +```ts +import { registerLogger } from '@vue-storefront/core'; + +const myLogger = { + debug: (message: any, ...args) => + console.debug('[VSF][debug]', message, ...args), + info: (message: any, ...args) => + console.info('[VSF][info]', message, ...args), + warn: (message: any, ...args) => + console.warn('[VSF][warn]', message, ...args), + error: (message: any, ...args) => + console.error('[VSF][error]', message, ...args) +}; + +const verbosity = 'error'; + +registerLogger(myLogger, verbosity); +``` + +::: diff --git a/packages/core/docs/advanced/performance.md b/packages/core/docs/advanced/performance.md new file mode 100644 index 0000000000..23e43c180a --- /dev/null +++ b/packages/core/docs/advanced/performance.md @@ -0,0 +1,49 @@ +# Performance + +This document will walk you through performance configuration options for `@vue-storefront/nuxt` package and allow you to easily reduce size of your app and improve performance. + +## Configuration + +`nuxt.config.js` file contains contains few `buildModules`, one of which is `@vue-storefront/nuxt`. It already has some options passed to it, however there are few more, which are not used by default: + +```javascript +// nuxt.config.js +['@vue-storefront/nuxt', { + // other options + performance: { + httpPush: true, + purgeCSS: { + enabled: false, + paths: [ + '**/*.vue' + ] + } + } +}] +``` + +### HTTP2 Push + +`httpPush` option (_enabled by default_) leverages [`http2` option in Nuxt.js](https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-render#http2). It's configured to automatically push all JavaScript files needed for the current page. If you want to override this behaviour, you can disable this option and use Nuxt.js configuration instead. + +If you can't use HTTP2, you can disable this option. In this case, Nuxt.js will still `preload` these scripts, which is only slightly slower than HTTP2 push. + +### PurgeCSS + +`purgeCSS` option (_disabled by default_) uses [`nuxt-purgecss` plugin](https://github.com/Developmint/nuxt-purgecss) to remove unused CSS and accepts the same options, with two differences: + +* when `enabled` is set to `false`, plugin will not be registered at all, not only be disabled, + +* `**/*.vue` is added to `paths` array to detect all `.vue` files in your project, including those from `_theme` directory. Without this, some styles used on the page would also be removed. + +If you decide to enable this plugin, we recommend using `enabled: process.env.NODE_ENV === 'production'`, to keep development mode as fast as possible. + +::: warning +Because PurgeCSS looks for whole class names in files, it may remove styles for dynamic classes. If you're using a dynamic class, please use whole names instead of concatinating variables (eg. `isDev ? 'some-style-dev' : 'some-style-prod'` instead of `some-style-${ isDev ? 'dev' : 'prod' }`. If this can't be avoided, add them to `whitelist` array. +::: + +## Best practices + +### Using StorefrontUI + +`@vue-storefront/nuxt` module automatically detects if you have `@storefront-ui/vue` installed and if so, registers [`@nuxtjs/style-resources` module](https://github.com/nuxt-community/style-resources-module). It automatically registers all variables, mixins and functions from StorefrontUI, which means you don't have to import them. **_Importing SCSS files from StorefrontUI might duplicate some styles, significantly increasing your bundle size and impact performance._** diff --git a/packages/core/docs/advanced/server-middleware.md b/packages/core/docs/advanced/server-middleware.md new file mode 100644 index 0000000000..06281275a2 --- /dev/null +++ b/packages/core/docs/advanced/server-middleware.md @@ -0,0 +1,152 @@ +# Server middleware + +:::danger Don't forget to reload the application +The application does not reload automatically after saving the changes in Server Middleware. Due to this, you have to restart the application manually. We are working on enabling Hot Reloading in future updates. +::: + +## What is Vue Storefront Middleware and why we need it? + +The Vue Storefront middleware is an Express proxy that takes the requests from the front-end, translates them to a given integration, and calls related API-client. + +We have implemented it for a variety of reasons. + +First of all, it allows us to provide a proven way of extensibility. As a developer, you have control of the requests and responses in the given platform with [extensions](/advanced/server-middleware.html#extending-integrations)) + +All platform credentials are stored only on the server side and not exposed to the frontend part of your application. + +Performance optimizations - since we moved the networking layer to the server-side, the final code shipped to the browser is way smaller, which impacts the initial loading time. + +## How it works (in a nutshell) + +The way it works represents the following diagram: + +
+ API Middleware Diagram +
+ +The API-client is being called only on the middleware, but you still can access it on the front-end side - how is that possible? + +When you access an API-client on the front-end side, you are accessing actually a stub, instead of a real API-client instance. This stub makes a call to the middleware ([Remote Procedure Call](https://en.wikipedia.org/wiki/Remote_procedure_call)), and asks for loading a specific integration and executing a function you are asking for. + +For example, the following code: +```js +context.$ct.getProduct({ id: 1 }) +``` + +Generates the request to our middleware: +- `POST / api/ct/getProduct` - a http call, where `ct` is a tag name of integation and `getProduct` is the name of a function needs to be called +- `http body` - the body of HTTP request we are sending array of arguments + +Middleware reads tag name of integration and the function name that needs to be called, executes it, and sends a response back to the browser as if it was transferred using a direct connection. + +## Configuration + +When it comes to configuration, middleware has a dedicated config called `middleware.config.js` that contains a section with integrations(`integrations`) along with their credentials and other options. + +```js +module.exports = { + integrations: { + : { + location: '@/server', + configuration: {} + extensions: (extensions) => extensions, + customQueries: {} + } + } +}; +``` + +Each entry under the `integrations` section starts with a tag name of given integration, and contains an object with the following fields: + +- `location` - points to the package of the API-client, related to given integration (server entry point) +- `configuration` - contains a configuration of given integration, such as credentials and others +- `extensions` - a function that returns a extensions (jump to the next section) +- `customQueries` - section that contains custom queries (graphql only) + +## Extending Integrations + +
+ Middleware Extensions +
+ +Middleware allows you to inject into the lifecycle of the entire network flow, starting with configuring a connection and ending with a final response. To use those things, we created an extension feature. + +You can define as many extensions as you want. Remember they are always correlated with a specific API-client (integration). How do they look like? Each extension has the following structure: + +```js +const extension = { + name: 'extension-name', + extendApiMethods: { + getProduct: async () => { /* ... */ } + }, + extendApp: (app) => { /* ... */ }, + hooks: (req, res) => { + return { + beforeCreate: ({ configuration }) => configuration, + afterCreate: ({ configuration }) => configuration, + beforeCall: ({ configuration, callName, args }) => args, + afterCall: ({ configuration, callName, args, response }) => response + } + } +} +``` + +- `name` - defines the unique name of an extension +- `extendApiMethods` - overrides the original functions from API-client +- `extendApp` - a function that gives you access to the express.js app +- `hooks` - defines lifecycle hooks of API-client +- `hooks:beforeCreate` - called before API-client creates a connection, takes the given configuration as an argument, and must return the configuration. Here you can attach something else to the configuration or even change it. +- `hooks:afterCreate` - Similar to the previous one, but called after the connection has been created. It also returns a configuration and you can change it. +- `hooks:beforeCall` - called before each API-client function. We have access to the configuration, function name, and its arguments. This function must return the arguments and based on the input parameters we can change it. +- `hooks:afterCall` - called after each API-client function.We have access to the configuration, function name, and its arguments. This function must return the response and based on the input parameters we can attach something to it. + + +To register a created extension, we have to add it do the middleware config file: + +```js +module.exports = { + integrations: { + : { + location: '@/server', + configuration: {} + extensions: (extensions) => [ + ...extensions, + { + name: 'our-extension' + hooks: () => { /* ... */} + } + ], + customQueries: {} + } + } +}; +``` + +## Separating middleware from Nuxt + +By default, Vue Storefront middleware is running within the Nuxt.js process. Sometimes there is a need to disconnect it from the app and run it as a separate and independent instance (process). + +Since it's just an Express application, you can do this, by creating a `middleware.js` file: + +```js +const { createServer } = require('@vue-storefront/middleware'); +const { integrations } = require('./middleware.config'); + +const app = createServer({ integrations }); + +app.listen(8181, () => { + console.log('Middleware started'); +}); +``` + +Now, when you run this using Node, your middleware should work separately. + +Additionally, you need to remove the middleware module entry from the `nuxt.config.js` and configure the domain, where your middleware is settled. + +```js +export default { + publicRuntimeConfig: { + middlewareUrl: 'https://api.commerce.com' + } +} +``` diff --git a/packages/core/docs/advanced/ssr-cache.md b/packages/core/docs/advanced/ssr-cache.md new file mode 100644 index 0000000000..18c6257470 --- /dev/null +++ b/packages/core/docs/advanced/ssr-cache.md @@ -0,0 +1,207 @@ +# Server Side Rendering Cache + +## Introduction + +Caching allows saving pages rendered on the server for later use, to avoid computationally expensive rendering from scratch when possible. This is especially useful when the application has pages that require a lot of computation, make many API calls, or change infrequently. It not only reduces the load on the server but also greatly improves performance. + +Caching SSR output in Vue Storefront requires two packages: +* `@vue-storefront/cache` - Nuxt.js module, that does the heavy lifting. It registers required plugins, creates [invalidation endpoint](#invalidating-tags), and hooks into the render cycle. +* **the driver** - thin layer on top of `@vue-storefront/cache` that integrates with specific caching solution, such as [Redis](https://redis.io/) or [Memcached](https://memcached.org/). + +## Installation + +### Add dependencies + +```sh +yarn add @vue-storefront/cache +yarn add # eg. @vsf-enterprise/redis-cache +``` + +### Configure Nuxt + +The next step is to register Nuxt module from `@vue-storefront/cache` package in `nuxt.config.js` with driver and invalidation configuration. + +::: warning Be careful +Make sure this package is added to the `modules` array, not `buildModules`. +::: + +```javascript +// nuxt.config.js + +export default { + modules: [ + ['@vue-storefront/cache/nuxt', { + invalidation: { + endpoint: '/cache-invalidate', + handlers: [ + // Handlers + ] + }, + driver: [ + '', + { + // Driver configuration + } + ] + }] + ] +}; +``` + +We can break down package configuration into two pieces: + +* `invalidation` (optional object) - contains URL to invalidate cache and array of invalidation functions. Refer to the [Invalidating cache](#invalidating-tags) section for more information. +* `driver` (array or string) - contains the path to or name of the driver package and its configuration. If the driver doesn't require any configuration, you can pass a string instead of an array. Refer to the documentation of the driver you use for more information. + +### Add tags + +If you follow the above steps and run the application, you won't see any performance difference. This is because only pages with tags are cached. + +Refer to the [Tags](#tags) section for more information. + +## How it works? + +When the page is requested, the cache driver checks if there is an already rendered page in the cache matching the current route. If there is, it will serve the cached version. Otherwise, the current page will be rendered on the server and served to the user, but if it contains tags, the result will be saved in the cache and used for subsequent requests. + +
+ Server Side Rendering request flow +
+ +## Tags + +Tags are strings associated with the rendered page and represent elements of the page that are dynamic and can change in the future. Each tag consists of a prefix and a unique ID associated with the dynamic element. +For example category with the ID of 1337 would create a tag `C1337`. + +A typical category page would have tags for: +* current category, +* all visible subcategories, +* all visible products. + +### Why we need tags? + +When at least one tag associated with the given page is [invalidated](#invalidating-tags), the whole page is removed from the cache and will be rendered from scratch on the next request. For example, if one of the products is modified or disabled, we should invalidate cache for pages where this product is visible: +* Product page for this particular product. +* Other product pages, where this product is listed (upsell or cross-sell). +* Homepage, if the product is displayed in the carousel or listed as a popular item. +* Category page, where this product is listed. +* Search page, where this product is part of the results. + +Additionally, all modifiers changing what is displayed on the page, such as pagination, filtering, and sorting options should be added as URL queries (for example `?sort=price-up&size=36&page=3`). This will cause different modifier combinations to be treated as different routes, and thus, cached separately. + +::: warning +Don't use tags on pages, components, or composables specific to the current user, such as user profile pages or cart components. +::: + +### Using tags + +Tags should be registered in Vue components or composables. During Server Side Rendering, tags registered in the current route are associated with the rendered page. + +To add tags, use `useCache` composable from `@vue-storefront/cache` package. + +```javascript +// pages/Category.vue +import { onSSR } from '@vue-storefront/core'; +import { useCache, CacheTagPrefix } from '@vue-storefront/cache'; + +export default { + setup() { + const { addTags } = useCache(); + + onSSR(() => { + addTags([ + { prefix: CacheTagPrefix.View, value: 'category' }, + { prefix: CacheTagPrefix.Category, value: id }, + // or + { prefix: 'V', value: 'category' }, + { prefix: 'C', value: id }, + ]); + }) + } +}; +``` + +### Invalidating tags + +::: tip +Check the documentation for your e-commerce integration to see if it provides any invalidation handlers. +::: + +As mentioned in [Installation](#installation) section, `@vue-storefront/cache` module provides option to create invalidation endpoint. + +Because each integration may pass data in a different format or multiple invalidation strategies may be needed at the same time, we need **invalidation handlers**. Each handler may return tags, which are later combined and passed to the driver for invalidation. + +Internally `prefix` and `value` provided to `addTags` method are combined into one string, so `{ prefix: 'V', value: 'category' }` becomes `Vcategory` and `{ prefix: 'C', value: 1337 }` becomes `C1337`. + +```javascript +invalidation: { + endpoint: '/cache-invalidate', + handlers: [ + // Integration specific handlers + ] +} +``` + +#### Default invalidation handler + +`@vue-storefront/cache` package provides a default handler that can be enabled using following configuration: + +```javascript +['@vue-storefront/cache/nuxt', { + invalidation: { + endpoint: '/cache-invalidate', + key: 'uniqueKey', + handlers: [ + '@vue-storefront/cache/defaultHandler' + ] + }, + driver: [ + // driver configuration + ] +}] +``` + +To invalidate the cache using it, visit an URL provided in the configuration with two query strings: + +* `key` - specified in the configuration and used to prevent unauthorized users from clearing the application's cache. For this reason, you should use long and hard-to-guess keys. +* `tags` - comma (`,`) separated tags to be invalidated. + +Using settings above and default Vue Storefront configuration, the invalidation URL should look like this: + +``` +http://localhost:3000/cache-invalidate?key=myUniqueKey&tags=Vcategory,C1337 +``` + +To invalidate all keys, pass `*` as a `key` value: + +``` +http://localhost:3000/cache-invalidate?key=myUniqueKey&tags=* +``` + +#### Creating an invalidation handler + +Invalidation handler is a function that returns an array of tags. It accepts an object with the following properties: + +* `request` (object) - Node.js HTTP request object; +* `response` (object) - Node.js HTTP response object; +* `options` (array) - `invalidation` object in `@vue-storefront/cache/nuxt` configuration; + +```javascript +function ({ request, response, options }) { + // Get tags from the "request" object + return tags; +}; +``` + +The handler should prevent unauthorized users from clearing the cache. One way of doing it is adding `key` to the configuration like in the [default invalidation handler](#default-invalidation-handler) and checking if `request` contains `options.key` in the URL queries or body. + +::: tip Don't throw errors in handlers +Because multiple handlers can be used at the same time to add support for different data formats, they should not throw errors. Throwing an error from the handler drops the request, which may be processed by another handler. + +If one of the properties is missing or the validation key is wrong, return an empty array. +::: + + +# What's next + +- Check out ready to use [Redis cache integration](../integrations/redis-cache.html) from Vue Storefront Enterprise +- Check how you can [build your own cache driver](../integrate/cache-driver.html). diff --git a/docs/build-docker.sh b/packages/core/docs/build-docker.sh similarity index 76% rename from docs/build-docker.sh rename to packages/core/docs/build-docker.sh index 78e8794d1b..5cd17ccf44 100755 --- a/docs/build-docker.sh +++ b/packages/core/docs/build-docker.sh @@ -1,3 +1,3 @@ TAG=`git rev-parse HEAD` -docker build -t registry.storefrontcloud.io/docs-storefrontcloud-io/v1:${TAG:0:8} -f Dockerfile . -docker push registry.storefrontcloud.io/docs-storefrontcloud-io/v1:${TAG:0:8} \ No newline at end of file +docker build -t registry.storefrontcloud.io/docs-storefrontcloud-io/v2:${TAG:0:8} -f Dockerfile . +docker push registry.storefrontcloud.io/docs-storefrontcloud-io/v2:${TAG:0:8} diff --git a/packages/core/docs/changelog/5871.js b/packages/core/docs/changelog/5871.js new file mode 100644 index 0000000000..e4109f32aa --- /dev/null +++ b/packages/core/docs/changelog/5871.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'Redirect to 404 page when category page does not exist', + link: 'https://github.com/vuestorefront/vue-storefront/issues/5871', + isBreaking: false, + breakingChanges: [], + author: 'Łukasz Jędrasik', + linkToGitHubAccount: 'https://github.com/lukaszjedrasik' +}; diff --git a/packages/core/docs/changelog/5891.js b/packages/core/docs/changelog/5891.js new file mode 100644 index 0000000000..846ef2d610 --- /dev/null +++ b/packages/core/docs/changelog/5891.js @@ -0,0 +1,15 @@ +module.exports = { + description: 'Remove unused `checkoutGetters`.', + link: 'https://github.com/vuestorefront/vue-storefront/issues/5891', + isBreaking: true, + breakingChanges: [ + { + module: '`@vue-storefront/core`', + before: '`checkoutGetters` was deprecated, but available in the API', + after: 'Removed `checkoutGetters`', + comment: '`checkoutGetters` was removed' + } + ], + author: 'Łukasz Jędrasik', + linkToGitHubAccount: 'https://github.com/lukaszjedrasik' +}; diff --git a/packages/core/docs/changelog/6082.js b/packages/core/docs/changelog/6082.js new file mode 100644 index 0000000000..7b9cef6f6e --- /dev/null +++ b/packages/core/docs/changelog/6082.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'Add subscribe to the newsletter modal on home page', + link: 'https://github.com/vuestorefront/vue-storefront/issues/5744', + isBreaking: false, + breakingChanges: [], + author: 'Adam Pawliński', + linkToGitHubAccount: 'https://github.com/AdamPawlinski' +}; diff --git a/packages/core/docs/changelog/6099.js b/packages/core/docs/changelog/6099.js new file mode 100644 index 0000000000..9694260b20 --- /dev/null +++ b/packages/core/docs/changelog/6099.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'Remediated missing dependency`', + link: 'https://github.com/vuestorefront/vue-storefront/issues/6099', + isBreaking: false, + breakingChanges: [], + author: 'jaydubb12', + linkToGitHubAccount: 'https://github.com/jaydubb12/vue-storefront' +}; diff --git a/packages/core/docs/changelog/6226.js b/packages/core/docs/changelog/6226.js new file mode 100644 index 0000000000..dc37248ff2 --- /dev/null +++ b/packages/core/docs/changelog/6226.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'Fix quantity input being immediately disabled after inserting single digit', + link: 'https://github.com/vuestorefront/vue-storefront/issues/6226', + isBreaking: false, + breakingChanges: [], + author: 'Igor Wojciechowski', + linkToGitHubAccount: 'https://github.com/igorwojciechowski' +}; diff --git a/packages/core/docs/changelog/6246.js b/packages/core/docs/changelog/6246.js new file mode 100644 index 0000000000..98bb25e603 --- /dev/null +++ b/packages/core/docs/changelog/6246.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'Remove "download all" button from the order list in my account', + link: 'https://github.com/vuestorefront/vue-storefront/pull/6246', + isBreaking: false, + breakingChanges: [], + author: 'Łukasz Jędrasik', + linkToGitHubAccount: 'https://github.com/lukaszjedrasik' +}; diff --git a/packages/core/docs/cli/CHANGELOG.md b/packages/core/docs/cli/CHANGELOG.md new file mode 100644 index 0000000000..c65d47b171 --- /dev/null +++ b/packages/core/docs/cli/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 2.0.6 + +- `@vue-storefront/nuxt-theme` as `dependency` not `devDependency` and using absolute paths in index of CLI to fix not working commands ([#4932](https://github.com/DivanteLtd/vue-storefront/issues/4932)) diff --git a/packages/core/docs/commercetools/README.md b/packages/core/docs/commercetools/README.md new file mode 100644 index 0000000000..cec1905ca5 --- /dev/null +++ b/packages/core/docs/commercetools/README.md @@ -0,0 +1,11 @@ +# commercetools integration for Vue Storefront + +:::tip There's more +This page only contains the documentation specific to commercetools integration. For more information, see the [Vue Storefront documentation](https://docs.vuestorefront.io/v2/). +::: + +[commercetools](https://www.vuestorefront.io/commercetools) integration for Vue Storefront is open-source and maintained by the core team. + +Some of the functionalities are available as part of our [Vue Storefront Enterprise](../general/enterprise.html) offering. We highly recommend it to reduce the development time and cost of your project. Feel free to [contact us](https://www.vuestorefront.io/contact/sales) to learn more. + +If you'd like to test it before diving in, see our [Enterprise demo](https://demo-ee.vuestorefront.io/) or [Open-source demo](https://vsf-next-demo.storefrontcloud.io/). diff --git a/packages/core/docs/commercetools/auth-middleware.md b/packages/core/docs/commercetools/auth-middleware.md new file mode 100644 index 0000000000..5a6066f78b --- /dev/null +++ b/packages/core/docs/commercetools/auth-middleware.md @@ -0,0 +1 @@ + diff --git a/packages/core/docs/commercetools/authorization-strategy.md b/packages/core/docs/commercetools/authorization-strategy.md new file mode 100644 index 0000000000..67fae57d0f --- /dev/null +++ b/packages/core/docs/commercetools/authorization-strategy.md @@ -0,0 +1,5 @@ +# Authorization + +We are following [HTTP Authorization guide](https://docs.commercetools.com/api/authorization) from Commercetools documentation and using their library - [TokenProvider](https://commercetools.github.io/nodejs/sdk/api/sdkAuth.html). A token is stored in the cookie as we access it both the client and server side. + +You can read more about Authentication in Vue Storefront [here](/guide/authentication.html). \ No newline at end of file diff --git a/packages/core/docs/commercetools/changelog.md b/packages/core/docs/commercetools/changelog.md new file mode 100644 index 0000000000..78e9525a27 --- /dev/null +++ b/packages/core/docs/commercetools/changelog.md @@ -0,0 +1,237 @@ +# Changelog + +## 1.3.1 + +- Revert changes to Webpack configuration and Google font loading ([#6203](https://github.com/vuestorefront/vue-storefront/pull/6203)) - [Filip Sobol](https://github.com/filipsobol) + + +## 1.3.0 + +- [BREAKING] Enable the purchase of item with selected supply channel and distribution channel ([#6161](https://github.com/vuestorefront/vue-storefront/pull/6161)) - [Alef Barbeli](https://github.com/alefbarbeli) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +the addToCart method has the signature addToCart ({ id, version }: CartDetails, product: ProductVariant, quantity: number): Promise; | now the addToCart method was change to enable the supply and distribution channels with the signature addToCart ({ id, version }: CartDetails, params: { product: ProductVariant; quantity: number; supplyChannel?: string; distributionChannel?: string;}): Promise; | The composable was changed to match this signature. The changes from Click & Collect / MultiStore are required to use this feature on Product.vue | api-client, +- Added missing mobile menu to CT ([#6184](https://github.com/vuestorefront/vue-storefront/pull/6184)) - [Łukasz Jędrasik](https://github.com/lukaszjedrasik) + , +- Added customQuery support for useUser factory params ([#5883](https://github.com/vuestorefront/vue-storefront/pull/5823)) - [vn-vlad](https://github.com/vn-vlad) +, +- Fix locale links in core and commercetools integration ([#5886](https://github.com/vuestorefront/vue-storefront/issues/5886)) - [Baroshem](https://github.com/Baroshem) +, +- Remove SSR from personalized elements ([#5925](https://github.com/vuestorefront/vue-storefront/pull/5925)) - [vn-vlad](https://github.com/vn-vlad) +, +- Add "useStore" implementation and plugin for creating locale, currency and country cookies ([#5945](https://github.com/vuestorefront/vue-storefront/pull/5945)) - [vn-vlad](https://github.com/vn-vlad) +, +- Add forgot password functionality ([#5968](https://github.com/vuestorefront/vue-storefront/issues/5968)) - [Baroshem](https://github.com/Baroshem) +, +- Click "Add New Address" button on the Checkout does not submit the form ([#5994](https://github.com/vuestorefront/vue-storefront/pull/6034)) - [lukaszjedrasik](https://github.com/lukaszjedrasik) +, +- Remove `generate` command from `package.json` ([#6035](https://github.com/vuestorefront/vue-storefront/pull/6035)) - [lukaszjedrasik](https://github.com/lukaszjedrasik) +, +- Refactor updateCart, fix retry logic in case of mismatch of cart ([#6050](https://github.com/vuestorefront/vue-storefront/pull/6054)) - [git-antonyuk](https://github.com/git-antonyuk) +, +- [BREAKING] refactor(commercetools): fix the frontend client bundling the commercetools-sdk and apollo client ([#6066](https://github.com/vuestorefront/vue-storefront/pull/6066)) - [bloodf](https://github.com/bloodf) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +the "createCommerceToolsConnection" were being exported by the api-client | the "createCommerceToolsConnection" is not being exported anymore | to use the current connection, you will need to access the context to call the API | api-client, +- Linked banner grids buttons on homepage ([#6070](https://github.com/vuestorefront/vue-storefront/pull/6070)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- Replace mocked email address in MyProfile password change tab to active user email ([#6079](https://github.com/vuestorefront/vue-storefront/pull/6079)) - [Adam Pawliński](https://github.com/AdamPawlinski) +, +- Removed hardcoded link to category in SearchResults.vue ([#6081](https://github.com/vuestorefront/vue-storefront/pull/6081)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- Added new product getter `getProductSku` ([#6107](https://github.com/vuestorefront/vue-storefront/pull/6107)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- Smartphone only promo code input added ([#6112](https://github.com/vuestorefront/vue-storefront/pull/6112)) - [vn-vlad](https://github.com/vn-vlad) +, +- Updates form validation scheme for street, number and city in the checkout and profile editing pages ([#6122](https://github.com/vuestorefront/vue-storefront/pull/6122)) - [Heitor Ramon Ribeiro](https://github.com/bloodf) +, +- [BREAKING] updated the removeCoupon interface to match the applyCoupon ([#6126](https://github.com/vuestorefront/vue-storefront/pull/6126)) - [Heitor Ramon Ribeiro](https://github.com/bloodf) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +the useCart composable method removeCoupon was using this call signature: ({ coupon: COUPON, customQuery?: CustomQuery }) | the method signature was changed to: ({ couponCode: string, customQuery?: CustomQuery }) | on each removeCoupon composable usage need to change the "coupon" to "couponCode" | composables, +- Add new getter for orders total and change return value of searchOrders ([#6132](https://github.com/vuestorefront/vue-storefront/issues/5968)) - [Baroshem](https://github.com/Baroshem) + +- Phone number validation via awesome-phonenumber ([#5951](https://github.com/vuestorefront/vue-storefront/issues/5951)) - [Fifciu](https://github.com/Fifciu) + +## 1.2.3 + +- Use cookies to read information about i18n ([#5919](https://github.com/vuestorefront/vue-storefront/pull/5919)) - [andrzejewsky](https://github.com/andrzejewsky) +, +- Resetting commercetools token when clearing user session ([#5920](https://github.com/vuestorefront/vue-storefront/pull/5920)) - [andrzejewsky](https://github.com/andrzejewsky) +, +- Fix calculation of cart totals ([#5932](https://github.com/vuestorefront/vue-storefront/pull/5932)) - [Filip Sobol](https://github.com/filipsobol) + + +## 1.2.2 + +- Allow searching by orderNumber ([#5858](https://github.com/vuestorefront/vue-storefront/pull/5858)) - [samriley](https://github.com/samriley) + + +## 1.2.1 + +- Add args parameter to custom queries ([#5854](https://github.com/vuestorefront/vue-storefront/issues/5854)) - [andrzejewsky](https://github.com/andrzejewsky) +, +- Export factory params for all composables to allow overriding ([#5862](https://github.com/vuestorefront/vue-storefront/pull/5862)) - [Filip Sobol](https://github.com/filipsobol) +, +- Export helper Apollo Link functions for easier overriding ([#5873](https://github.com/vuestorefront/vue-storefront/pull/5873)) - [Filip Sobol](https://github.com/filipsobol) + + +## 1.2.0 + +- Set 'vsf-commercetools-token' cookie expiration time to match token expiration time. ([#5774](https://github.com/vuestorefront/vue-storefront/pull/5774)) - [Filip Sobol](https://github.com/filipsobol) +, +- Reduce payload size for cart methods ([#5836](https://github.com/vuestorefront/vue-storefront/pull/5836)) - [Filip Sobol](https://github.com/filipsobol) + + +## 1.2.0-rc.3 + +- Fix getters in `cartGetters` to not throw errors when some properties don't exist ([#5699](https://github.com/vuestorefront/vue-storefront/pull/5699)) - [Filip Sobol](https://github.com/filipsobol) +, +- Fixed mapping for product attributes with type `set` ([#5708](https://github.com/vuestorefront/vue-storefront/pull/5708)) - [Filip Sobol](https://github.com/filipsobol) +, +- Fixed CT wishlist throwing CAPI error ([#5716](https://github.com/vuestorefront/vue-storefront/pull/5716)) - [Filip Sobol](https://github.com/filipsobol) + + +## 1.2.0-rc.2 + +- Adjust Checkout UI ([#5343](https://github.com/vuestorefront/vue-storefront/issues/5343)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- [BREAKING] Usage of api middleware ([#5361](https://github.com/vuestorefront/vue-storefront/pull/5361)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +one entrypoint | multiple entrypoints | We expose multiple entrypoints for server and client side interaction | commercetools, +- New part of checkout - shipping details, inside core and commercetools ([#5419](https://github.com/vuestorefront/vue-storefront/pull/5552)) - [Fifciu](https://github.com/Fifciu) +, +- Added `is-authenticated` middleware to protect user profile routes from guest access ([#5442](https://github.com/vuestorefront/vue-storefront/pull/5442)) - [Filip Sobol](https://github.com/filipsobol) +, +- Improvements for api middleware ([#5500](https://github.com/vuestorefront/vue-storefront/pull/5500)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) +, +- [BREAKING] New part of checkout - Billing details, inside core and commercetools ([#5552](https://github.com/vuestorefront/vue-storefront/pull/5552)) - [Fifciu](https://github.com/Fifciu) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +UserBillingAddress works properly | New API inside Checkout/UserBillingAddress.vue | Customized components to work with new checkout | commercetools-theme, +- [BREAKING] Quick search ([#5566](https://github.com/vuestorefront/vue-storefront/issues/5566)) - [Justyna Gieracka](https://github.com/justyna-13) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +{ changeSearchTerm } = useUiHelpers() | { setTermForUrl } = useUiHelpers(); | Changed changeSearchTerm name to setTermForUrl | useUiHelpers/index.ts, + | { getSearchTermFromUrl } = useUiHelpers(); | Created new function | useUiHelpers/index.ts, +- [BREAKING] Implementation of api middleware ([#5577](https://github.com/vuestorefront/vue-storefront/pull/5577)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +customQuery was used as a function | customQuery is a key-value object | The key is a query name, value is the name of a new query function, defined in the middleware config | commercetools, +- [BREAKING] New Payment API for Checkout ([#5587](https://github.com/vuestorefront/vue-storefront/pull/5587)) - [Fifciu](https://github.com/Fifciu) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +Dedicated composable for whole checkout | Dedicated composable for Shipping, Billing and Provider components | undefined | commercetools, +- State as a select field at both Checkout and MyAccount (shipping & billing). Support for freeAbove in shipping methods ([#5628](https://github.com/vuestorefront/vue-storefront/pull/5628)) - [Fifciu](https://github.com/Fifciu) + + +# 1.1.7 +- fixed error with login to the account ([#5613](https://github.com/vuestorefront/vue-storefront/issues/5613)) + +# 1.1.6 +- fix register function from CT useUser composable allows user to log in [#5613](https://github.com/vuestorefront/vue-storefront/issues/5613) + +# 1.1.5 +- remove deprecated field `description` from shipping methods query [#5614](https://github.com/vuestorefront/vue-storefront/issues/5614) + +# 1.1.6 +- fix register function from CT useUser composable allows user to log in [#5613](https://github.com/vuestorefront/vue-storefront/issues/5613) + +# 1.1.5 +- remove deprecated field `description` from shipping methods query [#5614](https://github.com/vuestorefront/vue-storefront/issues/5614) + +# 1.1.3 +- cover errors in re-try apollo-link that are not comming from graphql ([#5548](https://github.com/vuestorefront/vue-storefront/pull/5548)) + +# 1.1.2 +- moved from using `attributesList` to `attributesRaw` +- add 'once' to prevent font reload on each reactivity event ([#5513](https://github.com/DivanteLtd/vue-storefront/issues/5534)) + +## 1.1.1 +- fixed `vue-lazy-hydration` dependency in `nuxt-theme-module` ([#5406](https://github.com/DivanteLtd/vue-storefront/issues/5406)) + +## 1.1.0 +- fix getOrders api ([#5328](https://github.com/DivanteLtd/vue-storefront/issues/5328)) +- added bottom margin to fix visibility of last footer category ([#5253](https://github.com/DivanteLtd/vue-storefront/issues/5253)) +- [BREAKING] refactored names of many factory methods and composable methods, details in linked PR ([#5299](https://github.com/DivanteLtd/vue-storefront/pull/5299)) +- [BREAKING] changed signatures of factory methods to always 2 arguments, details in linked PR ([#5299](https://github.com/DivanteLtd/vue-storefront/pull/5299)) +- [BREAKING] removed `totalOrders` and `totalProducts` ([#5330](https://github.com/vuestorefront/vue-storefront/pull/5330)) +- removed `formatPrice` from `useUiHelpers`, replaced by vue18n `$n` function ([#5339](https://github.com/vuestorefront/vue-storefront/pull/5339)) +- added missing `i18n` tags ([#5337](https://github.com/vuestorefront/vue-storefront/issues/5337)) +- use updated factories `useUserBillingFactory`, `useUserShippingFactory` and `useWishlistFactory` ([5350](https://github.com/vuestorefront/vue-storefront/pull/5350)) +- use updated factories `useUserBillingFactory`, `useUserShippingFactory` and `useWishlistFactory` ([5350](https://github.com/vuestorefront/vue-storefront/pull/5350)) +- fix selecting country on checkout payment and shipping ([5386](https://github.com/vuestorefront/vue-storefront/pull/5386)) +- `createMyShoppingList` as a restricted anonymous operation + +## 1.0.1-rc.1 +- updated version of core + +## 1.0.0-rc.1 +- removed `availableFilters` and `availableSortingOptions` from `useProduct` ([#4856](https://github.com/DivanteLtd/vue-storefront/issues/4856)) +- removed `@import "~@storefront-ui/vue/styles";` from all components, because SFUI variables and mixins are now available globally and imports will drastically increase bundle size ([#5195](https://github.com/DivanteLtd/vue-storefront/issues/5195)) +- enabled "modern mode" in `yarn build` command ([#5203](https://github.com/DivanteLtd/vue-storefront/issues/5203)) +- added missing order getter to get item price ([#5231](https://github.com/DivanteLtd/vue-storefront/issues/5231)) +- retry updating the cart with new version if previous request failed due to a version mismatch ([#5264](https://github.com/DivanteLtd/vue-storefront/issues/5264)) +- removed logging level from nuxt.config.js to use defaults from core ([#5304](https://github.com/DivanteLtd/vue-storefront/issues/5304)) +- fixed broken focus in login form ([#5273](https://github.com/DivanteLtd/vue-storefront/issues/5273)) +- fixed select for changing variant on product page ([#5281](https://github.com/DivanteLtd/vue-storefront/issues/5281)) +- added token re-try strategy ([#5295](https://github.com/DivanteLtd/vue-storefront/pull/5295)) +- added discounts api getter ([#5154](https://github.com/DivanteLtd/vue-storefront/pull/5154)) +- added context implementation ([#5218](https://github.com/DivanteLtd/vue-storefront/pull/5218)) +- added context typings ([5290](https://github.com/DivanteLtd/vue-storefront/pull/5290)) + +## 0.2.6 + +- fix errors throw by some product getters ([#5089](https://github.com/DivanteLtd/vue-storefront/issues/5089)) +- The address `contactInfo` field is deprecated in the CT api. We have added support for the contact information fields directly in the address and will now show a warning when deprecated field is used ([#5083](https://github.com/DivanteLtd/vue-storefront/pull/5083)) +- removed `chosenShippingMethod` defaulting ([#5073](https://github.com/DivanteLtd/vue-storefront/issues/5073)) +- fix `useCheckout` - set loading fields to false when api-client throws ([#5096](https://github.com/DivanteLtd/vue-storefront/pull/5096)) + +## 0.2.5 + +- `customQuery` for checkout composables ([#5025](https://github.com/DivanteLtd/vue-storefront/issues/5025)) +- api-client apollo client no longer shared between requests ([#5056](https://github.com/DivanteLtd/vue-storefront/pull/5056)) + +## 0.2.4 + +- Remove defaulting for checkout shipping details ([#5026](https://github.com/DivanteLtd/vue-storefront/issues/5026)) + +### Changes + +- added `getTotalReviews` and `getAverageRating` to `productGetters` ([#4958](https://github.com/DivanteLtd/vue-storefront/issues/4958)) +- added '_rating' back to the product ([#4958](https://github.com/DivanteLtd/vue-storefront/issues/4958)) +- added mock for user shipping addresses in MyShippingDetails and Checkout's Shipping ([#4841](https://github.com/DivanteLtd/vue-storefront/issues/4841)) + +## 0.2.3 + +### Changes + +- adding ability to overriding `isTokenUserSession` check in api-client ([#4959](https://github.com/DivanteLtd/vue-storefront/issues/4959)) + +## 0.2.2 + +### Breaking changes + +- removed '_rating' from product ([#4906](https://github.com/DivanteLtd/vue-storefront/issues/4906)) + +### Changes + +- Fix types for CT api-client and composables packages ([#4924](https://github.com/DivanteLtd/vue-storefront/pull/4924)) +- fixed updateUser on useUser composable ([#4863](https://github.com/DivanteLtd/vue-storefront/issues/4863)) +- implemented useReviews on product page ([#4800](https://github.com/DivanteLtd/vue-storefront/issues/4800)) +- implemented faceting using useFacet factory ([#4853](https://github.com/DivanteLtd/vue-storefront/issues/4853)) +- fixed anonymous token loading ([#4917](https://github.com/DivanteLtd/vue-storefront/issues/4917)) +- fixed bugs related to customQuery ([#4933](https://github.com/DivanteLtd/vue-storefront/issues/4933), [#4913](https://github.com/DivanteLtd/vue-storefront/issues/4913)) + +## 0.1.0 + +- refactored setup using apiClientFactory ([#4777](https://github.com/DivanteLtd/vue-storefront/issues/4777)) diff --git a/packages/core/docs/commercetools/changelog/6104.js b/packages/core/docs/commercetools/changelog/6104.js new file mode 100644 index 0000000000..52da715afd --- /dev/null +++ b/packages/core/docs/commercetools/changelog/6104.js @@ -0,0 +1,8 @@ +module.exports = { + description: 'Enable payment summary section for desktop view', + link: 'https://github.com/vuestorefront/vue-storefront/pull/6104', + isBreaking: false, + breakingChanges: [], + author: 'vn-vlad', + linkToGitHubAccount: 'https://github.com/vn-vlad' +}; diff --git a/packages/core/docs/commercetools/composables/use-billing.md b/packages/core/docs/commercetools/composables/use-billing.md new file mode 100644 index 0000000000..3107680865 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-billing.md @@ -0,0 +1,95 @@ +# `useBilling` + +## Features + +`useBilling` composable can be used for: + +* Loading billing address for the current cart. +* Saving billing address for the current cart. + +## API + +- `load` - function for fetching billing address. When invoked, it requests data from the API and populates `billing` property. This method accepts a single optional `params` object. The `params` has the following option: + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + getBasicProfile: string + } + ``` + +- `save` - function for saving billing address. This method accepts a single `saveParams` object. The `saveParams` has the following options: + + - `billingDetails: Address` + + - `customQuery?: CustomQuery` + + ```ts + type Address = { + __typename?: "Address"; + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + contactInfo: AddressContactInfo; + phone?: Maybe; + email?: Maybe; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; + }; + + type CustomQuery = { + updateCart: string + } + ``` + +- `billing: Address` - a main data object that contains a billing address. + +- `loading: boolean` - a reactive object containing information about loading state of your `load` or `save` method. + +- `error: UseBillingErrors` - a reactive object containing the error message, if `load` or `save` failed for any reason. + + ```ts + interface UseBillingErrors { + load?: Error; + save?: Error; + } + ``` + +## Getters + +We do not provide getters for checkout and its parts. + +## Example + +```js +import { useBilling } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core' +export default { + setup () { + const { load, billing } = useBilling(); + onSSR(async () => { + await load(); + }); + return { + billing + }; + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-cart.md b/packages/core/docs/commercetools/composables/use-cart.md new file mode 100644 index 0000000000..c55484984a --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-cart.md @@ -0,0 +1,392 @@ +# `useCart` + +## Features + +`useCart` composable can be used to: + +* load cart information, +* add, update and remove items in the cart, +* applying and removing coupons, +* checking if product is already added to the cart. + +## API + +- `cart: Cart` - a main data object. + + ```ts + type Cart = { + __typename?: "Cart"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + cartState: CartState; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + ``` + +- `load` - function required to fetch cart from a server or create brand new if it doesn't exist. This method accepts a single `params` object. The `params` has the following option: + + - `customQuery?: customQuery` + + ```ts + type CustomQuery = { + getBasicProfile: string + } + ``` + +- `addItem` - function for adding products to the cart. This method accepts a single `params` object. The `params` has the following options: + + - `product: ProductVariant` + + - `quantity: any` + + - `customQuery?: customQuery` + + ```ts + type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; + } + + type CustomQuery = { + updateCart: string + } + ``` + +- `updateItemQty` - function for updating quantity of a product that is already in the cart. This method accepts a single `params` object. The `params` has the following options: + + - `product: LineItem` + + - `quantity: number` + + - `customQuery?: CustomQuery` + + ```ts + type LineItem = { + __typename?: "LineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + productSlug?: Maybe; + productType?: Maybe; + productTypeRef?: Maybe; + variant?: Maybe; + price: ProductPrice; + taxedPrice?: Maybe; + totalPrice?: Maybe; + quantity: Scalars["Long"]; + state: Array; + taxRate?: Maybe; + supplyChannel?: Maybe; + supplyChannelRef?: Maybe; + distributionChannel?: Maybe; + distributionChannelRef?: Maybe; + discountedPricePerQuantity: Array; + lineItemMode: LineItemMode; + priceMode: LineItemPriceMode; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + inventoryMode?: Maybe; + customFieldList?: Maybe>; + } + + type CustomQuery = { + updateCart: string + } + ``` + +- `removeItem` - function for removing a product that currently is in the cart. This method accepts a single `params` object. The `params` has the following options: + + - `product: LineItem` + + - `customQuery?: CustomQuery` + + ```ts + type LineItem = { + __typename?: "LineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + productSlug?: Maybe; + productType?: Maybe; + productTypeRef?: Maybe; + variant?: Maybe; + price: ProductPrice; + taxedPrice?: Maybe; + totalPrice?: Maybe; + quantity: Scalars["Long"]; + state: Array; + taxRate?: Maybe; + supplyChannel?: Maybe; + supplyChannelRef?: Maybe; + distributionChannel?: Maybe; + distributionChannelRef?: Maybe; + discountedPricePerQuantity: Array; + lineItemMode: LineItemMode; + priceMode: LineItemPriceMode; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + inventoryMode?: Maybe; + customFieldList?: Maybe>; + } + + type CustomQuery = { + updateCart: string + } + ``` + +- `isInCart` - function for checking if a product is currently in the cart. This method accepts a single `params` object. The `params` has the following option: + + - `product: ProductVariant` + + ```ts + type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; + } + ``` + +- `clear` - function for removing all items currently stored in cart. + +- `applyCoupon` - function for applying coupon to cart. This method accepts a single `params` object. The `params` has the following options: + + - `couponCode: string` + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + updateCart: string + } + ``` + +- `removeCoupon` - function for removing coupon applied to cart. This method accepts a single `params` object. The `params` has the following options: + + - `couponCode: string` + + - `customQuery?: CustomQuery` + + ```ts + interface AgnosticCoupon { + id: string; + name: string; + code: string; + value: number; + } + + type CustomQuery = { + updateCart: string + } + ``` + +- `loading: boolean` - a reactive object containing information about loading state of the cart. + +- `error: UseCartErrors` - reactive object containing the error message, if some properties failed for any reason. + + ```ts + interface UseCartErrors { + addItem: Error; + removeItem: Error; + updateItemQty: Error; + load: Error; + clear: Error; + applyCoupon: Error; + removeCoupon: Error; + } + ``` + +## Getters + +- `getTotals` - returns cart totals. + +- `getShippingPrice` - returns current shipping price. + +- `getItems` - returns all items from cart. + +- `getItemName` - returns product name. + +- `getItemImage` - returns product image. + +- `getItemPrice` - returns product price. + +- `getItemQty` - returns product quantity. + +- `getItemAttributes` - returns product attribute. + +- `getItemSku` - returns product SKU. + +- `getTotalItems` - returns products amount. + +- `getFormattedPrice` - returns product price with currency sign. + +- `getCoupons` - returns applied coupons. + +- `getDiscounts` - returns all discounts. + + ```ts + interface CartGetters { + getTotals: (cart: Cart) => AgnosticTotals; + getShippingPrice: (cart: Cart) => number; + getItems: (cart: Cart) => LineItem; + getItemName: (product: LineItem) => string; + getItemImage: (product: LineItem) => string; + getItemPrice: (product: LineItem) => AgnosticPrice; + getItemQty: (product: LineItem) => number; + getItemAttributes: (product: LineItem, filterByAttributeName?: Array) => Record; + getItemSku: (product: LineItem) => string; + getTotalItems: (cart: Cart) => number; + getFormattedPrice: (price: number) => string; + getCoupons: (cart: Cart) => AgnosticCoupon[]; + getDiscounts: (cart: Cart) => AgnosticDiscount[]; + } + + type LineItem = { + __typename?: "LineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + productSlug?: Maybe; + productType?: Maybe; + productTypeRef?: Maybe; + variant?: Maybe; + price: ProductPrice; + taxedPrice?: Maybe; + totalPrice?: Maybe; + quantity: Scalars["Long"]; + state: Array; + taxRate?: Maybe; + supplyChannel?: Maybe; + supplyChannelRef?: Maybe; + distributionChannel?: Maybe; + distributionChannelRef?: Maybe; + discountedPricePerQuantity: Array; + lineItemMode: LineItemMode; + priceMode: LineItemPriceMode; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + inventoryMode?: Maybe; + customFieldList?: Maybe>; + } + + interface AgnosticTotals { + total: number; + subtotal: number; + special?: number; + [x: string]: unknown; + } + + interface AgnosticPrice { + regular: number | null; + special?: number | null; + } + + interface AgnosticAttribute { + name?: string; + value: string | Record; + label: string; + } + + interface AgnosticCoupon { + id: string; + name: string; + code: string; + value: number; + } + + interface AgnosticDiscount { + id: string; + name: string; + description: string; + value: number; + code?: string; + } + ``` + +## Example + +```js +import { useCart, cartGetters } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core' + +export default { + setup () { + const { cart, removeItem, updateItemQty, load } = useCart(); + + onSSR(async () => { + await load(); + }) + + return { + removeItem, + updateItemQty, + products: computed(() => cartGetters.getItems(cart.value)), + totals: computed(() => cartGetters.getTotals(cart.value)), + totalItems: computed(() => cartGetters.getTotalItems(cart.value)) + } + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-category.md b/packages/core/docs/commercetools/composables/use-category.md new file mode 100644 index 0000000000..07f1655802 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-category.md @@ -0,0 +1,112 @@ +# `useCategory` + +## Features + +`useCategory` composable is responsible for fetching a list of categories. A common usage scenario for this composable is navigation. + +## API + +- `search` - a main querying function that is used to query categories from eCommerce platform and populate the `categories` object with the result. Every time you invoke this function API request is made. This method accepts a single `params` object. The `params` has the following options: + + - `searchParams` + + - `id: string` + - `slug: string` + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + categories: string + } + ``` + +- `categories: Category[]` - a main data object that contains an array of categories fetched by `search` method. + + ```ts + type Category = { + __typename?: "Category"; + id: Scalars["String"]; + key?: Maybe; + version: Scalars["Long"]; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales: Array; + ancestorsRef: Array; + ancestors: Array; + parentRef?: Maybe; + parent?: Maybe; + orderHint: Scalars["String"]; + externalId?: Maybe; + metaTitle?: Maybe; + metaKeywords?: Maybe; + metaDescription?: Maybe; + productCount: Scalars["Int"]; + stagedProductCount: Scalars["Int"]; + childCount: Scalars["Int"]; + children?: Maybe>; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + assets: Array; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + ``` + +- `loading: boolean` - a reactive object containing information about loading state of your `search` method. + +- `error: UseCategoryErrors` - reactive object containing the error message, if `search` failed for any reason. + + ```ts + interface UseCategoryErrors { + search: Error; + } + ``` + +## Getters + +- `getTree` - returns category tree. + + ```ts + interface CategoryGetters { + getTree: (category: Category) => AgnosticCategoryTree | null; + } + + interface AgnosticCategoryTree { + label: string; + slug?: string; + items: AgnosticCategoryTree[]; + isCurrent: boolean; + count?: number; + [x: string]: unknown; + } + ``` + +## Example + +```js +import { useCategory } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core' + +export default { + setup () { + const { categories, search, loading } = useCategory('menu-categories'); + + onSSR(async () => { + await search({}); + }); + + return { + categories, + loading + } + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-facet.md b/packages/core/docs/commercetools/composables/use-facet.md new file mode 100644 index 0000000000..f0ccbfabdd --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-facet.md @@ -0,0 +1,281 @@ +# `useFacet` + +:::warning +This feature is a part of our commercial offering but also exists in the Open Source version of our commercetools integration. + +Open Source implementation relies on GraphQL API (internally using `getProduct` and `getCategory` composables), which doesn't provide full faceting capabilities as does the dedicated REST-based faceting API offered in our Enterprise version. Please [contact our Sales team](https://www.vuestorefront.io/contact/sales) if you'd like to get access to it. +::: + +## Features + +`useFacet` composition function can be used to fetch data related to: + +* products, +* categories, +* breadcrumbs. + +What makes it powerful is the ability to accept multiple filters, allowing to narrow down the results to a specific category, search term, etc. + +For more information about faceting, please refer to [this page](../composables/use-facet.md). + +## API + +`useFacet` contains the following properties: + +- `search` - function for searching and classifying records, allowing users to browse the catalog data. It accepts a single object as a parameter with following signature: + + ```ts + interface AgnosticFacetSearchParams { + categorySlug?: string; + rootCatSlug?: string; + term?: string; + page?: number; + itemsPerPage?: number; + sort?: string; + filters?: Record; + metadata?: any; + [x: string]: any; + } + ``` + +- `result` - reactive data object containing the response from the backend. + +- `loading` - reactive object containing information about the loading state of `search`. + +- `error` - reactive object containing the error message, if `search` failed for any reason. + + ```ts + interface UseFacetErrors { + search: Error; + } + ``` + +## Getters +Because the `result` property is a raw response with some additional properties, it's recommended to use `facetGetters` for accessing any data from it. It includes the following helper functions: + +- `getAll` - returns all available facets. + +- `getGrouped` - returns grouped facets by facet name. + +- `getCategoryTree` - return the tree of nested categories. + +- `getSortOptions` - returns available and currently selected sorting options. + +- `getProducts` - returns products matching current filters. + +- `getPagination` - returns pagination information. + +- `getBreadcrumbs` - returns breadcrumbs information. + + ```ts + interface FacetsGetters { + getAll: (searchData: SearchData, criteria?: string[]) => AgnosticFacet[]; + getGrouped: (searchData: SearchData, criteria?: string[]) => AgnosticGroupedFacet[]; + getCategoryTree: (searchData: SearchData) => AgnosticCategoryTree; + getSortOptions: (searchData: SearchData) => AgnosticSort; + getProducts: (searchData: SearchData) => ProductVariant[]; + getPagination: (searchData: SearchData) => AgnosticPagination; + getBreadcrumbs: (searchData: SearchData) => AgnosticBreadcrumb[]; + } + + interface AgnosticFacet { + type: string; + id: string; + value: any; + attrName?: string; + count?: number; + selected?: boolean; + metadata?: any; + } + + interface AgnosticGroupedFacet { + id: string; + label: string; + count?: number; + options: AgnosticFacet[]; + } + + interface AgnosticCategoryTree { + label: string; + slug?: string; + items: AgnosticCategoryTree[]; + isCurrent: boolean; + count?: number; + [x: string]: unknown; + } + + interface AgnosticSort { + options: AgnosticFacet[]; + selected: string; + } + + type SearchData = FacetSearchResult + + interface FacetSearchResult { + data; + input: AgnosticFacetSearchParams; + } + + interface AgnosticFacetSearchParams { + categorySlug?: string; + rootCatSlug?: string; + term?: string; + page?: number; + itemsPerPage?: number; + sort?: string; + filters?: Record; + metadata?: any; + [x: string]: any; + } + + interface AgnosticPagination { + currentPage: number; + totalPages: number; + totalItems: number; + itemsPerPage: number; + pageOptions: number[]; + } + + interface AgnosticBreadcrumb { + text: string; + link: string; + } + + interface FacetResultsData { + products: ProductVariant[]; + categories: Category[]; + facets: Record; + total: number; + perPageOptions: number[]; + itemsPerPage: number; + } + + type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; + } + ``` + +## Configuration + +::: tip +Configuration can be changed only for the Enterprise version of this package. +::: + +Faceting configuration can be modified to change available sorting options, filters, etc. It must be passed to: +- `@vsf-enterprise/ct-faceting/nuxt` module in `nuxt.config.js`. +- `@vsf-enterprise/ct-faceting/server` integration in `middleware.config.js`. + +::: warning Keep your configuration synchronized +Parts of the configuration marked as `` must be identical in both files. +::: + +```javascript +// nuxt.config.js +export default { + buildModules: [ + ['@vsf-enterprise/ct-faceting/nuxt', { + // + }], + ] +}; + +// middleware.config.js +module.exports = { + integrations: { + ctf: { + location: '@vsf-enterprise/ct-faceting/server', + configuration: { + api: { + authHost: "", + projectKey: "", + clientId: "", + clientSecret: "", + scopes: [ + "" + ], + }, + faceting: { + host: "" + }, + // + } + } + } +}; +``` + +If the explicit configuration is not provided in place of ``, the following defaults will be used: + +```javascript +{ + pageOptions: [20, 50, 100], + subcategoriesLimit: 100, + availableFacets: [ + { facet: 'categories.id', type: 'string', option: 'subtree("*")', name: 'category', filteringStrategy: 'query' }, // Don't change the "name" of this facet + { facet: 'variants.attributes.size', type: 'string', option: '', name: 'size' }, + { facet: 'variants.attributes.color.key', type: 'string', option: '', name: 'color' } + ], + sortingOptions: [ + { id: 'latest', name: 'Latest', facet: 'createdAt', direction: 'desc' }, + { id: 'price-up', name: 'Price from low to high', facet: 'price', direction: 'asc' }, + { id: 'price-down', name: 'Price from high to low', facet: 'price', direction: 'desc' }, + { id: 'relevance', name: 'Relevance', facet: 'score', direction: 'desc' }, + ], + filteringStrategy: 'filter' +} +``` + +- `pageOptions` - an array of number of elements displayed per page. +- `subcategoriesLimit` - the maximum number of subcategories displayed for any given category. +- `availableFacets` - an array of filters available to the user. + - `facet` - facet expressions described on [this page](https://docs.commercetools.com/api/projects/products-search#termfacetexpression). + - `type` - `facet` data type. Valid values are `string`, `date`, `time`, `datetime`, `boolean` or `number`. + - `option` - filtering options described on [this page](https://docs.commercetools.com/api/projects/products-search#filters). + - `name` - facet alias described on [this page](https://docs.commercetools.com/api/projects/products-search#alias). `category` alias for the first facet shown above is a constant and shouldn't be changed. + - `filteringStrategy` - scope applied to this specific filter. Possible values are `filter`, `query` or `facets`. For more information refer to [this page](https://docs.commercetools.com/api/projects/products-search#filters). +- `sortingOptions` - an array of sorting options available to the user. + - `id` - unique `identifier` for the option. + - `name` - label for the option. + - `facet` - the name of the field to sort by. For more information refer to [this page](https://docs.commercetools.com/api/projects/products-search#sorting). + - `direction` - sorting direction. Valid values are `asc` or `desc`. +- `filteringStrategy` - fallback scope applied to the facets that don't have strategy defined. Possible values are `filter`, `query` or `facets`. For more information refer to [this page](https://docs.commercetools.com/api/projects/products-search#filters). + +## Example + +```js +import { useFacet, facetGetters } from '@vsf-enterprise/commercetools'; + +setup(props, context) { + const { result, search, loading } = useFacet(); + + onSSR(async () => { + await search({ + categorySlug: 'clothing', + sort: 'latest', + itemsPerPage: 10, + term: 'some search query' + }); + }); + + return { + products: computed(() => facetGetters.getProducts(result.value)), + categoryTree: computed(() => facetGetters.getCategoryTree(result.value)), + breadcrumbs: computed(() => facetGetters.getBreadcrumbs(result.value)), + sortBy: computed(() => facetGetters.getSortOptions(result.value)), + facets: computed(() => facetGetters.getGrouped(result.value, ['color', 'size'])), + pagination: computed(() => facetGetters.getPagination(result.value)), + loading + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-forgot-password.md b/packages/core/docs/commercetools/composables/use-forgot-password.md new file mode 100644 index 0000000000..6cf8ca27d8 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-forgot-password.md @@ -0,0 +1,184 @@ +# `useForgotPassword` + +## Features + +`useForgotPassword` composable can be used to: + +* generate reset password token +* set new password using token + +## API + +- `request` - function to generate reset password token. When invoked, it requests data from the API and populates the `token` property. This method accepts a single `params` object with the following properties: + + - `params: ResetPasswordParams` + + - `customQuery?: CustomQuery` + + ```typescript + interface ResetPasswordParams { + email: string; + } + + type CustomQuery = { + customerCreatePasswordResetToken: string + } + ``` + +- `setNew` - function to set new user password after `request`. When invoked, it requests data from the API and populates the `result` object. This method accepts a single `params` object with the following properties: + + - `params: SetNewPasswordParams` + + - `customQuery?: CustomQuery` + + ```typescript + interface SetNewPasswordParams { + tokenValue: string; + newPassword: string; + } + + type CustomQuery = { + customerResetPassword: string + } + ``` + +- `token: string` - reactive data string containing the reset token. + +- `loading: boolean` - reactive object containing information about loading state of `setNew` and `request` methods. + +- `error: UseForgotPasswordErrors` - reactive object containing the error message, if `setNew` or `request` failed for any reason. + + ```ts + interface UseForgotPasswordErrors { + request: Error; + setNew: Error; + } + ``` + +## Getters + +- `getResetPasswordToken` - returns generated reset password token. + +- `isPasswordChanged` - returns a boolean value of a password set status. + + ```ts + interface ForgotPasswordGetters { + getResetPasswordToken: (result: ForgotPasswordResult) => string + isPasswordChanged: (result: ForgotPasswordResult) => boolean + } + + interface ForgotPasswordResult { + resetPasswordResult: CreatePasswordResetTokenResponse; + setNewPasswordResult: ResetPasswordResponse; + } + + type CreatePasswordResetTokenResponse = QueryResponse<'customerCreatePasswordResetToken', CustomerPasswordToken>; + + type ResetPasswordResponse = QueryResponse<'customerResetPassword', Customer>; + + type CustomerPasswordToken = { + customerId: Scalars["String"]; + expiresAt: Scalars["DateTime"]; + value: Scalars["String"]; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + } + + type Customer = Versioned & { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; + }; + ``` + +## Example + +Requesting reset password token and setting new user password. + +```vue + + + +``` diff --git a/packages/core/docs/commercetools/composables/use-make-order.md b/packages/core/docs/commercetools/composables/use-make-order.md new file mode 100644 index 0000000000..d4e4ce8b27 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-make-order.md @@ -0,0 +1,107 @@ +# `useMakeOrder` + +## Features + +`useMakeOrder` composable is responsible for making an order + +## API + +- `make` - function for making an order. This method accepts a single optional `params` object. The `params` has the following option: + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + createMyOrderFromCart: string + } + ``` + +- `order: Order` - a main data object that contains a made order. + + ```ts + type Order = Versioned & { + __typename?: "Order"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + completedAt?: Maybe; + orderNumber?: Maybe; + orderState: OrderState; + stateRef?: Maybe; + state?: Maybe; + shipmentState?: Maybe; + paymentState?: Maybe; + syncInfo: Array; + returnInfo: Array; + lastMessageSequenceNumber: Scalars["Long"]; + cartRef?: Maybe; + cart?: Maybe; + /** This field contains non-typed data. Consider using `customFields` as a typed alternative. */ + customFieldsRaw?: Maybe>; + /** This field would contain type data */ + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + /** Custom fields are returned as a list instead of an object structure. */ + customFieldList?: Maybe>; + }; + ``` + +- `loading: boolean` - a reactive object containing information about loading state of your `make` method. + +- `error: UseMakeOrderErrors` - a reactive object containing the error message, if `load` or `save` failed for any reason. + + ```ts + interface UseMakeOrderErrors { + make?: Error; + } + ``` + +## Getters + +We do not provide getters for checkout and its parts. + +## Example + +```js +import { useMakeOrder } from '@vue-storefront/commercetools'; +export default { + setup () { + const { make, order } = useMakeOrder(); + return { + make, + order + }; + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-product.md b/packages/core/docs/commercetools/composables/use-product.md new file mode 100644 index 0000000000..d71cc46178 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-product.md @@ -0,0 +1,192 @@ +# `useProduct` + +## Features + +`useProduct` composable is responsible for fetching a list of products. + +## API + +- `search` - a main querying function that is used to query products from eCommerce platform and populate the `products` object with the result. Every time you invoke this function API request is made. This method accepts a single `params` object. The `params` has the following options: + + - `searchParams: ProductsSearchParams` + + - `customQuery?: CustomQuery` + + ```ts + interface ProductsSearchParams { + perPage?: number; + page?: number; + sort?: any; + term?: any; + filters?: Record; + catId?: string | string[]; + skus?: string[]; + slug?: string; + id?: string; + } + + type CustomQuery = { + products: string + } + ``` + +- `products: ProductVariant[]` - a main data object that contains an array of products fetched by `search` method. + + ```ts + type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; + } + ``` + +- `loading: boolean` - a reactive object containing information about loading state of your `search` method. + +- `error: UseProductErrors` - reactive object containing the error message, if `search` failed for any reason. + + ```ts + interface UseProductErrors { + search: Error; + } + ``` + +## Getters + +- `getName` - returns product name. + +- `getSlug` - returns product slug. + +- `getPrice` - returns product price. + +- `getGallery` - returns product gallery. + +- `getCoverImage` - returns cover image of product. + +- `getFiltered` - returns filtered product. + +- `getAttributes` - returns product attributes. + +- `getDescription` - returns product description. + +- `getCategoryIds` - returns all product categories. + +- `getId` - returns product ID. + +- `getFormattedPrice` - returns product price with currency sign. + +- `getTotalReviews` - returns total number of reviews product has. + +- `getAverageRating` - returns average rating from all reviews. + + ```ts + interface ProductGetters { + getName: (product: ProductVariant | Readonly) => string; + getSlug: (product: ProductVariant | Readonly) => string; + getPrice: (product: ProductVariant | Readonly) => AgnosticPrice; + getGallery: (product: ProductVariant) => AgnosticMediaGalleryItem[]; + getCoverImage: (product: ProductVariant) => string; + getFiltered: (products: ProductVariant[], filters: ProductVariantFilters | any = {}) => ProductVariant[]; + getAttributes: (products: ProductVariant[] | ProductVariant, filterByAttributeName?: string[]) => Record; + getDescription: (product: ProductVariant) => string; + getCategoryIds: (product: ProductVariant) => string[]; + getId: (product: ProductVariant) => string; + getFormattedPrice: (price: number) => string; + getTotalReviews: (product: ProductVariant) => number; + getAverageRating: (product: ProductVariant) => number; + } + + interface AgnosticPrice { + regular: number | null; + special?: number | null; + } + + interface AgnosticMediaGalleryItem { + small: string; + normal: string; + big: string; + } + + interface AgnosticAttribute { + name?: string; + value: string | Record; + label: string; + } + + type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; + } + + interface ProductVariantFilters { + master?: boolean; + attributes?: Record; + } + ``` + +## Examples + +```js +// search single product +import { useProduct, productGetters } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core' +import { computed } from '@vue/composition-api'; + +export default { + setup () { + const { products, search, loading, error } = useProduct(''); + + onSSR(async () => { + await search({ slug: 'super-t-shirt' }) + }) + + return { + loading, + error, + product: computed(() => productGetters.getFiltered(products.value, { master: true, attributes: context.root.$route.query })[0]), + option: computed(() => productGetters.getAttributes(products.value, ['color', 'size'])), + configuration: computed(() => productGetters.getCategoryIds(product.value)) + } + } +} +``` + +```js +// search products by ids +import { useProduct, productGetters } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core'; +import { computed } from '@vue/composition-api'; + +export defaut { + setup () { + const { products, search } = useProduct(''); + + onSSR(async () => { + await search({ ids: ['id-1', 'id-2'] }); + }); + + return { + products, + masterProducts: computed(() => productGetters.getFiltered(products.value, { master: true })) + }; + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-review.md b/packages/core/docs/commercetools/composables/use-review.md new file mode 100644 index 0000000000..696ef39d8e --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-review.md @@ -0,0 +1,157 @@ +# `useReview` + +> This feature is a part of our commercial offering and does not exist in Open Source version of commercetools integration. Read more about a Vue Storefront Enterprise Cloud [here](https://www.vuestorefront.io/enterprise) + +## Features + +`useReview` composable can be used to: + +* fetch: + * reviews list, + * average rating, + * rating distribution (number of reviews per rate). +* submit new reviews. + +## API + +- `search` - function for fetching review data. When invoked, it requests data from the API and populates `reviews` property. This method accepts a single params object. The `params` has the following option: + + - `productId: string` + + - `limit?: number` + + - `offset?: number` + + - `customQuery?: CustomQuery` + + ```typescript + type CustomQuery = { + reviews: string + } + ``` + +- `addReview` - function for posting new review. When invoked, it submits data to the API and populates `reviews` property with updated information. This method accepts a single params object. The `params` has the following options: + + - `productId: string` + + - `limit?: number` + + - `offset?: number` + + - `draft: ReviewDraft` + + - `customQuery?: CustomQuery` + + ```typescript + interface ReviewDraft { + authorName: string; + text: string; + rating: number; + } + + type CustomQuery = { + addReview: string + } + ``` + +- `reviews: Review[]` - reactive data object containing the response from the backend. + + ```ts + type Review = any; + ``` + +- `loading: boolean` - reactive object containing information about loading state of `search` and `addReview` methods. + +- `error: UseReviewErrors` - reactive object containing the error message, if `search` or `addReview` failed for any reason. + + ```ts + interface UseReviewErrors { + search: Error; + addReview: Error; + } + ``` + +## Getters + +- `getItems` - returns list of reviews. + +- `getTotalReviews` - returns total number of reviews product has. + +- `getAverageRating` - returns average rating from all reviews. + +- `getRatesCount` - returns rating distribution (number of reviews per rate). + +- `getReviewsPage` - returns current page, if results are paginated. + +- `getReviewId` - returns unique ID from an individual review item. + +- `getReviewAuthor` - returns author name from an individual review item. + +- `getReviewMessage` - returns message from an individual review item. + +- `getReviewRating` - returns rating from an individual review item. + +- `getReviewDate` - returns creation date from an individual review item. + + ```typescript + interface ReviewGetters { + getItems: (review: ReviewResponse) => Review[]; + getTotalReviews: (review: ReviewResponse) => number; + getAverageRating: (review: ReviewResponse) => number; + getRatesCount: (review: ReviewResponse) => AgnosticRateCount[]; + getReviewsPage: (review: ReviewResponse) => number; + getReviewId: (item: Review) => string; + getReviewAuthor: (item: Review) => string; + getReviewMessage: (item: Review) => string; + getReviewRating: (item: Review) => number; + getReviewDate: (item: Review) => string; + } + + type ReviewResponse = { + results: Review[], + total: number; + limit: number; + offset: number; + averageRating: number; + ratingsDistribution: { + [rating: number]: number; + }; + } + + type Review = any; + + interface AgnosticRateCount { + rate: number; + count: number; + } + ``` + +## Example + +```typescript +import { onSSR } from '@vue-storefront/core'; +import { useReview, reviewGetters } from '@vsf-enterprise/commercetoolss'; + +export default { + setup() { + const { + reviews, + search, + loading, + error + } = useReview(''); + + onSSR(async () => { + await search({ productId: '' }); + }); + + return { + reviews: computed(() => reviewGetters.getItems(reviews.value)), + averageRating: computed(() => reviewGetters.getAverageRating(reviews.value)), + totalReviews: computed(() => reviewGetters.getTotalReviews(reviews.value)), + loading, + error + }; + } +}; +``` diff --git a/packages/core/docs/commercetools/composables/use-shipping-provider.md b/packages/core/docs/commercetools/composables/use-shipping-provider.md new file mode 100644 index 0000000000..ee7fea76cf --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-shipping-provider.md @@ -0,0 +1,110 @@ +# `useShippingProvider` + +## Features + +`useShippingProvider` composable can be used for: + +* Loading shipping methods for the current cart. +* Selecting shipping method for the current cart. + +## API + +- `load` - function for fetching shipping method. When invoked, it requests data from the API and populates the `response` key inside the `state` property. This method accepts a single optional `params` object. The `params` has the following option: + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + getBasicProfile: string + } + ``` + +- `save` - function for selecting shipping method. This method accepts a single `saveParams` object. The `saveParams` has the following options: + + - `shippingMethod: ShippingMethod` + + - `customQuery?: CustomQuery` + + ```ts + type ShippingMethod = Versioned & { + __typename?: "ShippingMethod"; + id: Scalars["String"]; + version: Scalars["Long"]; + name: Scalars["String"]; + description?: Maybe; + zoneRates: Array; + isDefault: Scalars["Boolean"]; + predicate?: Maybe; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + key?: Maybe; + lastModifiedBy?: Maybe; + createdBy?: Maybe; + taxCategoryRef?: Maybe; + taxCategory?: Maybe; + }; + + type CustomQuery = { + updateCart: string + } + ``` + +- `state: ShippingProviderState` - a main data object that contains a shipping method + + ```ts + interface ShippingProviderState { + response: ShippingInfo + } + + type ShippingInfo = { + __typename?: "ShippingInfo"; + shippingMethodName: Scalars["String"]; + price: Money; + shippingRate: ShippingRate; + taxRate?: Maybe; + taxCategory?: Maybe; + deliveries: Array; + discountedPrice?: Maybe; + taxedPrice?: Maybe; + shippingMethodState: ShippingMethodState; + shippingMethod?: Maybe; + shippingMethodRef?: Maybe; + }; + ``` + +- `loading: boolean` - a reactive object containing information about loading state of your `load` or `save` method. + +- `error: UseShippingProviderErrors` - a reactive object containing the error message, if `load` or `save` failed for any reason. + + ```ts + interface UseShippingProviderErrors { + load?: Error; + save?: Error; + } + ``` + +## Getters + +We do not provide getters for checkout and its parts. + +## Example + +```js +import { useShippingProvider } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core'; +import { computed } from '@vue/composition-api'; + +export default { + setup () { + const { load, state } = useShippingProvider(); + + onSSR(async () => { + await load(); + }); + + return { + selectedShippingMethod: computed(() => state.value && state.value.response) + }; + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-shipping.md b/packages/core/docs/commercetools/composables/use-shipping.md new file mode 100644 index 0000000000..7dc6975fd9 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-shipping.md @@ -0,0 +1,98 @@ +# `useShipping` + +## Features + +`useShipping` composable can be used for: + +* Loading shipping address for the current cart. +* Saving shipping address for the current cart. + +## API + +- `load` - function for fetching shipping address. When invoked, it requests data from the API and populates `shipping` property. This method accepts a single optional `params` object. The `params` has the following option: + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + getBasicProfile: string + } + ``` + +- `save` - function for saving shipping address. This method accepts a single `saveParams` object. The `saveParams` has the following options: + + - `shippingDetails: Address` + + - `customQuery?: CustomQuery` + + ```ts + type Address = { + __typename?: "Address"; + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + contactInfo: AddressContactInfo; + phone?: Maybe; + email?: Maybe; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; + }; + + type CustomQuery = { + updateCart: string + } + ``` + +- `shipping: Address` - a main data object that contains a shipping address. + +- `loading: boolean` - a reactive object containing information about loading state of your `load` or `save` method. + +- `error: UseShippingErrors` - a reactive object containing the error message, if `load` or `save` failed for any reason. + + ```ts + interface UseShippingErrors { + load?: Error; + save?: Error; + } + ``` + +## Getters + +We do not provide getters for checkout and its parts. + +## Example + +```js +import { useShipping } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup () { + const { load, shipping } = useShipping(); + + onSSR(async () => { + await load(); + }); + + return { + shipping + }; + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-store.md b/packages/core/docs/commercetools/composables/use-store.md new file mode 100644 index 0000000000..97a76e179c --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-store.md @@ -0,0 +1,170 @@ +# `useStore` + +## Features + +`useStore` composable can be used for: + +* Loading available stores with related channels. +* Change and save selected store / channel. + +## API + +- `load` - function for fetching stores data. When invoked, it requests data from the API and populates `response` property. This method accepts a single optional `params` object. The `params` has the following option: + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + [key: string]: string + } + ``` + +- `change` - function for changing and saving selected store / channel. This method accepts a single params object. The params has the following options: + + - `currentStore: AgnosticStore` + + - `store: AgnosticStore` + + - `customQuery?: CustomQuery` + + ```ts + interface AgnosticStore { + name: string; + id: string; + description?: string; + locales?: AgnosticLocale[]; + currencies?: AgnosticCurrency[] + address?: AgnosticAddress; + geoLocation?: AgnosticGeoLocation; + [x: string]: unknown; + } + ``` + +- `response` - a main data object that contains loaded stores data. + + ```ts + type StoreQueryResult = { + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; + }; + + export type Store = { + id: Scalars["String"]; + version: Scalars["Long"]; + key: Scalars["String"]; + name?: Maybe; + nameAllLocales?: Maybe>; + languages?: Maybe>; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + distributionChannels: Array; + supplyChannels: Array; + }; + ``` + +- `loading: boolean` - a reactive object containing information about loading state of your `load` method. + +- `error: UseShippingErrors` - a reactive object containing the error message, if `load` or `save` failed for any reason. + + ```ts + export interface UseStoreErrors { + load: Error | null; + change: Error | null; + } + ``` + +## Getters + +`storeGetter` object contains following methods: + +- `getItems` - returns list of stores as `AgnosticStore` array. + +- `getSelected` - returns selected store as `AgnosticStore`. + + +Related references: + - [storeGetter](/commercetools/api-reference/commercetools.storegetters.html) + - [UseStoreGetters](/core/api-reference/core.usestoregetters.html) + - [StoresData](/commercetools/api-reference/commercetools.storesdata.html) + + ```typescript + export interface useStoreGetters { + getItems(stores: StoresData, criteria?: CRITERIA): AgnosticStore[]; + getSelected(stores: StoresData): AgnosticStore | undefined + } + + export interface StoresData extends StoreQueryResult { + _selectedStore: string; + } + + export type StoreQueryResult = { + __typename?: "StoreQueryResult"; + offset: Scalars["Int"]; + count: Scalars["Int"]; + total: Scalars["Long"]; + results: Array; + } + + export interface AgnosticStore { + name: string; + id: string; + description?: string; + locales?: AgnosticLocale[]; + currencies?: AgnosticCurrency[] + address?: AgnosticAddress; + geoLocation?: AgnosticGeoLocation; + [x: string]: unknown; + } + + export interface AgnosticLocale { + code: string; + label: string; + [x: string]: unknown; + } + + export interface AgnosticCurrency { + code: string; + label: string; + prefixSign: boolean; + sign: string; + [x: string]: unknown; + } + + export interface AgnosticAddress { + addressLine1: string; + addressLine2: string; + [x: string]: unknown; + } + + export interface AgnosticGeoLocation { + type: string; + coordinates?: unknown; + [x: string]: unknown; + } + ``` + +## Example + +```js +import { useStore, storeGetters } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup () { + const { load, response } = useStore(); + + onSSR(async () => { + await load(); + }); + + return { + stores: storesGetters.getItems(response.value), + sselectedStore: storesGetters.getSelected(response.value) + }; + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-user-billing.md b/packages/core/docs/commercetools/composables/use-user-billing.md new file mode 100644 index 0000000000..0602e78fce --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-user-billing.md @@ -0,0 +1,317 @@ +# `useUserBilling` + +> This feature is a part of our commercial offering and does not exist in Open Source version of commercetools integration. Read more about a Vue Storefront Enterprise Cloud [here](https://www.vuestorefront.io/enterprise) + +## Features + +`useUserBilling` composable can be used to: + +* fetch existing billing addresses, +* submit new billing addresses, +* modify and delete existing billing addresses. + +## API + +- `load` - function for fetching user addresses. When invoked, it requests data from the API and populates `billing` property. + +- `addAddress` - function for posting new billing address. This method accepts a single `params` object. The `params` has the following options: + + - `address: BillingAddressAddParams` + + - `customQuery?: customQuery` + + ```typescript + interface BillingAddressAddParams { + address: { + firstName: string; + lastName: string; + streetName: string; + postalCode: string; + city: string; + state: string; + country: string; + apartment: string; + phone: string; + isDefault?: boolean; + } + } + type customQuery = { + addBillingAddress: string + } + ``` + +- `deleteAddress` - function for deleting existing billing address. This method accepts a single `params` object. The `params` has the following options: + + - `address: BillingAddressDeleteParams` + + - `customQuery?: customQuery` + + ```typescript + interface BillingAddressDeleteParams { + address: { + id: string; + } + } + type customQuery = { + deleteBillingAddress: string + } + ``` + +- `updateAddress` - function for updating existing billing address. This method accepts a single `params` object. The `params` has the following options: + + - `address: BillingAddressUpdateParams` + + - `customQuery?: customQuery` + + ```typescript + interface BillingAddressUpdateParams { + address: { + id: string; + firstName: string; + lastName: string; + streetName: string; + postalCode: string; + city: string; + state: string; + country: string; + apartment: string; + phone: string; + isDefault?: boolean; + } + } + type customQuery = { + updateBillingAddress: string + } + ``` + +- `setDefaultAddress` - function for settings an existing billing address as default. This method accepts a single `params` object. The `params` has the following options: + + - `address: BillingAddressSetDefaultParams` + + - `customQuery?: customQuery` + + ```typescript + interface BillingAddressSetDefaultParams { + address: { + id: string; + } + } + type customQuery = { + setDefaultBillingAddress: string + } + ``` + +- `billing: User` - reactive data object containing response from the backend. + + ```ts + type Customer = { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + + type User = Customer; + ``` + +- `loading: boolean` - reactive object containing information about loading state of `load`, `addAddress`, `deleteAddress`, `updateAddress` and `setDefaultAddress` methods. + +- `error: UseUserBillingErrors` - reactive object containing the error message, if some properties failed for any reason. + +## Getters + +- `getAddresses` - returns list of billing addresses. + +- `getDefault` - returns a default billing address. + +- `getTotal` - returns total number of billing addresses user has. + +- `getId` - returns id from an individual address. + +- `getPostCode` - returns post code from an individual address. + +- `getStreetName` - returns street name from an individual address. + +- `getStreetNumber` - returns street number from an individual address. + +- `getCity` - returns city name from an individual address. + +- `getFirstName` - returns first name from an individual address. + +- `getLastName` - returns last name from an individual address. + +- `getCountry` - returns country name from an individual address. + +- `getPhone` - return phone number from an individual address. + +- `getEmail` - returns e-mail address from an individual address. + +- `getProvince` - returns province (state) from an individual address. + +- `getCompanyName` - returns company name from an individual address. + +- `getTaxNumber` - returns tax number from an individual address. + +- `getApartmentNumber` - returns apartment number from an individual address. + +- `isDefault` - return information if address is current default. + + ```typescript + interface UserBillingGetters { + getAddresses: (billing: User, criteria?: Record) => BillingAddress[]; + getDefault: (billing: User) => BillingAddress; + getTotal: (billing: User) => number; + getId: (address: BillingAddress) => string | number; + getPostCode: (address: BillingAddress) => string; + getStreetName: (address: BillingAddress) => string; + getStreetNumber: (address: BillingAddress) => string | number; + getCity: (address: BillingAddress) => string; + getFirstName: (address: BillingAddress) => string; + getLastName: (address: BillingAddress) => string; + getCountry: (address: BillingAddress) => string; + getPhone: (address: BillingAddress) => string; + getEmail: (address: BillingAddress) => string; + getProvince: (address: BillingAddress) => string; + getCompanyName: (address: BillingAddress) => string; + getTaxNumber: (address: BillingAddress) => string; + getApartmentNumber: (address: BillingAddress) => string | number; + isDefault: (address: BillingAddress) => boolean; + } + + type Customer = { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + + type User = Customer; + + type Address = { + __typename?: "Address"; + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + contactInfo: AddressContactInfo; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; + } + + type BillingAddress = Address & { + isDefault?: boolean; + } + ``` + +## Example + +```typescript +import { onSSR } from '@vue-storefront/core'; +import { useUserBilling, userBillingGetters } from '@vsf-enterprise/commercetools'; + +export default { + setup() { + const { + billing, + load, + addAddress, + deleteAddress, + updateAddress + } = useUserBilling(); + + onSSR(async () => { + await load(); + }); + + return { + billing: computed(() => userBillingGetters.getAddresses(billing.value)), + userBillingGetters + }; + } +}; +``` diff --git a/packages/core/docs/commercetools/composables/use-user-order.md b/packages/core/docs/commercetools/composables/use-user-order.md new file mode 100644 index 0000000000..44aa1fe31c --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-user-order.md @@ -0,0 +1,242 @@ +# `useUserOrder` + +## Features + +`useUserOrder` composable is responsible, as it's name suggests for interactions with user's order history from your eCommerce. + +## API + +- `searchOrders` - a main querying function that is used to query user's order history from eCommerce platform and populate the `orders` object with the result. This method accepts a single params object. The `params` has the following options: + + - `id?: string` + + - `page?: number` + + - `perPage?: number` + + - `customQuery?: customQuery` + +- `orders: OrderQueryResult` - a main data object that contains an array of orders fetched by `searchOrders` method and total number of orders. + + ```ts + type Order = { + __typename?: "Order"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + completedAt?: Maybe; + orderNumber?: Maybe; + orderState: OrderState; + stateRef?: Maybe; + state?: Maybe; + shipmentState?: Maybe; + paymentState?: Maybe; + syncInfo: Array; + returnInfo: Array; + lastMessageSequenceNumber: Scalars["Long"]; + cartRef?: Maybe; + cart?: Maybe; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + ``` + +- `loading: boolean` - a reactive object containing information about loading state of your `searchOrders` method. + +- `error: UseUserOrderErrors` - reactive object containing the error message, if some properties failed for any reason. + + ```ts + interface UseUserOrderErrors { + search: Error; + } + ``` + +## Getters + +- `getDate` - returns order date. + +- `getId` - returns order Id. + +- `getStatus` - returns order status. + +- `getPrice` - returns order price. + +- `getItems` - returns order items. + +- `getItemSku` - returns order item sku. + +- `getItemName` - returns order item name. + +- `getItemQty` - returns order item quantity. + +- `getItemPrice` - returns order item price. + +- `getFormattedPrice` - returns order price with currency sign. + +- `getOrdersTotal` - returns total number of orders (not affected by pgaination limit). + + ```ts + interface UserOrderGetters { + getDate: (order: Order) => string; + getId: (order: Order) => string; + getStatus: (order: Order) => AgnosticOrderStatus; + getPrice: (order: Order) => number; + getItems: (order: Order) => LineItem[]; + getItemSku: (item: LineItem) => string; + getItemName: (item: LineItem) => string; + getItemQty: (item: LineItem) => number; + getItemPrice: (item: LineItem) => number; + getFormattedPrice: (price: number) => string; + getOrdersTotal: (orders: OrderQueryResult) => number; + } + + type Order = { + __typename?: "Order"; + customerId?: Maybe; + customer?: Maybe; + customerEmail?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + customLineItems: Array; + totalPrice: Money; + taxedPrice?: Maybe; + shippingAddress?: Maybe
; + billingAddress?: Maybe
; + inventoryMode: InventoryMode; + taxMode: TaxMode; + taxRoundingMode: RoundingMode; + taxCalculationMode: TaxCalculationMode; + customerGroup?: Maybe; + customerGroupRef?: Maybe; + country?: Maybe; + shippingInfo?: Maybe; + discountCodes: Array; + refusedGifts: Array; + refusedGiftsRefs: Array; + paymentInfo?: Maybe; + locale?: Maybe; + shippingRateInput?: Maybe; + origin: CartOrigin; + storeRef?: Maybe; + store?: Maybe; + itemShippingAddresses: Array
; + completedAt?: Maybe; + orderNumber?: Maybe; + orderState: OrderState; + stateRef?: Maybe; + state?: Maybe; + shipmentState?: Maybe; + paymentState?: Maybe; + syncInfo: Array; + returnInfo: Array; + lastMessageSequenceNumber: Scalars["Long"]; + cartRef?: Maybe; + cart?: Maybe; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + + enum AgnosticOrderStatus { + Open = 'Open', + Pending = 'Pending', + Confirmed = 'Confirmed', + Shipped = 'Shipped', + Complete = 'Complete', + Cancelled = 'Cancelled', + Refunded = 'Refunded' + } + + type LineItem = { + __typename?: "LineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + productSlug?: Maybe; + productType?: Maybe; + productTypeRef?: Maybe; + variant?: Maybe; + price: ProductPrice; + taxedPrice?: Maybe; + totalPrice?: Maybe; + quantity: Scalars["Long"]; + state: Array; + taxRate?: Maybe; + supplyChannel?: Maybe; + supplyChannelRef?: Maybe; + distributionChannel?: Maybe; + distributionChannelRef?: Maybe; + discountedPricePerQuantity: Array; + lineItemMode: LineItemMode; + priceMode: LineItemPriceMode; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + inventoryMode?: Maybe; + customFieldList?: Maybe>; + } + ``` + +## Example + +```js +import { useUserOrder, orderGetters } from '@vue-storefront/commercetools'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup() { + const { orders, search, loading, error } = useUserOrder(); + + onSSR(async () => { + await search(); + }); + + return { + // extract a list of orders from a `orders` object + orders: computed(() => orderGetters.getItems(shipping.value)), + }; + } +}; +``` diff --git a/packages/core/docs/commercetools/composables/use-user-shipping.md b/packages/core/docs/commercetools/composables/use-user-shipping.md new file mode 100644 index 0000000000..11bd913f92 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-user-shipping.md @@ -0,0 +1,327 @@ +# `useUserShipping` + +> This feature is a part of our commercial offering and does not exist in Open Source version of commercetools integration. Read more about a Vue Storefront Enterprise Cloud [here](https://www.vuestorefront.io/enterprise) + +## Features + +`useUserShipping` composable can be used to: + +* fetch existing shipping addresses, +* submit new shipping addresses, +* modify and delete existing shipping addresses. + +## API + +- `load` - function for fetching user addresses. When invoked, it requests data from the API and populates `shipping` property. + +- `addAddress` - function for posting new shipping address. This method accepts a single `params` object. The `params` has the following options: + + - `address: ShippingAddressAddParams` + + - `customQuery?: customQuery` + + ```typescript + interface ShippingAddressAddParams { + address: { + firstName: string; + lastName: string; + streetName: string; + postalCode: string; + city: string; + state: string; + country: string; + apartment: string; + phone: string; + isDefault?: boolean; + } + } + type customQuery = { + addShippingAddress: string + } + ``` + +- `deleteAddress` - function for deleting existing shipping address. This method accepts a single `params` object. The `params` has the following options: + + - `address: ShippingAddressDeleteParams` + + - `customQuery?: customQuery` + + ```typescript + interface ShippingAddressDeleteParams { + address: { + id: string; + } + } + type customQuery = { + deleteShippingAddress: string + } + ``` + +- `updateAddress` - function for updating existing shipping address. This method accepts a single `params` object. The `params` has the following options: + + - `address: ShippingAddressUpdateParams` + + - `customQuery?: customQuery` + + ```typescript + interface ShippingAddressUpdateParams { + address: { + id: string; + firstName: string; + lastName: string; + streetName: string; + postalCode: string; + city: string; + state: string; + country: string; + apartment: string; + phone: string; + isDefault?: boolean; + } + } + type customQuery = { + updateShippingAddress: string + } + ``` + +- `setDefaultAddress` - function for settings an existing shipping address as default. This method accepts a single `params` object. The `params` has the following options: + + - `address: ShippingAddressSetDefaultParams` + + - `customQuery?: customQuery` + + ```typescript + interface ShippingAddressSetDefaultParams { + address: { + id: string; + } + } + type customQuery = { + setDefaultShippingAddress: string + } + ``` + +- `shipping: User` - reactive data object containing response from the backend. + + ```ts + type Customer = { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + + type User = Customer; + ``` + +- `loading: boolean` - reactive object containing information about loading state of `load`, `addAddress`, `deleteAddress`, `updateAddress` and `setDefaultAddress` methods. + +- `error: UseUserShippingErrors` - reactive object containing the error message, if some properties failed for any reason. + + ```ts + interface UseUserShippingErrors { + addAddress: Error; + deleteAddress: Error; + updateAddress: Error; + load: Error; + setDefaultAddress: Error; + } + ``` + +## Getters + +- `getAddresses` - returns list of shipping addresses. + +- `getDefault` - returns a default shipping address. + +- `getTotal` - returns total number of shipping addresses user has. + +- `getId` - returns id from an individual address. + +- `getPostCode` - returns post code from an individual address. + +- `getStreetName` - returns street name from an individual address. + +- `getStreetNumber` - returns street number from an individual address. + +- `getCity` - returns city name from an individual address. + +- `getFirstName` - returns first name from an individual address. + +- `getLastName` - returns last name from an individual address. + +- `getCountry` - returns country name from an individual address. + +- `getPhone` - return phone number from an individual address. + +- `getEmail` - returns e-mail address from an individual address. + +- `getProvince` - returns province (state) from an individual address. + +- `getCompanyName` - returns company name from an individual address. + +- `getTaxNumber` - returns tax number from an individual address. + +- `getApartmentNumber` - returns apartment number from an individual address. + +- `isDefault` - return information if address is current default. + + ```typescript + interface UserShippingGetters { + getAddresses: (shipping: User, criteria?: Record) => ShippingAddress[]; + getDefault: (shipping: User) => ShippingAddress; + getTotal: (shipping: User) => number; + getId: (address: ShippingAddress) => string | number; + getPostCode: (address: ShippingAddress) => string; + getStreetName: (address: ShippingAddress) => string; + getStreetNumber: (address: ShippingAddress) => string | number; + getCity: (address: ShippingAddress) => string; + getFirstName: (address: ShippingAddress) => string; + getLastName: (address: ShippingAddress) => string; + getCountry: (address: ShippingAddress) => string; + getPhone: (address: ShippingAddress) => string; + getEmail: (address: ShippingAddress) => string; + getProvince: (address: ShippingAddress) => string; + getCompanyName: (address: ShippingAddress) => string; + getTaxNumber: (address: ShippingAddress) => string; + getApartmentNumber: (address: ShippingAddress) => string | number; + isDefault: (address: ShippingAddress) => boolean; + } + + type Customer = { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + + type User = Customer; + + type Address = { + __typename?: "Address"; + id?: Maybe; + title?: Maybe; + salutation?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + streetName?: Maybe; + streetNumber?: Maybe; + additionalStreetInfo?: Maybe; + postalCode?: Maybe; + city?: Maybe; + region?: Maybe; + state?: Maybe; + country: Scalars["Country"]; + company?: Maybe; + department?: Maybe; + building?: Maybe; + apartment?: Maybe; + pOBox?: Maybe; + contactInfo: AddressContactInfo; + additionalAddressInfo?: Maybe; + externalId?: Maybe; + key?: Maybe; + } + + type ShippingAddress = Address & { + isDefault?: boolean; + } + ``` + +## Example + +```typescript +import { onSSR } from '@vue-storefront/core'; +import { useUserShipping, userShippingGetters } from '@vsf-enterprise/commercetools'; + +export default { + setup() { + const { + shipping, + load, + addAddress, + deleteAddress, + updateAddress + } = useUserShipping(); + + onSSR(async () => { + await load(); + }); + + return { + shipping: computed(() => userShippingGetters.getAddresses(shipping.value)), + userShippingGetters + }; + } +}; +``` diff --git a/packages/core/docs/commercetools/composables/use-user.md b/packages/core/docs/commercetools/composables/use-user.md new file mode 100644 index 0000000000..0b9a5f72ff --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-user.md @@ -0,0 +1,162 @@ +# `useUser` + +## Features + +`useUser` composable can be used to: + +- manage user authentication +- manage authentication data like email address, login or password. + +If you want to fetch/save other user data you should use the following composables: +- [`useUserBilling`](./use-user-billing.md) +- [`useUserShipping`](./use-user-shipping.md) +- [`useUserOrders`](./use-user-orders.md) + +## API + +- `user` - reactive object containing information about current user. + + ```ts + type Customer = { + __typename?: "Customer"; + customerNumber?: Maybe; + email: Scalars["String"]; + password: Scalars["String"]; + addresses: Array
; + defaultShippingAddressId?: Maybe; + defaultBillingAddressId?: Maybe; + shippingAddressIds: Array; + billingAddressIds: Array; + isEmailVerified: Scalars["Boolean"]; + customerGroupRef?: Maybe; + externalId?: Maybe; + key?: Maybe; + firstName?: Maybe; + lastName?: Maybe; + middleName?: Maybe; + title?: Maybe; + locale?: Maybe; + salutation?: Maybe; + dateOfBirth?: Maybe; + companyName?: Maybe; + vatId?: Maybe; + customerGroup?: Maybe; + defaultShippingAddress?: Maybe
; + defaultBillingAddress?: Maybe
; + shippingAddresses: Array
; + billingAddresses: Array
; + storesRef: Array; + stores: Array; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + customFieldList?: Maybe>; + } + ``` + +- `updateUser` - function for updating user data. When invoked, it submits data to the API and populates user property with updated information. This method accepts a single `params` object. The `params` has the following option: + + - `user: UPDATE_USER_PARAMS` + + ```ts + interface UPDATE_USER_PARAMS { + email: string; + password: string; + firstName?: string; + lastName?: string; + } + ``` + +- `register: UseUserRegisterParams` - function for creating a new user. When invoked, it submits new user data to the API and saves them. This method accepts a single `params` object. The `params` has the following option: + + - `user: UseUserRegisterParams` + + ```ts + interface UseUserRegisterParams { + email: string; + password: string; + firstName?: string; + lastName?: string; + } + ``` + +- `login: UseUserLoginParams` - function for log in a user based on a username and password. This method accepts a single `params` object. The `params` has the following option: + + - `user: UseUserLoginParams` + + ```ts + interface UseUserLoginParams { + username: string; + password: string; + } + ``` + +- `logout` - function for logout a user. + +- `changePassword` - function for changing user password. This method accepts a single `params` object. The `params` has the following options: + + - `currentPassword: string` + + - `newPassword: string` + +- `loading: boolean` - reactive object containing information about loading state of `user`. + +- `isAuthenticated: boolean` - checks if user is authenticated or not. + +- `error: UseUserErrors` - reactive object containing the error message, if some properties failed for any reason. + + ```ts + interface UseUserErrors { + updateUser: Error; + register: Error; + login: Error; + logout: Error; + changePassword: Error; + load: Error; + } + ``` + +## Getters + +- `getFirstName` - returns user first name. + +- `getLastName` - returns user last name. + +- `getFullName` - returns full user name. + +- `getEmailAddress` - returns user email address. + + ```ts + interface UserGetters { + getFirstName: (user: Customer) => string; + getLastName: (user: Customer) => string; + getFullName: (user: Customer) => string; + getEmailAddress: (user: Customer) => string; + } + ``` + +## Example + +```js +import { useUser } from '@vue-storefront/commercetools'; + +export default { + setup () { + const { user, register, login, loading } = useUser(); + + return { + register, + login, + loading, + firstName: userGetters.getFirstName(user.value), + email: userGetters.getEmailAddress(user.value) + } + } +} +``` diff --git a/packages/core/docs/commercetools/composables/use-wishlist.md b/packages/core/docs/commercetools/composables/use-wishlist.md new file mode 100644 index 0000000000..c190baabe6 --- /dev/null +++ b/packages/core/docs/commercetools/composables/use-wishlist.md @@ -0,0 +1,274 @@ +# `useWishlist` + +> This feature is a part of our commercial offering and does not exist in Open Source version of commercetools integration. Read more about a Vue Storefront Enterprise Cloud [here](https://www.vuestorefront.io/enterprise) + +## Features + +`useWishlist` composable is responsible, for integrating with wishlist from Commercetools. It allows to: + +- fetch products from wishlist +- add products to wishlist +- remove products from wishlist +- check if product is on wishlist + +## API + +- `wishlist: Wishlist` - a main data object. + + ```ts + type ShoppingList = { + __typename?: "ShoppingList"; + key?: Maybe; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales?: Maybe>; + customerRef?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + textLineItems: Array; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + } + + type Wishlist = ShoppingList; + ``` + +- `load` - function used to retrieve wishlist products. When invoked, it requests data from the API and populates `wishlist` property. This method accepts a single `params` object. The `params` has the following option: + + - `customQuery?: CustomQuery` + + ```ts + type CustomQuery = { + createMyShoppingList: string + } + ``` + +- `addItem` - function used to add new product to wishlist. When invoked, it submits data to the API and populates `wishlist` property with updated information. This method accepts a single `params` object. The `params` has the following options: + + - `product: ProductVariant` + + - `customQuery?: customQuery` + + ```ts + type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; + } + + type CustomQuery = { + updateShoppingList: string + } + ``` + +- `removeItem` - function that removes products from the wishlist. It submits data to the API and populates updated `wishlist` property. This method accepts a single `params` object. The `params` has the following options: + + - `product: LineItem` + + - `customQuery?: customQuery` + + ```ts + type LineItem = { + __typename?: "LineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + name?: Maybe; + nameAllLocales: Array; + productSlug?: Maybe; + productType?: Maybe; + productTypeRef?: Maybe; + variant?: Maybe; + price: ProductPrice; + taxedPrice?: Maybe; + totalPrice?: Maybe; + quantity: Scalars["Long"]; + state: Array; + taxRate?: Maybe; + supplyChannel?: Maybe; + supplyChannelRef?: Maybe; + distributionChannel?: Maybe; + distributionChannelRef?: Maybe; + discountedPricePerQuantity: Array; + lineItemMode: LineItemMode; + priceMode: LineItemPriceMode; + customFieldsRaw?: Maybe>; + customFields?: Maybe; + custom?: Maybe; + shippingDetails?: Maybe; + inventoryMode?: Maybe; + customFieldList?: Maybe>; + } + + type CustomQuery = { + updateShoppingList: string + } + ``` + +- `clear` - function that removes all products from the wishlist and populates clear `wishlist` property. + +- `isInWishlist` - function that checks if product is on the wishlist. It returns boolean value. This method accepts a single `params` object. The `params` has the following option: + + - `product: ProductVariant` + + ```ts + type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; + } + ``` + +- `loading: boolean` - a reactive object containing information about loading state of the cart. + +- `error: UseWishlistErrors` - reactive object containing the error message, if some properties failed for any reason. + + ```ts + interface UseWishlistErrors { + addItem: Error; + removeItem: Error; + load: Error; + clear: Error; + } + ``` + +## Getters + +- `getItems` - returns list of products on wishlist + +- `getItemName` - returns product's name from wishlist. + +- `getItemImage` - returns product's image from wishlist. + +- `getItemPrice` - returns product's price from wishlist. + +- `getItemQty` - returns quantity of product which is on wishlist. + +- `getItemAttributes` - returns product variant attribute chosen by its name. + +- `getItemSku` - returns product's SKU code. + +- `getTotals` - returns price of products. + +- `getTotalItems` - returns amount of all items that are currently on wishlist. + +- `getFormattedPrice` - returns price in formatted manner taking into account local specifics. + + ```typescript + interface WishlistGetters { + getTotals: (wishlist: Wishlist) => AgnosticTotals; + getItems: (wishlist: Wishlist) => ShoppingListLineItem[]; + getItemName: (product: ShoppingListLineItem) => string; + getItemImage: (product: ShoppingListLineItem) => string; + getItemPrice: (product: ShoppingListLineItem) => AgnosticPrice; + getItemQty: (product: ShoppingListLineItem) => number; + getItemAttributes: (product: ShoppingListLineItem, filterByAttributeName?: string[]) => ({}); + getItemSku: (product: ShoppingListLineItem) => string; + getTotalItems: (wishlist: Wishlist) => number; + getFormattedPrice: (price: number) => string; + }; + + interface AgnosticTotals { + total: number; + subtotal: number; + special?: number; + [x: string]: unknown; + } + + interface AgnosticPrice { + regular: number | null; + special?: number | null; + } + + type Wishlist = { + __typename?: "ShoppingList"; + key?: Maybe; + name?: Maybe; + nameAllLocales: Array; + description?: Maybe; + descriptionAllLocales?: Maybe>; + slug?: Maybe; + slugAllLocales?: Maybe>; + customerRef?: Maybe; + customer?: Maybe; + anonymousId?: Maybe; + lineItems: Array; + textLineItems: Array; + custom?: Maybe; + deleteDaysAfterLastModification?: Maybe; + id: Scalars["String"]; + version: Scalars["Long"]; + createdAt: Scalars["DateTime"]; + lastModifiedAt: Scalars["DateTime"]; + createdBy?: Maybe; + lastModifiedBy?: Maybe; + } + + type ShoppingListLineItem = { + __typename?: "ShoppingListLineItem"; + id: Scalars["String"]; + productId: Scalars["String"]; + variantId?: Maybe; + productTypeRef: Reference; + productType: ProductTypeDefinition; + quantity: Scalars["Int"]; + addedAt: Scalars["DateTime"]; + name?: Maybe; + nameAllLocales: Array; + deactivatedAt?: Maybe; + custom?: Maybe; + productSlug?: Maybe; + variant?: Maybe; + } + ``` + +## Example + +```typescript +import { onSSR } from '@vue-storefront/core'; +import { useWishlist, wishlistGetters } from '@vsf-enterprise/commercetools'; + +export default { + setup() { + const { load: loadWishlist } = useWishlist(); + + const wishlistItems = computed(() => wishlistGetters.getItems()); + + onSSR(async () => { + await loadWishlist(); + }); + + return { + loadWishlist, + wishlistItems + }; + } +}; +``` diff --git a/packages/core/docs/commercetools/configuration.md b/packages/core/docs/commercetools/configuration.md new file mode 100644 index 0000000000..143f1016f6 --- /dev/null +++ b/packages/core/docs/commercetools/configuration.md @@ -0,0 +1,105 @@ +# Configuration + + +Commercetools configuration is located in two places: + +- nuxt.config.js is a place where you're configuring properties related only to the frontend part of your application. + +- middleware.config.js is a place where you're configuring the Commercetools SDK, Apollo and extensions. You will put there API keys, integration configurations, custom GraphQL queries and new API endpoints. + +## Nuxt Commercetools configuration + +```js +// nuxt.config.js +['@vue-storefront/commercetools/nuxt', { + i18n: { + useNuxtI18nConfig: true + } +}] +``` + +- `useNuxtI18nConfig` - when this property is set to true, `@vue-storefront/commercetools/nuxt` package will use `i18n` config object provided in `nuxt.config.js`. When set to false, `i18n` config should be declared directly inside this package configuration. You can read more about `i18n` config in Vue Storefront [here](../advanced/internationalization.md) + +## Middleware Commercetools configuration + +You can read more about middleware configuration in Vue Storefront [here](../advanced/server-middleware.html#configuration) + +```js +// middleware.config.js +module.exports = { + integrations: { + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { + api: { + uri: 'https://.com//graphql', + authHost: 'https://auth.sphere.io', + projectKey: '', + clientId: '', + clientSecret: '', + scopes: [ + 'manage_products:', + /* other scope rules */ + ] + } + } + } + } +}; +``` + +### `api` + +- `uri` - link to your Commercetools GraphQL API instance. +- `authHost` - link to Commercetools Authentication Server. It is used to request an access token from commercetools OAuth 2.0 service. To choose the nearest service, please visit [Commercetools hosts list](https://docs.commercetools.com/api/authorization) +- `projectKey` - name of your Commercetools project, i.e. `my-awesome-vsf-project` +- `clientId` - unique Commercetools Client ID. Visit [Commercetools documentation](https://docs.commercetools.com/tutorials/getting-started#creating-an-api-client) for more details about creating an API Client +- `clientSecret` - Commercetools secret API key. Visit [Commercetools documentation](https://docs.commercetools.com/tutorials/getting-started#creating-an-api-client) for more details about creating an API Client +- `scopes` - The scope constrains the endpoints to which a client has access, and whether a client has read or write access to an endpoint. Visit [Commercetools documentation](https://docs.commercetools.com/api/scopes#top) for more details about Scopes. + +By default, the internationalization settings, such as `currency`, `locale`, and `country` are loaded from cookies. To override this behavior you can set those properties inside the `configuration` section. + +```js +// middleware.config.js +module.exports = { + integrations: { + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { + api: { /* ... */}, + currency: 'EUR', + locale: 'en', + country: 'US' + } + } + } +}; +``` + + +### `acceptLanguage` + +An array of possible locales Commercetools will use. You can read more about Commercetools internationalization configuration [here](https://docs.commercetools.com/api/projects/orders-import#language-filtering) + +```js +acceptLanguage: ['en-gb', 'en-us'] +``` + +### `languageMap` + +If you supply a `languageMap` during the setup, this will be used to map a locale to the accepted languages. + +```js +languageMap: { + 'en-gb': ['en-gb', 'en-us'], + 'en-us': ['en-us', 'en-gb'], +} +``` + +### `inventoryMode` + +If you want to change the way your commercetools inventory is being tracked, you can provide `inventoryMode` option in the middleware configuration. It can be set to one of the following values: `None`, `TrackOnly`, and `ReserveOnOrder`. When not specified, the Inventory Mode is set to `None` by default. You can read more about Inventory Modes in [commercetools documentation](https://docs.commercetools.com/api/projects/carts#inventorymode). + +```js +inventoryMode: 'TrackOnly' +``` diff --git a/packages/core/docs/commercetools/enterprise/use-wishlist.md b/packages/core/docs/commercetools/enterprise/use-wishlist.md new file mode 100644 index 0000000000..02f9b3bd14 --- /dev/null +++ b/packages/core/docs/commercetools/enterprise/use-wishlist.md @@ -0,0 +1,115 @@ +--- +platform: Commercetools +--- + +# Wishlist + +[[toc]] + +## Features + +`useWishlist` composition API function is responsible, for integrating with wishlist from Commercetools. It allows to: + +- fetch products from wishlist +- add products to wishlist +- remove products from wishlist +- check if product is on wishlist + +## API + +`useWishlist` contains following properties: + +- `load` - function used to retrieve wishlist products. When invoked, it requests data from the API and populates `wishlist` property. + +- `addToWishlist` - function used to add new product to wishlist. When invoked, it submits data to the API and populates `wishlist` property with updated information. + +- `removeFromWishlist` - function that removes products from the wishlist. It submits data to the API and populates updated `wishlist` property. + +- `clearWishlist` - function that removes all products from the wishlist and populates clear `wishlist` property. + +- `isInWishlist` - function that checks if product is on the wishlist. It returns boolean value. + +## Getters + +Because `wishlist` property is a raw response with some additional properties, it's recommended to use `wishlistGetters` for accessing any data from it. It includes following helper functions: + +- `getWishlistItems` - returns list of products on wishlist + +- `getWishlistItemName` - returns product's name from wishlist. + +- `getWishlistItemImage` - returns product's image from wishlist. + +- `getWishlistItemPrice` - returns product's price from wishlist. + +- `getWishlistItemQty` - returns quantity of product which is on wishlist. + +- `getWishlistItemAttributes` - returns product variant attribute chosen by its name. + +- `getWishlistItemSku` - returns product's SKU code. + +- `getWishlistShippingPrice` - returns price of products. + +- `getWishlistTotalItems` - returns amount of all items that are currently on wishlist. + +- `getFormattedPrice` - returns price in formatted manner taking into account local specifics. + +Interface for the above getter looks like this: + +```typescript +interface WishlistGetters = { + getTotals: getWishlistTotals, + getShippingPrice: getWishlistShippingPrice, + getItems: getWishlistItems, + getItemName: getWishlistItemName, + getItemImage: getWishlistItemImage, + getItemPrice: getWishlistItemPrice, + getItemQty: getWishlistItemQty, + getItemAttributes: getWishlistItemAttributes, + getItemSku: getWishlistItemSku, + getTotalItems: getWishlistTotalItems, + getFormattedPrice +}; +``` + +## Usage + +When you already installed `@vsf-enterprise/ct-wishlist` as a dependency, there are few minor modifications required to make it work. + +The first step is to add `@vsf-enterprise/ct-wishlist` to `build > transpile` array in `nuxt.config.js`. + +Then we need to replace the import of `useWishlist` and `wishlistGetters` everywhere they are used from `@vue-storefront/commercetools` to `@vsf-enterprise/ct-wishlist`: + +```javascript +// Before +import { /* other imports */, useWishlist, wishlistGetters } from '@vue-storefront/commercetools'; + +// After +import { /* other imports */ } from '@vue-storefront/commercetools'; +import { useWishlist, wishlistGetters } from '@vsf-enterprise/ct-wishlist'; +``` + +## Examples + +Fetching products currently on wishlist: + +```typescript +import { onSSR } from '@vue-storefront/core'; +import { useWishlist, wishlistGetters } from '@vsf-enterprise/ct-wishlist'; + +export default { + setup() { + const { load: loadWishlist } = useWishlist(); + + const wishlistItems = computed(() => wishlistGetters.getItems()); + + onSSR(async () => { + await loadWishlist(); + }); + + return { + loadWishlist, + wishlistItems + }; + } +}; +``` diff --git a/packages/core/docs/commercetools/extensions/user-groups.md b/packages/core/docs/commercetools/extensions/user-groups.md new file mode 100644 index 0000000000..3560a85db1 --- /dev/null +++ b/packages/core/docs/commercetools/extensions/user-groups.md @@ -0,0 +1,79 @@ +# `User groups` + +> This feature is a part of our commercial offering and does not exist in Open Source version of commercetools integration. Read more about a Vue Storefront Enterprise Cloud [here](https://www.vuestorefront.io/enterprise) + +## Features + +User groups is the feature commonly used to assign dedicated benefits, such as discounts, price rules, special prices to the specific type of customers, instead of each individual customer separately. + +## API + +```ts +interface CustomerIdentifier { + id: string; + version: number; +} + +type UpdateResult = MutationResponse<'customer', Customer> +``` + +- `addCustomerToGroup: (customer: CustomerIdentifier, group: ResourceIdentifierInput) => Promise` - adds user to the group +- `removeCustomerFromGroup: (customer: CustomerIdentifier) => Promise` - removes user from the group +- `setup` - it configures the api client within the enterprise package. It expects the fully configured apollo client, so we recommend to configure the original api-client first and pass this configuration to the enterprise package (example below). + +## Example + +We strongly recommend to use these functions in our middleware as they require wider permissions. Using it purely on the front-end side affects security. + +In order to create our middleware, firstly you have to register your middleware in the configuration: + +```js +import customerGroupsMiddleware from './customerGroupsMiddleware' + +['@vue-storefront/{PLATFORM}/nuxt', { + apiMiddleware: { + extend: customerGroupsMiddleware + } +}] +``` + +Secondly, the implementation itself: + +```js +// customerGroupsMiddleware.js +import { + setup as originSetup, + getSettings as getOriginSettings +} from '@vue-storefront/commercetools-api'; +import { + addCustomerToGroup, + setup as serverSetup +} from '@vsf-enterprise/customer-groups'; + +export default (app) => { + // API client setup + originSetup({ + api: { + uri: '{URI ENDPOINT FOR GRAPHQL}', + authHost: '{AUTH URI}', + projectKey: '{YOUR PROJECT KEY}', + clientId: '{CLIENT ID}', + clientSecret: '{SECRET}', + scopes: ['{RIGHT SCOPE}'] + } + }); + + // Passing the configured API client + serverSetup(getOriginSettings()); + + app.get('/add-to-customer-group', async (req, res) => { + const customer = { id: 'eb7d1289-3063-4158-8bef-4caaa1ef476c', version: 5 }; + const group = { id: 'd0df9c72-5248-4da4-ac78-826a59e7dc47' }; + const response = await addCustomerToGroup(customer, group); + + res.send({ response }); + }); +}; +``` + +Please note that we configure the API client from scratch, because we should use a different one for the server-side communication with the different scopes and permissions. diff --git a/packages/core/docs/commercetools/feature-list.md b/packages/core/docs/commercetools/feature-list.md new file mode 100644 index 0000000000..c8073d9510 --- /dev/null +++ b/packages/core/docs/commercetools/feature-list.md @@ -0,0 +1,106 @@ +# Feature list + + +# eCommerce integration (theme) + +## Product Tile +1. Display product price +2. Display product name +3. Display average rating +4. Display number of reviews +5. Display product catalog discounts +6. Quick Add to Cart (default variant) +7. Quick Add to Wishlist + +## Product Listing Page +1. Products + 1. Display a list of products for a specific category + 2. Display number of total products found +2. Category + 1. Display a list of categories (same level and one level deeper) + 2. Display Breadcrumbs +3. Pagination + 1. Display pagination + 2. Change the number of items per page +4. Filtering + 1. Filter products by attributes and price (faceting) + 2. Filtering is reflected in the URL +5. Sorting + 1. Sort by price + 2. Sort by newly added + 3. Sorting is reflected in the URL +6. UI + 1. Horizontal/Vertical product list switch + +## Product Details Page +1. Product + 1. Display master variant + 2. Display product name + 3. Display product price + 4. Display discounted price + 5. Display product description +- Product Variants + - Display product variants (by default color and size) + - Change variant + - Variant change is reflected in the URL +- Product Gallery + - Show all product images as thumbnails + - Show one big product image + - Change big product image with one of thumbnails +- Reviews + - Display average rating + - Display total number of reviews + - Add review as logged in user (no UI) + - Add review as logged out user (no UI) +- Add to cart + - Add product variant to cart + - Change quantity +- Suggested products + - Display a list of suggested products from the same category +- Other + - Add to Wishlist + +## Cart +- Product + - Display product name + - Display product price + - Display product discounts + - Add product to cart + - Remove product from cart + - Change product Quantity + +- Summary + - Show total price + + +## Wishlist +- Product + - Display product name + - Display product price + - Display product discounts + - Add product to wishlist + - Remove product from wishlist + +## Checkout +- Shipping + - Choose from saved shipping addresses (logged in) + - Add new shipping address (logged in) + - Change default shipping address (logged in) +- Billing + - Choose from saved billing addresses (logged in) + - Add new billing address (logged in) + - Change default billing address (logged in) +- Payment +- Review +- Order summary +- Coupons + +## User Profile + + +# CMS + +# Cloud diff --git a/packages/core/docs/commercetools/getting-started.md b/packages/core/docs/commercetools/getting-started.md new file mode 100644 index 0000000000..4dfdcf073a --- /dev/null +++ b/packages/core/docs/commercetools/getting-started.md @@ -0,0 +1,40 @@ +# Getting started + + +## Configuring your Commercetools integration + +If you [generated your project from our CLI](/general/getting-started.html) your shop will be connected to our demo Commercetools instance. + +If you haven't generated your project just to play with Vue Storefront and understand its capabilities the first thing you should do after setting it up is changing the credentials to point into your instance. + +You can generate credentials for Commercetools API in Commercetools Merchant Center by going into: + +_Settings > API clients > "Create new api Client"_ and picking _"Mobile & single-page application client"_ template. + +Then paste these credentials into `ct` config object inside `integrations` in `middleware.config.js`: + +```js + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { + api: { + uri: 'https://.com/vsf-ct-dev/graphql', + authHost: 'https://auth.sphere.io', + projectKey: 'vsf-ct-dev', + clientId: '', + clientSecret: '', + scopes: [ + //* scopes */ + ] + }, + currency: 'USD', + country: 'US' + } + } +``` + +There is plenty of other configuration options and you can check them [here](./configuration.md) + +## Configuring other integrations + +Depending on the configuration and if you're using Enterprise version you could have additional integrations to set up. You will find their configurations in `middleware.config.js` diff --git a/packages/core/docs/commercetools/maintainers.md b/packages/core/docs/commercetools/maintainers.md new file mode 100644 index 0000000000..8b7112aa72 --- /dev/null +++ b/packages/core/docs/commercetools/maintainers.md @@ -0,0 +1,15 @@ +# Maintainers and support + +The Commercetools integration is developed and maintained by **Vue Storefront core team**. + + +## Support + +In case of any questions, reach out to the maintainers in the `#commercetools` channel on our [Discord](https://discord.vuestorefront.io) server. You can also [hire the core team](https://www.vuestorefront.io/support) to support you in delivering your project. + + \ No newline at end of file diff --git a/packages/core/docs/commercetools/use-facet.md b/packages/core/docs/commercetools/use-facet.md new file mode 100644 index 0000000000..856c0282df --- /dev/null +++ b/packages/core/docs/commercetools/use-facet.md @@ -0,0 +1,271 @@ +# `useFacet` + +:::warning +This feature is a part of our commercial offering but also exists in the Open Source version of our commercetools integration. + +Open Source implementation relies on GraphQL API (internally using `getProduct` and `getCategory` composables), which doesn't provide full faceting capabilities as does the dedicated REST-based faceting API offered in our Enterprise version. Please [contact our Sales team](https://www.vuestorefront.io/contact/sales) if you'd like to get access to it. +::: + +## Features + +`useFacet` composition function can be used to fetch data related to: + +* products, +* categories, +* breadcrumbs. + +What makes it powerful is the ability to accept multiple filters, allowing to narrow down the results to a specific category, search term, etc. + +For more information about faceting, please refer to [this page](../composables/use-facet.md). + +## API + +`useFacet` contains the following properties: + +- `search` - function for searching and classifying records, allowing users to browse the catalog data. It accepts a single object as a parameter with following signature: + +```ts +interface AgnosticFacetSearchParams { + categorySlug?: string; + rootCatSlug?: string; + term?: string; + page?: number; + itemsPerPage?: number; + sort?: string; + filters?: Record; + metadata?: any; + [x: string]: any; +} +``` + +- `result` - reactive data object containing the response from the backend. + +- `loading` - reactive object containing information about the loading state of `search`. + +- `error` - reactive object containing the error message, if `search` failed for any reason. + +```ts +interface UseFacetErrors { + search: Error; +} +``` + +## Getters +Because the `result` property is a raw response with some additional properties, it's recommended to use `facetGetters` for accessing any data from it. It includes the following helper functions: + +- `getAll` - returns all available facets. + +- `getGrouped` - returns grouped facets by facet name. + +- `getCategoryTree` - return the tree of nested categories. + +- `getSortOptions` - returns available and currently selected sorting options. + +- `getProducts` - returns products matching current filters. + +- `getPagination` - returns pagination information. + +- `getBreadcrumbs` - returns breadcrumbs information. + + +```ts +interface FacetsGetters { + getAll: (searchData: SearchData, criteria?: string[]) => AgnosticFacet[]; + getGrouped: (searchData: SearchData, criteria?: string[]) => AgnosticGroupedFacet[]; + getCategoryTree: (searchData: SearchData) => AgnosticCategoryTree; + getSortOptions: (searchData: SearchData) => AgnosticSort; + getProducts: (searchData: SearchData) => ProductVariant[]; + getPagination: (searchData: SearchData) => AgnosticPagination; + getBreadcrumbs: (searchData: SearchData) => AgnosticBreadcrumb[]; +} + +interface AgnosticFacet { + type: string; + id: string; + value: any; + attrName?: string; + count?: number; + selected?: boolean; + metadata?: any; +} + +interface AgnosticGroupedFacet { + id: string; + label: string; + count?: number; + options: AgnosticFacet[]; +} + +interface AgnosticCategoryTree { + label: string; + slug?: string; + items: AgnosticCategoryTree[]; + isCurrent: boolean; + count?: number; + [x: string]: unknown; +} + +interface AgnosticSort { + options: AgnosticFacet[]; + selected: string; +} + +type SearchData = FacetSearchResult + +interface FacetSearchResult { + data; + input: AgnosticFacetSearchParams; +} + +interface AgnosticFacetSearchParams { + categorySlug?: string; + rootCatSlug?: string; + term?: string; + page?: number; + itemsPerPage?: number; + sort?: string; + filters?: Record; + metadata?: any; + [x: string]: any; +} + +interface AgnosticPagination { + currentPage: number; + totalPages: number; + totalItems: number; + itemsPerPage: number; + pageOptions: number[]; +} + +interface AgnosticBreadcrumb { + text: string; + link: string; +} + +interface FacetResultsData { + products: ProductVariant[]; + categories: Category[]; + facets: Record; + total: number; + perPageOptions: number[]; + itemsPerPage: number; +} + +type ProductVariant = { + __typename?: "ProductVariant"; + id: Scalars["Int"]; + key?: Maybe; + sku?: Maybe; + prices?: Maybe>; + price?: Maybe; + images: Array; + assets: Array; + availability?: Maybe; + attributesRaw: Array; + attributes: ProductType; + attributeList: Array; +} +``` + +## Configuration + +::: warning +Configuration can be changed only for the Enterprise version of this package. +::: + +Faceting configuration can be modified to change available sorting options, filters, etc. + +If the explicit configuration is not provided, the following defaults will be used: + +```javascript +{ + pageOptions: [ + 20, + 50, + 100 + ], + subcategoriesLimit: 100, + availableFacets: [ + { facet: 'categories.id', type: 'string', option: 'subtree("*")', name: 'category' }, // Don't change the "name" of this facet + { facet: 'variants.attributes.size', type: 'number', option: '', name: 'size' }, + { facet: 'variants.attributes.color.key', type: 'string', option: '', name: 'color' } + ], + sortingOptions: [ + { id: 'latest', name: 'Latest', facet: 'createdAt', direction: 'desc' }, + { id: 'price-up', name: 'Price from low to high', facet: 'price', direction: 'asc' }, + { id: 'price-down', name: 'Price from high to low', facet: 'price', direction: 'desc' }, + { id: 'relevance', name: 'Relevance', facet: 'score', direction: 'desc' }, + ], + filteringStrategy: 'filter' +} +``` + +- `pageOptions` - an array of number of elements displayed per page. +- `subcategoriesLimit` - the maximum number of subcategories displayed for any given category. +- `availableFacets` - an array of filters available to the user. + - `facet` - facet expressions described on [this page](https://docs.commercetools.com/api/projects/products-search#termfacetexpression). + - `type` - `facet` data type. Valid values are `string`, `date`, `time`, `datetime`, `boolean` or `number`. + - `option` - filtering options described on [this page](https://docs.commercetools.com/api/projects/products-search#filters). + - `name` - facet alias described on [this page](https://docs.commercetools.com/api/projects/products-search#alias). `category` alias for the first facet shown above is a constant and shouldn't be changed. +- `sortingOptions` - an array of sorting options available to the user. + - `id` - unique `identifier` for the option. + - `name` - label for the option. + - `facet` - the name of the field to sort by. For more information refer to [this page](https://docs.commercetools.com/api/projects/products-search#sorting). + - `direction` - sorting direction. Valid values are `asc` or `desc`. +- `filteringStrategy` - scope applied to filters. Possible values are `filter`, `query` or `facets`. For more information refer to [this page](https://docs.commercetools.com/api/projects/products-search#filters). + +If the default configuration is modified, two identical copies must be passed to: +- `@vsf-enterprise/ct-faceting/nuxt` module in `nuxt.config.js`. +- `@vsf-enterprise/ct-faceting/server` integration in `middleware.config.js`. + +```javascript +// nuxt.config.js +export default { + buildModules: [ + ['@vsf-enterprise/ct-faceting/nuxt', { + // options + }], + ] +}; + +// middleware.config.js +module.exports = { + integrations: { + ctf: { + location: '@vsf-enterprise/ct-faceting/server', + configuration: { + // options + } + } + } +}; +``` + +## Example + +```js +import { useFacet, facetGetters } from '@vsf-enterprise/commercetools'; + +setup(props, context) { + const { result, search, loading } = useFacet(); + + onSSR(async () => { + await search({ + categorySlug: 'clothing', + sort: 'latest', + itemsPerPage: 10, + term: 'some search query' + }); + }); + + return { + products: computed(() => facetGetters.getProducts(result.value)), + categoryTree: computed(() => facetGetters.getCategoryTree(result.value)), + breadcrumbs: computed(() => facetGetters.getBreadcrumbs(result.value)), + sortBy: computed(() => facetGetters.getSortOptions(result.value)), + facets: computed(() => facetGetters.getGrouped(result.value, ['color', 'size'])), + pagination: computed(() => facetGetters.getPagination(result.value)), + loading + } +} +``` diff --git a/packages/core/docs/composables/use-cart.md b/packages/core/docs/composables/use-cart.md new file mode 100644 index 0000000000..ce0a2b44c1 --- /dev/null +++ b/packages/core/docs/composables/use-cart.md @@ -0,0 +1,42 @@ +# `useCart` + +## When to use it? + +Use `useCart` to: +- fetch current cart +- add/remove/change quantity of cart items +- apply/remove discount coupons +- check if a product is already in the cart + +## How to use it in your project? + +```js +import { useCart } from '{INTEGRATION}' +import { onSSR } from '@vue-storefront/core' + +export default { + setup () { + const { + isInCart, + addToCart, + load: loadCart, + removeFromCart, + clearCart, + updateQuantity, + applyCoupon, + removeCoupon, + loading + } = useCart() + + onSSR(async () => { + await loadCart() + }) + + return { + cart, + loading + } + } +} +``` + diff --git a/packages/core/docs/composables/use-user.md b/packages/core/docs/composables/use-user.md new file mode 100644 index 0000000000..04fb3d30ec --- /dev/null +++ b/packages/core/docs/composables/use-user.md @@ -0,0 +1,44 @@ +# `useUser` + +## When to use it? + +Use `useUser` to: +- manage user authentication +- manage authentication data like email address, login or password. + +If you want to fetch/save other user data you should use the following composables: +- [`useUserBilling`](./use-user-billing.md) +- [`useUserShipping`](./use-user-shipping.md) +- [`useUserOrder`](./use-user-order.md) + +## How to use it in your project? + +```js +import { useUser } from '{INTEGRATION}' +import { onSSR } from '@vue-storefront/core' + +export default { + setup () { + const { + user, + isAuthenticated, + updateUser, + register, + login, + logout, + changePassword, + load, + loading + } = useUser() + + onSSR(async () => { + await load() + }) + + return { + user, + loading + } + } +} +``` diff --git a/packages/core/docs/composables/use-wishlist.md b/packages/core/docs/composables/use-wishlist.md new file mode 100644 index 0000000000..171d2f2f0e --- /dev/null +++ b/packages/core/docs/composables/use-wishlist.md @@ -0,0 +1,37 @@ +# `useWishlist` + +## When to use it? + +Use `useWishlist` to: +- fetch current wishlist +- add/remove/change quantity of wishlist items +- check if a product is already in the wishlist + +## How to use it in your project? + +```js +import { useWishlist } from '{INTEGRATION}' +import { onSSR } from '@vue-storefront/core' + +export default { + setup () { + const { + wishlist, + addToWishlist, + removeFromWishlist, + clearWishlist, + isInWishlist, + load: loadWishlist + } = useWishlist() + + onSSR(async () => { + await loadWishlist() + }) + + return { + wishlist, + loading + } + } +} +``` diff --git a/packages/core/docs/contributing/README.md b/packages/core/docs/contributing/README.md new file mode 100644 index 0000000000..01cee5af0a --- /dev/null +++ b/packages/core/docs/contributing/README.md @@ -0,0 +1,46 @@ +# Contributing + +We are delighted that you want to contribute to Vue Storefront. But first, you need to know that we use some standards to maintain a good community and have an aligned code history. + +## Starting + +To start your contribution, you first can pick an issue from our [GitHub](https://github.com/DivanteLtd/vue-storefront/) that has a label [`bug`](https://github.com/vuestorefront/vue-storefront/issues?q=is%3Aopen+is%3Aissue+label%3Abug), [`feature request`](https://github.com/vuestorefront/vue-storefront/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22), [`docs`](https://github.com/vuestorefront/vue-storefront/issues?q=is%3Aopen+is%3Aissue+label%3Adocs), [`good first issue`](https://github.com/vuestorefront/vue-storefront/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) or you can create your issue alongside your pull request. + +## Coding Rules + +To keep the code base of our repository neat, we apply a set of rules for our contributors. These rules are established by our core maintainers, so you will find an organized code repository no matter where you come. + +## Code Standards + +- `eslint` is king +- Favor micro-library over swiss army knives (`rimraf`, `ncp` vs. `fs-extra`) - Just in case you really need one :) +- Follow the coding naming [style and conventions](api-design-philosophy.html) +- Always provide a [changelog](creating-changelog.html) and update the documentation if needed +- Add tests for your changes, and see if the current tests are no conflicting with the changes you made + +## Commit standards + +To create a commit history line where everyone can understand and follow the core maintainers, use a set of common rules that is simple and easy to use. + +>To make everyone's life easier, the repository is commitizen friendly and provides the npm run-script `commit`. + +- Use the [conventional-changelog](https://github.com/conventional-changelog) pattern +- We have husky commit message hook available +- Always use present tense phrases +- The commit message must have a maximum of 100 characters +- The commit message format must be in the format `$type($scope): $message` + +## Create a pull request + +Being part of an open-source project is one of a kind experience. Where you will learn, improve your skill and help everyone in the community at the same time. + +We have to remember to check our checklist before opening a pull request and make sure that the PR follows the standards so it can be approved faster. + +- The code follow our [code standards](#code-standards) +- The commit line follow our [commit standards](#commit-standards) +- The docs have been updated (if needed) +- The changelog has been updated +- The pull request template is fully filled with information about the PR +- The CLA on the PR is signed by the commits authors +- There are no conflicts on the pull request +- All GitHub actions has passed diff --git a/packages/core/docs/contributing/api-design-philosophy.md b/packages/core/docs/contributing/api-design-philosophy.md new file mode 100644 index 0000000000..2a4cbc7d76 --- /dev/null +++ b/packages/core/docs/contributing/api-design-philosophy.md @@ -0,0 +1,81 @@ +# Vue Storefront API Design Philosophy + +While designing something so complex as Vue Storefront it's vital to set up rules that will guide us when designing APIs. The purpose of those rules is to make sure that problems ale solved in a similar, predictable and consistent to understand way which will highly contribute to the learning curve of the framework itself. + + +## General rules + + +1. We build **simple**, **declarative** and **general-purpose** APIs that are not tied to implementation details or a specific solution to a problem. That way we can ensure that our APIs will remain general-purpose and won't break on updates even if we do heavy changes in the underlying business logic. +2. API Surface should be possibly minimal. If there is a feature that can be achieved with already existing APIs we shouldn't add new ones just to make it simplier. +3. Focus on good defaults and embracing [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) paradigm. Every API should work as it is for most of the use cases and have ability to be configured for other ones. +4. If you introduce a new, commonly used feature (like cache) try to provide a default configuration out of the box and let users customize it so they don't have to configure for the most common use cases, only for custom ones. This approach will dreastically reduce the number of boilerplate code users has to write. +```js +import { ueProduct } from '@vue-storefront/{eCommerce}' +import { cacheManager } from '@vue-storefront/core' + +const { search } = useProduct() + +// composable is handling most common scenario under the hood +search({ id: '123'}) // under the hood adds cache tag `P123` + +// you can always modify the default tags +cacheManager.setTags((tags) => { + tags.push('C123') + return tags +}) +``` +5. APIs should not limit the users. If we can't fulfill all use cases with parameters we should provide extension points so users can do this by themselves. +6. Same code should work on every platform (excluding specific object properties and search params) +7. Follow Domain-Driven Design principles. Try to keep everything related to a specific domain within its composable. +``` +"Separating concerns by files is as effective as separating school friendships by desks. Concerns are “separated” when there is no coupling: changing A wouldn’t break B. Increasing the distance without addressing the coupling only makes it easier to add bugs. +~ Dan Abramov +``` +8. Composables should be independent and rely on each other only if they are from the same group (`useUser` `useUserOrder`). The only exception is `useUser` that has to be used in many other composables. +9. If you introduce a new feature shared across all/many composables (like Logging/cache) users should be able to configure this feature from core/core nuxt module. +```ts +// every function in composables is using logger +const removeFromCart = async (product: CART_ITEM, customQuery?: CustomQuery) => { + Logger.debug('userCart.removeFromCart', { product }) + loading.value = true; + const updatedCart = await factoryParams.removeFromCart( + { + currentCart: cart.value, + product + }, + customQuery + ); + cart.value = updatedCart; + loading.value = false; +}; +``` +```js +// there is only one palce where we're configuring the logger/cache - core +['@vue-storefront/nuxt', { + coreDevelopment: true, + logger: customLogger +}] +``` +10. If a feature is platform-specific and not shared across whole application provide integration through its config/nuxt module. +11. Provide a core interface for every feature, no matter if its paid or not (implementation can be paid, the way of implementing this feature by the user has to be always provided) + +## Composables + +We try to cover each subdomain of the eCommerce domain with a dedicated composable. For example we have a composable for Users Management domain, inventory domain, product catalog domain etc. If you have to add a new feature always think about business domain it correlates to and based on that decide if it should be a new composable or an existing one. + +If composables share the same category/prefix it means that they most likely also share the same context eg. `useUserOrder` `useUserShipping` `useUserBilling` are all subcomposables of `useUser` and their content depends on this composable. + +Each composable has usually 3 pieces: +- main data object (eg `products`) +- supportive data object/s (eg `loading`, `error`) +- search/load function (eg `search`) + +```js +const { search, products, loading, erro } = useProduct() +``` + +### search or load +As a rule of thumb use +- `search` when you have to pass some search parameters (eg. in products search) +- `load` when you just have to load some content based on cookies/local storage etc (eg. cart load) \ No newline at end of file diff --git a/packages/core/docs/contributing/changelog.md b/packages/core/docs/contributing/changelog.md new file mode 100644 index 0000000000..d8af58f1d7 --- /dev/null +++ b/packages/core/docs/contributing/changelog.md @@ -0,0 +1,313 @@ +# Changelog + +## 2.4.1 + +- Fix `@vue-storefront/cache` package ([#6198](https://github.com/vuestorefront/vue-storefront/pull/6198)) - [Filip Sobol](https://github.com/filipsobol) +, +- Revert changes to Webpack configuration and Google font loading ([#6203](https://github.com/vuestorefront/vue-storefront/pull/6203)) - [Filip Sobol](https://github.com/filipsobol) +, +- Fix addToCart request body structure in e2e tests ([#6205](https://github.com/vuestorefront/vue-storefront/pull/6207)) - [Igor Wojciechowski](https://github.com/igorwojciechowski) + + +## 2.4.0 + +- Fix hydration bug on category page ([#5744](https://github.com/vuestorefront/vue-storefront/issues/5744)) - [Adam Pawliński](https://github.com/AdamPawlinski) +, +- Updates Category.vue in nuxt-theme module; enables wishlist-related actions being triggered depending on current wishlist state ([#5756](https://github.com/vuestorefront/vue-storefront/issues/5756)) - [Igor Wojciechowski](https://github.com/igorwojciechowski) +, +- Added customQuery parameter for useUserFactory methods ([#5883](https://github.com/vuestorefront/vue-storefront/pull/5823)) - [vn-vlad](https://github.com/vn-vlad) +, +- Fix locale links in core and commercetools integration ([#5886](https://github.com/vuestorefront/vue-storefront/issues/5886)) - [Baroshem](https://github.com/Baroshem) +, +- cart item qty selector disabled on loading ([#5924](https://github.com/vuestorefront/vue-storefront/pull/5924)) - [vn-vlad](https://github.com/vn-vlad) +, +- Add "useStoreFactory" function and related type definitions ([#5945](https://github.com/vuestorefront/vue-storefront/pull/5945)) - [vn-vlad](https://github.com/vn-vlad) +, +- Add forgot password functionality ([#5968](https://github.com/vuestorefront/vue-storefront/issues/5968)) - [Baroshem](https://github.com/Baroshem) +, +- fix/my account page not loading content pages list ([#5982](https://github.com/vuestorefront/vue-storefront/pull/5986)) - [Łukasz Śliwa](https://github.com/lsliwaradioluz) +, +- Add useSearchFactory implementation ([#6015](https://github.com/vuestorefront/vue-storefront/pull/6015)) - [vn-vlad](https://github.com/andrzejewsky) +, +- Remove `generate` command from `package.json` ([#6035](https://github.com/vuestorefront/vue-storefront/pull/6035)) - [lukaszjedrasik](https://github.com/lukaszjedrasik) +, +- Export `UseFacetFactoryParams` interface, add generics to the `Context` interface, add minor fixes to base theme ([#6061](https://github.com/vuestorefront/vue-storefront/pull/6061)) - [Filip Sobol](https://github.com/filipsobol) +, +- Removed buttons on hero slider on homepage ([#6068](https://github.com/vuestorefront/vue-storefront/pull/6068)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- Removed "Download app" banner form product and homepage ([#6069](https://github.com/vuestorefront/vue-storefront/pull/6069)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- Linked banner grids buttons on homepage ([#6070](https://github.com/vuestorefront/vue-storefront/pull/6070)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- Switch places between state and country inputs in shipping form ([#6071](https://github.com/vuestorefront/vue-storefront/pull/6071)) - [Łukasz Śliwa](https://github.com/lsliwaradioluz) +, +- Implement mobile menu ([#6077](https://github.com/vuestorefront/vue-storefront/pull/6077)) - [lukaszjedrasik](https://github.com/lukaszjedrasik) +, +- Removed hardcoded link to category in SearchResults.vue ([#6081](https://github.com/vuestorefront/vue-storefront/pull/6081)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- change the core logger behavior to match the console ([#6085](https://github.com/vuestorefront/vue-storefront/pull/6085)) - [bloodf](https://github.com/bloodf) +, +- Cache-control headers for home, product and category page ([#6093](https://github.com/vuestorefront/vue-storefront/pull/6093)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) +, +- Add `checkout.js` middleware ([#6121](https://github.com/vuestorefront/vue-storefront/pull/6121)) - [Filip Sobol](https://github.com/filipsobol) +, +- Updates form validation scheme for street, number and city in the checkout and profile editing pages ([#6122](https://github.com/vuestorefront/vue-storefront/pull/6122)) - [Heitor Ramon Ribeiro](https://github.com/bloodf) +, +- Fixed Nuxt-CLI spamming babel warning due plugin configuration ([#6123](https://github.com/vuestorefront/vue-storefront/pull/6123)) - [Heitor Ramon Ribeiro](https://github.com/bloodf) +, +- [BREAKING] updated the removeCoupon interface to match the applyCoupon ([#6126](https://github.com/vuestorefront/vue-storefront/pull/6126)) - [Heitor Ramon Ribeiro](https://github.com/bloodf) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +the useCart composable method removeCoupon was using this call signature: ({ coupon: COUPON, customQuery?: CustomQuery }) | the method signature was changed to: ({ couponCode: string, customQuery?: CustomQuery }) | on each removeCoupon composable usage need to change the "coupon" to "couponCode" | composables, +- Add new getter for orders total and change return value of searchOrders ([#6132](https://github.com/vuestorefront/vue-storefront/issues/5968)) - [Baroshem](https://github.com/Baroshem) +, +- Removed the beta tag from the Shopify integration ([#6143](https://github.com/vuestorefront/vue-storefront/pull/6143)) - [Heitor Ramon Ribeiro](https://github.com/bloodf) +, +- Fixed MyAccount page rendering ([#6171](https://github.com/vuestorefront/vue-storefront/pull/6172)) - [Łukasz Jędrasik](https://github.com/lukaszjedrasik) + + +## 2.3.3 + +- Add args parameter to custom queries ([#5854](https://github.com/vuestorefront/vue-storefront/issues/5854)) - [andrzejewsky](https://github.com/andrzejewsky) +, +- Make useUser `login` parameters optional ([#5868](https://github.com/vuestorefront/vue-storefront/pull/5868)) - [Filip Sobol](https://github.com/filipsobol) + + +## 2.3.0 + +- update Storefront UI version ([#5437](https://github.com/vuestorefront/vue-storefront/pull/5437/files)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- [BREAKING] update Storefront UI version ([#5691](https://github.com/vuestorefront/vue-storefront/issues/5691)) - [Justyna Gieracka](https://github.com/justyna-13) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +toggleCategoryGridView | changeToCategoryGridView, changeToCategoryListView | toggleCategoryGridView has been divided into two functions: changeToCategoryGridView and changeToCategoryListView | /composables/useUiState.ts, +- [BREAKING] mocked results for search ([#5709](https://github.com/vuestorefront/vue-storefront/pull/5709/files)) - [Justyna Gieracka](https://github.com/justyna-13) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +term | phrase | term is changed to phrase | acomposables/useUiHelpers/index.ts , +- added SfLoader to VsfShippingProvider to correctly handle loading state ([#5725](https://github.com/vuestorefront/vue-storefront/issues/5725)) - [Baroshem](https://github.com/baroshem) +, +- Fix hydration bug on category page ([#5744](https://github.com/vuestorefront/vue-storefront/issues/5744)) - [Adam Pawliński](https://github.com/AdamPawlinski) +, +- customQuery support for user billing/shipping ([#5746](https://github.com/vuestorefront/vue-storefront/pull/5781)) - [vn-vlad](https://github.com/vn-vlad) +, +- [BREAKING] Pass integration configuration to 'extendApp' ([#5774](https://github.com/vuestorefront/vue-storefront/pull/5774)) - [Filip Sobol](https://github.com/filipsobol) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +`extendApp` accepted only Express.js app as a parameter: `extendApp(app)` | `extendApp` accepts object containing `app` and `configuration` properties: `extendApp({ app, configuration })` | Allow access to integration configuration when extending application. | @vue-storefront/middleware, +- added missing class for text button ([#5827](https://github.com/vuestorefront/vue-storefront/issues/5827)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- add _key to enhanceProduct ([#5829](https://github.com/vuestorefront/vue-storefront/issues/5829)) - [Baroshem](https://github.com/baroshem) +, +- add functionality to search products by ids ([#5847](https://github.com/vuestorefront/vue-storefront/issues/5847)) - [Baroshem](https://github.com/baroshem) + + +## 2.3.0-rc.3 + +- Fix VsfShippingProvider to correctly show errors related to no shipping methods available for certain country ([#5463](https://github.com/vuestorefront/vue-storefront/issues/5463)) - [Baroshem](https://github.com/Baroshem) +, +- [BREAKING] Fix infinite loading for login and register by implementing error handling in core module and in LoginModal ([#5508](https://github.com/vuestorefront/vue-storefront/issues/5508)) - [Baroshem](https://github.com/Baroshem) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +There was no error handling and thats why the infinite loading was appearing | Errors are handled immediately | add try/catch to middleware, set more appriopriate value to err.value in useUserFactory, and implement error handling in LoginModal | core/middleware, LoginModal, +- revert change https://github.com/vuestorefront/vue-storefront/pull/5593 as it was breaking the mobile view after latest release ([#5514_1](https://github.com/vuestorefront/vue-storefront/issues/5514)) - [Baroshem](https://github.com/Baroshem) +, +- removed helpers/filters from @vue-storefront/nuxt-theme and @vue-storefront/commercetools-theme because they are useless now ([#5620](https://github.com/vuestorefront/vue-storefront/issues/5620)) - [Kacper Małkowski](https://github.com/porithe/) +, +- add error handling for GraphQL queries with complexity over limit ([#5692](https://github.com/vuestorefront/vue-storefront/issues/5692)) - [Baroshem](https://github.com/Baroshem) +, +- extend #5692 thrown error to fix also this issue ([#5693](https://github.com/vuestorefront/vue-storefront/issues/5693)) - [Baroshem](https://github.com/Baroshem) +, +- Add ability to access express instance in the middleware extensions ([#5714](https://github.com/vuestorefront/vue-storefront/pull/5714)) - [andrzejewsky](https://github.com/andrzejewsky) + + +## 2.3.0-rc.2 + +- added MegaMenu to theme ([#5267](https://github.com/vuestorefront/vue-storefront/issues/5267)) - [Łukasz Jędrasik](https://github.com/lukaszjedrasik) +, +- [BREAKING] Implementation of api middleware ([#5361](https://github.com/vuestorefront/vue-storefront/pull/5361)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +apiClientFactory | apiClientFactory | Api client factory is being called only on our middleware. It is no longer supported in the borwser. For client-side interaction please use proxied version | core, +- added useUiNotification composable ([#5363](https://github.com/vuestorefront/vue-storefront/issues/5363)) - [Łukasz Jędrasik](https://github.com/lukaszjedrasik) +, +- Moved dependencies from integration themes to nuxt-theme-module ([#5404](https://github.com/vuestorefront/vue-storefront/issues/5404)) - [Łukasz Jędrasik](https://github.com/lukaszjedrasik) +, +- [BREAKING] New part of checkout - shipping details, inside core and commercetools ([#5419](https://github.com/vuestorefront/vue-storefront/pull/5552)) - [Fifciu](https://github.com/Fifciu) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +Using checkoutGetters | Removed checkoutGetters | Accesing checkout-related properties directly | core, +CartPreview.vue uses checkoutGetters | CartPreview.vue is implemented per integration as we do not use getters for checkout-related stuff | We have to implement CartPreview component per integration | nuxt-theme-module, +- [BREAKING] Added `is-authenticated` middleware to protect user profile routes from guest access. By default it emits an error message asking to implement vendor-specific validation. ([#5442_1](https://github.com/vuestorefront/vue-storefront/pull/5442)) - [Filip Sobol](https://github.com/filipsobol) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +- | [Link](https://docs.vuestorefront.io/v2/integrate/integration-guide.html#creating-a-middleware) | Create new middleware that checks if customer is logged in, and if not, redirects to homepage. | middleware/is-authenticated.js, +- Updated the default logger to use matching console log levels, like debug, info, warn, error. ([#5442_2](https://github.com/vuestorefront/vue-storefront/pull/5442)) - [Filip Sobol](https://github.com/filipsobol) +, +- Improvements for api middleware ([#5500](https://github.com/vuestorefront/vue-storefront/pull/5500)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) +, +- Categories loader overlaping footer fixed ([#5507](https://github.com/vuestorefront/vue-storefront/issues/5507)) - [Justyna Gieracka](https://github.com/justyna-13) +, +- [BREAKING] Unify case of theme's directories ([#5511](https://github.com/vuestorefront/vue-storefront/issues/5511)) - [Fifciu](https://github.com/Fifciu) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +pages/Checkout.vue imported components from ~/components/checkout | pages/Checkout.vue imports components from ~/components/Checkout | Nested directories should be in PascalCase. Remember to update imports | nuxt-theme-module, +pages/Checkout.vue imported components from ~/components/checkout | pages/Checkout.vue imports components from ~/components/Checkout | Nested directories should be in PascalCase. Remember to update imports | commercetools-theme, +- removed overflow-x: hidden from html tag ([#5514](https://github.com/vuestorefront/vue-storefront/issues/5514)) - [Przemysław Rakowski](https://github.com/RakowskiPrzemyslaw) +, +- [BREAKING] Update Cache library ([#5524](https://github.com/vuestorefront/vue-storefront/pull/5524)) - [Filip Sobol](https://github.com/filipsobol) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +- | - | Please see "Advanced > SSR Cache" and "Build integration > Cache drive" pages in the documentation for more information. | @vue-storefront/cache, +- Add typings for middleware ([#5530](https://github.com/vuestorefront/vue-storefront/pull/5530)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) +, +- [BREAKING] New part of checkout - useBillingFactory, inside core ([#5552](https://github.com/vuestorefront/vue-storefront/pull/5552)) - [Fifciu](https://github.com/Fifciu) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +Integrations implement useCheckout | Integrations implement useBilling | New factories dedicated for the checkout | core, +- Added 'CartPreview.vue' components to 'components/Checkout' directory asking for integration-specific implementation. ([#5561](https://github.com/vuestorefront/vue-storefront/pull/5561)) - [Filip Sobol](https://github.com/filipsobol) +, +- [BREAKING] Quick search ([#5566](https://github.com/vuestorefront/vue-storefront/issues/5566)) - [Justyna Gieracka](https://github.com/justyna-13) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +{ changeSearchTerm } = useUiHelpers() | { setTermForUrl } = useUiHelpers(); | Changed changeSearchTerm name to setTermForUrl | useUiHelpers/index.ts, + | { getSearchTermFromUrl } = useUiHelpers(); | Created new function | useUiHelpers/index.ts, + | SearchResults.vue | Added new 'SearchResults.vue' component | @vue-storefront/nuxt-theme, +AppHeader.vue | AppHeader.vue | Modified 'AppHeader.vue' to add use new 'SearchResults.vue' component | @vue-storefront/nuxt-theme, +- [BREAKING] Implementation of api middleware ([#5577](https://github.com/vuestorefront/vue-storefront/pull/5577)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +customQuery was used as a function | customQuery is a key-value object | The key is a query name, value is the name of a new query function, defined in the middleware config | core, +- [BREAKING] Add 'useMakeOrder' and rename composable 'useUserOrders' to 'useUserOrder' ([#5584](https://github.com/vuestorefront/vue-storefront/pull/5584)) - [Filip Sobol](https://github.com/filipsobol) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +useUserOrders | useUserOrder | Renamed composable 'useUserOrders' to 'useUserOrder'. | core and commercetools, +'placeOrder' from 'useCheckout' composable was used to place new orders. | Added new 'useMakeOrder' composable and used it's 'make' function to place new orders. | Use new 'useMakeOrder' composable to place orders. | core and commercetools, +- [BREAKING] New Payment API for Checkout ([#5587](https://github.com/vuestorefront/vue-storefront/pull/5587)) - [Fifciu](https://github.com/Fifciu) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +Dedicated composable for whole checkout | Dedicated composable for Shipping, Billing and Provider components | undefined | core, + +- Fix subtotal prices displayed in CartSidebar.vue ([#5932](https://github.com/vuestorefront/vue-storefront/pull/5932)) - [Filip Sobol](https://github.com/filipsobol) + +- [BREAKING] fix naming convention for isOnCart and isOnWishlist to isInCart and isInWishlist ([#5592](https://github.com/vuestorefront/vue-storefront/issues/5592)) - [Jakub Andrzejewski](https://github.com/Baroshem) + +| Before | After | Comment | Module +| ------ | ----- | ------ | ------ +variables and methods had names isOnCart and isOnWishlist | variables and methods have names isInCart and isInWishlist | fix naming convention for isOnCart and isOnWishlist to isInCart and isInWishlist | useCart/useWishlist, +- Fix outside click handler for app header search ([#5595](https://github.com/vuestorefront/vue-storefront/pull/5595)) - [Patryk Andrzejewski](https://github.com/andrzejewsky) +, +- save method for useShippingProvider, deprecated field ShippingMethod.description switched to ShippingMethod.localizedDescription, fixed cart getter for counting totals ([#5598](https://github.com/vuestorefront/vue-storefront/pull/5598)) - [Fifciu](https://github.com/Fifciu) +, +- state field in useShippingProvider instead of response, setState method ([#5624](https://github.com/vuestorefront/vue-storefront/pull/5624)) - [Fifciu](https://github.com/Fifciu) +, +- Make "ejs" a production dependency ([#5630](https://github.com/vuestorefront/vue-storefront/pull/5630)) - [Filip Sobol](https://github.com/filipsobol) +, +- Fix for error states reactivity in core factories and example of watching errors in docs ([#5654](https://github.com/vuestorefront/vue-storefront/pull/5654)) - [Fifciu](https://github.com/Fifciu) +, +- Properly parse module options by using "<%= serialize(options) %>" instead of "JSON.parse('<%= JSON.stringify(options) %>')" ([#5664](https://github.com/vuestorefront/vue-storefront/pull/5664)) - [Filip Sobol](https://github.com/filipsobol) +, +- Lazy hydrating sidebar on category's view when idle not when visible, so it works properly with EE useFacet ([#5667](https://github.com/vuestorefront/vue-storefront/pull/5667)) - [Fifciu](https://github.com/Fifciu) + + + +# 2.2.3 +- fix register function from CT useUser composable allows user to log in [#5613](https://github.com/vuestorefront/vue-storefront/issues/5613) + +# 2.2.1 +- fixed `vue-lazy-hydration` dependency in `nuxt-theme-module` and improved typings in Boilerplate ([#5403](https://github.com/DivanteLtd/vue-storefront/issues/5403)) + +## 2.2.0 +- added bottom margin to fix visibility of last footer category ([#5253](https://github.com/DivanteLtd/vue-storefront/issues/5253)) +- [BREAKING] refactored names of many factory methods and composable methods, details in linked PR ([#5299](https://github.com/DivanteLtd/vue-storefront/pull/5299)) +- [BREAKING] changed signatures of factory methods to always 2 arguments, details in linked PR ([#5299](https://github.com/DivanteLtd/vue-storefront/pull/5299)) +- [BREAKING] composables are always returning one field for the response, removed `totalOrders` and `totalProducts`, `useCartFactory` and `useUserFactory` are returning only composables ([#5330](https://github.com/vuestorefront/vue-storefront/pull/5330)) +- [BREAKING] update composables in Boilerplate to match new factories signatures ([#5389](https://github.com/DivanteLtd/vue-storefront/pull/5389)) +- removed `formatPrice` from `useUiHelpers`, replaced by vue18n `$n` function ([#5339](https://github.com/vuestorefront/vue-storefront/pull/5339)) +- updated integration boilerplate to work with refactored names of factories mentioned above ([#5348](https://github.com/DivanteLtd/vue-storefront/pull/5348)) +- change `useUserBillingFactory`, `useUserShippingFactory` and `useWishlistFactory` to return composable, move `setWishlist` inside of composable ([5350](https://github.com/vuestorefront/vue-storefront/pull/5350)) +- optimize loading of fonts and their stylesheets from Google Fonts and introduce lazy hydration to improve performance ([#5326](https://github.com/DivanteLtd/vue-storefront/pull/5326)) +- added missing `i18n` tags ([#5337](https://github.com/vuestorefront/vue-storefront/issues/5337)) +- fix adding to cart button on product page ([#5375](https://github.com/vuestorefront/vue-storefront/pull/5375)) +- typed error ref for each core's factory ([#4956](https://github.com/vuestorefront/vue-storefront/issues/4956)) +- added ID to Loggers in core factories ([#5351](https://github.com/vuestorefront/vue-storefront/issues/5351)) +- script for changelog generation for core ([#5256](https://github.com/DivanteLtd/vue-storefront/issues/5256)) + +## 2.1.1-rc.1 +- updated version of nuxt composition-api + +## 2.1.0-rc.1 +- removed `availableFilters` and `availableSortingOptions` from `useProduct` ([#4856](https://github.com/DivanteLtd/vue-storefront/issues/4856)) +- removed `@import "~@storefront-ui/vue/styles";` from all components, because SFUI variables and mixins are now available globally and imports will drastically increase bundle size ([#5195](https://github.com/DivanteLtd/vue-storefront/issues/5195)) +- added new performance options to `@vue-storefront/nuxt` package ([#5195](https://github.com/DivanteLtd/vue-storefront/issues/5195)) +- open active category and highlight current subcategory on the Category page ([#5244](https://github.com/DivanteLtd/vue-storefront/issues/5244)) +- added missing order getter to get item price ([#5231](https://github.com/DivanteLtd/vue-storefront/issues/5231)) +- changed default logging level to 'warn' and 'error' in development and production mode respectively ([#5304](https://github.com/DivanteLtd/vue-storefront/issues/5304)) +- fixed broken focus in login form ([#5273](https://github.com/DivanteLtd/vue-storefront/issues/5273)) +- fixed select for changing variant on product page ([#5281](https://github.com/DivanteLtd/vue-storefront/issues/5281)) +- fixed checkboxes in filters sidebar are not clickable on mobile ([#5246](https://github.com/DivanteLtd/vue-storefront/pull/5246)) +- fixed no option to close a login modal ([#5243](https://github.com/DivanteLtd/vue-storefront/pull/5243)) +- fixed category page for mobile ([#5238](https://github.com/DivanteLtd/vue-storefront/pull/5238)) +- fixed issue with CSS waterfall ([#5229](https://github.com/DivanteLtd/vue-storefront/pull/5229)) +- added support for HTTP/2 push for JS assets ([#5179](https://github.com/DivanteLtd/vue-storefront/pull/5179)) +- added discounts api getter ([#5154](https://github.com/DivanteLtd/vue-storefront/pull/5154)) +- added context implementation ([#5218](https://github.com/DivanteLtd/vue-storefront/pull/5218)) +- added context typings ([5290](https://github.com/DivanteLtd/vue-storefront/pull/5290)) + +## 2.0.12 + +- fix ssr implementation, transition on client-side ([#5103](https://github.com/DivanteLtd/vue-storefront/pull/5103)) + +## 2.0.11 + +- fixed SSR in useWishlistFactory ([#5076](https://github.com/DivanteLtd/vue-storefront/issues/5076)) +- added `components/MyAccount/BillingAddressForm.vue` and `components/UserBillingAddress.vue` components for vendor-specific address formats ([#5069](https://github.com/DivanteLtd/vue-storefront/issues/5069)) + +## 2.0.10 + +- added `useContent` and `renderContent` ([#4848](https://github.com/DivanteLtd/vue-storefront/issues/4848)) +- added `useUiState` composable instead `ui-state.ts` ([#4876](https://github.com/DivanteLtd/vue-storefront/issues/4876)) +- added `components/MyAccount/ShippingAddressForm.vue` and `components/UserShippingAddress.vue` components for vendor-specific address formats ([#5032](https://github.com/DivanteLtd/vue-storefront/issues/5032)) +- added `getStreetNumber`, `getId`, `getApartmentNumber` and `isDefault` to `shippingGetters` ([#5032](https://github.com/DivanteLtd/vue-storefront/issues/5032)) +- support for customQuery in `loadWishlist`, `removeFromWishlist` and `addToWishlist` from useWishlistFactory ([#5059](https://github.com/DivanteLtd/vue-storefront/issues/5059)) + +## 2.0.9 + +- added `getEmailAddress` getter to `userGetters` ([#4939](https://github.com/DivanteLtd/vue-storefront/pull/4939)) +- added `getTotalReviews` and `getAverageRating` to `productGetters` ([#4958](https://github.com/DivanteLtd/vue-storefront/issues/4958)) +- fix ssr transitions +- updated UseUserShipping & UserShippingGetters interfaces, implemented them in useUserShippingFactory, written & updated tests ([#4841](https://github.com/DivanteLtd/vue-storefront/issues/4841)) +- fix `sharedRef` nullable assigning + +## 2.0.8 + +- renamed `refreshUser` to `load` in `useUser`, user shouldn't be automatically loaded now [#4917](https://github.com/DivanteLtd/vue-storefront/pull/4917) +- implementing logger feature [#4911](https://github.com/DivanteLtd/vue-storefront/pull/4911) +- fixed cart hydration issues [#4942](https://github.com/DivanteLtd/vue-storefront/pull/4942) + +## 2.0.6 + +- renamed useReviews factory to useReview [#4800](https://github.com/DivanteLtd/vue-storefront/pull/4800) +- added useUserBillingFactory and useUserShippingFactory factory [#4809](https://github.com/DivanteLtd/vue-storefront/pull/4809) + +## 2.0.5 + +- added useReviews factory [#4775](https://github.com/DivanteLtd/vue-storefront/pull/4775) +- refactored apiClientFactory [#4777](https://github.com/DivanteLtd/vue-storefront/pull/4777) +- removed useLocale types [#4777](https://github.com/DivanteLtd/vue-storefront/pull/4777) +- created useFacet factory and types [#4853](https://github.com/DivanteLtd/vue-storefront/pull/4853) diff --git a/packages/core/docs/contributing/creating-changelog.md b/packages/core/docs/contributing/creating-changelog.md new file mode 100644 index 0000000000..9a71f0ba45 --- /dev/null +++ b/packages/core/docs/contributing/creating-changelog.md @@ -0,0 +1,51 @@ +# Creating changelog + +To keep our changelog consistent, it is created automatically. +Now you don't need to worry about its structure and order of the information. +There are just a few steps to do in order to create the changelog. + +## Steps + +1. Go to folder `/changelog`, create a `.js` file, and give it the same name as a number of your current issue from GitHub. For example `4523.js` +2. Copy this code below, paste it inside your `.js` file and fill it in with your data + +```js +module.exports = { + description: "description of your changes", + link: "link to your issue", + isBreaking: true or false, //(depends if you applied some breaking changes), + breakingChanges: [ //leave it empty if there aren't any breaking changes + { + module: "affected module", + before: "how it was", + after: "how it is", + comment: "quick migration guide" + } + ], + author: "your name", + linkToGitHubAccount: "link to your github account", +} +``` +3. Save all changes and that's all + + diff --git a/packages/core/docs/contributing/server-side-rendering.md b/packages/core/docs/contributing/server-side-rendering.md new file mode 100644 index 0000000000..95403ddd6c --- /dev/null +++ b/packages/core/docs/contributing/server-side-rendering.md @@ -0,0 +1,76 @@ +# Server-side rendering + +In Vue Storefront we rely on Nuxt.js as a server-side rendering provider and we highly recommend this as well. +However Nuxt.js has everything we need when it comes to SSR, we had to provide a bit more to this implementation. + +First of all. As we still wait for fully integration Vue 3 and Nuxt.js and the current SSR based on +composition-api didn't cover our needs, we had to temporarily sort it out on our own. + +## Configurable SSR + +As this is a temporary workaround, we have provided a configurable mechanism that can provide your own SSR implementation. +It's pretty useful for non-Nuxt.js users - if for some reason you don't want to use Nuxt.js, you can still provide your own SSR. + +The configuration is done by single function called `configureSSR` that takes two fields: + +- `vsfRef` - a special implementation of `ref` that can work on ssr. +- `onSSR` - you may have seen that in our theme, it's the function that can handle async calls, and share the results between server and the client side. It works in the same way as `fetch` from nuxt.js + vue 2 + +::: tip + For now, vsfRef is just ssrRef from `@nuxtjs/composition-api`, and `onSSR` is our implementation with using `onServerPrefetch`. + In the future vsfRef will be replaced by ordinary `ref` and `onSSR` will become `useFetch` from Nuxt.js +::: + + +## Current implementation + +Below is the our current implementation. By default we provide it as a nuxt-module (`@vue-storefront/nuxt`) so if you are Nuxt.js user, you don't have to do anything - this implementation is provided automatically. If you are not using Nuxt.js, try to copy this piece od code and modify to cover your needs. The result code will be still very similar. + +```js +import { configureSSR } from '@vue-storefront/core' +import { ssrRef, getCurrentInstance, onServerPrefetch } from '@nuxtjs/composition-api'; + +const ssrPlugin = () => { + let previousRoute = ''; + + configureSSR({ + vsfRef: ssrRef, + onSSR: (fn) => { + onServerPrefetch(fn); + if (typeof window !== 'undefined') { + const vm = getCurrentInstance(); + if (previousRoute !== vm.$route.fullPath) { + fn(); + } + + previousRoute = vm.$route.fullPath; + } + } + }); +}; + +export default ssrPlugin; +``` + +## Future replacement + +Once Vue 3 and Nuxt.js can talk together, we'll replace this implementation using `configureSSR` so it will be transparent for you. + +## Usage + + +```js +setup () { + const product = vsfRef({}, 'product-key') + + onSSR(async () => { + product.value = await getProduct(); + }); + + return { product } +} +``` + +::: tip + Remember to provide an unique key for `vsfRef`, it's demanded for the correct hydration. +::: diff --git a/packages/core/docs/contributing/themes.md b/packages/core/docs/contributing/themes.md new file mode 100644 index 0000000000..003bb92718 --- /dev/null +++ b/packages/core/docs/contributing/themes.md @@ -0,0 +1,68 @@ +# Working with themes + +For core development purposes we aim to have only one default theme. Thanks to that we have much less code to maintain, can apply changes faster and maintain same quality across every platform themes. + +Core Development Theme and Project Themes are different things. Core development Theme is a base for a project (client) theme that is generated through CLI. CLI concatenates default theme from nuxt module with integration-specific files and outputs a regular Nuxt project containing all of that files. For client projects there is no inheritance mechanism. + +Because of that, it's very important to keep the agnostic APIs in default theme. Clients can do whatever they want in their projects but we should keep agnosticism for maintenance purposes. + +Default theme is located in `packages/core/nuxt-theme-module` folder and recognized as `@vue-storefront/nuxt-theme` + +## Configuration + +To inherit the default theme from your integration theme you need to install a `@vue-storefront/nuxt-theme` [package](https://www.npmjs.com/package/@vue-storefront/nuxt-theme). + +In `nuxt.config.js` within your integration theme, add `@vue-storefront/nuxt-theme` into `buildModules` key, as a second value you can pass options e.g: + + +```json +// nuxt.config.js +{ + buildModules: [ + ['@vue-storefront/nuxt-theme', { + generate: { + replace: { + apiClient: '@vue-storefront/commercetools-api', + composables: '@vue-storefront/commercetools' + } + } + } + ] + ] +} +``` + +From now on you can overwrite the default theme with your own files. + +Default theme is available in `/packages/core/nuxt-theme-module/theme/` folder. In it, you will find imports using `ejs` syntax. This is the reason you should provide a config for `@vue-storefront/nuxt-theme` module. In your theme you should use them when importing integration-specific packages: + + +```ts +// nuxt-theme-module/theme/pages/Product.vue +import { useProduct, useCart, productGetters, useReview, reviewGetters } from '<%= options.generate.replace.composables %>'; +``` + +```ts +// your-theme/theme/pages/Product.vue +import { useProduct, useCart, productGetters, useReview, reviewGetters } from '@vue-storefront/commercetools'; +``` + +## How it works + +Under the hood what this module does is: + +1. Compiling lodash templates from `@vue-storefront/nuxt-theme` to `_theme` folder of your integration. +2. Aliasing components, layouts and pages to `_theme` folder +3. Watching changes in ``@vue-storefront/nuxt-theme` package and rebuilding `theme` folder on each change. + +## Magic comments + +Sometimes you might need to have some code only for core development purposes (mostly in `nuxt.config.js`).In that case youu should wrap this code with these comments: +```js +// @core-development-only-start +core development only stuff +// @core-development-only-end +``` +::: warning +Be cautious with this feature! The more differences will be between the theme you develop and the theme customer sees the more unreliable it could get. +::: \ No newline at end of file diff --git a/packages/core/docs/core/composables.md b/packages/core/docs/core/composables.md new file mode 100644 index 0000000000..3bdc5d704a --- /dev/null +++ b/packages/core/docs/core/composables.md @@ -0,0 +1,44 @@ + +# Composables + +While API Client is a data layer for the application composables are the business logic part based on [Vue.js Composition API](https://vue-composition-api-rfc.netlify.com/). This package contains the following Composition API functions responsible for interacting with specific parts of eCommerce platform logic: +- `useProduct` to fetch, filter and sort products +- `useCategory` to fetch categories +- `useCart` to manage basket +- `useUser` to manage authorization and user profile + - `useUserOrder` (subcomposable to `useUser`) to manage user orders that has already been placed + - `useUserAddresses` (subcomposable to `useUser`) to manage user shipping addresses +- `useCheckout` to manage order processing + +Each of this function works independently and combined they are covering all the logic of your eCommerce platform. + +Every composable is exposing more or less following properties: +- **Main data object** - a single, readonly object that the rest of the composition function interacts with or depends on. For example in `useProduct` it's a `products` object that `search()` function interacts with. +- **Main function that interacts with data object** which usually calls the API and updates the main data object, For example in `useProduct` and `useCategory` it's a `search` method, in `useCart` it's a `load()` method etc. +- **Supportive data objects** which are depending directly on indirectly on the main data object, for example, `loading`, `error` or `isAuthenticated` from `useUser` that is depending on `user` object. + +Let's see an example of how Vue Storefront Composable could look like. This is how you can perform a product search: + + + +At the moment of invoking a `search` method the`loading` property value changes to `true`. Once the api call is done `products` object is populated with the result and `loading` becomes `false` again. + +## Getters +::: tip +Usage of getters is coompletely optional. Use them where it makes sense to you. +::: + +Sometimes its hard to extract certain subproperties from complex data objects like products or categories and display them in the UI in a simple way. This is the reason why we introduced **getters**. Every getter is a simple function you can use to extract certain subproperties from specific objects. Every composable has its corresponding getter functions. + + + +___ + +::: tip Writing backend-agnostic code +In many cases using getters can save you time and contribute to cleaner code but there is one more thing about getters that makes them really useful. They're always returning agnostic data formats. No matter which platform we're using `productGetters.getAttributes()` will always return the data in a same, UI-friendly format. + +Because interfaces for composables and getters are the same for every platform the above code from above example will work exactly the each one of them. Because of that you can use getters to keep your frontend code agnostic regarding eCommerce platform. This approach can be really useful when you're considering a migration to different platform in the near future +::: + + + diff --git a/packages/core/docs/core/getting-started.md b/packages/core/docs/core/getting-started.md new file mode 100644 index 0000000000..d79e6d0af7 --- /dev/null +++ b/packages/core/docs/core/getting-started.md @@ -0,0 +1,51 @@ +# Getting started + +[[toc]] + +---- +To start working with Vue Storefront for {{ $frontmatter.platform }} you have to install two core packages that will integrate your {{ $frontmatter.platform }} instance with Vue Storefront. + +- [**{{ $frontmatter.platform }} API Client**](./api-client) - A data layer of your application that connects directly to {{ $frontmatter.platform }}. It provides a friendly abstraction layer over network calls with some additional overriding capabilities. It can be used as it is in any JavaScript application but doesn't provide much value by itself. API Client is usually used in the combination with Composition Functions package and is not used directly in the UI layer. +- [**{{ $frontmatter.platform }} Composition Functions**](./composables) - A set of **declarative** composition API functions allowing to interact with eCommerce logic in your Vue application that provides additional functionalities like Vue reactivity, SSR support, client-side request caching etc. This is the main integration package and it uses API Client under the hood. + +## Installation +### With Vue Storefront CLI (recommended) + +::: tip Best for new projects +If you're starting a new Vue Storefront project and you're ok with using Nuxt using CLI its the best option for you. +::: + +This is the easiest and fastest way of bootstrapping new Vue Storefront project. With Vue Storefront CLI you can generate preconfigured, working boilerplate shop in one minute! + +```bash +npm i -g @vue-storefront/cli +``` + +```bash +vsf init +``` + +### Nuxt project installation + +First, install the packages: + + + +Once packages are installed you need to add VSF Nuxt module, and integration Nuxt Module in `nuxt.config.js` to the `buildModules` section: + + + +`@vue-storefront/nuxt` - allows to use raw source for listed packages and adds dedicated plugins +`@vue-storefront//nuxt` - installs integration with eCommerce backend + +### Non-Nuxt project installation + +First, install the packages: + + + +Once packages are installed you need to invoke the `setup` method that will configure your {{ $frontmatter.platform }} integration before using any other method from the integration. You can read how to configure it [here](./api-client). + + + +In the next chapters, you will learn in-depth about {{ $frontmatter.platform }} API Client and Composition API functions. diff --git a/packages/core/docs/core/theme/auth-middleware.md b/packages/core/docs/core/theme/auth-middleware.md new file mode 100644 index 0000000000..ed62c713aa --- /dev/null +++ b/packages/core/docs/core/theme/auth-middleware.md @@ -0,0 +1,18 @@ +# Auth Middleware + +The Auth Middleware provides a straightforward way to limit access to certain pages to logged users only. +This middleware relies on integration-specific `useUser` composable that exposes `isAuthenticated` property. + +## Usage + +The only thing you need to do to use the Auth Middleware is importing it in the page `vue` and using as a middleware for that page, like in the example below: + +```js +import auth from '../middleware/auth'; + +export default { + name: 'MyAccount', + middleware: auth, + ... +} +``` diff --git a/packages/core/docs/core/use-cart.md b/packages/core/docs/core/use-cart.md new file mode 100644 index 0000000000..6372df57a7 --- /dev/null +++ b/packages/core/docs/core/use-cart.md @@ -0,0 +1,37 @@ +# Cart + +[[toc]] + +## Features + +`useCart` composition function can be used to: + +* load cart information, +* add, update and remove items to the cart, +* applying and removing coupons, +* checking if product is already added to the cart. + +## API + +`useCart` contains following properties: + +- `cart` - a main data object that contains cart structure in platform specific structure + +- `load` - function required to fetch cart from a server or create brand new if it doesn't exist. +- `addToCart` - function for adding products to the cart +- `updateQuantity` - function for updating quantity of a product that is already in the cart +- `removeFromCart` - function for removing a product that currently is in the cart +- `isInCart` - function for checking if a product is currently in the cart +- `clearCart` - function for removing all items currently stored in cart +- `coupon` - reactive data object containing coupon details +- `applyCoupon` - function for applying coupon to cart +- `removeCoupon` - function for removing coupon applied to cart +- `loading` - a reactive object containing information about loading state of the cart + +## Usage + +Cart composable is a service designed for supporting a single cart and access it everywhere with ease. +Initialization of a cart requires using `load()` when calling `useCart()` for the first time. Keep in mind that upon +execution of `load`, the cart will get loaded only once, if a wishlist has already been loaded, nothing happens. + + diff --git a/packages/core/docs/core/use-product.md b/packages/core/docs/core/use-product.md new file mode 100644 index 0000000000..03754fefc5 --- /dev/null +++ b/packages/core/docs/core/use-product.md @@ -0,0 +1,20 @@ +# Products + +* [Features](#features) +* [API](#api) + +## Features + +`useProduct` composition API function is responsible for fetching a list of products. + +## API + +`useProduct` contains following properties: + +- `search` - a main querying function that is used to query products from eCommerce platform and populate the `products` object with the result. Every time you invoke this function API request is made. This method accepts a single `params` object, + + + +- `products` - a main data object that contains an array of products fetched by `search` method, + +- `loading` - a reactive object containing information about loading state of your `search` method. diff --git a/packages/core/docs/core/use-wishlist.md b/packages/core/docs/core/use-wishlist.md new file mode 100644 index 0000000000..d0f18fd6a4 --- /dev/null +++ b/packages/core/docs/core/use-wishlist.md @@ -0,0 +1,24 @@ +# Wishlist + +`useWishlist` composition API function is responsible, as its name suggests, for interactions with wishlist in your eCommerce. This function returns following values: + +- `wishlist` - a main data object that contains wishlist structure in platform specific structure + + + +- `load` - function required to fetch wishlist from a server or create brand new if it doesn't exist. +- `addToWishlist` - function for adding products to the wishlist +- `removeFromWishlist` - function for removing a product that currently is in the wishlist +- `isInWishlist` - function for checking if a product is currently in the wishlist +- `clearWishlist` - function for removing all items currently stored in wishlist +- `loading` - a reactive object containing information about loading state of the wishlist + +## Wishlist initialization + +Wishlist composable is a service designed for supporting a single wishlist and access it everywhere with ease. +Initialization of a wishlist requires using `load()` when calling `useWishlist()` for the first time. Keep in mind that upon +execution of `load`, the wishlist will get loaded only once, if a wishlist has already been loaded, nothing happens. + + + + diff --git a/packages/core/docs/core/utils/server-side-rendering.md b/packages/core/docs/core/utils/server-side-rendering.md new file mode 100644 index 0000000000..2c189b2ce4 --- /dev/null +++ b/packages/core/docs/core/utils/server-side-rendering.md @@ -0,0 +1,103 @@ +# Server-side rendering + +Sometimes our composables needs to load some data and share that state between client and the server side. +We provide our custom mechanism to create server-side rendered pages used with composables functions. + +## Usage + +In the most cases, the composable functions provide SSR support (it is always transparent for developer). +To build that composable you have to create a new composable-factory with using `useSSR` function. + +```js +import { useSSR } from '@vue-storefront/core'; + +const useCategory = (cacheId) => { + const { initialState, saveToInitialState} = useSSR(cacheId); + const categories = ref(initialState || []); + + const search = async (params) => { + categories.value = await factoryParams.categorySearch(params); + saveToInitialState(categories.value ) + }; + + return { + search, + categories: computed(() => categories.value) + }; +}; +``` + +The `useSSR` returns `initialState` field that keeps shared state between client-side and server-side under the key you provided as an argument to the created composable (`cacheId`) and `saveToInitialState` function to populate loaded data into the cache. Now created factory supports SSR. + +## Your own SSR implementation + +It's possible to create your own implementation of shared-state. In that case you have to provide implementation of the `useSSR`. + +```js +import { configureSSR } from '@vue-storefront/core'; + +configureSSR({ + useSSR: (id: string) => { + const initialState = {}; // persisted cache + + const saveToInitialState = (value: any) => { + // saving loaded data + }; + + return { + initialState, + saveToInitialState + }; + } +}); +``` + +## Temporary solution + +By default we do support SSR and shared-state using nuxt features. Furthermore, we can't use multiple async calls in the setup function that depend on each other (eg. loading products by id of category that you have to fetch first). To solve this problem we provide a temporary solution - `onSSR`. + +in your theme: + +```js +import { useExample } from 'your/integration/composables'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup() { + const { search: searchOne, exampleOne } = useExample('examples-page1'); + const { search: searchTwo, exampleTwo } = useExample('examples-page2'); + + onSSR(async () => { + await searchOne(); + await searchTwo(exampleOne.id); + }) + + return { + examples + } + } +} + +``` + +In the future, Vue 3 will provide an async setup and `onSSR` won't be needed anymore: + +```js +import { useExample } from 'your/integration/composables'; +import { onSSR } from '@vue-storefront/core'; + +export default { + async setup() { + const { search: searchOne, exampleOne } = useExample('examples-page1'); + const { search: searchTwo, exampleTwo } = useExample('examples-page2'); + + await searchOne(); + await searchTwo(exampleOne.id); + + return { + examples + } + } +} + +``` diff --git a/packages/core/docs/enterprise/feature-list.md b/packages/core/docs/enterprise/feature-list.md new file mode 100644 index 0000000000..c3b670c07c --- /dev/null +++ b/packages/core/docs/enterprise/feature-list.md @@ -0,0 +1,392 @@ +--- +sidebar: false +--- +# Feature list + +The following document lists the most important (but not all!) features of [Vue Storefront Cloud](https://www.vuestorefront.io/enterprise) offering. + + +[[toc]] + +## Key Platform Features + +**1. No UI/functional limitations** + +Vue Storefront is just a framework and you can build your UI in any way you want. anything that is possible with Nuxt is also possible with Vue Storefront + +**2. Backend-agnostic** + +We have the same interfaces for the same types of vendors (eCommerce/Search/CMS/...). Because of that you can keep most of your frontend uanware of the backend services. If you decide to change any part of your eCommerce stack in the future your frontend could remain untouched. + +**3. Focus on performance** + +We're providing a bulletproof architecture, great default, best practices and tools to make sure your app will perform smooth. Majority of business logic is handled on the server side to make sure client bundles are small. + +**4. No vendor lock-in** + +You own your code so its up to you what services you will use for eCommerce or CMS. It's up to you where and how you will host it and deploy. Vue Storefront will never lock you into its services and you always. + +**5. Focus on quick time to market** + +We've built Vue Storefront to make eCommerce frontends development process as enjoyable and smooth as possible. Just like Vue and Nuxt themselves we're focusing on getting things done, providing good defaults and giving our users integrations and extensions they they can use to speed up their development process. + +**6. All-in-one modular solution.** + +Vue Storefront is a set of tools that can work together (or independently) to support you in all areas of development. We provide out of the box integrations, framework for connecting all of them, library of UI components and a cloud hosting service. + +You can let us take care of all aspects of your eCommerce presence or replace any of those parts with your own, custom services. + +**7. Huge and vibrant community** + +Vue Storefront is the only truly Open Source eCommerce frontend framework with an active and vibrant community of thousands developers all around the world. If you choose Vue Storefront You're never left on your own. + + +## Integrations + +### eCommerce +- Commercetools (standard eCommerce scope [listed below](#ecommerce-integration-theme)) +### Content Management System (CMS) +- Storyblok (standard CMS scope [listed below](#content-management-system)) +- Contentstack (standard CMS scope [listed below](#content-management-system)) +- Amplience (standard CMS scope [listed below](#content-management-system)) +### Payments +- Checkout.com + 1. Credit Card, Klarna, Paypal and Sofort payment methods + 2. Saving Card and paying with a stored card if not-guest + 3. Possible to request CVC/CVV during saved card payment + 4. Loading available payment methods for the provided cart + 5. Redirect to finalize payment support (e.g. 3ds for cards redirect for PayPal or Sofort) + 6. Possible to provide both success and error URL (redirect after a redirect) + 7. Choosing whether to save a payment method or not + 8. Customizing Frames (credit card sdk from CKO) + 9. Customizing Klarna + 10. Removing saved payment instruments + 11. Support for channels + 12. Possible to provide a custom reference for the payment +- Adyen + 1. Payments with Credit Card supported by Frames' SDK with 3DS Support + 2. Saving Credit Cards, paying with them, and removing them from storage (you have to create own UI here) + 3. Possible to force providing CVV/CVC each time while paying with Saved Card + 4. Payments with PayPal + 5. Payments with Sofort + 6. Payments with Klarna Pay Later +### Loyalty & Discounts +- Talon.one + 1. Applying discount assigned to a single campaign by discount code + 2. Applying discount assigned to a single campaign by defined cart rules + 3. Discounts tracking in Talon.one panel +## eCommerce integration and theme + +### Product Tile + + + +1. Display product price +2. Display product name +3. Display average rating +4. Display number of reviews +5. Display product catalog discounts +6. Quick Add to Cart (default variant) +7. Quick Add to Wishlist + +### Product Listing Page + + + + +1. Products + 1. Display a list of products for a specific category + 2. Display number of total products found +2. Category + 1. Display a list of categories (same level and one level deeper) + 2. Display Breadcrumbs +3. Pagination + 1. Display pagination + 2. Change the number of items per page +4. Filtering + 1. Filter products by attributes and price (faceting) + 2. Filtering is reflected in the URL +5. Sorting + 1. Sort by price + 2. Sort by newly added + 3. Sorting is reflected in the URL +6. UI + 1. Horizontal/Vertical product list switch + +### Product Details Page + + + +1. Product + 1. Display master variant + 2. Display product name + 3. Display product price + 4. Display discounted price + 5. Display product description +2. Product Variants + 1. Display product variants (by default color and size) + 2. Change variant + 3. Variant change is reflected in the URL +3. Product Gallery + 1. Show all product images as thumbnails + 2. Show one big product image + 3. Change big product image with one of thumbnails +4. Reviews + 1. Display average rating + 2. Display total number of reviews + 3. Add review as logged in user (no UI) + 4. Add review as logged out user (no UI) +5. Add to cart + 1. Add product variant to cart + 2. Change quantity +6. Suggested products + 1. Display a list of suggested products from the same category +7. Other + 1. Add to Wishlist + +### Cart + + + +- Product + - Display product name + - Display product price + - Display product discounts + - Add product to cart + - Remove product from cart + - Change product Quantity +- Summary + - Show total price + + +### Wishlist + + + +- Product + - Display product name + - Display product price + - Display product discounts + - Add product to wishlist + - Remove product from wishlist + +### Checkout + + + +- Order types + - Logged in + - Guest +- Shipping + - Choose from saved shipping addresses (logged in) + - Add new shipping address (logged in) + - Change default shipping address (logged in) + - Provide Shipping Details + - Fill in First and Last Name + - Fill in Street name and apartment number + - Fill in City and Zip Code +- Billing + - Choose from saved billing addresses (logged in) + - Add new billing address (logged in) + - Change default billing address (logged in) + - Provide Billing Details + - Fill in First and Last Name + - Fill in Street name and apartment number + - Fill in City and Zip Code +- Payment + - Choose Payment Method +- Coupons + - Add discount coupon +- Order summary + - Display total price + - Display shipping price + - Display Tax +- Thank you page + - Display order number + +### User Authentication + + + +- Log in with email and password +- Create a new account + +### User Profile + + + +- My Profile (Personal Details) + - Change First Name + - Change Last Name + - Change email + - Change password by using the old one and a new one +- Shipping Details + - Delete Shipping Address + - Change Shipping Address + - Set Shipping Address as default +- Billing Details + - Delete Billing Address + - Change Billing Address + - Set Billing Address as default +- Order History + - See Order Status + - See Order Id + - See Order Price + - Download Orders +- Log Out + +### Error page + + + +- Error Page for 404/server errors + +## Content Management System +1. Theme + 1. Home Page Managed by the CMS + 2. Header and navigation managed by CMS + 3. Footer Managed by CMS + 4. Generic Static Page managed by CMS +2. Features + 1. Display a single document + 2. Display multiple documents + 3. Transform output to components + 4. Content Scheduling + 5. Internationalization + 6. Content Live Preview + 7. Add new static page from CMS Admin Panel without deployment + 8. Connection to eCommerce platform from within CMS Admin Panel + 9. Custom Components support +3. Out of the box components available in the CMS + - Accordion + - Alert + - Banner + - Call to action + - Carousel + - Editorial (renders HTML) + - Footer + - Gallery + - Grid + - Heading + - Hero + - Link + - List + - Navigation list + - Product Card + - Product Slider + - Steps + - Tabs +## Internationalization +1. Frontend translations based on `nuxt-i18n` + 1. String translations based on keys + 2. Currencies translation + 3. Automatic routes generation and custom paths + 4. Search Engine Optimization + 5. Lazy-loading of translation messages + 6. Redirection based on auto-detected language + 7. Different domain names for different languages +2. eCommerce platform internationalization connected with `nuxt-i18n` +3. CMS platform internationalization connected with `nuxt-i18n` + +## Progressive Web App +1. Automatic Web Manifest generation +2. Automatic icons scaling +3. Static Assets Caching (partial offline support) +4. Add to Home Screen + +## Performance +- Two output modes + - Modern mode without polyfills for modern browsers (20-30% smaller) + - Legacy mode for old browsers (minority) +- Server-Side Rendering +- Server-Side Tag-based Cache +- Majority of JS code is executed on the server side to keep small bundles +- Automatic route-based code splitting (incl. CSS) +- Static assets client-side caching via Service Worker +- CSS Purging +- Preconnects to CDNs/Google Fonts +- HTTP/2 Push +- Lazy Hydration of below-the-fold elements + +## Middleware +- Server-side Express API middleware + +## Cloud + +**1. Hosting** + +Our tech team will manage your instance backups and the infrastructure based on Google Cloud for optimal performance. + +**2. Automatic Backups** + +We perform daily backups. Each user has direct access to the backup drive via storefrontcloud-cli tool or via the support line. + +**3. Multiregions** + +Our shop can be deploy at any region of the World, possibly close to you customers. + +**4. 24/7 aviability monitoring** + +Our Cloud team is using internal monitoring systems to provide you with SLA. Moreover, users get access to the New Relic monitoring tool (included in the price, on demand) for monitoring applications and performance on their own. + +**5. Access and errors log** + +We can insert access and error logs in any systems that you prefer. + +**6. Gitlab / Github source-code management** + +Part of the service is Git repository hosting and Git-based deployment process. We’re offering this to our clients with private Gitlab accounts, available here: https://code.storefrontcloud.io + +**7. Automatic deployments** + +Each deployment is managed via storefrontcloud-cli and the Gitlab repository. We’re using fully automatic deployment flow with the evidence of changes (gitlog) and easy restore process in case of failed deploy. + +**8. As many environments as you need** + +You can use our CI/CD pipeline to build environment per each PR. + +**9. Staging enviroment** + +Vue Storefront Cloud clients are getting access to the second (and possible third, fourth, etc.) environments that are not used for production. These environments are still synchronized with the backend and can be used for development and testing purposes, however, are not covered by full SLA and are not using CDN (Content Delivery Network) and other optimization features that are enabled for the the production environment by default. + +**10. Production ready setup** + +The infrastructure is optimized for performance and safety. After signing up, you will get access to a production-grade environment with a deployed default version of Vue Storefront. All your team/agency will need to do is customize it, and apply custom features and a layout. + +**11. CLI access to env (via kubect)** + +The platform is accessible and can be managed via the storefrontcloud-cli tool. It’s a dedicated tool to manage the Kubernetes cluster and Vue Storefront deployments, backups, and logs. Please find more information in our knowledge base, available here: https://help.storefrontcloud.io + +**12. All of the Commercetools OOTB Features ready on enviroment** + +By default, Vue Storefront Cloud is integrated with commercetools. This service is included in the price. All you need are commercetools API access details to get the products, categories, and user accounts in sync with your instance. + +**13. Access via API** + +All Vue Storefront Cloud config options are available via API + +**14. SLA & Support** + +The Storefront Cloud team will guarantee 99,8% SLA for the infrastructure and Vue Storefront base app (modifications and custom integrations created by merchant/agency are excluded). Our clients can use our dedicated Slack support channel to contact the team and check the tickets’ statuses. + +**15. API Integratios with 3rd party services OOTB** + +The Vue Storefront list of integrations is growing every day. It offers a range of standard integrations and an API that users can use to sync other applications with their own backend systems. + +**16. Customer Dashboard** + +The Vue Storefront Cloud has a customer dasboard with very usefull features, like: traffic, instances, environments + +**17. CDN** + +**18. VSF sandboxes** + +We can run sandbox/demo instances in a few mintues, with one click. + +**19. GEO-Fancing** + +We have geo and ddos protection. You can decide, in which country your store is available. + +**20. Rich Content** + +With token access protection, VSF offer selling virtual content with user access managment. \ No newline at end of file diff --git a/packages/core/docs/general/enterprise.md b/packages/core/docs/general/enterprise.md new file mode 100644 index 0000000000..0900c37755 --- /dev/null +++ b/packages/core/docs/general/enterprise.md @@ -0,0 +1,35 @@ +# What is Vue Storefront Enterprise + +> Vue Storefront Enterprise is currently available only for commercetools + +Vue Storefront Enterprise is a commercial offering from the Vue Storefront core team built on top of the Open Source product. Its goal is to give you all the tools you need to launch your shop and provide you with ready-to-use integrations that will reduce the development time and cost of your project. + +## Differences between Open Source and Enterprise versions + +In Enterprise Edition you're getting everything that's in our Open Source. On top of that we also provide: + +- access to [Vue Storefront Cloud](https://www.vuestorefront.io/cloud); +- additional [integrations](./integrations/) with third-party services; +- extended integration with eCommerce platform with advanced features. + +Everything with badge in the documentation is only available for our Enterprise customers. + +You can learn more about our commercial offering on the [Enterprise](https://www.vuestorefront.io/enterprise) page. + +## How to get access to Vue Storefront Enterprise + +If you'd like to use Vue Storefront Enterprise in your project, please [contact our Sales team](https://www.vuestorefront.io/contact/sales). + +## How to use Vue Storefront Enterprise + +Enterprise packages within `@vsf-enterprise` scope are part of our private registry. To make use of them, create a `.npmrc` file in the root of your project with the following content: + +``` +@vsf-enterprise:registry=https://registrynpm.storefrontcloud.io +``` + +Then log into your account with your Vue Storefront Enterprise credentials: + +```bash +npm adduser --registry https://registrynpm.storefrontcloud.io +``` diff --git a/packages/core/docs/general/installation.md b/packages/core/docs/general/installation.md new file mode 100644 index 0000000000..98ad830707 --- /dev/null +++ b/packages/core/docs/general/installation.md @@ -0,0 +1,79 @@ +# Installation + +## Prerequisites + +Before proceeding, make sure you have [Node 10+](https://nodejs.org/en/) and [Yarn 1](https://classic.yarnpkg.com/lang/en/) installed. + +## Using Vue Storefront CLI + +The easiest way to get started with Vue Storefront is to set up your project using our CLI. You can run it using the `npx` command: + +```bash +# Run Vue Storefront CLI +npx @vue-storefront/cli init +``` +Enter the name of the project and select the backend platform you wish to use. + +Once selected, the CLI creates the project files in the directory matching your project name. The only thing left is to go to this directory in the terminal and install the dependencies: + +```bash +# Go to project folder +cd + +# Install required dependencies +yarn install +``` + +Now the project is ready. To start the application in development mode, use the `yarn dev` command: + +```bash +# Start the project in development mode +yarn dev +``` + +You can read more about available commands and environments on [commands](https://nuxtjs.org/docs/2.x/get-started/commands/) page in Nuxt.js documentation. + +## Recommended tools + +### Vue.js Devtools + +We strongly recommend installing [Vue.js Devtools](https://github.com/vuejs/vue-devtools#installation) in your browser. It's an excellent tool for viewing component structure and their current state, inspecting events and routes, and much more. + +
+ Process of installing Vue.js Devtools plugin in Chrome browser +
+ +*(Vue.js Devtools installation in Chrome browser)* + +
+ Usage of Vue.js Devtools with Vue Storefront application +
+ +*(Vue.js Devtools usage example)* + +### Vetur for VS Code +For those using Visual Studio Code as their main code editor, we also recommend using [Vetur extension](https://marketplace.visualstudio.com/items?itemName=octref.vetur). +It speeds up development of Vue.js-based applications by providing, amongst many others, features like Vue.js code autocompletion and syntax highlighting. + +To install Vetur extension: +1. Open VS Code +2. Open `Extensions` +3. Search for `Vetur` +4. Click `Install` + +
+ Process of installing of Vetur plugin in Visual Studio Code +
+ +*(Vetur installation in Visual Studio Code marketplace)* + +
+ Example of autocompletion provided by Vetur +
+ +*(Example of code autocompletion provided by Vetur)* + +## What's next? + +- Learn about [key concepts in Vue Storefront](./key-concepts.html) to confidently work with your newly created Vue Storefront project. +- Check out the platform-specific docs in the `eCommerce platforms` category to learn more about your integration. diff --git a/packages/core/docs/general/key-concepts.md b/packages/core/docs/general/key-concepts.md new file mode 100644 index 0000000000..b3eb3a78c6 --- /dev/null +++ b/packages/core/docs/general/key-concepts.md @@ -0,0 +1,66 @@ +# Key concepts + +This document will walk you through the most important concepts of Vue Storefront and help you understand the ideas behind it. + + +## Progressive technology + +It comes as no surprise that Vue Storefront is mostly based on Vue-related technologies. The most important one is Nuxt.js, which is built on top of Vue.js. While Vue.js provides tools to create user interfaces and +templates, Nuxt.js takes care of server-side rendering, routing, and internationalization. + +It is recommended to get familiar with [Vue.js](https://vuejs.org/) and [Nuxt.js](https://nuxtjs.org/) +before starting development in Vue Storefront. + +## Reusability + +To provide customizable and easy to maintain applications, Vue +Storefront offers composables. +**Composables are the main public API of Vue Storefront** and in many cases the only API you'll work with. They are functions that implement most of the business logic of the application. You can treat each composable as an independent micro-application. + +```js +const { search, product, loading, error } = useProduct(); +``` + +You can read more about them on the [Composables](/guide/composables.html) page. + +## Extendability and custom integrations + +Although some integrations are already provided by the Vue Storefront team, we enable developers to create their own integrations with any platform by extending the Server Middleware. It's the key component that works as a bridge between the frontend and your backend of choice. + +You can read more about Vue Storefront Middleware and its configuration on the [Server Middleware](/advanced/server-middleware.html) +page. + +## Easy configuration + +We did our best to keep configuration as simple and intuitive as possible. Therefore, Vue Storefront +applications can be configured by modifying just two files: + +- `nuxt.config.js` - main Nuxt.js configuration file used to control your Nuxt App and frontend-related features of Vue Storefront; +- `middleware.config.js` - VSF-specific configuration file used to add, configure and extend the [Server Middleware](/advanced/server-middleware.html); + +You can read more about it on the [Configuration](/guide/configuration.html) page. + +## Backend-agnostic + +One of the main goals of Vue Storefront is to work with almost any platform while using the same API. +No matter which service you configure as a backend, you will always use the same getters and composables. +This makes it easy to work with VSF projects on different tech stacks or trying new services without making any heavy investments. + +Some things are different for each platform though. The main data object of each composable +(like `products` in `useProduct`) is **always** a plain response from your platform. However, if you use these objects with their dedicated getters (like `productGetters` for `useProduct`), they will always return the data in the same format. + +```js +const { search, products } = useProduct() + +console.log(products) // type: CommerceToolsProduct[] + +console.log(productGetters.getAttributes(products.value)) // type: AgnosticProductAttribute[] +``` + +Parameters of functions returned by composables are different for each platform + +```js +const { search, products } = useProduct() + +search(params) // `params` are different depending on backend platform +``` diff --git a/packages/core/docs/general/support.md b/packages/core/docs/general/support.md new file mode 100644 index 0000000000..678c99c48d --- /dev/null +++ b/packages/core/docs/general/support.md @@ -0,0 +1,30 @@ +# Support + +## How to get help? + +Feel invited to join our community at [Discord](http://discord.vuestorefront.io/) - this is the place, where you can ask your questions, start interesting discussions, and meet Vue Storefront Core Team Members. + +To learn more about Vue Storefront, please visit our official [Vue Storefront Documentation](https://docs.vuestorefront.io/) page. + +Check out our [Forum](https://forum.vuestorefront.io/) and [Twitter](https://twitter.com/VueStorefront) to get the latest news and announcements. + +## How to ask for help? + +First, check our docs page - your answer may be already available there. Use the search feature - this will allow you to find specific sections quickly. While submitting a bug or feature request, search for possible duplicates - there is no reason to open a new issue when a similar one already exists. + +When possible, try to provide all the necessary information to reproduce the issue. If you encounter difficulties while following docs guides - always add links to specific pages/sections. Attach relevant logs, error messages, or screenshots to make your question easier to answer. + +## Found an Issue? + +If you would like to submit a bug or have a feature suggestion, visit our project [GitHub page](https://github.com/vuestorefront/vue-storefront) and create an issue. + +## Consulting services + +If you need more help or advice, please visit our [Professional Services page](https://www.vuestorefront.io/support). We can help you with: + +- Code review +- Production-ready sealing +- DevOps setup +- Core Team consultation + +We can assist you with project setup and resolving any problems. With technical workshops, you'll get up to speed with Vue Storefront development. diff --git a/packages/core/docs/general/where-to-start.md b/packages/core/docs/general/where-to-start.md new file mode 100644 index 0000000000..2ac3327ebc --- /dev/null +++ b/packages/core/docs/general/where-to-start.md @@ -0,0 +1,91 @@ +# Where to start? + +This document will show where you should start if you are: + +[[toc]] + +## Considering using Vue Storefront + +Vue Storefront is a **platform-agnostic e-commerce PWA frontend framework** that can work with any eCommerce backend API. Sounds great, right? But that is not all. + +With our product, you can easily solve the most common problems in the eCommerce e.g. +- [Long time to market](/#long-time-to-market) +- [Slow, unresponsive online shop](/#slow-unresponsive-online-shop) +- [Unwieldy architectural decisions](/#unwieldy-architectural-decisions) +- [Painful or impossible migrations](/#painful-or-impossible-migrations) +- [Lack of platform-specific competencies](/#lack-of-platform-specific-competencies) +- [Lack of flexibility](/#lack-of-flexibility) + +Also, we are proud of: +- 300+ [Live Implementations](https://www.vuestorefront.io/live-projects) +- 100+ [Agency Partners](https://www.vuestorefront.io/partner-agencies) +- active [Discord community](http://discord.vuestorefront.io/) + +You can visit [Introduction](/) or [Key conepts](/general/key-concepts.html) pages for more details about Vue Storefront. + +**Introduction** section will guide you through: +- [What is Vue Storefront?](/#what-is-vue-storefront) +- [Problems Vue Storefront solves](/#problems-vue-storefront-solves) +- [eCommerce integrations](/#ecommerce-integrations) +- [Tech stack](/#tech-stack) +- [What's next](/#what-s-next) + +**Key concepts** section will guide you through: +- [Progressive technology](/general/key-concepts.html#progressive-technology) +- [Reusability](/general/key-concepts.html#reusability) +- [Extendability and custom integrations](/general/key-concepts.html#extendability-and-custom-integrations) +- [Easy configuration](/general/key-concepts.html#easy-configuration) +- [Backend-agnostic](/general/key-concepts.html#backend-agnostic) + +## Developer creating a shop + +As a developer, you will be creating more or less advanced shops with many amazing features. Vue Storefront meets your expectations and equips you with everything that can solve the problems you will face. This will reduce the time it takes to deliver your store. + +You should start by familiarizing yourself with the **Guides** category. It includes all you need to know to create a shop successfully. If you want to dive deeper, check out the **Advanced** category. + +**Guides** section will guide you through: +- [Theme](../guide/theme.html) +- [Configuration](../guide/configuration.html) +- [Composables](../guide/composables.html) +- [Getters](../guide/getters.html) +- [Product catalog](../guide/product-catalog.html) +- [Authentication](../guide/authentication.html) +- [User profile](../guide/user-profile.html) +- [Cart and Wishlist](../guide/cart-and-wishlist.html) +- [Checkout](../guide/checkout.html) + +**Advanced** section will guide you through: +- [Architecture](../advanced/architecture.html) +- [Application context](../advanced/context.html) +- [Calling platform API](../advanced/calling-platform-api.html) +- [Extending GraphQL Queries](../advanced/extending-graphql-queries.html) +- [Server Middleware](../advanced/server-middleware.html) +- [Internationalization](../advanced/internationalization.html) +- [Performance](../advanced/performance.html) +- [SSR Cache](../advanced/ssr-cache.html) +- [Logging](../advanced/logging.html) +- [API Reference](../core/api-reference/) + +To be up to date, we recommend following our [Roadmap](https://www.notion.so/vuestorefront/Vue-Storefront-2-Next-High-level-Roadmap-201cf06abb314b84ad01b7b8463c0437) and joining our [Discord community](http://discord.vuestorefront.io/). + +## Integrator + +If you are using a custom backend or want to use platforms or technologies not listed on our [Integrations](../integrations/) page, you likely need to create an integration. + +Vue Storefront integrates with a wide range of technologies and each has its own needs, so there is no generic tutorial for all of them. + +However, depending on the kind of integrations you want to build, you should familiarize yourself with **Building Integration** section. Especially integration guide for [eCommerce](../integrate/integration-guide.html) and [CMS](../integrate/cms.html) platforms. + +## Designer creating a theme + +Our default theme is mostly based on a powerful design system for e-commerce called [Storefront UI](https://www.storefrontui.io/). If you don't want to use it, you can replace it with any other UI library or custom components, by changing the templates in Vue files. + +At the start you should familiarize yourself with the specially prepared [theme](../guide/theme.html) guide. + +**Theme** section will guide you through: +- [Directory structure](../guide/theme.html#directory-structure) +- [Storefront UI](../guide/theme.html#directory-structure) +- [How to customize the theme](../guide/theme.html#customizing-the-theme) +- [Routing](../guide/theme.html#routing) +- [How to update styles](../guide/theme.html#updating-styles) +- [Preinstalled modules and libraries](../guide/theme.html#preinstalled-modules-and-libraries) diff --git a/packages/core/docs/guide/authentication.md b/packages/core/docs/guide/authentication.md new file mode 100644 index 0000000000..da9b61d917 --- /dev/null +++ b/packages/core/docs/guide/authentication.md @@ -0,0 +1,207 @@ +# Authentication + +Authentication is a process of recognizing the user's identity. It allows to associate incoming requests with an account or a person when provided credentials are compared with ones in the database. + +All operations related to this process can be handled with methods exposed by `useUser` composable + +## Registering a new user + +Registering a new user can be done using `register` method. +```vue + + + +``` + +## Checking if the user is logged in + +Many interactions in the application are only available ( `useUser` prefixed functions like `useUserOrder`) or look different if the customer is logged in. To check if user is authenticated, we will use `isAuthenticated` method from `useUser`. + +Like with all other composables, it's important to remember to call `load` before accessing any other property or function of `useUser`. Otherwise, `isAuthenticated` will always return `false`. + +```js{8,16} +import { useUser } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup () { + const { + load, + isAuthenticated + } = useUser(); + + onSSR(async () => { + await load(); + }); + + return { + isAuthenticated + }; + } +} +``` + +## Logging in + +Signing in can be done using `login` method. +```vue + + + +``` +`user` [ref](https://v3.vuejs.org/api/refs-api.html#ref) either contains an object of the signed-in user or equals `null` + +## Logging out + +Signing out can be done using `logout` method. +```vue + + + +``` + +## Forgot Password + +Usually, the process of resetting a user password consists of two steps: + +1. Generating reset password token for a given email address: + +```vue + + + +``` + +2. Setting a new user password using the token and new password. + +```vue + + + +``` + +## How does it work in integrations? + +Below you can find detailed information on how we're handling authentication in different integrations. + diff --git a/packages/core/docs/guide/cart-and-wishlist.md b/packages/core/docs/guide/cart-and-wishlist.md new file mode 100644 index 0000000000..0eb5eef59e --- /dev/null +++ b/packages/core/docs/guide/cart-and-wishlist.md @@ -0,0 +1,675 @@ +# Cart and wishlist + +Customer's cart and wishlist can be managed using `useCart` and `useWishlist` composables respectively, provided by every integration. Data can be accessed using `cartGetters` and `wishlistGetters`. + +## Loading and creating the cart + +The `load` method will load your cart from the server or create a new one if it doesn't exist. The `cart` object will be `null` until you load it. + + +```js +import { useCart } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core'; +export default { + setup() { + const { + cart, + load + } = useCart(); + + onSSR(async () => { + await load(); + }); + + return { + cart, + load + }; + } +}; +``` + + +## Adding item to the cart + +To add the product to the cart you can use `addItem` method: + +```vue + + +``` + +## Removing items and changing their quantity + +To remove an item from the cart use `removeItem` method, and similarly to update quantity use `updateItemQty` method: + +```vue + + + +``` + +## Checking if an item is in the cart + +To check if a specific product configuration is already in the cart, pass it to `isInCart` method: + +```js +import { computed } from '@vue/composition-api'; +import { useCart } from '{INTEGRATION}'; + +export default { + props: { + products: { + type: Array, + required: true + } + }, + setup() { + const { isInCart } = useCart(); + + return { + isInCart + }; + } +}; + +``` + +## Removing all cart items at once + +To clear cart items (not delete it) use `clear` method. + +```vue + + +``` + + +## Applying and removing discount coupons + +You can apply promotional coupons to your cart with `applyCoupon` and remove with `removeCoupon` method: + +```vue + + +``` + +## Loading and creating the wishlist + +The `load` method will load your cart from the server or create a new one if it doesn't exist. The `wishlist` object will be `null` until you load it. + +```vue + +``` + + +## Adding an item to the wishlist + +To add the product to the wishlist you can use `addItem` method: + +```vue + + +``` + +## Removing an item from the wishlist + +To remove an item from the cart use `removeItem` method. + +```vue + + +``` + +## Checking if an item is on the wishlist + +To check if a product is already on the wishlist pass it to `isInWishlist` method: + +```vue + + +``` + +## Removing all wishlist items at once + +Cleaning the wishlist can be achieved by `clear` property. + +```vue + + +``` + +## Summary + +### Common usage example + +Here is an example of how both composables can be used in real-life applications. We have three components: a product list, a cart, and a wishlist. In the examples, we cover the logic of adding/removing items from the wish list and cart, working with getters, and loading state. + +The product list: + +```vue + + +``` + +The cart component: + +```vue + + +``` + +The wishlist component: + +```vue + + +``` diff --git a/packages/core/docs/guide/checkout.md b/packages/core/docs/guide/checkout.md new file mode 100644 index 0000000000..e8370aa98c --- /dev/null +++ b/packages/core/docs/guide/checkout.md @@ -0,0 +1,365 @@ +# Checkout + +> This document only outlines the general checkout flow. Each eCommerce, Payment provider, and Shipping provider could implement it slightly differently. Please follow the instructions from the documentation of your payment or shipping provider to learn about its caveats + +Checkout is a process of providing shipping and billing addresses and selecting shipping and payment methods needed to place an order and pay for it. + +## Collecting and saving shipping details + +Shipping details are information about the recipient's address required to ship the order. + +You can load shipping details by calling the `load` method in `useShipping` composable and accessing the `shipping` property after loading is done. +```js{8,16} +import { useShipping } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup () { + const { + load, + shipping + } = useShipping(); + + onSSR(async () => { + await load(); + }); + + return { + shipping + }; + } +} +``` +`shipping` property returns `null` if the `load` function was not invoked or nothing is saved. + +You can use the `save` method to save shipping details so they are available next time you `load` them. + +```vue{2,15,24} + + + +``` + +## Selecting a shipping method + +`VsfShippingProvider` is a component that aggregates one or more shipping methods from a single provider like FedEx or DHL. This component is usually the only thing that you need to integrate a particular vendor into your project and is always delivered as third-party integration. + +The component is responsible for: +- Loading and displaying available shipping methods. +- Loading selected shipping method. +- Selecting and configuring shipping method. + +All you have to do is to import a component and add it to the template. + +```vue + +``` + +`VsfShippingProvider` sets `state.value._status` property of `useShippingProvider` to `true` or `false`. The property informs whether a user is ready to go to the next step (`true`) or not (`false`). + +### Extending `VsfShippingProvider` and reacting to its events + +You can pass asynchronous functions to the `VsfShippingProvider` component to hook into different events within its lifecycle, override initial function parameters or react to specific events like method selection. Let's call these methods "hooks". + +Because every shipping provider is different, not all of them are present in every integration. Always refer to the documentation of a specific provider to learn which hooks are available. + +- **beforeLoad** `(config => config)` - Called before loading shipping methods. +- **afterLoad** `(shippingMethodsResponse => shippingMethodsResponse.shippingMethods)` - Called after loading shipping methods. +- **beforeSelect** `(shippingMethod => shippingMethod)` - Called before selecting shipping method. +- **afterSelect** `(selectedShippingMethod => void)` - Called after selecting shipping method. +- **beforeSelectedDetailsChange** `(details => details)` - Called before modifying currently picked shipping method, e.g. selecting parcel locker on the map. +- **afterSelectedDetailsChange** `(details => void)` - Called after modifying currently picked shipping method. +- **onError** `(({ action, error }) => void)` - Called when some operation throws an error. + +```vue + +``` + +### Accessing current shipping method's details outside the component + +Sometimes you have to show the information about a selected shipping method in a different place than the `VsfShippingProvider` component. + +For such cases, you can use `useShippingProvider` composable. It has been made for loading and saving a current shipping method. After loading the data via the `load` method, it stores the information in some property of a `state` object, so you can access it from many places. + +```ts +import { useShippingProvider } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core'; +import { computed } from '@vue/composition-api'; + +export default { + setup () { + const { load, state } = useShippingProvider(); + + onSSR(async () => { + await load(); + }); + + return { + selectedShippingMethod: computed(() => ...) + } + } +} +``` + +## Collecting and saving billing details + +Billing details are information about the payer's address used by store owners to prepare invoices and payment providers to evaluate the probability of fraud payment. + +You can load billing details by calling the `load` method in `useBilling` composable and accessing the `billing` property after loading is done. + +```js{8,16} +import { useBilling } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup () { + const { + load, + billing + } = useBilling(); + + onSSR(async () => { + await load(); + }); + + return { + billing + }; + } +} +``` +`billing` property returns `null` if the `load` function was not invoked or nothing is saved. + +You can use the `save` method to save billing details. + +```vue{2,15,24} + + + +``` + +## Making an order + +After the user has provided all the information required by your eCommerce, you are ready to *make an order*. To do that, you have to call a `make` method from the `useMakeOrder` composable. +```js +import { useMakeOrder } from '{INTEGRATION}'; + +export default { + setup () { + const { make } = useMakeOrder(); + + return { + make + } + } +} +``` + +When the order is created, you can redirect the user to the page thanking them for making an order and refresh the cart. + +```js +import { useMakeOrder, useCart } from '{INTEGRATION}'; + +export default { + setup (_, context) { + const { make, order } = useMakeOrder(); + const { setCart } = useCart(); + + const processOrder = async () => { + await make(); + context.root.$router.push(`/checkout/thank-you?order=${order.value.id}`); + setCart(null); + } + } +} +``` + +## Payment providers + +A `VsfPaymentProvider` is a component that provides one or more payment methods. One such component integrates one third-party provider of payments like Checkout.com or Adyen. This component is usually the only thing that you need to integrate a particular vendor into your project and is always delivered as third-party integration. + +The component is responsible for: +- Loading and displaying available payment methods. +- Loading selected payment method. +- Picking and configuring payment method. + +The first thing you have to do is to import the component and add it to the template. + +```vue + + + +``` + +The next step is making a payment. Each package with a payment provider might use a slightly different approach, but below we described the two most common. + +### SDK takes the full control + +If the payment provider's SDK handles the whole payment and you can only provide your own callbacks for certain events. You want to make an order in the `beforePay` async hook. +```vue + + + +``` + +### SDK allows externalizing pay method + +If the payment provider's SDK handles the process of configuring payment but allows you to decide when to finalize then: +- Composable shares a `pay` method. +- Composable shares a `status` boolean ref that informs if you are ready to call `pay`. + +```vue + + + +``` + +### Extending `VsfPaymentProvider` and reacting to its events + +You can pass asynchronous functions to the `VsfPaymentProvider` component to hook into different events within its lifecycle, override initial function parameters or react to specific events like method selection. Let's call these methods "hooks". + +Because every payment provider is different, not all of them are present in every integration. Always refer to the documentation of a specific provider to learn which hooks are available. + +- **beforeLoad** `(config => config)` - Called before loading payment methods. +- **afterLoad** `(paymentMethodsResponse => paymentMethodsResponse.paymentMethods)` - Called after loading payment methods. +- **beforeSelect** `(paymentMethod => paymentMethod)` - Called before selecting payment method. +- **afterSelect** `(selectedPaymentMethod => void)` - Called after selecting payment method. +- **beforePay** `(paymentDetails => paymentDetails)` - Called before pay. +- **afterPay** `(paymentResponse => void)` - Called after pay. +- **beforeSelectedDetailsChange** `(details => details)` - Called before modifying currently picked payment method, e.g. changing credit card's details. +- **afterSelectedDetailsChange** `(details => void)` - Called after modifying currently picked payment method, e.g. changing credit card's details. +- **onError** `(({ action, error }) => void)` - Called when some operation throws an error. + +```vue + +``` + +### Why some integrations have a mocked `VsfPaymentProvider`? + +There are eCommerce backends that do not provide any payment methods out-of-the-box, e.g. commercetools. For these, you provide a mock component that has exactly the same interface like a real integration so you can easily swap it with a payment integration of your choice. You can find available payment integrations [here](/integrations/). diff --git a/packages/core/docs/guide/composables.md b/packages/core/docs/guide/composables.md new file mode 100644 index 0000000000..efb59f64fb --- /dev/null +++ b/packages/core/docs/guide/composables.md @@ -0,0 +1,128 @@ +# Composables + +> If you are not familiar with Composition APIs, we recommend reading [this article](/guide/composition-api.html) first. + +Composable is a function that uses [Composition API](/guide/composition-api.html) under the hood. Composables are the main public API of Vue Storefront and, in most cases, the only API except configuration you will work with. + +You can treat composables as independent micro-applications. They manage their own state, handle server-side rendering, and rarely interact with each other (except [useUser](/composables/use-user.html) and [useCart](/composables/use-cart.html)). No matter what integration you are using, your application will always have the same set of composables with the same interfaces. + +To use a composable, you need to import it from an integration you use, and call it on the component `setup` option: + +```js +import { useProduct } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core` + +export default { + setup() { + const { products, search } = useProduct(); + + onSSR(async () => { + await search(searchParams); + }); + + return { + products + }; + } +}; +``` + +> `onSSR` is used to perform an asynchronous request on the server side and convey the received data to the client. You will learn more about it next section. + +For some composables (like `useProduct`) you will need to pass a unique ID as a parameter (it can be a product ID, category ID etc.). Others (like `useCart`) do not require an ID passed. You can always check a composable signature in the [API Reference](../core/api-reference/core.html). + +## Anatomy of a composable + +Every Vue Storefront composable usually returns three main pieces: + +- **Main data object**. A read-only object that represents data returned by the composable. For example, in `useProduct` it's a `products` object, in `useCategory` it's `categories` etc. +- **Supportive data objects**. These properties depend directly or indirectly on the main data object. For example, `loading`, `error` or `isAuthenticated` from `useUser` depend on a `user`. +- **Main function that interacts with data object**. This function usually calls the API and updates the main data object. For example in `useProduct` and `useCategory` it's a `search` method,in `useCart` it's a `load` method. The rule of thumb here is to use `search` when you need to pass some search parameters. `load` is usually called when you need to load some content based on cookies or `localStorage` +- **platform-specific API access**. This is the section where you can reach out to the API functions that are specific to the platform you are using. By default we provide an agnostic approach of using the same API and function calls for each platform, however sometimes there is a need to use something very specific to the certain service, thus you can easily access it over the `api` object. + +```js +import { useProduct } from '{INTEGRATION}'; + +const { products, search, loading, api } = useProduct(''); + +search({ slug: 'super-t-shirt' }); // agnostic access + +api.addCartInsurence({ id: 't-shirt-01' }); // platform-specific API + +return { products, search, loading }; +``` + +### Using `onSSR` for server-side rendering + +By default, Vue Storefront supports conveying server-side data to the client with Nuxt.js 2 where `setup` function is synchronous. Because of that, we can't use asynchronous functions if their results depend on each other (e.g. by loading `products` using `id` of a category that you have to fetch first). + +To solve this issue, we provide a temporary solution - `onSSR`: + +```js +import { useProduct, useCategory } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup() { + const { categories, search: searchCategory } = useCategory(); + const { products, search: searchProduct, loading } = useProduct(); + + onSSR(async () => { + await searchCategory({ slug: 'my-category' }); + await searchProduct({ catId: categories.value[0].id }); + }); + + return { + products, + loading + }; + } +}; +``` + +`onSSR` accepts a callback where we should call our `search` or `load` method asynchronously. This will change `loading` state to `true`. Once the API call is done, main data object (`products` in this case) will be populated with the result, and `loading` will become `false` again. + +In the future, Vue 3 will provide an async setup and `onSSR` won't be needed anymore. + +## What composables I can use + +Vue Storefront integrations are exposing the following composables: + +#### Product Catalog + +- [`useProduct`](../core/api-reference/core.useproduct.html) - Managing a single product with variants (or a list). +- [`useCategory`](../core/api-reference/core.usecategory.html) - Managing category lists (but not category products). +- [`useFacet`](../core/api-reference/core.usefacet.html) - Complex catalog search with filtering. +- [`useReview`](../core/api-reference/core.usereview.html) - Product reviews. + +#### User Profile and Authorization + +- [`useUser`](../core/api-reference/core.useuser.html) - Managing user sessions, credentials and registration. +- [`useUserShipping`](../core/api-reference/core.useusershipping.html) - Managing shipping addresses. +- [`useUserBilling`](../core/api-reference/core.useuserbilling.html) - Managing billing addresses. +- [`useUserOrder`](../core/api-reference/core.useuserorder.html) - Managing past and active user orders. + +#### Shopping Cart + +- [`useCart`](../core/api-reference/core.usecart.html) - Loading the cart, adding/removing products and discounts. + +#### Wishlist/Favourite + +- [`useWishlist`](../core/api-reference/core.usewishlist.html) - Loading the wishlist, adding/removing products. + +#### CMS Content + +- [`useContent`](../core/api-reference/core.usecontent.html) - Fetching the CMS content. It is usually used in combination with ``component. + +#### Checkout + +- [`useShipping`](../core/api-reference/core.useshipping.html) - Saving the shipping address for a current order. +- [`useShippingProvider`](../core/api-reference/core.useshippingprovider.html) - Choosing a shipping method for a current order. Shares data with `VsfShippingProvider` component. +- [`useBilling`](../core/api-reference/core.usebilling.html) - Saving the billing address for a current order. +- `usePaymentProvider` - Choosing a payment method for a current order. Shares data with `VsfPaymentProvider` component +- [`useMakeOrder`](../core/api-reference/core.usemakeorder.html) - Placing the order. + +#### Other + +- [`useVSFContext`](../core/api-reference/core.usevsfcontext.html) - Accessing the integration API methods and client instances. +In a real-world application, these composables will most likely use different backend services under the hood yet still leverage the same frontend API. For instance within the same application `useProduct` and `useCategory` could use `commercetools`, `useCart` some ERP system, `useFacet` - Algolia etc. diff --git a/packages/core/docs/guide/composition-api.md b/packages/core/docs/guide/composition-api.md new file mode 100644 index 0000000000..7d3c2f7378 --- /dev/null +++ b/packages/core/docs/guide/composition-api.md @@ -0,0 +1,189 @@ +# Composition API + +> If you already have an experience with Vue Composition API, you can skip this section and start using [Vue Storefront Composables](/guide/composables.html) + +Composition API is a new way of abstracting and reusing the logic added in Vue 3.0. It allows you to create and observe a reactive state both inside the Vue component and outside of it as a standalone function. + +Let's try to build functionality for submitting a form with two fields: user name and password. + +```html + +``` + +In the Vue component, Composition API methods should be called inside the new component option called `setup`: + +```html + +``` + +First of all, we need two reactive values that will be bound to form fields. They can be created with Vue `ref` method: + +```js +import { ref } from 'vue'; + +export default { + setup() { + const username = ref(''); + const password = ref(''); + + return { username, password }; + } +}; +``` + +The argument passed to `ref` is an _initial value_ of our reactive state properties. Under the hood, `ref` creates an object with a single property `value`: + +```js +setup() { + const username = ref(''); + const password = ref(''); + + console.log(username.value) // => '' + + return { username, password }; +} +``` + +After we returned reactive properties from the `setup`, they become available in Vue component options (such as `data` or `methods`) and in component template. `refs` returned from setup are automatically unwrapped - this means you don't access the `.value` anymore: + +```html + + + +``` + +Methods can be also created inside the `setup` option: + +```js{6-11} +export default { + setup() { + const username = ref(''); + const password = ref(''); + + const submitForm = () => { + // remember that inside `setup` you need to access ref's value + console.log(username.value); + }; + + return { username, password, submitForm }; + } +}; +``` + +Let's disable a button when username or password is empty. In order to do this, we can create a computed property - and this can be also done with the Composition API inside the `setup`: + +```html{5,15-19} + + + +``` + +Now, we can make a final step and abstract all the form functionality _outside_ the component - in a standalone function: + +```js +// useForm.js + +import { ref, computed } from 'vue'; + +export function useForm() { + const username = ref(''); + const password = ref(''); + + const submitForm = () => { + console.log(username.value); + }; + + const isValid = computed( + () => username.value.length > -1 && password.value.length > 0 + ); + + return { username, password, submitForm, isValid }; +} +``` + +This function is what we call a **composable**: a reusable piece of logic with the reactive state. Composables can be imported and used in Vue components: + +```html{10,14-16} + + + +``` + +Vue Storefront uses composables as its main API. We will take a look over them in the next [article](/guide/composables.html). \ No newline at end of file diff --git a/packages/core/docs/guide/configuration.md b/packages/core/docs/guide/configuration.md new file mode 100644 index 0000000000..f26d16b2ba --- /dev/null +++ b/packages/core/docs/guide/configuration.md @@ -0,0 +1,97 @@ +# Configuration + +Usually, the first thing to do after setting up a fresh Vue Storefront project is configuring it. The bare minimum is to provide the API credentials for your integrations. + +Your Vue Storefront-related configuration is located in two places: + +- `nuxt.config.js` is a place where you're configuring properties related only to the frontend part of your application. +- `middleware.config.js` is a place where you're configuring your integration middleware. You will put there API keys, integration configurations, custom GraphQL queries and new API endpoints. + +## Configuring Integrations + +Most of the integrations business logic is placed in the [Integration Middleware](/v2/advanced/server-middleware). Therefore they're configurable through the `integrations` field in `middleware.config.js`. + +```js +// middleware.config.js +module.exports = { + integrations: { + // integration configs + } +}; +``` + +Sometimes integrations also expose a Nuxt module to configure frontend-related properties and [i18n](/v2/advanced/internationalization). + +```js +// nuxt.config.js +[`@vue-storefront/{INTEGRATION}/nuxt` { + i18n: { + // i18n config + } +}] +``` + +## Configuring Nuxt + +We try to use the most common modules from Nuxt Community whenever it's possible. For internationalization we are using `nuxt-i18n`, for PWA capabilities `@nuxtjs/pwa` etc. You can find a list of the Nuxt modules used in the default theme [here](theme.html#preinstalled-modules-and-libraries). Each of them is configured in a way that works best for the majority of users. + +There are some features and behaviors that are specific to Vue Storefront only yet not specific to a certain integration. You can configure such things through `@vue-storefront/nuxt` module. + +[//]: # 'TODO: Add documentation for VSF/NUXT module' + +Below you can find its default configuration: + +```js +// nuxt.config.js +[ + `@vue-storefront/nuxt`, + { + // use only if you're developing an integration + // adds theme inheritance mechanism + coreDevelopment: false, + logger: { + // read about this part in `Advanced/Logging` section + }, + performance: { + httpPush: true, + // installs https://purgecss.com/guides/nuxt.html + // CAUTION: Could break classess generated dynamically (eg variable + '-secondary') + purgeCSS: { + enabled: false, + paths: ['**/*.vue'], + }, + }, + // use `module` field from `package.json` for included packages + // custom configuration will be merged with the default one + // this property is used mainly to process ESM and benefit the most from treeshaking + useRawSource: { + dev: ['@storefront-ui/vue', '@storefront-ui/shared'], + prod: ['@storefront-ui/vue', '@storefront-ui/shared'], + }, + }, +]; +``` +::: danger +It's unsafe and not recommended to remove `@vue-storefront/nuxt` from your project. Integrations and other modules rely on it. +::: + +## Configuring Middleware + +There are certain things related to Integration Middleware that are not tied to any specific integration like setting custom GraphQL queries you can configure. You can read more about the Middleware and its configuration [here](/v2/advanced/server-middleware). + +## Integration References + + +### Setting up official eCommerce integrations + + + +### Configuration references of official eCommerce integrations + + \ No newline at end of file diff --git a/packages/core/docs/guide/error-handling.md b/packages/core/docs/guide/error-handling.md new file mode 100644 index 0000000000..b497f4e32c --- /dev/null +++ b/packages/core/docs/guide/error-handling.md @@ -0,0 +1,81 @@ +# Error handling + +Each [composable](/guide/composables.html) has a computed property called `errors` which stores errors from all methods within that composable. It's an object with keys matching the names of the methods within the composable, and Error instance or `null` as a value. + +```vue + + + +``` +The `@vue-storefront/core` package exports interfaces for `error` objects for every composable. Let's take a look at the one from the `useCart` composable: + +```ts +export interface UseCartErrors { + addItem: Error; + removeItem: Error; + updateItemQty: Error; + load: Error; + clear: Error; + applyCoupon: Error; + removeCoupon: Error; +} +``` + +:::details Where does the error come from? + +Inside each async method, we catch thrown errors and save them in the `error` object under the key corresponding to the called method. + +```ts +const { addItem, error } = useCart(); + +addItem({ product: null }); // throws an error +error.value.addItem; // here you access error thrown by addItem function +``` + +::: + +:::details Where can I find an interface of the error property for a specific composable? + +When you are writing a code inside a script part of the Vue component, your IDE should give you hints on every different type of the composable. That's why you probably do not need to check these interfaces in the core's code. + +However, if you still want to check interfaces, you could find them in [core API reference](../core/api-reference/core.html). + +::: + +### How to listen for errors? + +Let's imagine you have some global component for error notifications. You want to send information about every new error to this component. But how do you know when a new error appears? +You can observe an error object with a watcher: + +```ts +import { useUiNotification } from '~/composables'; + +const { cart, error } = useCart(); +const { send } = useUiNotification(); + +watch(() => ({...error.value}), (error, prevError) => { + if (error.addItem && error.addItem !== prevError.addItem) + send({ type: 'danger', message: error.addItem.message }); + if ( + error.removeItem && + error.removeItem !== prevError.removeItem + ) + send({ type: 'danger', message: error.removeItem.message }); +}); +``` + +In this example, we are using `useUiNotification` - a composable that handles notifications state. You can read more about it in the API reference. diff --git a/packages/core/docs/guide/getters.md b/packages/core/docs/guide/getters.md new file mode 100644 index 0000000000..88d446a09e --- /dev/null +++ b/packages/core/docs/guide/getters.md @@ -0,0 +1,69 @@ +# Getters + +> The getters are closely connected to the composables. If you are not already familiar with the concept, please refer to the composables chapter first. + +## What are getters? + +Getters are functions that are used to get the data from the raw API responses. They return an agnostic or a primitive types and allow you to write the same code regardless of the backend used. +Each composable has its own dedicated getter, e.g. `useCart` and `cartGetters`. **You should only use getters dedicated to specific composable**. + +## When should I use them? + +**Getters should be used whenever possible to extract the data from composables**, e.g., when you want to get the quantity of the products in the cart in UI components. + +## How can I use getters? + +The getters accept arguments to get the data. As an example, we will use cart functionalities. +Most functions in `cartGetters` accept whole `cart` object or individual cart items. This data needs to be extracted from `useCart` composable first. +As the first example, we will use `getTotalItems` from `cartGetters` to get the total number of items in the cart. + +```vue + + + +``` + +**It's important to use getters as the computed property** to have them always updated: + +```vue + +``` diff --git a/packages/core/docs/guide/product-catalog.md b/packages/core/docs/guide/product-catalog.md new file mode 100644 index 0000000000..13dbcbc162 --- /dev/null +++ b/packages/core/docs/guide/product-catalog.md @@ -0,0 +1,249 @@ +# Product Catalog + +There are two composables used to interact with the product catalog - `useProduct` and `useFacet`. + +`useProduct` allows loading products and their variants when some parameters like IDs, SKUs, or slugs are already known. + +`useFacet` allows more complex queries where categories, search terms, filters, and other options can narrow down and sort the results. + +## Fetching a single product and its variants + +`useProduct` composable is used primarily on the Product Details Page to display information about a single product and its variants. + +Use the `search` method to fetch the product and its variants. The response is available in the `products` object. + +```vue + + +``` + +## Accessing product data + +Once products are loaded using `useProduct`, access them using `productGetters`. Depending on the product, configuration, and integration used, the response might contain one or more products or variants. Use the `getFiltered` getter to access them. + +For a full list of available getters, please refer to the [ProductGetters ](../core/api-reference/core.productgetters.html) interface. + +### Accessing products list + +Use the `getFiltered` getter without the second parameter to get the list of all products. + +```vue{4,18-24} + +``` + +### Accessing master variant + +In most of the eCommerce backends, there is a so-called _master variant_. You can think of it as a default configuration for the product displayed to the user if they haven't selected any other configuration. + +Let's use the `getFiltered` getter again, but this time pass `{ master: true }` as a second parameter to only get the master variant. + +```vue{4,18-24} + +``` + +:::warning `getFiltered` always returns an array +Even when `{ master: true }` is passed, the `getFiltered` getter still returns an array of products. In the example above, we used `[0]` to access the first element, but it might differ depending on the integration. +::: + +### Accessing products with specific attributes + +To only get the products with specific attributes, pass the `{ attributes }` object as a second parameter. + +```vue{4,18-29} + +``` + +## Fetching a list of products and available filters + +Faceted search allows users to narrow down search results by applying multiple filters (called dimensions) to the catalog data. In the clothing shop, facet (dimension) would be a brand, size, color, etc. + +`useFacet` composable is used primarily on the Category Page to display products matching specified filters. + +Use the `search` method to fetch the products. The response is available in the `result` object. + +```vue + +``` + +For a full list of parameters, please refer to the [AgnosticFacetSearchParams](../core/api-reference/core.agnosticfacetsearchparams.html) interface. + +## Accessing catalog data + +Once data is loaded using `useFacet`, access it using `facetGetters`. Get the data such as products, sorting and filtering options, pagination, and much more. + +```vue{4,22-36} + +``` + +For a full list of available getters, please refer to the [FacetsGetters](../core/api-reference/core.facetsgetters.html) interface. + +The image below shows where each of these getters fits into a Category page. + +![An example showing how each getter provides data for different parts of the Category page](./../images/faceting.jpg) diff --git a/packages/core/docs/guide/theme.md b/packages/core/docs/guide/theme.md new file mode 100644 index 0000000000..d48dadbb54 --- /dev/null +++ b/packages/core/docs/guide/theme.md @@ -0,0 +1,152 @@ +# Theme + +[[toc]] + +## Directory structure + +If you followed our [Installation guide](/general/installation.html), you should have a fully functional e-commerce application. As mentioned in previous documents, Vue Storefront extends Nuxt.js, so the structure of both applications is similar. Most directories come from Nuxt.js, and you can read more about them on their [Directory Structure](https://nuxtjs.org/docs/2.x/get-started/directory-structure) page. + +* [.nuxt](https://nuxtjs.org/docs/2.x/directory-structure/nuxt); +* [components](https://nuxtjs.org/docs/2.x/directory-structure/components); +* `composables` - contains custom composables that override your integration or are not part of the Vue Storefront core. It may include composables specific to your theme; +* `helpers` - contains helper functions. It is a good place to store methods used in the multiples places of the application; +* `lang` - contains translations for your application. Available locales are configured in the `nuxt.config.js` file; +* [layouts](https://nuxtjs.org/docs/2.x/directory-structure/layouts); +* [middleware](https://nuxtjs.org/docs/2.x/directory-structure/middleware); +* [pages](https://nuxtjs.org/docs/2.x/directory-structure/pages); +* [static](https://nuxtjs.org/docs/2.x/directory-structure/static); +* `middleware.config.js` - configuration file for the [Server Middleware](/advanced/server-middleware.html); +* [nuxt.config.js](https://nuxtjs.org/docs/2.x/directory-structure/nuxt-config); + +## Storefront UI + +
+ +
+ +Almost every component in our default theme uses components whose names start with `Sf`. These come from [Storefront UI](http://storefrontui.io/) - a design system and library of Vue.js components dedicated to e-commerce, maintained by the Vue Storefront core team. It comes with [Storybook](https://storybook.storefrontui.io/) to help you customize and test the components. + +The library is fully customizable. It can be used in different contexts and with different designs. +It's excellent for the multi-tenancy model as a shared UI library that can be customized differently for each tenant. + +To learn more about Storefront UI, please check its [Documentation](https://docs.storefrontui.io/). + +::: tip Want to use another UI library? No problem! +If you don't want to use Storefront UI, feel free to remove it from your project. It's just a UI layer, and the project can work with any other UI library or a custom code. +::: + +## Customizing the theme + +### Changing existing pages, components, and layouts + +To update the existing components, you need to identify them first. Vue.js Devtools helps us in that. Open the tool and click on the `Select` button above the component tree, then click on the DOM element you want to update. One of the components in the tree should get highlighted. You can look for the component with the same name in the `layout`, `pages`, or `components` directories and update it to your needs. However, there are few exceptions to this rule. + +#### `Sf` components + +If the name of the component starts with `Sf` (indicating that it comes from Storefront UI), you should refer to the direct **parent** component. The behavior and look of such components can be changed by passing different properties and using slots. Refer to the StorefrontUI documentation linked above for more information. + +#### `LazyHydrate` and `Anonymous Component` components + +These two components come from the `vue-lazy-hydration` library and are wrappers around other components. In Vue Storefront they are used to improve the performance by deferring the hydration process (when components become interactive) and don't affect the look of other components. + +If you encounter one of these components, you should refer to the direct **child** component. + +### Adding new page + +To add a new page, create a new component in the `pages` folder and name it the same as your route using `PascalCase`. + +As an example, let's create the `AboutUs.vue` component. This by itself creates a new route named `/aboutus` (thanks to [File System Routing](https://nuxtjs.org/docs/2.x/features/file-system-routing/) in Nuxt.js) and in some cases might be enough. However, to follow the convention of using `kebab-case` in URLs, let's use [extendRoutes](https://nuxtjs.org/guides/configuration-glossary/configuration-router#extendroutes) in `nuxt.config.js` to have more control over the route. + +Add following configuration to `nuxt.config.js` to create new route `/about-us`: + +```javascript +// nuxt.config.js + +export default { + router: { + extendRoutes(routes, resolve) { + routes.push({ + name: 'AboutUs', + path: '/about-us', + component: resolve(__dirname, 'pages/AboutUs.vue') + }); + } + } +}; +``` + +## Routing + +Out of the box, some routes are injected via `@vue-storefront/nuxt-theme` module: + +- Home Page (`/`); +- Category Page (`/c/:slug_1/:slug_2?/:slug_3?/:slug_4?/:slug_5?`); +- Product Page (`/p/:id/:slug/`); +- User Profile Page (`/my-account/:pageName?`); +- Checkout (`/checkout`): + - Shipping (`/checkout/shipping`); + - Billing (`/checkout/billing`); + - Payment (`/checkout/payment`); + - Thank You page (`/checkout/thank-you`); +- Custom 404 page; + +To override existing routes or adding your own, use [extendRoutes](https://nuxtjs.org/guides/configuration-glossary/configuration-router#extendroutes) in `nuxt.config.js`. Additionally, Nuxt.js automatically registers components created in the `pages` folder as new routes. You can read more about this on the [File System Routing](https://nuxtjs.org/docs/2.x/features/file-system-routing/) page. + +## Updating styles + +There are few ways of updating the default styles. Below we describe the most optimal ways for the most common cases. + +### Adding global styleheet + +To add global styles applied to all pages, use the [css property](https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-css/) in `nuxt.config.js`. + +### Adding stylesheet to specific layout, page, or component + +To add a stylesheet to a specific component, use `@import` regardless if you are using CSS, SCSS, or LESS. + +```vue + +``` + +### Using variables, mixins, and function in components + +Usually, to access style variables, mixins, and functions, we have to import them in every component separately. Thanks to [@nuxtjs/style-resources](https://github.com/nuxt-community/style-resources-module#readme) module, we can register them in `nuxt.config.js` and access them without extra `@import` statements. + +:::danger Be careful +Stylesheets in `styleResources` should **only** contain variables, mixins, and functions. During the build process, the components import these stylesheets. Any styles declared in them are added to every component, which can significantly hurt the performance and application size. +::: + +We use this approach to have access to StorefrontUI helpers in all components: + +```js +// nuxt.config.js +export default { + styleResources: { + scss: [require.resolve('@storefront-ui/shared/styles/_helpers.scss', { paths: [process.cwd()] })] + }, +}; +``` + +## Preinstalled modules and libraries + +Below you can find a list of the most important Nuxt Modules and libraries that come preinstalled with the default theme: + +### Nuxt.js modules + +- `@nuxtjs/pwa`; +- `nuxt-i18n`; +- `@vue-storefront/nuxt`; + - `@nuxtjs/composition-api`; + - `@nuxt/typescript-build`; + - `@nuxtjs/style-resources`; + - `nuxt-purgecss`; +- `@vue-storefront/nuxt-theme`; + - `vue-lazy-hydration`; + +### Libraries + +- [`@storefront-ui/vue`](https://storefrontui.io); +- [`wee-validate`](https://vee-validate.logaretm.com/v3); +- [`lodash`](https://lodash.com/); diff --git a/packages/core/docs/guide/user-profile.md b/packages/core/docs/guide/user-profile.md new file mode 100644 index 0000000000..6ef13b1c6d --- /dev/null +++ b/packages/core/docs/guide/user-profile.md @@ -0,0 +1,289 @@ +# User profile + +## Loading current user + +To access data of the currently logged-in user, we can use another property of `useUser` called simply `user`. + +```js{8,16} +import { useUser } from '{INTEGRATION}'; +import { onSSR } from '@vue-storefront/core'; + +export default { + setup () { + const { + load, + user + } = useUser(); + + onSSR(async () => { + await load(); + }); + + return { + user + }; + } +} +``` + +`user` property will return `null` if the user is not logged-in. `userGetters` should handle such cases and return empty data like `''`, `[]` etc. depending on the expected return data type. To prevent empty elements in the template, it's a good practice to check if the user is logged-in before using getters. + +```vue{3-5} + + + +``` + +## Updating user credentials + +Updating user data (except for the current password, which is described in the [Changing password](#changing-password) section) can be done using `updateUser` method in `useUser` composable. + +```js +import { useUser } from '{INTEGRATION}'; + +export default { + setup () { + const { error, updateUser } = useUser(); + + const onSubmit = async (formData) => { + await updateUser({ user: formData }); + // "error.value.updateUser" should be empty if request was successful + }; + + return { + onSubmit + }; + } +} +``` + +## Changing password + +Updating user password can be done using `changePassword` method in `useUser` composable. It requires the current and new password, to confirm user identity. + +```js +import { useUser } from '{INTEGRATION}'; + +export default { + setup () { + const { error, changePassword } = useUser(); + + const onSubmit = async (formData) => { + await changePassword({ + current: formData.currentPassword, + new: formData.newPassword + }); + // "error.value.changePassword" should be empty if request was successful + }; + + return { + onSubmit + }; + } +} +``` + +## Managing addresses (billing and shipping) + +Managing billing and shipping addresses is done using [useUserBilling](/core/api-reference/core.useuserbilling.html) and [useUserShipping](/core/api-reference/core.useusershipping.html) composables. + +Both have almost identical signature (properties, methods and getters), so examples below will only show usage of `useUserBilling`. + +### Displaying a list of addresses + +To get a list of addresses, use `load` and `billing` or `shipping` properties and `getAddresses` method on corresponding getter. + +```vue + + + +``` + +### Adding, updating, and deleting addresses + +`useUserBilling` and `useUserShipping` composables expose number of methods to manage addresses: + +* `addAddress` +* `deleteAddress` +* `updateAddress` +* `setDefault` + +Below is the example of using `deleteAddress` method. + +```vue{7-9,23,32} + + + +``` + +For more information, please refer to documentation for [useUserBilling](/core/api-reference/core.useuserbilling.html) and [useUserShipping](/core/api-reference/core.useusershipping.html) composables. + +## Displaying user orders + +To get a list of orders, use `search` and `orders` properties and `getItems` method on `orderGetters`. + +```vue + + + +``` + +For more information, please refer to documentation for [useUserOrder](/core/api-reference/core.useuserorder.html) composable. + +## Protecting user profile routes + +If there is a page that should be accessible only to logged-in users, such as user profile, you can use `is-authenticated` middleware in the page-level component: + +```js +export default { + middleware: [ + 'is-authenticated' + ] +} +``` +## Integration References + +### `useUser` + + + +### `useUserBilling` + + + +### `useUserShipping` + + diff --git a/packages/core/docs/images/architecture.svg b/packages/core/docs/images/architecture.svg new file mode 100644 index 0000000000..9e2d033358 --- /dev/null +++ b/packages/core/docs/images/architecture.svg @@ -0,0 +1,3 @@ + + +
Composables
Composables
API Client
API Client
Factories (interfaces) & Utilities
Factories (interfaces) & Utilities
Utilities
Utilities
Factories & Utilities
Factories & Utilities
Vue Storefromt Core
@vue-storefront/core
Vue Storefromt Core...
Build / Routing
Build / Routing
Nuxt Module
@vue-storefront/nuxt(-theme)
Nuxt Module...
Data / Actions
Data / Actions
Composables
Composables
Composables
Composables
Nuxt Theme
Nuxt Theme
CMS
CMS
Payment
Payment
...
...
Data / Actions
Data / Actions
eCommerce
eCommerce
CMS
CMS
Payment
Payment
Components
Components
Storefront UI
@storefront-ui/vue
Storefront UI...
Configuration
Configuration
Middleware / SSR config
Configuration
Middleware / SSR config...
Nuxt Module
Nuxt Module
Extensions
Extensions
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/packages/core/docs/images/diagram-general.png b/packages/core/docs/images/diagram-general.png new file mode 100644 index 0000000000..5b5b4522df Binary files /dev/null and b/packages/core/docs/images/diagram-general.png differ diff --git a/packages/core/docs/images/faceting.jpg b/packages/core/docs/images/faceting.jpg new file mode 100644 index 0000000000..49f6372266 Binary files /dev/null and b/packages/core/docs/images/faceting.jpg differ diff --git a/packages/core/docs/images/general/vs-code-vetur-install.gif b/packages/core/docs/images/general/vs-code-vetur-install.gif new file mode 100644 index 0000000000..6a6b457f3e Binary files /dev/null and b/packages/core/docs/images/general/vs-code-vetur-install.gif differ diff --git a/packages/core/docs/images/general/vs-code-vetur.gif b/packages/core/docs/images/general/vs-code-vetur.gif new file mode 100755 index 0000000000..601ed1c430 Binary files /dev/null and b/packages/core/docs/images/general/vs-code-vetur.gif differ diff --git a/packages/core/docs/images/general/vue-js-devtools-install.gif b/packages/core/docs/images/general/vue-js-devtools-install.gif new file mode 100755 index 0000000000..d34b1e96f0 Binary files /dev/null and b/packages/core/docs/images/general/vue-js-devtools-install.gif differ diff --git a/packages/core/docs/images/general/vue-js-devtools.gif b/packages/core/docs/images/general/vue-js-devtools.gif new file mode 100755 index 0000000000..a6e9240eb2 Binary files /dev/null and b/packages/core/docs/images/general/vue-js-devtools.gif differ diff --git a/packages/core/docs/images/middleware-diagram.jpg b/packages/core/docs/images/middleware-diagram.jpg new file mode 100644 index 0000000000..5bb91adf66 Binary files /dev/null and b/packages/core/docs/images/middleware-diagram.jpg differ diff --git a/packages/core/docs/images/middleware-extensions.jpg b/packages/core/docs/images/middleware-extensions.jpg new file mode 100644 index 0000000000..995639058c Binary files /dev/null and b/packages/core/docs/images/middleware-extensions.jpg differ diff --git a/packages/core/docs/images/ssr-flow.drawio b/packages/core/docs/images/ssr-flow.drawio new file mode 100644 index 0000000000..59e9aa6b50 --- /dev/null +++ b/packages/core/docs/images/ssr-flow.drawio @@ -0,0 +1 @@ +7Vpde5owFP41XnaPgFB7OW2127qtm21dL1OIkBmJDcGP/fqdSAICVnGP2trZGzmHk8/3Pe8xsTWrPZp1ORoHX5mHac2se7OadVkzTbPumPAhPfPEYxim8viceImvnjl65A9WgdobEw9Hype4BGNUkHHe6bIwxK7I+RDnbJoPGzDq5Rxj5OOSo+ciWvb2iSeCxNs0zzP/NSZ+oEc2nIvkzQjpYLWSKEAemy65rKua1eaMieRpNGtjKndP70v/07xPb4ZO9/OP6Bndt77cfXs4SzrrbNMkXQLHodht1wrLCaKx2i+1VjHXG8hZHHpYdlKvWa1AjCg8GvD4GwsxV4CjWDBwMS4C5rMQ0RvGxipuwEKhwowG2Dj0PkpgwXYpiiLiJs4OoVSNApZq0QQrEpwNU/RklykUMpiiJ0xbyB36i6m2GWUcXoUsxLIrD+igVpNN7yrztirurkIhYjF38Zo4S5EccR+v689J4uT8lpiqsOtiNsKCzyGAY4oEmeTpjFRW+Glchjw8KPC3IIJVIsKtTCxoFS3m8BzjSAALXmaHxGUaEIF7Y7TYoCkoSp4xRSb4En6F4gaQtwNpgrnAs7Xbqt42VHJredPJPs20wrCUL1jSCd1u50A0SkA8gnz+d0kJMPP5r3RkMB6l8cHW5uVs+eXlXFmLdameq+pm1cx2Kma2YVRMbcXBM1iWbTs5ImoeVs5+1fstI7DWLIQNBhFMtsjKdBL/TlS7RNRvrMTTzUAfO5NTnmbUfMwx8w3z1Nyapxb8HYinWYwa3rKN/NBGQYOTBatWuye8s7ZEklBueSBtF7nwaXXKqh2w0VMcbS6UOQJKqnTQiFC57mtMJ1gQF61gPqLEDyXtgUaYr2Y2DElCHywns+4WueTkktHYY801C0A27Wo11ygCvrOi26yiZceuVFvU3LyWGa+oZRryfRTdi/p5/suf/dZrrt6NsgbB8VkgEkaLrfKjk/pUVh+rUVV+6vuSH83dJVh7kPlpQeE4iqlYI0hHevbSl0tv5vClvxEtAfETgAA6aygW103vDYhiSpj2AYG4bXb5w/2T3SPX435LsHsx/F7lXup0tHihHOMZEUvNwEqLODxnjaRxkBJuVi3h9YolXOvFAc8eZiFDirUg2YXS2WNzR87eDjErE6t8z9dDk0zYkmNMcoB57ypn119b5ard9Z2E7liEbsVZZV0SHsu9y3vRvvIFzinXqp7xda4ZS5mW5d1ec21dCu3uWuD1v1NYu8ora2d5BWb2+3YSnv2bgHX1Fw== \ No newline at end of file diff --git a/packages/core/docs/images/ssr-flow.jpg b/packages/core/docs/images/ssr-flow.jpg new file mode 100644 index 0000000000..5fa2ed32d1 Binary files /dev/null and b/packages/core/docs/images/ssr-flow.jpg differ diff --git a/packages/core/docs/images/templates.png b/packages/core/docs/images/templates.png new file mode 100644 index 0000000000..af8d7d0a1d Binary files /dev/null and b/packages/core/docs/images/templates.png differ diff --git a/packages/core/docs/images/theme/storefront-ui.jpg b/packages/core/docs/images/theme/storefront-ui.jpg new file mode 100644 index 0000000000..650c66dffb Binary files /dev/null and b/packages/core/docs/images/theme/storefront-ui.jpg differ diff --git a/packages/core/docs/images/vsfn.png b/packages/core/docs/images/vsfn.png new file mode 100644 index 0000000000..05c43d20e0 Binary files /dev/null and b/packages/core/docs/images/vsfn.png differ diff --git a/packages/core/docs/integrate/cache-driver.md b/packages/core/docs/integrate/cache-driver.md new file mode 100644 index 0000000000..6e40cfa514 --- /dev/null +++ b/packages/core/docs/integrate/cache-driver.md @@ -0,0 +1,94 @@ +# Integrating cache driver + +::: warning Want to build an integration? +If you want to integrate with Vue Storefront, don't hesitate to get in touch with the core team on our [Discord](https://discord.vuestorefront.io/) server. We are eager to help you to ensure its high quality and maybe even officially recommend it 😉 +::: + +## Introduction + +::: warning Before you get started +This page assumes you are familiar with caching in Vue Storefront. Please [see this page](../advanced/ssr-cache.md) for more information. +::: + +Cache driver is not a standalone plugin, but an extension depending on `@vue-storefront/cache` module that does the heavy lifting. + +It's a function that returns an object containing the following properties. + +```javascript +function CacheDriver (options) { + const client = new Driver(options); + + return { + async invoke({ route, context, render, getTags }) {}, + + async invalidate({ request, response, tags }) {} + }; +}; +``` + +## Invoking driver + +`invoke` method is called every time a route is rendered on the server. It's called with following parameters: + +* `route` (object) - contains information about the current route; +* `context` (object) - contains information about the current context; +* `render` (function) - function to render the page, returns a Promise with HTML; +* `getTags` (function) - function to get all tags generated during render. Must be called after `render`; + +Because calling `render()` is computationally expensive, it's the step we want to avoid. For this reason, `invoke` should work like this: + +```javascript +{ + async invoke({ route, render, getTags }) { + const key = `page:${ route }`; + const cachedResponse = await client.get(key); + + if (cachedResponse) { + return cachedResponse; + } + + const content = await render(); + const tags = getTags(); + + if (!tags.length) { + return content; + } + + await client.set( + key, + content, + tags + ); + + return content; + } +} +``` + +## Invalidating cache + +`invalidate` method is called when the cache invalidation page is visited. It is called with following parameters: + +* `request` (object) - Node.js HTTP request object; +* `response` (object) - Node.js HTTP response object; +* `tags` (array) - Array containing tags to invalidate; + +By default, `@vue-storefront/cache` calls it and returns an empty response with `200 OK` or in case of error `500 Internal Server Error` HTTP code. + +`response` object passed to it can optionally be modified to add body (like JSON or a text) and custom HTTP headers. + +This method should be able to delete all tags when `*` is one of the keys passed to the `tags` array. + +```javascript +{ + async invalidate({ request, response, tags }) { + if (tags.includes('*')) { + // invalidate all tags + await client.invalidateAll(); + } else { + // invalidate provided tags + await client.invalidate(tags); + } + } +} +``` diff --git a/packages/core/docs/integrate/checklist.md b/packages/core/docs/integrate/checklist.md new file mode 100644 index 0000000000..eb7cb78dd8 --- /dev/null +++ b/packages/core/docs/integrate/checklist.md @@ -0,0 +1,86 @@ + # Integration checklist + +When developing an integration, a good way to find if you have a complete integration, is by checking this checklist if is missing any implementation. + +Fell free to copy this checklist and past it on your repository as a current issue, so you can have a track of what is needed to be done. + +## Developing an integration checklist + +### Repository +```md +- [ ] I've added `ISSUE_TEMPLATES` to my repository +- [ ] I've added workflows for Auto-Publishing on my repository +- [ ] I've added the `CONTRIBUTING.md` file to my repository +- [ ] I've added the `CODE_OF_CONDUCT.md` file to my repository +- [ ] I've added instructions on how to start the project for new contributors on the `README.md` file +- [ ] I've added the `eslint` rules to my repository +``` + +### Docs +```md +- [ ] I've created the initial documentation of my integration +- [ ] I've added information on how to contribute on the integration +- [ ] I've added information on how to contribute on the documentation +- [ ] I've added information on how to use the integration in a new project in my documentation +- [ ] I've created the documentation for the `api-client` methods +- [ ] I've created the documentation for the `composables` composable +- [ ] I've created the documentation for the `composables` getters +- [ ] I've created the documentation for the `composables` nuxt module options +- [ ] I've created the documentation for the `theme` +``` +### API-Client +```md +- [ ] I've created a `index.server.ts` that exposes the required methods +- [ ] I've created the `index.ts` that exposes the methods, types and interfaces of the `api-client` +- [ ] I've implemented the required methods for the user to be able to use the integration +- [ ] I've implemented the required typings for all the **API** methods that are being exposed +- [ ] My implementation follows the process of GDPR or similar laws +- [ ] My implementation is not exporting to the frontend packages and services required only by the backend +- [ ] I've configured the `rollup.config.js` to match the correct configuration for the `index.server.ts` and `index.ts` +- [ ] I've created unit testing for the `api-client` methods +- [ ] I've created integration tests for the `api-client` methods +``` + +### Composables +```md +- [ ] I've created the basic composable for the integration +- [ ] I've created the basic getters for the integration +- [ ] I've created the **Nuxt** module for the integration +- [ ] I've created unit testing for the `composables` methods +- [ ] I've created unit testing for the `composables` getters +- [ ] I've created integration tests for the `composables` methods +``` +### Theme +```md +- [ ] I've added the information on how to start using the integration on the `README.md` file +- [ ] My theme follows the principles of Storefront UI +- [ ] I've created E2E tests for the implementation +- [ ] I've created unit testing for the `theme` components +- [ ] I've created integration tests for the `theme` components +``` + +## Publishing an integration checklist + +### Repository +```md +- [ ] The packages dependencies and devDependencies are updated +``` +### Docs +```md +- [ ] The docs have been updated with the latest changes +``` +### API-Client +```md +- [ ] The packages dependencies and devDependencies are updated +- [ ] The version of `api-client` follows the SemVer model, and has been bumped +``` +### Composables +```md +- [ ] The packages dependencies and devDependencies are updated +- [ ] The version of `composables` follows the SemVer model, and has been bumped +``` +### Theme +```md +- [ ] The packages dependencies and devDependencies are updated +- [ ] The version of `theme` follows the SemVer model, and has been bumped +``` diff --git a/packages/core/docs/integrate/cms.md b/packages/core/docs/integrate/cms.md new file mode 100644 index 0000000000..d40e7c381b --- /dev/null +++ b/packages/core/docs/integrate/cms.md @@ -0,0 +1,133 @@ +# Integrating CMS + +::: warning Want to build an integration? +If you want to integrate with Vue Storefront, don't hesitate to get in touch with the core team on our [Discord](https://discord.vuestorefront.io/) server. We are eager to help you to ensure its high quality and maybe even officially recommend it 😉 +::: + +## What is needed + +Here are the things that are expected from CMS integration: + +### Configuration + +This part is usually either using CMS JavaScript SDK under the hood or calls the API directly. Config should contain at least the API endpoint and credentials. + +- a configuration via `setup` method for non-Nuxt apps +```js +// public API +setup(config) +``` +- a Nuxt module that runs the `setup` method for Nuxt apps +```js +// public API +['@vue-storefront/{cms}/nuxt', config] +``` + +### Content fetching + +Another (and the most important) thing that we need is a composable to fetch the content from our CMS. + +```js +// public API +const { search, content, loading, error } = useContent() +``` + +To create this composable you should use `useContentFactory` from Vue Storefront core. + +```ts +// integration +import { useContentFactory } from '@vue-storefront/core' + +// CONTENT and CONTENT_SEARCH_PARAMS are your CMS-specific types, we advise to have at least 'id' param for search +const useContent = useContentFactory({ + // (params: CONTENT_SEARCH_PARAMS) => Promise; + search (params) { + // write your content fetching logic here + return content + } +}) +``` +The factory will output a composable fulfilling our interfaces. That's all you have to write in terms of content fetching. + +### Content rendering (optional) + +Many CMS systems allow to control the page layout by returning a list of components as a JSON file. These components are then rendered in the application. The component that is mapping the `content` object into Vue components and renders them is called `RenderContent`. + +```html + +``` + +Now you need to prepare special structure for your components. Create the `extractComponents` function that will filter or modify all the metadata and return component name along with props. Follow the exampole. + +```typescript +function extractComponents(response: ResponseFromYourCMS) { + // filter or modify your response if needed + return { + componentName: string + props: {} + }[] +} +``` + +Pass it to the `RenderContent` component as a `content` prop. To register components use computed value. + +```vue + + + + +``` + +Inside your application you'll also need to register your UI components that willing to render. + +## Usage example in the real application + +```html + + + +``` diff --git a/packages/core/docs/integrate/good-practices.md b/packages/core/docs/integrate/good-practices.md new file mode 100644 index 0000000000..0c3535ae5b --- /dev/null +++ b/packages/core/docs/integrate/good-practices.md @@ -0,0 +1,98 @@ +# Good practices + +When dealing with an open-source project, you have to remember that the project is not just for you or your team, +there will be a lot of developers using it too. + +There are some good practices that you can establish in our repository. + +## README files +All of your packages must contain a `README.md` file. This file must have some basic information like: + +- What is this package for? +- Who made this package? +- How I can install this package? +- How I can use this package? +- How I can contribute? Or link to the `CONTRIBUTING.md`; + +### Adding the README to all the Packages +It's always good to add your `README.md` file to the packages in your integration. +So when people are looking on NPM, they can see a more detailed information about the project. +You can copy and paste the main README.md file from the root folder, or create a new one from each package. + +### Displaying the contributors of the project + +In your `README.md` you can use it to display the names of the contributors of the project. You can use the [all-contributors](https://github.com/all-contributors/all-contributors) CLI to help you with this. + +If you are using the `ecommerce-integration-boilerplate` in your project, there are some commands already prepared to use in conjunction with `all-contributors` CLI. + +#### Adding a new contributor + +You can add a new contributor to the project, by running the command `yarn contributors:add` on the root directory of your project. This will bring the `all-contributors` CLI, and there you can fill the information for the new contributor. + +#### Generating the contributors list + +You can always refresh the contributors list, you need to execute the command `yarn contributors:generate` on the root directory of your project. Then the `README.md` file on the root directory, will be automatically updated to reflect the changes made to the `.all-contributorsrc` file. + +## Roadmaps +When creating new integrations, defining a roadmap can take some time and is not easy as people think. But there are some ways to develop essential roadmaps for the community, pointing out your future steps of the integration and help newcomers. + +### Creating a roadmap +There are two ways of developing a roadmap inside your repository. You can create a pinned issue for each planned roadmap, or create a GitHub Projects for the roadmap. Here we will teach you how to create both of the implementation. + +#### GitHub Projects +On your repository, enable the **Projects** feature. Then go to the **Projects** tab and create a new **Project** with the title `Roadmap`, and your can add the description you want. Choose the **Template** as `None`, and create your new **Project** board. + +Then you will be asked to add new columns. Here you add the first column as `F.A.Q`, and the second one as your first release on the roadmap. Then, if you want to create more columns for future releases and planing the future roadmap of your integration, fell free to add. + +When creating a new card, you can use this template as a base for the card: + +```md +#### {Group Title} +----- +- [ ] {Feature to be implemented or Bug to be fixed} +... +``` + +For the `F.A.Q` column, you can use this template as a base: + +```md +#### Q: {Question} +**A:** {Answer} +``` + +#### Pinned Issue +On your repository **Issues** list, create a new **issue** with the title `[ROADMAP] {INTEGRATION NAME} - {NEXT RELEASE}`, then pin the issue. + +Inside the issue, you need to point what is going to be developed for the next release. If there are open issues, you can link those issues to each topic of the roadmap. For example: + +```md +title: [ROADMAP] {INTEGRATION NAME} - {NEXT RELEASE} +------- + +This roadmap includes all the development and enhancements needed for releasing a stable version of the {INTEGRATION NAME} integration + +This roadmap is mutable and can be updated in the process. + +Follow the issue to receive notifications on each change. + +## F.A.Q + +#### Q: {Question} +**A:** {Answer} + +------- + +## Roadmap + +### Theme +- Fix the XYZ bug | #{ISSUE NUMBER} +- Add a new feature | #{ISSUE NUMBER} + +### Composables +- Change the getter structure | #{ISSUE NUMBER} +- Add a new feature to composable | #{ISSUE NUMBER} + +### API-Client +- Update the server middleware | #{ISSUE NUMBER} +- Add a new feature | #{ISSUE NUMBER} +``` diff --git a/packages/core/docs/integrate/integration-guide.md b/packages/core/docs/integrate/integration-guide.md new file mode 100644 index 0000000000..39ca5ebb64 --- /dev/null +++ b/packages/core/docs/integrate/integration-guide.md @@ -0,0 +1,372 @@ +# Integration guide + +:::danger Don't forget to reload the application +The application does not reload automatically after saving the changes in Server Middleware. Due to this, you have to restart the application manually. We are working on enabling Hot Reloading in future updates. +::: + +::: warning Want to build an integration? +If you want to integrate with Vue Storefront, don't hesitate to get in touch with the core team on our [Discord](https://discord.vuestorefront.io/) server. We are eager to help you to ensure its high quality and maybe even officially recommend it 😉 +::: + +## Introduction + +Integrating an eCommerce platform with Vue Storefront sounds scary. Luckily, many of our partners and community members with different seniority levels have successfully done it. We are sure that even without prior experience with Vue Storefront, you can too. + +This document will guide you through the process of creating integration and explain the concepts behind Vue Storefront. + +## Requirements + +Before we get started, make sure that: + +- platform you want to integrate has REST or GraphQL API, +- you have installed [Node 10+](https://nodejs.org/en/), [Yarn 1](https://classic.yarnpkg.com/lang/en/) and [Git](https://git-scm.com/), +- you are familiar with JavaScript and TypeScript, +- you are familiar with [Composition API](../guide/composition-api.html) and [Composables](../guide/composables.html). + +## Project structure + +To make it easy to get started, we created an [eCommerce integration boilerplate](https://github.com/vuestorefront/ecommerce-integration-boilerplate). + +It's a monorepo, which is a single repository containing multiple related projects. Each directory inside `packages` contains one project. There are three projects: + +- `api-client`, +- `composables`, +- `theme`. + +### API client + +`api-client` is the **_server layer_** that extends our [Server Middleware](../advanced/server-middleware.html). It creates an API client (like `Apollo` for GraphQL or `Axios` for plain HTTP) that communicates with your eCommerce platform. It acts as a proxy between the users and the platform. + +Here, you will create new endpoints that accept parameters sent from the frontend and use them to fetch or submit data to the platform. + +### Composables + +`composables` consists of two parts: + +- [Composables](../guide/composables.html) manage the state, prepare and send the request to the `api-client`, then save the response. If necessary, they also modify the response to simplify getters. + +- [Getters](../guide/getters.html) extract data from API responses provided by `composables` and return them in formatted and agnostic form. + +Here, you will create new methods for `composables` to fetch the data and new `getters` to extract different pieces of information from that data. + +### Theme + +This project is a template for generating new Vue Storefront shops. It's a [Nuxt.js](https://nuxtjs.org/) application that contains pages, Vue components, and assets. It uses `composables` to interact with the platform and `getters` to display the data to the user. + +Out of the box, the `theme` project doesn't contain much - just a few configuration files and empty directories. However, this doesn't mean that you have to create the whole theme from scratch. When your integration is ready, you can use our CLI to combine this project with our base theme to generate a new Nuxt.js application with all necessary pieces inside. + +Here, you will create new components, scripts, and assets to override or extend our base theme. + +## Scope + +The default theme in Vue Storefront comes with support for many functionalities out of the box, which is excellent if you don't want to deal with the UI or styling. However, this comes at a cost. The fewer functionalities your platform supports, the more overriding it requires. + +It's hard to list all functionalities your platform should support. Still, you can get a general idea by browsing individual composables in the [`packages/composables/src` folder](https://github.com/vuestorefront/ecommerce-integration-boilerplate/tree/master/packages/composables/src) in the boilerplate repository. For example, [`useCart` composable](https://github.com/vuestorefront/ecommerce-integration-boilerplate/blob/master/packages/composables/src/useCart/index.ts) has the following handlers: + +- `load`, +- `addItem`, +- `removeItem`, +- `updateItemQty`, +- `clear`, +- `applyCoupon`, +- `removeCoupon`, +- `isInCart`. + +API of your platform should have endpoints for most of these operations unless some can be performed on the frontend. One such example would be `isInCart`, which accepts `currentCart` and `product` as parameters. In most cases, this is enough information to check if the product is already in the cart without calling the API. + +## Getting started + +### Fork boilerplate repository + +Now that we explained the basics, let's start creating an integration. Open the [eCommerce integration boilerplate repository](https://github.com/vuestorefront/ecommerce-integration-boilerplate) and click the `Use this template` button. This creates a copy of a repository and allows you to make changes without affecting the original project. Enter the name of the new repository and click `Create repository from template`. + +Once the new repository is ready, clone it locally. + +### Name your project + +**Before you start making changes and installing dependencies**, let's update the name of the packages and integrations. Doing it now prevents issues with linking dependencies later. + +Open the "Search and Replace" tool inside your code editor and replace all instances of `<% INTEGRATION %>` with the name of your project or platform. The name must be in lowercase, without spaces or any special characters. + +### Install dependencies + +After renaming all packages, we can safely install dependencies and not worry about dependencies linking. + +Open the terminal in the root of the repository and run: + +```bash +yarn install +``` + +### Test it + +Once dependencies are installed, run `yarn build`, then `yarn dev`. Open the link shown in the terminal and test the page to confirm it's working. + +Since we are mocking all functionalities in the boilerplate, different parts of the application might not update properly (e.g., the cart). However, you should not see any errors in the terminal or browser console when you open different pages and click various buttons. You might see some warnings about missing translations (starting with `[vue-i18n]`), but you don't have to worry about them now. + +## Connect to the platform + +Let's start by creating an API client that will communicate with the eCommerce platform. As mentioned above, the `api-client` does precisely that, so this is the project to update. + +### Structure of the `api-client` project + +You will see only two files and one empty directory when you open the `packages/api-client/src` folder. That's not a lot, considering how much code some Node.js servers need, but thanks to abstractions we created, you don't need more. So what are these files for? + +- `index.ts` is a file that should **not** contain any server-side code but export things that `composables` or `theme` projects might need. Great examples are integration-specific TypeScript types for request and response bodies or helper functions. +- `index.server.ts` is a file that contains server-side code. Inside of it `apiClientFactory` creates `createApiClient` method and exports it. Server Middleware calls this method on every request to create a fresh API client and to handle integration-specific endpoints. + +### Add API client + +API client is a library that handles sending requests to the eCommerce platform and parsing its responses. + +:::warning +Examples below use `axios` to handle HTTP requests. However, you can use other libraries if your platform uses GraphQL or has dedicated clients. +::: + +In terminal, go to `packages/api-client` and install `axios`: + +```bash +cd packages/api-client +yarn add axios +``` + +Now in the code editor, open `packages/api-client/src/index.server.ts`. Inside of it, there is the `onCreate` method. + +`onCreate` accepts the `settings` parameter, which is a configuration provided in `packages/theme/middleware.config.js`. By default, it's an empty object but can be any configuration you need. + +`onCreate` must return an object with at least `config` and `client` properties but it can have any number of custom properties if needed. This object is later available in API endpoints. + +Let's update the `onCreate` method to create and return a new Axios instance. + +```typescript +// packages/api-client/index.server.ts +import axios from 'axios'; + +const onCreate = (settings) => { + const client = axios.create({ + baseURL: settings.api.url + }); + + return { + config: settings, + client + }; +}; +``` + +In the example above we passed `settings.api.url` to `axios.create`, but it's not defined in `middleware.config.js`. Let's add it: + +```javascript +// packages/theme/middleware.config.js +module.exports = { + integrations: { + sloth: { // name of your integration + location: '@sloth/api/server', // name of your api-client package followed by `/server` + configuration: { + api: { + url: '' // URL of your eCommerce platform + } + } + } + } +}; +``` + +## Implement `useProduct` functionality + +It would be impossible to write a tutorial for implementing every composable because some might differ wildly between the platforms. For this reason, we will explain how they work and show you how to implement them on the example of `useProduct` composable, `getProduct` API endpoint, and `productGetters`. + +### Understand composables + +Before implementing any composable, you should get familiar with its TypeScript interfaces. + +Let's start with the [UseProduct interface](../core/api-reference/core.useproduct.html) (note the capital `U`). It uses [Typescript generics](https://www.typescriptlang.org/docs/handbook/2/generics.html). The reason is that we want to provide an excellent development experience by providing types for the data stored in and returned from composables. However, each platform has a unique data structure, and we don't want to make any assumptions. That's why it's up to integrators to provide the types. + +`UseProduct` accepts two generics: +- `PRODUCTS` represents the structure of the products returned by the API, +- `PRODUCT_SEARCH_PARAMS` represents parameters accepted by the `search` method. + +It also has three properties and a method called `search`. Fortunately, we don't have to create them ourselves for every composable. All composables are created using [factories](https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)), which accept an object that holds the logic for composable methods. **Factory will take care of creating all properties, methods and even handling errors and loading state, so you can focus on integrating it with the API.** + +### Implement `useProduct` composable + +Now, when we understand how composables are created, let's see what parameters the `useProduct` factory expects. Because this composable is relatively small and has only one method, the [UseProductFactoryParams interface](../core/api-reference/core.useproductfactoryparams.html) also expects one handler - `productsSearch`. + +Open `packages/composables/src/useProduct/index.ts`. This file already calls `useProductFactory` and passes `params` matching the above interface. With this done, the only thing left is to implement `productsSearch` method. + +Every method in `factoryParams` has at least one argument called [context](../core/api-reference/core.integrationcontext.html). Second, optional argument is an object holding parameters passed to composable method and [customQuery](../core/api-reference/core.customquery.html). + +Remove placeholder code from `productsSearch` method and add the following: + +```typescript +// Replace `sloth` with the name of your package defined in `packages/composables/nuxt/plugin.js` +const data = await context.$sloth.api.getProduct(params); + +return data; +``` + +This method calls an API endpoint called `getProduct`. It doesn't exist yet, so let's create it. + +:::tip Passing parameters to the API +An HTTP request is sent to the Server Middleware whenever you call any method in the `context.$sloth.api` object. Additionally, all parameters passed to them will be included in the payload. Sending too much data may result in poor performance, so try to pass as few parameters as possible. +::: + +:::tip Composable dependencies +Sometimes you need to use composable as a dependency inside another one. You can access these composables in the `provide` function in the factory params. This function is called when composable is created and returned data is available in the `context` object. + +```ts +import { useCart } from '@vue-storefront/commercetools'; + +interface UserContext extends Context { + setUser: (user) => void; +} + +const factoryParams: UseUserFactoryParams = { + provide() { + return useCart(); + }, + load: async (context: UserContext) => { + const { data } = await context.$ct.api.getUser(); + + context.setCart(data.activeCart); + + return data.user; + }, + +}; +``` +::: + +### Understand `api-client` + +In the previous section, we added a call to the `getProduct` endpoint. Before we implement this endpoint, you should understand why we need `api-client` in the first place. + +As mentioned in [Project structure](#project-structure) section, `api-client` is a server that acts as a proxy. All requests to and from various APIs pass through it. You might be wondering why we choose this architecture. Theoretically, calling APIs directly from the browser would result in a better performance. + +While this might be true for simple scenarios, it doesn't scale well. Some of the benefits of using such proxy are: + +- **Caching** - selected responses can be cached to improve the performance significantly. While it's possible to do caching in the browser, each customer would have to make at least one request to a given endpoint. +- **Lower cost** - when responses are cached, fewer requests are sent to the eCommerce platform. Depending on the provider, it might reduce the cost. +- **Smaller bundle** - with all the clients installed and configured on the server, the final bundle sent to the browsers can be much smaller. This is especially true for APIs based on GraphQL. +- **Security** - API configuration, secrets, and keys are stored on the server and are not sent to the browser. + +### Implement `getProduct` endpoint + +Create new file called `getProduct.ts` in `packages/api-client/src/api` folder. Inside of it, add the following function: + +```typescript +export async function getProduct(context, params) { + +} +``` + +This function has two arguments: +- `context` which includes: + - `config` - integration configuration, + - `client` - API client created in `packages/api-client/src/index.server.ts`, + - `req` - HTTP request object, + - `res` - HTTP response object, + - `extensions` - extensions registered within integration, + - `customQueries` - custom GraphQL queries registered within integration (used only with GraphQL), + - `extendQuery` - helper function for handling custom queries (used only with GraphQL). +- `params` - parameters passed from composable. + +We can call platform API using `config` and `client` properties in `context` and data from `params`. + +In the example below we use `axios` instance created before to call `products` API. This is just an example and you should modify it to fit your integration: + +```typescript +export async function getProduct(context, params) { + // Create URL object containing full endpoint URL + const url = new URL('/products', context.config.api.url); + + // Add parameters passed from composable as query strings to the URL + params.id && url.searchParams.set('id', params.id); + params.catId && url.searchParams.set('catId', params.catId); + params.limit && url.searchParams.set('limit', params.limit); + + // Use axios to send a GET request + const { data } = await context.client.get(url.href); + + // Return data from the API + return data; +} +``` + +Every new API handler must be added to the `apiClientFactory` in `packages/api-client/src/index.server.ts` to be available. + +```typescript +// packages/api-client/src/index.server.ts +import { getProduct } from './api/getProduct'; + +// Unrelated code omitted + +const { createApiClient } = apiClientFactory({ + onCreate, + api: { + getProduct + } +}); +``` + +### Understand getters + +Now that we can request and save API data, we need to display it to the user. Every integration can use and extend our base theme for that. However, accessing the data straight from integrations isn't possible because each uses unique data structures. To do that, we need getters. + +Getters allow us to take raw responses from the API and map or extract data to a standard format, which we use in the template. + +### Implement `productGetters` + +Open `packages/composables/src/getters/productGetters.ts`. There is a bunch of functions returned in a single object. Each of them has at least one argument: either a raw response from the API (`products`) or a single item extracted from it (`product`). + +Although types of arguments are unknown and specific to your integration, return types are already defined and must match those defined in [ProductGetters interface](../core/api-reference/core.productgetters.html). + +You need to implement all of these functions and, if necessary, add your own. + +:::tip What if data is not available? +We recommend that you always add fallback value in getters. This will prevent errors during Server-Side Rendering and avoid some edge cases if data or nested properties are not available, e.g., still being retrieved from the backend. + +```typescript +export const getProductName = (product: ProductVariant): string => { + // Return empty string if "product" object or "name" property are not available + return product?.name || ''; +}; +``` +::: + +## Create a theme + +Some forms or checkout components are blank in the default theme because they display or modify integration-specific data. For this reason, you need to create few Vue components and JavaScript files: + +| Component | Props | Emits event | +|----------------------------------------------|-------------------------------------|-------------| +| components/UserBillingAddress.vue | { address: Object } | | +| components/UserShippingAddress.vue | { address: Object } | | +| components/Checkout/CartPreview.vue | | | +| components/MyAccount/BillingAddressForm.vue | { address: Object, isNew: Boolean } | ✔ | +| components/MyAccount/ShippingAddressForm.vue | { address: Object, isNew: Boolean } | ✔ | +| components/MyAccount/PasswordResetForm.vue | | ✔ | +| components/MyAccount/ProfileUpdateForm.vue | | ✔ | +| composables/useUiHelpers/index.ts | | | +| middleware/checkout.js | | | +| middleware/is-authenticated.js | | | + +### Create Vue components + +Form components emit events in the following format: + +```js +emit('submit', { + form: Object, + onComplete: (data: any) => {}, + onError: (error: Error) => {} +}) +``` + +When such an event is sent, the application will handle communication with the API. If the request is successful, the `onComplete` callback will be called with the response from the API. Otherwise, `onError` will be called with the error caught. + +### Create a middleware + +`checkout` and `is-authenticated` middlewares are used to prevent access to selected pages. + +Please refer to [Nuxt.js middleware documentation](https://nuxtjs.org/docs/2.x/directory-structure/middleware/) for more information. diff --git a/packages/core/docs/integrate/supporting-custom-queries.md b/packages/core/docs/integrate/supporting-custom-queries.md new file mode 100644 index 0000000000..41a6251797 --- /dev/null +++ b/packages/core/docs/integrate/supporting-custom-queries.md @@ -0,0 +1,52 @@ +# Supporting custom GraphQL queries + +:::warning +This functionality is only available to integrations using GraphQL to communicate with the platform API. +::: + +Unlike REST APIs, which return most or all available data on every request, GraphQL allows us to specify what information we want to get. It's great for performance, but developers will likely need data that you didn't include in the default GraphQL query. + +Vue Storefront provides a way to dynamically change the default GraphQL queries for selected API requests as described in the [Extending GraphQL Queries](../advanced/extending-graphql-queries.html) document. + +The name of the custom query is available within the second argument in composable factory methods. It must be passed as the last parameter when calling an API method. + +```ts +// packages/src/composables/src/useProduct +import { ComposableFunctionArgs } from '@vue-storefront/core'; + +const productFactoryParams: UseProductFactoryParams = { + async productSearch (context: Context, params: ComposableFunctionArgs) { + // Extract `customQuery` from the rest of parameters + const { customQuery, ...searchParams } = params; + + // Change "yourIntegration" to the name of the integration + const product = await context.$yourIntegration.api.getProduct(searchParams, customQuery); + } +} +``` + +In `api-client` API methods, you can call `context.extendQuery` with `customQuery` parameter, default query, and default variables. It will return queries and variables to use in the GraphQL client. + +```ts +// packages/src/api-client/src/api/getProduct +import { CustomQuery } from '@vue-storefront/core'; + +const getProduct = async (context, params, customQuery: CustomQuery) => { + // Load and parse default and custom query + const { products } = context.extendQuery( + customQuery, + { + products: { + query: defaultQuery, + variables: defaultVariables + } + } + ); + + // Pass query and variables to GraphQL client + return context.client({ + query: gql`${products.query}`, + variables: products.variables, + }); +}; +``` diff --git a/packages/core/docs/integrations/README.md b/packages/core/docs/integrations/README.md new file mode 100644 index 0000000000..9f1f6c4ecb --- /dev/null +++ b/packages/core/docs/integrations/README.md @@ -0,0 +1,31 @@ +# Integrations + +This page lists Vue Storefront 2 integrations created by the Vue Storefront team or our partners. If integration you are interested in isn't listed below, [contact our Sales team](https://www.vuestorefront.io/contact/sales) or core team on our [Discord](https://discord.vuestorefront.io) server. + +:::tip Vue Storefront Enterprise +Packages marked as `Enterprise` are part of the [Vue Storefront Enterprise](/general/enterprise.html) offering. +::: + +## eCommerce platforms + +Below you can find a list of integrations with eCommerce platforms. These integrations are the backbone of every Vue Storefront project. + + + + +## Other integrations + +Below you can find a list of optional integrations. While not strictly necessary, they provide great functionalities and significantly shorten time-to-market. + +:::warning Filter results by eCommerce platform +Some integrations are available only for selected eCommerce platforms. Use the dropdown below to filter integrations by supported eCommerce platforms. +::: + + + diff --git a/packages/core/docs/integrations/adyen.md b/packages/core/docs/integrations/adyen.md new file mode 100644 index 0000000000..b1fa9dd40c --- /dev/null +++ b/packages/core/docs/integrations/adyen.md @@ -0,0 +1,249 @@ +# Adyen + +>This feature is part of the Enterprise version. Please [contact our Sales team](https://www.vuestorefront.io/contact/sales) if you'd like to use it in your project. + +## Introduction + +This package provides integration with [Adyen](https://www.adyen.com/). For more information about payment integrations, please refer to the [payment providers](../guide/checkout.html#payment-providers) page. + +## Installation + +1. Install required packages: + +```sh +yarn add @vsf-enterprise/adyen +``` + +2. Add `@vsf-enterprise/adyen` to raw sources: +```js +// nuxt.config.js + +export default { + buildModules: [ + ['@vue-storefront/nuxt', { + coreDevelopment: true, + useRawSource: { + dev: [ + '@vue-storefront/commercetools', + '@vue-storefront/core', + '@vsf-enterprise/adyen' + ], + prod: [ + '@vue-storefront/commercetools', + '@vue-storefront/core', + '@vsf-enterprise/adyen' + ] + } + }] + ] +}; +``` + +3. Register `@vsf-enterprise/adyen/nuxt` module with following configuration: + +```js +// nuxt.config.js + +export default { + modules: [ + ['@vsf-enterprise/adyen/nuxt', { + availablePaymentMethods: [ + 'scheme', + 'paypal' + ], + clientKey: '', + environment: 'test', + methods: { + paypal: { + merchantId: '', + intent: 'capture' + } + } + }] + ] +}; +``` + +* `availablePaymentMethods` - An array of available payment methods. There are [available values](https://docs.adyen.com/payment-methods). +* `clientKey` - Your client's key. There is information [how to find it](https://docs.adyen.com/development-resources/client-side-authentication#get-your-client-key). +* `environment` - `test` or `live` +* `methods.paypal`: + * `intent`: `capture` to take cash immediately or `authorize` to delay a charging. + * `merchantId`: 13-chars code that identifies your merchant account. There is information [how to find it](https://www.paypal.com/us/smarthelp/article/FAQ3850). + + +4. Add `@vsf-enterprise/adyen/server` integration to the middleware with the following configuration: +```js +// middleware.config.js + +adyen: { + location: '@vsf-enterprise/adyen/server', + configuration: { + ctApi: { + apiHost: '', + authHost: '', + projectKey: '', + clientId: '', + clientSecret: '', + scopes: [ + 'manage_orders:', + 'manage_payments:' + ] + }, + adyenMerchantAccount: '', + origin: 'http://localhost:3000', + buildRedirectUrlAfterAuth (paymentAndOrder, succeed) { + let redirectUrl = `/checkout/thank-you?order=${paymentAndOrder.order.id}`; + if (!succeed) { + redirectUrl += '&error=authorization-failed'; + } + return redirectUrl; + }, + buildRedirectUrlAfterError (err) { + return '/?server-error'; + } + } +} +``` + +* `configuration`: + * `ctApi` - You need `manage_orders` and `manage_payments` scopes to make it work properly, base on [that page](../commercetools/getting-started.html#configuring-your-commercetools-integration) during configuring this property. Then for `apiHost` you have to use only the base URL - `https://.com/` instead of `https://.com/vsf-ct-dev/graphql` + * `adyenMerchantAccount` - Name of your Adyen's merchant account + * `origin` - URL of your frontend. You could check it by printing out `window.location.origin` in the browser's console on your website. + * `buildRedirectUrlAfterAuth` - `(paymentAndOrder: PaymentAndOrder, succeed: boolean) => string` - A method that tells the server where to redirect the user after coming back from payment gateway. You can test it with [these cards](https://docs.adyen.com/development-resources/test-cards/test-card-numbers#test-3d-secure-authentication). + * `buildRedirectUrlAfter3ds1Auth` - deprecated in favor of `buildRedirectUrlAfterAuth` + * `buildRedirectUrlAfterError` - `(err: Error) => string` - A method that tells the server where to redirect the user if error has been thrown inside `cardAuthAfterRedirect` controller. + * `buildRedirectUrlAfter3ds1Error` - deprecated in favor of `buildRedirectUrlAfterError` + +```ts +type PaymentAndOrder = Payment & { order: Order } +``` + +5. Add an `origin` to the allowed origins in Adyen's dashboard. You can do it in the same place where you looked for the `clientKey`. + +6. Commercetools shares [Adyen integration](https://github.com/commercetools/commercetools-adyen-integration). We recommend to deploy it as a Google Function or an AWS Lambda. Make sure to configure and deploy both [extension](https://github.com/commercetools/commercetools-adyen-integration/tree/master/extension) and [notification](https://github.com/commercetools/commercetools-adyen-integration/tree/master/notification) module. Check readme of [the repository](https://github.com/commercetools/commercetools-adyen-integration) for details. + +:::warning Bigger permissions for extensions +As you can see in `commercetools-adyen-integration` repository, commercetools recommends to use `manage_project` scope for both notification and extension module. +::: + +7. Use `PaymentAdyenProvider.vue` as a last step of the checkout process. This component will mount Adyen's Web Drop In and handle payment process for you. +```vue + +``` + +`afterPay` is called just after payment is authorized and order placed. There you can make actions like redirecting to the thank you page and clearing a cart. +```js +const afterPayAndOrder = async ({ order }) => { + context.root.$router.push(`/checkout/thank-you?order=${order.id}`); + setCart(null); +}; +``` + +### Paypal configuration +Configuration of PayPal is well-described in [Adyen's documentation](https://docs.adyen.com/payment-methods/paypal/web-drop-in). + +### Klarna configuration +To enable Klarna, you have to add a new payment method in Adyen's dashboard. Then you should add specified methods to the `availablePaymentMethods` array in `nuxt.config.js`: +```js +// nuxt.config.js + +export default { + modules: [ + ['@vsf-enterprise/adyen/nuxt', { + availablePaymentMethods: [ + 'scheme', + 'paypal', + 'klarna', + 'klarna_account', + 'klarna_paynow' + ], + // ... + }] + ] +}; +``` + +Read [Adyen's document about the Klarna](https://docs.adyen.com/payment-methods/klarna#supported-countries) to check which Klarna payment methods are available for individual countries. + +:::warning Phone number +If your users can provide a phone number then make sure it is with **area code**. Otherwise, Klarna will throw an error because of an improper phone number format. +::: + + +## API +`@vsf-enterprise/adyen` exports a *useAdyen* composable. +`@vsf-enterprise/adyen/src/PaymentAdyenProvider` exports a [VSF Payment Provider](../commercetools/getting-started.html#configuring-your-commercetools-integration) component as a default. + +## Composable +`useAdyen` composable returns a few properties and methods. + +#### Properties +* `error` - Computed<_AdyenError_> - errors' state of asynchronous methods. +* `loading` - Computed<_Boolean_> informing if composable is performing some asynchronous method right now. +* `paymentObject` - Computed<_any_> containing payment object in the commercetools. It is updated by `createContext`, `payAndOrder`, `submitAdditionalPaymentDetails` methods. + +```ts +interface AdyenError { + submitAdditionalPaymentDetails: Error | null, + createContext: Error | null, + payAndOrder: Error | null +} +``` + +#### Methods +* `createContext` - Loads a cart, then fetching available payment methods for the loaded cart. At the end, a method stores a response inside `paymentObject`. +* `buildDropinConfiguration` - `(config: AdyenConfigBuilder): any` - Builds a configuration object for Adyen's Web Drop-In. +* `payAndOrder` - Setting value of the custom field called `makePaymentRequest` in the commercetools' payment object. Commercetools will send it to the Adyen and give you the response. As a last step, a method is storing a response inside the `paymentObject`. +* `submitAdditionalPaymentDetails` - Setting value of the custom field `submitAdditionalPaymentDetailsRequest` in the commercetools' payment. Commercetools will send it to the Adyen and give you the response. As a last step, a method is storing a response inside the `paymentObject`. + +```ts +interface AdyenConfigBuilder { + paymentMethodsResponse, + onChange = (state, component) => {}, + onSubmit = (state, component) => {}, + onAdditionalDetails = (state, component) => {}, + onError = (state) => {} +} +``` + +## Components +`PaymentAdyenProvider` component fetches available payment methods and mounts Adyen's Web Drop-In. It takes care of the whole flow of the payment. It allows you to hook into some events by passing functions via props. +* `beforeLoad` - `config => config` - Called just before creating an instance of the `AdyenCheckout` and mounting a Drop-In. +* `beforePay` - `stateData => stateData` - Called just before calling a `payAndOrder`. Here we can modify the payload. +* `afterPay` - `paymentAndObject: PaymentAndOrder => void` - Called after we got result code equal `Authorized` from the Adyen, and an order has been placed. +* `afterSelectedDetailsChange` - Called inside `onChange` of Adyen's Drop-In. +* `onError` - `(data: { action: string, error: Error | string }) => void` - Called after we got an error from either Adyen or our API. + +## Placing an order +If the transaction is authorized, the server's controller for `payAndOrder`/`submitAdditionalPaymentDetails` will place an order in Commercetools and apply the `order` object to the response. Thanks to that, we have only one request from the client to both finalize/authorize a payment and make an order. + +## Checkout.com +Adyen's module isn't compatible with [Checkout.com's module](https://github.com/vuestorefront/checkout-com). + +## FAQ + +### How to debug data flow? + +Open the `Network` tab in the browser's devtools. Each payment request will have commercetools [Payment object](https://docs.commercetools.com/api/projects/payments#payment) in the response. You can check `custom.fields` to see what data was sent to Adyen and what was the response (or error). Available custom fields are listed [here](https://github.com/commercetools/commercetools-adyen-integration/blob/master/extension/resources/web-components-payment-type.json). + +### Error: NotFound: URI not found: //carts/ +`ctApi.apiHost` property inside your `middleware.config.js` contains wrong path. It should be `https://.com/` instead of `https://.com//graphql` + +### Error: The type with the key 'ctp-adyen-integration-web-components-payment-type' was not found +You have to add new types and extension to commercetools as described on these pages: +- [Extension Module](https://github.com/commercetools/commercetools-adyen-integration/blob/master/extension/docs/HowToRun.md#commercetools-project-requirements), +- [Notification Module](https://github.com/commercetools/commercetools-adyen-integration/blob/master/notification/docs/HowToRun.md#commercetools-project-requirements). + +For more information, see the 6th step of the [Adyen's installation guide](./adyen.html#installation). + +### Klarna Pay Later does not work for United States +Klarna Pay Later is not supported in the United States. However, sometimes it is added when you enable Klarna in Adyen's dashboard. If you have this problem, contact Adyen's support to remove it. + +### 3DS2 Auth doesn't work in one environment +There might be a situation when you can finish 3DS2 Auth in the local environment but not in the other, like staging. When this happens, make sure to change `origin` in the `middleware.config.js` from `http://localhost:3000` to the URL of your staging environment. + +### Structure of DetailsRequest contains the following unknown fields... +Update extension and notification modules to the [newest available version](https://github.com/commercetools/commercetools-adyen-integration/releases) by updating the tag in `extension.Dockerfile` and `notification.Dockerfile`. diff --git a/packages/core/docs/integrations/bazaarvoice.md b/packages/core/docs/integrations/bazaarvoice.md new file mode 100644 index 0000000000..a89d7b1ea2 --- /dev/null +++ b/packages/core/docs/integrations/bazaarvoice.md @@ -0,0 +1,136 @@ +# Bazaarvoice + +::: warning Paid feature +This feature is part of the Enterprise version. Please [contact our Sales team](https://www.vuestorefront.io/contact/sales) if you'd like to use it in your project. +::: + +## Introduction + +This package provides integration with [Bazaarvoice](https://www.bazaarvoice.com/). + +Bazaarvoice (BV) provides two SDKs to integrate with their platform - `bvapi.js` and `bv.js`. The latter is recommended because of the performance improvements and this is what is package uses under the hood. + +We recommend reading [this documentation page](https://knowledge.bazaarvoice.com/wp-content/conversations-prr/en_US/display/integrating_content_bv_js.html) before using the package. + +## Installation + +Install the required package: + +```sh +yarn add @vsf-enterprise/bazaarvoice +``` + +Register its Nuxt module with the following configuration: + +```javascript +// nuxt.config.js + +export default { + buildModules: [ + ['@vue-storefront/nuxt', { + useRawSource: { + dev: [ + // other packages + '@vsf-enterprise/bazaarvoice' + ], + prod: [ + // other packages + '@vsf-enterprise/bazaarvoice' + ] + } + }], + [ + '@vsf-enterprise/bazaarvoice/nuxt', + { + clientName: '', + siteId: '', + environment: '', + locale: '' + } + ], + ] +}; +``` + +* `clientName` - the lowercase name of the client provided by Bazaarvoice. +* `siteId` - the ID of the zone coming from Bazaarvoice configuration hub. Defaults to `main_site`. +* `environment` - the deployment environment. Valid values are `production` and `staging`. Defaults to `staging`. +* `locale` - the locale used by the library, eg. `en_US`. + +::: warning Domain white list +For security reasons, Bazaarvoice uses a white list of allowed domains. +::: + +::: tip Use localhost +If you get an `Uncaught Bazaarvoice is not configured for the domain` error in the console when working locally, try using `localhost` instead of the IP address provided by the Nuxt (eg. `localhost:3000`). +::: + +## API + +`@vsf-enterprise/bazaarvoice` exports few Vue.js components and `useBazaarvoice` composable. + +### Composable + +`useBazaarvoice` composable returns two computed properties: +* `loading` - indicates whether BV loader finished initialization. +* `settings` - object containing package configuration. + +### Components + +The following components are available, but some require additional features to be enabled. Please refer to Bazaarvoice documentation linked above for more information. + +::: tip Performance matters +Bazaarvoice library has to load few resources and make some API calls. To improve the overall performance of the application, it's loaded only when one of the components from the library is used. +::: + +#### `BvInlineRating` + +The inline rating component displays the average rating and the total number of reviews. It can be used on a category or search page and will make only one call per page, even if used multiple times. + +```html + +``` + +#### `BvQuestions` + +Questions component displays questions and answers provided by the customers regarding the specific product. + +```html + +``` + +#### `BvRatingSummary` + +The rating summary component (also called "fast stars") displays average rating, the total number of reviews, and rating distribution when hovered. + +```html + +``` + +When the component is clicked, it will scroll to `BvReviews` component. However, if the component is not visible by default (eg. is used in tab component), you can use `onReviewsClick` prop to pass a function to handle it. + +#### `BvReviewHighlights` + +Reviews highlights component shows a high-level summary of ratings and reviews. + +```html + +``` + +#### `BvReviews` + +The reviews component has a button to add a new review but also displays a list of ratings and reviews, rating distribution, and many more. + +```html + +``` + +#### `BvSellerRatings` + +Seller rating component displays up to 12 four- and five-star reviews. + +```html + +``` diff --git a/packages/core/docs/integrations/list/storyblok.js b/packages/core/docs/integrations/list/storyblok.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/docs/integrations/redis-cache.md b/packages/core/docs/integrations/redis-cache.md new file mode 100644 index 0000000000..00249a5076 --- /dev/null +++ b/packages/core/docs/integrations/redis-cache.md @@ -0,0 +1,52 @@ +# Redis cache + +::: warning Paid feature +This feature is part of the Enterprise version. Please [contact our Sales team](https://www.vuestorefront.io/contact/sales) if you'd like to use it in your project. +::: + +## Introduction + +This package provides integration with [Redis](https://redis.io/). For more information about this topic, please refer to [SSR Cache](../advanced/ssr-cache.md) page. + +## Installation + +Install required packages: + +```sh +yarn add @vue-storefront/cache +yarn add @vsf-enterprise/redis-cache +``` + +Register `@vue-storefront/cache` module with following configuration: + +```javascript +// nuxt.config.js + +export default { + modules: [ + ['@vue-storefront/cache/nuxt', { + invalidation: { + // Invalidation options + }, + driver: [ + '@vsf-enterprise/redis-cache', + { + defaultTimeout: 86400, + redis: { + host: 'localhost', + port: 6379, + password: 'password' + } + } + ] + }] + ] +}; +``` + +We can break down package configuration into two pieces: + +* `invalidation` - please refer to [SSR Cache configuration](../advanced/ssr-cache.md) page. +* `driver` - object containing: + * `defaultTimeout` - number of seconds until records expire, even if not invalidated; + * `redis` - object directly passed to [ioredis](https://github.com/luin/ioredis/blob/master/API.md#new-redisport-host-options); \ No newline at end of file diff --git a/packages/core/docs/migrate/2.1.0-rc.1/integrators.md b/packages/core/docs/migrate/2.1.0-rc.1/integrators.md new file mode 100644 index 0000000000..bdccaaddd0 --- /dev/null +++ b/packages/core/docs/migrate/2.1.0-rc.1/integrators.md @@ -0,0 +1,163 @@ +# Integrators upgrade notes + + +## API client functions + +### Before +```js +const getProduct = async (id) => { + const { client, config } = getSettings(); + + return client.get('/product', id) +} +``` + +### After +```js +const getProduct = async ({ client, config }, id) => { + return client.get('/product', id) +} +``` + +## API client creation + +### Before +```js +import { apiClientFactory } from '@vue-storefront/core'; + +let apiClient = null; + +const onCreate = (setupConfig) => { + apiClient = new SomeConnection({ + host: setupConfig.api.host, + auth: { + username: setupConfig.api.auth.username, + password: setupConfig.api.auth.password + }, + shopId: setupConfig.api.shopId + }); + } + +const { setup, update, getSettings } = apiClientFactory({ + defaultSettings: {}, + onCreate +}); + +export { + apiClient, + getSettings, + setup, + update +}; +``` + +### After + +```js +import { apiClientFactory } from '@vue-storefront/core'; +import getProduct from './api/getProduct'; + +const onCreate = (setupConfig) => { + const apiClient = new SomeConnection({ + host: setupConfig.api.host, + auth: { + username: setupConfig.api.auth.username, + password: setupConfig.api.auth.password + }, + shopId: setupConfig.api.shopId + }); + + return { + client: apiClient, + config: setupConfig + } +} + +const { createApiClient } = apiClientFactory({ + tag: 'tag', + defaultSettings: {}, + onCreate, + api: { + getProduct + } +}); + +export { createApiClient }; +``` + +## Nuxt plugin for integration + +### Before +```js +import { setup } from '@vue-storefront/integration-name'; + +export default ({ app }) => { + setup({ + api: { + host: '<%= options.api.host %>', + auth: { + username: '<%= options.api.auth.username %>', + password: '<%= options.api.auth.password %>' + }, + shopId: selectedLocale.shopId + }, + }); +} +``` + +### After +```js +import { integrationPlugin } from '@vue-storefront/integration-name' + +export default integrationPlugin(({ app, integration }) => { + integration.configure({ + api: { + host: '<%= options.api.host %>', + auth: { + username: '<%= options.api.auth.username %>', + password: '<%= options.api.auth.password %>' + }, + shopId: selectedLocale.shopId + }, + }) +}); +``` + +## Usage api in factoryParams + +### Before +```js +import { getCart, addToCart } from '@vue-storefront/integration-name'; + +const factoryParams = { + loadCart: async () => { + const { data } = await getCart(); + + return data.cart; + }, + addToCart: async (params) => { + const { currentCart, product, quantity } = params; + const { data } = await addToCart(loadedCart, product, quantity, customQuery); + + return data.cart; + }, +}; +``` +### After + +```js +const factoryParams = { + loadCart: async (context) => { + const { data } = await context.$tag.api.getCart(); + + return data.cart; + }, + addToCart: async (context, params) => { + const { currentCart, product, quantity } = params; + const { data } = await context.$tag.api.addToCart(loadedCart, product, quantity, customQuery); + + return data.cart; + }, +}; +``` + diff --git a/packages/core/docs/migrate/2.1.0-rc.1/overview.md b/packages/core/docs/migrate/2.1.0-rc.1/overview.md new file mode 100644 index 0000000000..486aa106b4 --- /dev/null +++ b/packages/core/docs/migrate/2.1.0-rc.1/overview.md @@ -0,0 +1,70 @@ +# Migration guide 2.1.0-rc.1 + +This guide contains a list of changes, in order to upgrade your VSF next to the 2.1.0-rc.1 version. + +## The general overview + +In general, besides the smaller changes, we totally have changed the way of accessing configuration or API functions. +Always, instead of using configuration or networking stored in the global objects, we recommend to use context, provided by us. + +Each time you want to access an integration stuff, such as network client, configuration or API functions please use `useVSFContext` + +```js +import { useVSFContext } from '@vue-storefront/core'; + +export default { + setup () { + const { $integrationTag } = useVSFContext(); + } +} + +``` + +For more information, please read the section [Application Context](/general/context) in the general docs. + +## List of changes for certain users + +- [Changes for integrators](./integrators.md) +- [Changes for projects](./projects.md) + + +## Common problems + +### Composition API already installed + +``` +[vue-composition-api] already installed. Vue.use(VueCompositionAPI) should be called only once. +``` + +**Solution**: Please make sure that you have everywhere the same version of vue composition api as the one we use in the core + +### Using Composition API before is being installed + +``` +Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function. +``` +**Solution**: if you are using compositon API (eg. `ref`) above the component, please make sure that you have `vue-composition-api` installed, or move these calls inside of the `setup` function. + +### Some of our package doesn't work, the context doesn't return anything or has missing data + +**Solution**: Please make sure that you put package in the raw sources section, and if the integration has nuxt module and extend an existing one, should be aded before our core nuxt module and the one with integration + +```js +{ + ['@vue-storefront/some-new-integration/nuxt', {}], // 2 + ['@vue-storefront/nuxt', { + coreDevelopment: true, + useRawSource: { + dev: [ + '@vue-storefront/core' + '@vue-storefront/other-package' // 1 + ], + prod: [ + '@vue-storefront/core', + '@vue-storefront/other-package' // 1 + ] + } + }], + ['@vue-storefront/some-integration/nuxt', {}] +} +``` diff --git a/packages/core/docs/migrate/2.1.0-rc.1/projects.md b/packages/core/docs/migrate/2.1.0-rc.1/projects.md new file mode 100644 index 0000000000..e11a54c771 --- /dev/null +++ b/packages/core/docs/migrate/2.1.0-rc.1/projects.md @@ -0,0 +1,128 @@ +# Project upgrade notes + +## Access to API client + +### Before + +```js +import { getSettings } from '@vue-storefront/integration-name-api'; + +export default { + setup () { + const apiClientSettings = getSettings() + } +} +``` + +### After +```js +import { useVSFContext } from '@vue-storefront/core'; + +export default { + setup () { + const { $tag } = useVSFContext() + + // $tag.config + // $tag.client + // $tag.api + } +} +``` + +## Usage API client in plugins or middlewares + +### Before +```js +import { getProduct } from '@vue-storefront/integration-name-api'; + +export default async ({ app }) => { + const product = await getProduct({ id: 1 }) +} +``` + +### After + +```js +export default async ({ app, $vsf }) => { + const product = await $vsf.$tag.api.getProduct({ id: 1 }) +} +``` + +## Price formatting + +### Before + +```js +import { productGetters, useProduct } from '@vue-storefront/integration-name'; + + + +export default { + setup () { + const { product } = useProduct(); + + return { product, productGetters } + } +} +``` + +### After + +```js +import { productGetters, useProduct } from '@vue-storefront/integration-name'; +import { useUiHelpers } from '~/composables'; + + + +export default { + setup () { + const th = useUiHelpers(); + const { product } = useProduct(); + + return { product, productGetters, th } + } +} +``` + +## Configuring integration + +### Before +```js +import { setup } from '@vue-storefront/integration-name'; + +export default ({ app }) => { + setup({ + api: { + host: '<%= options.api.host %>', + auth: { + username: '<%= options.api.auth.username %>', + password: '<%= options.api.auth.password %>' + }, + shopId: selectedLocale.shopId + }, + }); +} +``` + +### After +```js +import { integrationPlugin } from '@vue-storefront/integration-name' +import { getProduct } from './your-api'; + +export default integrationPlugin(({ app, integration }) => { + integration.configure({ + api: { + host: '<%= options.api.host %>', + auth: { + username: '<%= options.api.auth.username %>', + password: '<%= options.api.auth.password %>' + }, + shopId: selectedLocale.shopId + }, + }, { getProduct }) +}); +``` diff --git a/packages/core/docs/migrate/2.2.0/integrators.md b/packages/core/docs/migrate/2.2.0/integrators.md new file mode 100644 index 0000000000..4842620e10 --- /dev/null +++ b/packages/core/docs/migrate/2.2.0/integrators.md @@ -0,0 +1,28 @@ +# Integrators upgrade notes + +## Factory usage + +We have changes a bit the naming and signatures of core factory functions. Below is the full list of what hs been implemented or changed: + +| Factory | Old method | New method | Old signature | New signature | +|------------|--------|---------------|---------------|---| +| useCartFactory | addToCart | addItem | context, { currentCart: cart.value, product, quantity }, customQuery | context, { currentCart: cart.value, product, quantity, customQuery } | +| useCartFactory | loadCart | load | context: Context, customQuery?: CustomQuery | context: Context, { customQuery?: any } | +| useCartFactory | removeFromCart | removeItem | context: Context, params: { currentCart: CART, product: CART_ITEM }, customQuery?: CustomQuery | context: Context, params: { currentCart: CART, product: CART_ITEM, customQuery?: CustomQuery } | +| useCartFactory | updateQuantity | updateItemQty | context: Context, params: { currentCart: CART, product: CART_ITEM, quantity: number }, customQuery?: CustomQuery | context: Context, params: { currentCart: CART, product: CART_ITEM, customQuery?: CustomQuery } | +| useCartFactory | clearCart | clear | context: Context, prams: { currentCart: CART } | context: Context, params: { currentCart: CART } | +| useCartFactory | applyCoupon | No changes | context: Context, params: { currentCart: CART; couponCode: string }, customQuery?: CustomQuery | context: Context, params: { currentCart: CART, couponCode: string, customQuery?: CustomQuery } | +| useCartFactory | removeCoupon | No changes | context: Context, params: { currentCart: CART; coupon: COUPON }, customQuery?: CustomQuery | context: Context, params: { currentCart: CART; coupon: COUPON, customQuery?: CustomQuery } | +| useCategoryFactory | categorySearch | No changes | context: Context, searchParams: CATEGORY_SEARCH_PARAMS, customQuery: CustomQuery | context: Context, params: CATEGORY_SEARCH_PARAMS & { customQuery?: CustomQuery } | +| useProductFactory | productsSearch | No changes | context: Context, searchParams: PRODUCT_SEARCH_PARAMS, customQuery?: CustomQuery | context: Context, params: PRODUCT_SEARCH_PARAMS & { customQuery?: CustomQuery } | +| useReviewFactory | searchReviews | No changes | context: Context, params: REVIEWS_SEARCH_PARAMS, customQuery?: CustomQuery | context: Context, params: REVIEWS_SEARCH_PARAMS & { customQuery?: CustomQuery } | +| useReviewFactory | addReview | No changes | context: Context, params: REVIEW_ADD_PARAMS, customQuery?: CustomQuery | context: Context, params: REVIEW_ADD_PARAMS & { customQuery?: CustomQuery } | +| useUserBillingFactory | setDefault | setDefaultAddress | context: Context, params: { address: Readonly; shipping: Readonly; }) | No changes | +| useUserShippingFactory | setDefault | setDefaultAddress | context: Context, params: { address: Readonly; shipping: Readonly; }) | No changes | +| useUserFactory | loadUser | load | context: Context, | context: Context, params?: {} | +| useUserOrdersFactory | searchOrders | No changes | context: Context, params: ORDER_SEARCH_PARAMS, customQuery?: CustomQuery | context: Context, params: ORDER_SEARCH_PARAMS & { customQuery?: CustomQuery } | +| useWishlistFactory | addToWishlist | addItem | context, { currentWishlist: WISHLIST, product: PRODUCT }, customQuery | context, { currentWishlist: WISHLIST, product: PRODUCT, customQuery } | +| useWishlistFactory | loadWishlist | load | context: Context, customQuery?: CustomQuery | No changes | +| useWishlistFactory | removeFromWishlist | removeItem | context: Context, params: { currentWishlist: WISHLIST, product: WISHLIST_ITEM }, customQuery?: CustomQuery | context: Context, params: { currentWishlist: WISHLIST, product: WISHLIST_ITEM, customQuery?: CustomQuery } | +| useWishlistFactory | clearWishlist | clear | context: Context, params: { currentWishlist: WISHLIST } | No changes | +| useWishlistFactory | loadWishlist | load | context: Context, customQuery?: CustomQuery | context: Context, { customQuery?: any } | \ No newline at end of file diff --git a/packages/core/docs/migrate/2.2.0/overview.md b/packages/core/docs/migrate/2.2.0/overview.md new file mode 100644 index 0000000000..008ed9405f --- /dev/null +++ b/packages/core/docs/migrate/2.2.0/overview.md @@ -0,0 +1,8 @@ +# Migration guide 2.2.0 + +This version introduces many renamings or changes related to the function declarations among of the core. We had to proceed with this in order to keep the convention and unify the naming across whole VSF. + +We changes the composables and our factories according to the following rules: +- each composable return always one field with the response from the api +- each composable function takes one argument wich is an object of given parameters +- each factory param function takes two argumens, first one is context (as it was before) and second one contains a function parmeters along with other options (such as customQuery) diff --git a/packages/core/docs/migrate/2.2.0/projects.md b/packages/core/docs/migrate/2.2.0/projects.md new file mode 100644 index 0000000000..f2b0351ea1 --- /dev/null +++ b/packages/core/docs/migrate/2.2.0/projects.md @@ -0,0 +1,45 @@ +# Project upgrade notes + +## Composables usage + +We have changes a bit the naming and signatures of composable functions. Below is the full list of what hs been implemented or changed: + +| Composable | Method | Old signature | New signature | +|------------|--------|---------------|---------------| +| useCart | addToCart | (product: PRODUCT, quantity: number, customQuery?: CustomQuery) | ({ product: PRODUCT, quantity: number, customQuery?: CustomQuery }) | +| useCart | removeFromCart | (product: CART_ITEM, customQuery?: CustomQuery) | ({ product: CART_ITEM, customQuery?: CustomQuery }) | +| useCart | updateQuantity | (product: CART_ITEM, quantity?: number, customQuery?: CustomQuery) | ({ product: CART_ITEM, quantity?: number, customQuery?: CustomQuery }) | +| useCart | load | (customQuery?: CustomQuery) | ({ customQuery?: CustomQuery } = {}) | +| useCart | clearCart | () | () | +| useCart | applyCoupon | (couponCode: string, customQuery?: CustomQuery) | ({ couponCode: string, customQuery?: CustomQuery }) | +| useCart | isOnCart | (product: PRODUCT) | ({ product: PRODUCT }) | +| useCart | removeCoupon | (coupon: COUPON, customQuery?: CustomQuery) | ({ coupon: COUPON, customQuery?: CustomQuery }) | +| useCart | setCart | (newCart: CART) | ({ newCart: CART }) | +| useCategory | search | (searchParams: CATEGORY_SEARCH_PARAMS, customQuery?: CustomQuery) | ({ ...searchParams: CATEGORY_SEARCH_PARAMS, customQuery?: CustomQuery }) | +| useContent | search | (params: CONTENT_SEARCH_PARAMS) | No changes | +| useFacet | search | (params?: AgnosticFacetSearchParams) | No changes | +| useProduct | search | (searchParams: PRODUCT_SEARCH_PARAMS, customQuery?: CustomQuery) | ({ ...searchParams: PRODUCT_SEARCH_PARAMS, customQuery?: CustomQuery }) | +| useReview | search | (searchParams: REVIEWS_SEARCH_PARAMS, customQuery?: CustomQuery) | ({ ...searchParams: REVIEWS_SEARCH_PARAMS, customQuery?: CustomQuery }) | +| useReview | addReview | (addParams: REVIEW_ADD_PARAMS, customQuery?: CustomQuery) | ({ ...addParams: REVIEW_ADD_PARAMS, customQuery?: CustomQuery }) | +| useUserBilling | addAddress | (address: USER_BILLING_ITEM) | ({ address: USER_BILLING_ITEM }) | +| useUserBilling | deleteAddress | (address: USER_BILLING_ITEM) | ({ address: USER_BILLING_ITEM }) | +| useUserBilling | updateAddress | (address: USER_BILLING_ITEM) | ({ address: USER_BILLING_ITEM }) | +| useUserBilling | setDefault | (address: USER_BILLING_ITEM) | ({ address: USER_BILLING_ITEM }) | +| useUserBilling | load | () | () | +| useUserShipping | addAddress | (address: USER_SHIPPING_ITEM) | ({ address: USER_SHIPPING_ITEM }) | +| useUserShipping | deleteAddress | (address: USER_SHIPPING_ITEM) | ({ address: USER_SHIPPING_ITEM }) | +| useUserShipping | updateAddress | (address: USER_SHIPPING_ITEM) | ({ address: USER_SHIPPING_ITEM }) | +| useUserShipping | setDefault | (address: USER_SHIPPING_ITEM) | ({ address: USER_SHIPPING_ITEM }) | +| useUserShipping | load | () | () | +| useUser | updateUser | (params: UPDATE_USER_PARAMS) | ({ user: UPDATE_USER_PARAMS }) | +| useUser | register | (registerUserData: REGISTER_USER_PARAMS) | ({ user: REGISTER_USER_PARAMS }) | +| useUser | login | (loginUserData: { username: string; password: string; }) | ({ user: LOGIN_USER_PARAMS }) | +| useUser | logout | () | () | +| useUser | changePassword | (currentPassword: string, newPassword: string) | ({ currentPassword: string, newPassword: string }) | +| useUser | load | () | () | +| useUserOrders | searchOrders | (searchParams: ORDER_SEARCH_PARAMS, customQuery?: CustomQuery) | ({ ...searchParams: ORDER_SEARCH_PARAMS, customQuery?: CustomQuery } = {}) | +| useWishlist | addToWishlist | (product: PRODUCT, customQuery?: CustomQuery) | ({ product: PRODUCT, customQuery?: CustomQuery }) | +| useWishlist | removeFromWishlist | (product: WISHLIST_ITEM, customQuery?: CustomQuery) | ({ product: WISHLIST_ITEM, customQuery?: CustomQuery }) | +| useWishlist | load | (customQuery?: CustomQuery) | ({ customQuery?: CustomQuery } = {}) | +| useWishlist | clearWishlist | () | () | +| useWishlist | isOnWishlist | (product: PRODUCT) | ({ product: PRODUCT }) | \ No newline at end of file diff --git a/packages/core/docs/migrate/2.3.0-rc.2/commercetools.md b/packages/core/docs/migrate/2.3.0-rc.2/commercetools.md new file mode 100644 index 0000000000..019fa263e9 --- /dev/null +++ b/packages/core/docs/migrate/2.3.0-rc.2/commercetools.md @@ -0,0 +1,35 @@ +# Migration guide 2.3.0-rc.2 for commercetools + +## Introduction + +This migration guide helps developers using our `commercetools` integration to upgrade to version 2.3.0-rc.2. + +For more information about this version, refer to the [Overview](./overview.md) page. + +## Changes + +We made changes to the following files: +- added new composable `useBilling`, +- added new composable `useShipping`, +- added new composable `useShippingProvider` +- added new component `components/Checkout/VsfShippingProvider`, +- added new component `components/Checkout/VsfPaymentProviderMock`, +- added new components `pages/Checkout/Billing.vue`, +- added new helper `helpers/Checkout/getShippingMethodPrice.ts`, +- added new middleware `middleware/is-authenticated.js`, +- added `middleware.config.js`, +- updated `components/Checkout/UserBillingAddresses.vue`, +- updated `components/Checkout/UserShippingAddresses.vue`, +- updated `composables/useUiHelpers/index.ts`: + - renamed `changeSearchTerm` to `setTermForUrl`, + - added `getSearchTermFromUrl`, +- updated translations in `theme/lang`, +- updated `middleware/checkout.js`, +- updated `pages/Checkout.vue`, +- updated `pages/Checkout/Payment.vue`, +- updated `pages/Checkout/Shipping.vue`, +- updated icons in `static/icons`, +- updated `nuxt.config.js`, +- updated `package.json`, +- deleted component `pages/Checkout/OrderReview.vue`, +- deleted component `pages/Checkout/PersonalDetails.vue`. \ No newline at end of file diff --git a/packages/core/docs/migrate/2.3.0-rc.2/integrators.md b/packages/core/docs/migrate/2.3.0-rc.2/integrators.md new file mode 100644 index 0000000000..08506e219d --- /dev/null +++ b/packages/core/docs/migrate/2.3.0-rc.2/integrators.md @@ -0,0 +1,221 @@ +# Migration guide 2.3.0-rc.2 for Integrators + +## Introduction + +This migration guide helps Integrators make their integrations and plugins compatible with version 2.3.0-rc.2. + +It only contains code examples. For more information about this version, refer to the [Overview](./overview.md) page. + +## API middleware + +### Updating `api-client` +As described on the [Overview](./overview.md) page, API middleware lives next to Nuxt.js. That's why from now on, `api-client` must generate two files: +- one that exposes types, GraphQL fragments, and other files that don't contain any business logic, +- the other that returns `createApiClient` method created using `apiClientFactory` that exposes all the APIs that will be consumed by the middleware. + +You should have two entry points in `src` folder: `index.ts` and `index.server.ts`: + +```typescript +// api-client/src/index.ts +export * from './types'; +``` + +```typescript +// api-client/src/index.server.ts +import { apiClientFactory } from '@vue-storefront/core'; +import * as api from './api'; + +const onCreate = settings => ({ + config: {}, + client: {} +}); + +const { createApiClient } = apiClientFactory({ + onCreate, + api +}); + +export { + createApiClient +}; +``` + +We have to re-configure Rollup to build both files. Basic configuration should look like this: + +```javascript{22,36} +// api-client/rollup.config.js +import pkg from './package.json'; +import typescript from 'rollup-plugin-typescript2'; + +// External packages +const external = [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) +]; + +// Plugins +const plugins = [ + typescript({ + typescript: require('typescript') + }) +]; + + +export default [ + // Client + { + input: 'src/index.ts', + output: [ + { + file: 'lib/index.cjs.js', // Add this directory to .gitignore + format: 'cjs', + sourcemap: true + }, + { + file: 'lib/index.es.js', // Add this directory to .gitignore + format: 'es', + sourcemap: true + } + ], + external, + plugins + }, + + // Server + { + input: 'src/index.server.ts', + output: [ + { + file: 'server/index.js', // Add this directory to .gitignore + format: 'cjs', + sourcemap: true + } + ], + external, + plugins + }, +]; +``` + +We also changed how custom queries work. To make your APIs compatible use `context.extendQuery` method instead of `getCustomQuery`: + +```typescript +// before +async function getProduct(context, params, customQueryFn?: CustomQueryFn) => { + // ... + const { query, variables } = getCustomQuery( + customQueryFn, { defaultQuery, defaultVariables } + ); + + const request = await context.client.query({ + query: gql`${query}`, + variables, + // ... + }); +} + +// after +import { CustomQuery } from '@vue-storefront/core'; + +async function getProduct(context, params, customQuery?: CustomQuery) => { + // ... + const { products } = context.extendQuery( + customQuery, { products: { query: defaultQuery, variables: defaultVariables } } + ); + + const request = await context.client.query({ + query: gql`${products.query}`, + variables: products.variables + }); +}; +``` + +In the example above `products` matches the name of the GraphQL query. + +`extendsQuery` decides whether to load default query (implemented in the integration) or custom one, defined by the user in the project. + +### Updating `composables` + +In `nuxt/plugin.js` add the tag name of your integration package as a first argument of `integration.configure` method: + +```javascript +// composables/nuxt/plugin.js +import { integrationPlugin } from '@vue-storefront/core'; + +const moduleOptions = <%= serialize(options) %>; + +export default integrationPlugin(({ integration }) => { + integration.configure('boilerplate', { + ...moduleOptions + // other options + }); +}); +``` + +### Updating `theme` + +As described on the [Overview](./overview.md) page, we need to create `middleware.config.js` in the root of the `theme`: + +```javascript +// theme/middleware.config.js +module.exports = { + integrations: { + '': { + location: '', + configuration: '' + } + } +}; + +``` + +- `` - the name of your integration and must match the one provided in the `composables/nuxt/plugin.js`. +- `` - path to your server package and must match the output of `api-client/src/index.server.ts` built by Rollup (usually `@your-api-integration/server`). +- `` - integration configuration that previously lived in `nuxt.config.js`. + +The last step is to add `'@vue-storefront/middleware/nuxt'` to the `modules` in `nuxt.config.js`. + +:::warning Be careful +Make sure this package is added to the `modules` array, not `buildModules`. +::: + +```javascript +// nuxt.config.js +export default { + modules: [ + '@vue-storefront/middleware/nuxt' + ] +}; +``` + +## Checkout + +### Updating `composables` + +We added the following composables: +- `useShipping` for handling shipping information, +- `useBilling` for handling billing information, +- `useShippingProvider` for handling shipping providers, +- `useMakeOrder` for placing the final order. + +We added two new components to delegate UI-related logic: +- `components/Checkout/VsfPaymentProviderMock` (`Mock` because commercetools doesn't handle payments), +- `components/Checkout/VsfShippingProvider`. + +We also used this opportunity to cleanup other composables: +- removed `useCheckout`, +- removed `checkoutGetters`, +- renamed `useUserOrders` to `useUserOrder` to be consistent with other composables, +- renamed `isOnCart` and `isOnWishlist` in `useCart` composable to `isInCart` and `isInWishlist`. + +## UI + +We added and updated multiple UI elements. Please refer to the [UI section](./overview.md#ui) of the Overview page for more details. + +Notable changes are: +- renamed `components/checkout` directory in core theme to `components/Checkout` (note the capital `C`). Please update your imports, +- added new integration-specific components: + - `components/Checkout/CartPreview.vue` (previously implemented as `components/checkout/CartPreview.vue` in the core theme), + - `components/Checkout/UserBillingAddresses.vue`, + - `components/Checkout/UserShippingAddresses.vue`, +- added new integration-specific middleware `middleware/is-authenticated.js`, diff --git a/packages/core/docs/migrate/2.3.0-rc.2/overview.md b/packages/core/docs/migrate/2.3.0-rc.2/overview.md new file mode 100644 index 0000000000..7dd4cdf985 --- /dev/null +++ b/packages/core/docs/migrate/2.3.0-rc.2/overview.md @@ -0,0 +1,168 @@ +# Migration guide 2.3.0-rc.2 + +## Introduction + +In this release, we introduced few new APIs but also some breaking changes. + +Breaking changes are related to the introduction of our new API middleware, an overhaul of the checkout, and updates to `@vue-storefront/cache` library. + +If you are an integrator, please also refer to the [Integrators](./integrators.md) page. + +## API middleware + +If you watched the talk of our CTO at Vue.js Amsterdam you may be already aware of this change. If not, you can find his slides [here](https://slides.com/filiprakowski/vuejs-amsterdam-2021). + +In short, it's meant to reduce the amount of code we send to the browser and perform as many operations on the backend as possible. This greatly improves security and frontend performance but also gives you more control of what is sent to the 3rd party providers. + +Before, every request sent to the providers was made directly from the browser (besides the first visit on the page which is Server-Side Rendered), which meant that it had to have all the logic required to create and send the request and to process the response. In some cases browser had to load heavy libraries like GraphQL and have access to sensitive information, such as API keys. +This meant that Vue Storefront would not scale as well as we would like. + +To fix these issues we moved most of the logic to the new API middleware. It's an Express server that lives beside Nuxt.js. It can be run in parallel on the same server and separately on a different server, for better scalability. +Now, instead of making API calls directly to the provider, the browser will request data from the API middleware. + +### Changes + +1. Create a new configuration files in the root of your Nuxt project named `middleware.config.js`. Content is dependant on the integration you use. As an example we will use our `commercetools` integration: + +```javascript +// middleware.config.js +module.exports = { + integrations: { + ct: { + location: '@vue-storefront/commercetools-api/server', + configuration: { + api: { + uri: '', + authHost: '', + projectKey: '', + clientId: '', + clientSecret: '', + scopes: [] + }, + currency: '', + country: '' + } + } + } +}; + +``` + +2. Add `'@vue-storefront/middleware/nuxt'` to the `modules` in `nuxt.config.js`. + +:::warning Be careful +Make sure this package is added to the `modules` array, not `buildModules`. +::: + +```javascript +// nuxt.config.js +export default { + modules: [ + '@vue-storefront/middleware/nuxt' + ] +}; +``` + +3. Update custom queries. + +Previously, custom queries were callbacks passed as an additional argument in most of our composable methods. Because we moved API communication to the backend, it's no longer possible to pass callbacks from UI (frontend) to `api-client` (backend). + +From now on, custom queries are registered in `middleware.config.js` (with the same signature as before), and used on the frontend by providing their unique name: + +```javascript{7,17} +// middleware.config.js +module.exports = { + integrations: { + ct: { + // other options + customQueries: { + 'MyCustomQuery': (query, variables) => {}, + } + } + } +}; + +// Some component +const { search } = useProduct(); + +onSSR(async () => { + await search({ customQuery: { products: 'MyCustomQuery' } }) +}); +``` + +As you can in the example above, `customQuery` is now an object that accepts object with: +- key matching the name of GraphQL query to override, +- value matching the name of the handler in `middleware.config.js`. + +## Checkout + +One of the most important pieces missing in VSF Next was Checkout, which originally was supposed to be handled by a single `useCheckout` composable. However, we decided that this approach won't scale well because the checkout process is very different in every integration and it might not work well, if multiple providers will be used to handle different steps of the checkout process, like payment, shipping, etc. + +### Changes + +We split responsibilities of different steps into multiple new composables: + +- `useShipping` for handling shipping information, +- `useBilling` for handling billing information, +- `useShippingProvider` for handling shipping providers, +- `useMakeOrder` for placing the final order. + +We also used this opportunity to cleanup other composables: +- removed `useCheckout`, +- removed `checkoutGetters`, +- renamed `useUserOrders` to `useUserOrder` to be consistent with other composables, +- renamed `isOnCart` and `isOnWishlist` in `useCart` composable to `isInCart` and `isInWishlist`. + +These changes affected multiple Vue components. Please refer to [UI section](#ui) for more information. + +## Cache + +We updated `@vue-storefront/cache` library which previously was only experimental. It contains some breaking changes, but mostly related to naming in the configuration. + +Please refer to [SSR Cache](../../advanced/ssr-cache.md) documentation for more information. + +## UI + +We added and updated multiple UI elements: + +- renamed `components/checkout` directory in core theme to `components/Checkout` (note the capital `C`). Please update your imports. +- added new components: + - `components/Notification.vue`, + - `components/SearchResults.vue`, + - `pages/Checkout/Billing.vue` with route `/checkout/billing`, +- added new integration-specific components: + - `components/Checkout/CartPreview.vue` (previously implemented in the core theme), + - `components/Checkout/VsfPaymentProvider.vue`, + - `components/Checkout/VsfShippingProvider.vue`, +- updated components: + - `components/AppFooter.vue`, + - `components/AppHeader.vue`, + - `components/BottomNavigation.vue`, + - `components/Checkout/CartSidebar.vue`, + - `components/InstagramFeed.vue`, + - `components/LocaleSelector.vue`, + - `components/MobileStoreBanner.vue`, + - `components/TopBar.vue`, + - `components/WishlistSidebar.vue`, + - `layouts/default.vue`, + - `layouts/error.vue`, + - `pages/Category.vue`, + - `pages/Checkout.vue`, + - `pages/Checkout/Payment.vue`, + - `pages/Checkout/Shipping.vue`, + - `pages/Home.vue`, + - `pages/MyAccount.vue`, + - `pages/MyAccount/OrderHistory.vue`, + - `pages/Product.vue`, +- deleted components: + - `pages/Checkout/OrderReview.vue` and route `/checkout/order-review`, + - `pages/Checkout/PersonalDetails.vue` and route `/checkout/personal-details`, + - `components/Checkout/UserBillingAddress.vue`, + - `components/Checkout/userShippingAddress.vue`, +- updated `composables/useUiHelpers/index.ts`: + - renamed `changeSearchTerm` to `setTermForUrl`, + - added `getSearchTermFromUrl`, +- updated translations in `theme/lang`, +- added new integration-specific middleware `middleware/is-authenticated.js`, +- added new composable `useUiNotification` in `composables/useUiNotification/index.ts` (exported in `composables/index.ts`), + diff --git a/packages/core/docs/migrate/2.3.0-rc.3/commercetools.md b/packages/core/docs/migrate/2.3.0-rc.3/commercetools.md new file mode 100644 index 0000000000..dacd8322cf --- /dev/null +++ b/packages/core/docs/migrate/2.3.0-rc.3/commercetools.md @@ -0,0 +1,18 @@ +# Migration guide 2.3.0-rc.3 for commercetools + +## Introduction + +This migration guide helps developers using our `commercetools` integration to upgrade to version 2.3.0-rc.3. + +For more information about this version, refer to the [Overview](./overview.md) page. + +## Changes + +- Fixed mapping for product attributes with type `set`. +- Fixed cart getters to not throw errors when some properties are missing. +- Fixed error thrown by the Composition API plugin when importing `useWishlist`. + +We also made changes to the following files: +- updated `components/Checkout/VsfShippingProvider.vue`, +- deleted unused `packages/commercetools/theme/helpers/filters/getFiltersForUrl.js`, +- deleted unused `packages/commercetools/theme/helpers/filters/getFiltersFromUrl.js`. diff --git a/packages/core/docs/migrate/2.3.0-rc.3/integrators.md b/packages/core/docs/migrate/2.3.0-rc.3/integrators.md new file mode 100644 index 0000000000..cdd5ef8962 --- /dev/null +++ b/packages/core/docs/migrate/2.3.0-rc.3/integrators.md @@ -0,0 +1,25 @@ +# Migration guide 2.3.0-rc.3 for Integrators + +## Introduction + +This migration guide helps Integrators make their integrations and plugins compatible with version 2.3.0-rc.3. + +It only contains code examples. For more information about this version, refer to the [Overview](./overview.md) page. + +## Changes + +- Added `extendApp` to Server Middleware extensions (`ApiClientExtension` interface). It allows direct access to the Express server instance. + +```typescript +export const customExtension = { + name: 'customExtension', + + extendApp: (app: Express) => { + app.use(customPlugin()); + + app.get('/customEndpoint', customMiddleware(), (request, response) => { + // + }); + } +}; +``` \ No newline at end of file diff --git a/packages/core/docs/migrate/2.3.0-rc.3/overview.md b/packages/core/docs/migrate/2.3.0-rc.3/overview.md new file mode 100644 index 0000000000..79f23e88fd --- /dev/null +++ b/packages/core/docs/migrate/2.3.0-rc.3/overview.md @@ -0,0 +1,18 @@ +# Migration guide 2.3.0-rc.3 + +## Introduction + +This release mainly contains bug fixes and some new features, as we are getting closer to the stable 2.3.0 release. + +## Changes + +- Fixed `error` property clearing in `useUser`. +- Improved error handling in Server Middleware. When the request to the external backend fails, Server Middleware now returns `HTTP 500` code with the error message in the body. +- Added `extendApp` to Server Middleware extensions (`ApiClientExtension` interface). It allows direct access to the Express server instance. + +We also made changes to the following files: +- updated `components/LoginModal.vue`, +- updated `layouts/default.vue`, +- removed unused `helpers/filters/getFiltersForUrl.js`, +- removed unused `helpers/filters/getFiltersFromUrl.js`, +- removed unused `helpers/filters/index.js`. diff --git a/packages/core/docs/migrate/2.3.0/commercetools.md b/packages/core/docs/migrate/2.3.0/commercetools.md new file mode 100644 index 0000000000..b5b46238bd --- /dev/null +++ b/packages/core/docs/migrate/2.3.0/commercetools.md @@ -0,0 +1,33 @@ +# Migration guide 2.3.0 for commercetools + +## Introduction + +This migration guide helps developers using our `commercetools` integration to upgrade to version 2.3.0. + +For more information about this version, refer to the [Overview](./overview.md) page. + +## Changes + +- Added E2E (cypress) tests: + - use `yarn test:e2e` to start Cypress, + - use `test:e2e:hl` to run Cypress tests in headless mode. Useful for running the tests in CI/CD workflows, + - use `test:e2e:generate:report` to generate Cypress report. +- **[BREAKING]** Fixed helpers used for search on the Category page to read `phrase` URL query string instead of `term`. +- Added `SfLoader` to `VsfShippingProvider` to correctly handle the loading state. +- Added expiration time to `vsf-commercetools-token` cookie matching token expiration time received from commercetools. Previously session cookies were used, which browsers delete when the current session ends. This forced users to log in more often than necessary. +- Fixed an issue in the cart where API calls would result in `HTTP 413 Payload Too Large` error code due to large payload size. Cart method calls will now only include cart `id` and `version` instead of the whole cart object. + +We also made changes to the following files: +- updated `components/Checkout/CartPreview.vue`, +- updated `components/Checkout/UserBillingAddresses.vue`, +- updated `components/Checkout/UserShippingAddresses.vue`, +- updated `components/Checkout/VsfPaymentProviderMock.vue`, +- updated `components/Checkout/VsfShippingProvider.vue`, +- updated `components/MyAccount/BillingAddressForm.vue`, +- updated `components/MyAccount/PasswordResetForm.vue`, +- updated `components/MyAccount/ProfileUpdateForm.vue`, +- updated `components/MyAccount/ShippingAddressForm.vue`, +- updated `pages/Checkout/Billing.vue`, +- updated `pages/Checkout/Payment.vue`, +- updated `pages/Checkout/Shipping.vue`, +- updated `pages/Checkout/ThankYou.vue`. diff --git a/packages/core/docs/migrate/2.3.0/integrators.md b/packages/core/docs/migrate/2.3.0/integrators.md new file mode 100644 index 0000000000..3385b233e5 --- /dev/null +++ b/packages/core/docs/migrate/2.3.0/integrators.md @@ -0,0 +1,27 @@ +# Migration guide 2.3.0 for Integrators + +## Introduction + +This migration guide helps Integrators make their integrations and plugins compatible with version 2.3.0. + +It only contains code examples. For more information about this version, refer to the [Overview](./overview.md) page. + +## Changes + +- Added E2E (cypress) tests to `boilerplate` theme. + +- **[BREAKING]** Changed the signature of `extendApp` in Server Middleware extensions introduced in the previous release from `extendApp(app)` to `extendApp({ app, configuration })` + +```typescript +export const customExtension = { + name: 'customExtension', + + extendApp: ({ app, configuration }: { app: Express, configuration: any }) => { + app.use(customPlugin(configuration)); + + app.get('/customEndpoint', customMiddleware(), (request, response) => { + // + }); + } +}; +``` \ No newline at end of file diff --git a/packages/core/docs/migrate/2.3.0/overview.md b/packages/core/docs/migrate/2.3.0/overview.md new file mode 100644 index 0000000000..9da553d3eb --- /dev/null +++ b/packages/core/docs/migrate/2.3.0/overview.md @@ -0,0 +1,49 @@ +# Migration guide 2.3.0 + +## Introduction + +This release is the first one we officially call **Vue Storefront 2** (previously Vue Storefront Next). We hosted Vue Storefront Summit 2021, which you can learn more about [in our blog post](https://blog.vuestorefront.io/vue-storefront-summit-2021-short-summary-and-every-video/). +During the Summit, we announced many new eCommerce integrations and move from Slack to Discord server. [Join us](https://discord.vuestorefront.io/) to stay up-to-date and ask for help. + +In this release, we focused on bug fixes and test coverage but also introduced few changes. + +## Changes + +- Added new `v-e2e` directive. It adds `data-e2e` attribute to the element in development mode or when app is ran with `NUXT_ENV_E2E` environment variable and can be used as selectors for E2E tests. +- Added E2E (cypress) tests. +- Updated Storefront UI to the latest version. +- Fix hydration bug on the Category page. +- Added support for custom queries in methods in `useUserBilling` and `useUserShipping` composables. +- **[BREAKING]** Added `changeToCategoryListView` and renamed `toggleCategoryGridView` to `changeToCategoryGridView` in `composables/useUiState.ts`. + +We also made changes to the following files: +- updated `components/AppFooter.vue`, +- updated `components/AppHeader.vue`, +- updated `components/BottomNavigation.vue`, +- updated `components/CartSidebar.vue`, +- updated `components/Checkout/VsfPaymentProvider.vue`, +- updated `components/Checkout/VsfShippingProvider.vue`, +- updated `components/LocaleSelector.vue`, +- updated `components/LoginModal.vue`, +- updated `components/RelatedProducts.vue`, +- updated `components/SearchResults.vue`, +- updated `components/TopBar.vue`, +- updated `components/WishlistSidebar.vue`, +- updated `layouts/account.vue`, +- updated `layouts/default.vue`, +- updated `layouts/error.vue`, +- updated `pages/Category.vue`, +- updated `pages/Checkout/Billing.vue`, +- updated `pages/Checkout/Payment.vue`, +- updated `pages/Checkout/Shipping.vue`, +- updated `pages/Checkout/ThankYou.vue`, +- updated `pages/Home.vue`, +- updated `pages/MyAccount.vue`, +- updated `pages/MyAccount/BillingDetails.vue`, +- updated `pages/MyAccount/LoyaltyCard.vue`, +- updated `pages/MyAccount/MyNewsletter.vue`, +- updated `pages/MyAccount/MyProfile.vue`, +- updated `pages/MyAccount/MyReviews.vue`, +- updated `pages/MyAccount/OrderHistory.vue`, +- updated `pages/MyAccount/ShippingDetails.vue`, +- updated `pages/Product.vue`. diff --git a/packages/core/docs/migrate/2.4.0/commercetools.md b/packages/core/docs/migrate/2.4.0/commercetools.md new file mode 100644 index 0000000000..64f0ee44c5 --- /dev/null +++ b/packages/core/docs/migrate/2.4.0/commercetools.md @@ -0,0 +1,27 @@ +# Migration guide 2.4.0 for commercetools + +## Introduction + +In 2.4.0 release, we introduce Forgot Password functionality and basic support for Stores and Channels in commercetools - you can now select a specific store and its corresponding channel, e.g., to display prices for a selected channel. We also fix some bugs related to Checkout forms and add new getters. + +## Changes + +- **[BREAKING]** Enable the purchase of an item with selected supply channel and distribution channel +- Added `storeGetters`, `useStore` composable and plugin for creating locale, currency, and country cookies +- Added `forgotPasswordGetters` and `useForgotPassword` composable +- Added `getProductSku` getter to `productGetters` +- Extended `middleware.config.js` with `inventoryMode` optional property - when added to the configuration, Inventory Mode is set on cart creation +- Added smartphone only promo code input +- Removed hardcoded link to the category in `SearchResults.vue` +- Replaced mocked email address in MyProfile password change tab to active user email +- Fixed bug with input characters limits on Checkout forms preventing submitting the form with a street name less than five characters long and apartment number with less than two characters long +- Added e2e tests for Checkout + +We also made changes to the following files: +- Added `pages/MyAccount/MyProfile.vue`. +- Updated `components/MyAccount/BillingAddressForm.vue`. +- Updated `components/MyAccount/ShippingAddressForm.vue`. +- Updated `pages/Checkout/Billing.vue`. +- Updated `pages/Checkout/Payment.vue`. +- Updated `pages/Checkout/Shipping.vue`. +- Updated `lang/de.js` and `lang/en.js`. diff --git a/packages/core/docs/migrate/2.4.0/integrators.md b/packages/core/docs/migrate/2.4.0/integrators.md new file mode 100644 index 0000000000..e47503a60d --- /dev/null +++ b/packages/core/docs/migrate/2.4.0/integrators.md @@ -0,0 +1,11 @@ +# Migration guide 2.4.0 for Integrators + +## Introduction + +In 2.4.0, from the Integrator point of view, the most important change is a new `checkout.js` middleware - its main goal is to manage access to the checkout steps. + +## Changes + +- Added `checkout.js` middleware that integrators have to implement to prevent access to checkout steps when previous steps were not yet completed. +- Refactor naming and signatures of some of core factory functions. +- Refactor boilerplate getters. diff --git a/packages/core/docs/migrate/2.4.0/overview.md b/packages/core/docs/migrate/2.4.0/overview.md new file mode 100644 index 0000000000..378ba91887 --- /dev/null +++ b/packages/core/docs/migrate/2.4.0/overview.md @@ -0,0 +1,46 @@ +# Migration guide 2.4.0 + +## Introduction + +This version introduces many renaming or changes related to the function declarations among the core. +We had to proceed with this in order to keep the convention and unify the naming across whole VSF. + +We changed the composables and our factories according to the following rules: +- each composable always return one field with the response from the api +- each composable function takes one argument which is an object of given parameters +- each factory param function takes two arguments, first one is context (as it was before) and second one contains a function parmeters along with other options (such as customQuery +## Changes + +- Added `useForgotPasswordFactory`. +- Added `useSearchFactory`. +- Added `useStoreFactory`. +- Added Cache-Control headers for Home, Category, and Product pages. +- Implemented mobile menu. +- Updated form validation scheme for street, number, and city in the checkout and profile editing pages. +- Changed the core logger to match the CLI outputs. + +We also made changes to the following files: +- Added `components/HeaderNavigation.vue`. +- Added `pages/ResetPassword.vue`. +- Added `layouts/blank.vue`. +- Updated `components/AppHeader.vue`. +- Updated `components/BottomNavigation.vue`. +- Updated `components/CartSidebar.vue`. +- Updated `components/Checkout/CartPreview.vue`. +- Updated `components/Checkout/VsfPaymentProvider.vue`. +- Updated `components/Checkout/VsfShippingProvider.vue`. +- Updated `components/LoginModal.vue`. +- Updated `components/MyAccount/BillingAddressForm.vue`. +- Updated `components/MyAccount/PasswordResetForm.vue`. +- Updated `components/MyAccount/ProfileUpdateForm.vue`. +- Updated `components/ShippingAddressForm.vue`. +- Updated `components/SearchResults.vue`. +- Updated `components/UserBillingAddress.vue`. +- Updated `components/UserShippingAddress.vue`. +- Updated `components/WishlistSiderbar.vue`. +- Updated `pages/Category.vue`. +- Updated `pages/Checkout/Billing.vue`. +- Updated `pages/Checkout/Payment.vue`. +- Updated `pages/Checkout/Shipping.vue`. +- Updated `pages/Home.vue`. +- Updated `pages/Product.vue`. diff --git a/packages/core/docs/migrate/2.4.0/projects.md b/packages/core/docs/migrate/2.4.0/projects.md new file mode 100644 index 0000000000..7725daff3a --- /dev/null +++ b/packages/core/docs/migrate/2.4.0/projects.md @@ -0,0 +1,9 @@ +# Project upgrade notes + +## Composables usage + +We have changed a bit the naming and signatures of composable functions. Below is the full list of what hs been implemented or changed: + +| Composable | Method | Old signature | New signature | +|------------|--------|---------------|---------------| +| useCart | removeCoupon | ({ coupon: COUPON, customQuery?: CustomQuery }) | ({ couponCode: string, customQuery?: CustomQuery }) | diff --git a/packages/core/docs/migrate/index.md b/packages/core/docs/migrate/index.md new file mode 100644 index 0000000000..1fe8279f48 --- /dev/null +++ b/packages/core/docs/migrate/index.md @@ -0,0 +1,8 @@ +# Migration guides + +- [2.4.0](./2.4.0/overview.md) +- [2.3.0](./2.3.0/overview.md) +- [2.3.0-rc.3](./2.3.0-rc.3/overview.md) +- [2.3.0-rc.2](./2.3.0-rc.2/overview.md) +- [2.2.0](./2.2.0/overview.md) +- [2.1.0-rc.1](./2.1.0-rc.1/overview.md) \ No newline at end of file diff --git a/packages/core/docs/package.json b/packages/core/docs/package.json new file mode 100644 index 0000000000..1aa48b9176 --- /dev/null +++ b/packages/core/docs/package.json @@ -0,0 +1,33 @@ +{ + "name": "docs", + "version": "0.0.1", + "private": true, + "description": "", + "license": "MIT", + "scripts": { + "dev": "yarn build:core && yarn build:ct && vuepress dev", + "build": "yarn build:core && yarn build:ct && NODE_OPTIONS=--max_old_space_size=4096 vuepress build", + + "core-cache-ref": "cd ../cache && api-extractor run --local", + "core-core-ref": "cd ../core && api-extractor run --local", + "core-middleware-ref": "cd ../middleware && api-extractor run --local", + "core-ref-md": "api-documenter markdown --i core/api-reference --o core/api-reference", + "build:core": "yarn core-cache-ref && yarn core-core-ref && yarn core-middleware-ref && yarn core-ref-md", + + "ct-api-ref": "cd ../../commercetools/api-client && api-extractor run --local", + "ct-comp-ref": "cd ../../commercetools/composables && api-extractor run --local", + "ct-ref-md": "api-documenter markdown --i commercetools/api-reference --o commercetools/api-reference", + "build:ct": "yarn ct-api-ref && yarn ct-comp-ref && yarn ct-ref-md" + }, + "devDependencies": { + "@vue-storefront/commercetools-api": "~1.2.0", + "@microsoft/api-documenter": "^7.13.7", + "@microsoft/api-extractor": "^7.18.3", + "typescript": "^3.6.4", + "vuepress": "^1.2.0" + }, + "dependencies": { + "sass-loader": "^8.0.2", + "vue-multiselect": "^2.1.6" + } +} diff --git a/packages/core/docs/scripts/changelog.js b/packages/core/docs/scripts/changelog.js new file mode 100644 index 0000000000..e9b1b54b5f --- /dev/null +++ b/packages/core/docs/scripts/changelog.js @@ -0,0 +1,39 @@ +const fs = require('fs'); + +const commandArgs = process.argv.slice(2); + +const getCliArgument = (name, number) => { + return commandArgs[commandArgs.findIndex(arg => arg === name) + number]; +}; + +const releaseVersion = getCliArgument('--v', 1); + +const pathIn = getCliArgument('--in', 0) ? getCliArgument('--in', 1) : '../changelog'; + +const pathOut = getCliArgument('--out', 0) ? getCliArgument('--out', 1) : '../contributing/changelog.md'; + +const prNumbers = fs.readdirSync(pathIn); + +const numberOfPR = prNumbers.map(el => el.substr(0, el.lastIndexOf('.'))); + +const finalData = prNumbers.map(el => require(`${pathIn}/${el}`)).map((el, i) => ` +- ${el.isBreaking ? '[BREAKING]' : ''} ${el.description} ([#${numberOfPR[i]}](${el.link})) - [${el.author}](${el.linkToGitHubAccount}) +${el.isBreaking ? ` +| Before | After | Comment | Module +| ------ | ----- | ------ | ------` + +el.breakingChanges.map(br => '\n' + br.before + ' | ' + br.after + ' | ' + br.comment + ' | ' + br.module) : ''}`); + +const changelogData = fs.readFileSync(pathOut).toString().split('\n'); + +const versionExists = changelogData + .map(el => el.indexOf(releaseVersion)) + .findIndex(el => el > -1); + +changelogData.splice(versionExists > -1 ? versionExists + 1 : 1, 0, versionExists > -1 ? finalData : '\n## ' + releaseVersion + '\n' + finalData); +const text = changelogData.join('\n'); + +fs.writeFile(pathOut, text, (err) => { + if (err) return err; +}); + +prNumbers.map(el => fs.unlinkSync(`${pathIn}/${el}`)); diff --git a/packages/core/middleware/__tests__/helpers/getAgnosticStatusCode.spec.ts b/packages/core/middleware/__tests__/helpers/getAgnosticStatusCode.spec.ts new file mode 100644 index 0000000000..5c54ee30e7 --- /dev/null +++ b/packages/core/middleware/__tests__/helpers/getAgnosticStatusCode.spec.ts @@ -0,0 +1,59 @@ +import getAgnosticStatusCode from '../../src/helpers/getAgnosticStatusCode'; +const expectedStatusCode = 400; + +describe('[middleware-helpers] getAgnosticStatusCode', () => { + it('retrieves the status code from simple object', () => { + const testData = { + statusCode: expectedStatusCode + }; + + const statusCode = getAgnosticStatusCode(testData, 'statusCode'); + + expect(statusCode).toBe(expectedStatusCode); + }); + + it('retrieves the status code from nested object', () => { + const testData = { + a: { + b: 4, + statusCode: expectedStatusCode + } + }; + + const statusCode = getAgnosticStatusCode(testData, 'statusCode'); + + expect(statusCode).toBe(expectedStatusCode); + }); + + it('retrieves the status code from nested object inside an array', () => { + const testData = { + a: [ + { + statusCode: expectedStatusCode + } + ] + }; + + const statusCode = getAgnosticStatusCode(testData, 'statusCode'); + + expect(statusCode).toBe(expectedStatusCode); + }); + + it('returns only the value of the first matched key', () => { + const testData = { + statusCode: expectedStatusCode, + status: 500 + }; + + const statusCode = getAgnosticStatusCode(testData, 'statusCode', 'status'); + + expect(statusCode).toBe(expectedStatusCode); + }); + + it('handles values of type other than \'object\' correctly', () => { + expect(getAgnosticStatusCode('string', 'statusCode')).toBeUndefined; + expect(getAgnosticStatusCode(null, 'statusCode')).toBeUndefined; + expect(getAgnosticStatusCode(undefined, 'statusCode')).toBeUndefined; + expect(getAgnosticStatusCode(300, 'statusCode')).toBeUndefined; + }); +}); diff --git a/packages/core/middleware/api-extractor.json b/packages/core/middleware/api-extractor.json new file mode 100644 index 0000000000..f12f30fd57 --- /dev/null +++ b/packages/core/middleware/api-extractor.json @@ -0,0 +1,10 @@ +{ + "extends": "../../api-extractor.base.json", + "mainEntryPointFilePath": "./lib/index.d.ts", + "dtsRollup": { + "untrimmedFilePath": "./lib/.d.ts" + }, + "docModel": { + "apiJsonFilePath": "/core/docs/core/api-reference/.api.json" + } +} diff --git a/packages/core/middleware/jest.config.js b/packages/core/middleware/jest.config.js new file mode 100644 index 0000000000..721a788cd9 --- /dev/null +++ b/packages/core/middleware/jest.config.js @@ -0,0 +1,9 @@ +const baseConfig = require('./../../jest.base.config'); + +module.exports = { + ...baseConfig, + collectCoverage: true, + setupFilesAfterEnv: [ + './__tests__/setup.ts' + ] +}; diff --git a/packages/core/middleware/nuxt/index.js b/packages/core/middleware/nuxt/index.js new file mode 100644 index 0000000000..9a96d0c704 --- /dev/null +++ b/packages/core/middleware/nuxt/index.js @@ -0,0 +1,9 @@ +const { createServer } = require('@vue-storefront/middleware'); + +module.exports = function VueStorefrontMiddleware () { + const { integrations } = require(this.nuxt.options.rootDir + '/middleware.config.js'); + const handler = createServer({ integrations }); + const serverMiddleware = { path: '/api', handler }; + + this.addServerMiddleware(serverMiddleware); +}; diff --git a/packages/core/middleware/package.json b/packages/core/middleware/package.json new file mode 100644 index 0000000000..b0448d03dd --- /dev/null +++ b/packages/core/middleware/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vue-storefront/middleware", + "version": "2.4.1", + "description": "", + "main": "lib/index.cjs.js", + "module": "lib/index.es.js", + "types": "lib/index.d.ts", + "scripts": { + "build": "rimraf lib && rollup -c", + "test": "jest", + "prepublish": "yarn build" + }, + "author": "Vue Storefront", + "license": "MIT", + "dependencies": { + "consola": "2.15.3", + "express": "^4.17.1", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5" + }, + "devDependencies": { + "@types/express": "^4.11.1", + "@types/node": "^12.12.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/core/middleware/rollup.config.js b/packages/core/middleware/rollup.config.js new file mode 100644 index 0000000000..10dc02f5e2 --- /dev/null +++ b/packages/core/middleware/rollup.config.js @@ -0,0 +1,4 @@ +import pkg from './package.json'; +import { generateBaseConfig } from '../../rollup.base.config'; + +export default generateBaseConfig(pkg); diff --git a/packages/core/middleware/src/createServer.ts b/packages/core/middleware/src/createServer.ts new file mode 100644 index 0000000000..8a6cde7ca5 --- /dev/null +++ b/packages/core/middleware/src/createServer.ts @@ -0,0 +1,56 @@ +import express, { Request, Response, Express } from 'express'; +import cookieParser from 'cookie-parser'; +import cors from 'cors'; +import consola from 'consola'; +import { MiddlewareConfig, ApiClientExtension, CustomQuery } from '@vue-storefront/core'; +import { registerIntegrations } from './integrations'; +import getAgnosticStatusCode from './helpers/getAgnosticStatusCode'; + +const app = express(); +app.use(express.json()); +app.use(cookieParser()); +app.use(cors()); + +interface MiddlewareContext { + req: Request; + res: Response; + extensions: ApiClientExtension[]; + customQueries: Record; +} + +interface RequestParams { + integrationName: string; + functionName: string; +} + +function createServer (config: MiddlewareConfig): Express { + consola.info('Middleware starting....'); + consola.info('Loading integrations...'); + + const integrations = registerIntegrations(app, config.integrations); + + consola.success('Integrations loaded!'); + + app.post('/:integrationName/:functionName', async (req: Request, res: Response) => { + const { integrationName, functionName } = req.params as any as RequestParams; + const { apiClient, configuration, extensions, customQueries } = integrations[integrationName]; + const middlewareContext: MiddlewareContext = { req, res, extensions, customQueries }; + const createApiClient = apiClient.createApiClient.bind({ middleware: middlewareContext }); + const apiClientInstance = createApiClient(configuration); + const apiFunction = apiClientInstance.api[functionName]; + try { + const platformResponse = await apiFunction(...req.body); + + res.send(platformResponse); + } catch (error) { + res.status(getAgnosticStatusCode(error, 'status', 'statusCode') || 500); + res.send(error); + } + }); + + consola.success('Middleware created!'); + + return app; +} + +export { createServer }; diff --git a/packages/core/middleware/src/helpers/getAgnosticStatusCode.ts b/packages/core/middleware/src/helpers/getAgnosticStatusCode.ts new file mode 100644 index 0000000000..acc88f1ef4 --- /dev/null +++ b/packages/core/middleware/src/helpers/getAgnosticStatusCode.ts @@ -0,0 +1,14 @@ +const getAgnosticStatusCode = (obj: unknown, ...searchedKeys: string[]) => { + if (!obj || typeof obj !== 'object') return; + for (const key of Object.keys(obj)) { + if (searchedKeys.includes(key)) { + return obj[key]; + } + const statusCode = getAgnosticStatusCode(obj[key], ...searchedKeys); + if (statusCode) { + return statusCode; + } + } +}; + +export default getAgnosticStatusCode; diff --git a/packages/core/middleware/src/index.ts b/packages/core/middleware/src/index.ts new file mode 100644 index 0000000000..4588b86e3d --- /dev/null +++ b/packages/core/middleware/src/index.ts @@ -0,0 +1,11 @@ +/** + * Core Vue Storefront 2 library for Server Middieware. + * + * @remarks + * The `@vue-storefront/middleware` library is a Nuxt.js module, that is core piece + * required to create Server Middleware in Vue Storefront 2. + * + * @packageDocumentation + */ + +export * from './createServer'; diff --git a/packages/core/middleware/src/integrations.ts b/packages/core/middleware/src/integrations.ts new file mode 100644 index 0000000000..32ecad582a --- /dev/null +++ b/packages/core/middleware/src/integrations.ts @@ -0,0 +1,63 @@ +import consola from 'consola'; +import { Express } from 'express'; +import { + Integration, + ApiClientFactory, + ApiClientExtension, + IntegrationsSection, + CustomQuery +} from '@vue-storefront/core'; + +interface IntegrationLoaded { + apiClient: ApiClientFactory; + configuration: any; + extensions: ApiClientExtension[]; + customQueries?: Record +} + +type IntegrationsLoaded = Record + +const createRawExtensions = (apiClient: ApiClientFactory, integration: Integration): ApiClientExtension[] => { + const extensionsCreateFn = integration.extensions; + const predefinedExtensions = (apiClient.createApiClient as any)._predefinedExtensions; + return extensionsCreateFn ? extensionsCreateFn(predefinedExtensions) : predefinedExtensions; +}; + +const lookUpExternal = (curr: string | ApiClientExtension): ApiClientExtension[] => + // eslint-disable-next-line global-require + typeof curr === 'string' ? require(curr) : [curr]; + +const createExtensions = (rawExtensions: ApiClientExtension[]): ApiClientExtension[] => rawExtensions + .reduce((prev, curr) => [...prev, ...lookUpExternal(curr)], []); + +const registerIntegrations = (app: Express, integrations: IntegrationsSection): IntegrationsLoaded => + Object.entries(integrations).reduce((prev, [tag, integration]) => { + consola.info(`- Loading: ${tag} ${integration.location}`); + + // eslint-disable-next-line global-require + const apiClient: ApiClientFactory = require(integration.location); + const rawExtensions: ApiClientExtension[] = createRawExtensions(apiClient, integration); + const extensions: ApiClientExtension[] = createExtensions(rawExtensions); + + extensions.forEach(({ name, extendApp }) => { + consola.info(`- Loading: ${tag} extension: ${name}`); + + if (extendApp) { + extendApp({ app, configuration: integration.configuration }); + } + }); + + consola.success(`- Integration: ${tag} loaded!`); + + return { + ...prev, + [tag]: { + apiClient, + extensions, + configuration: integration.configuration, + customQueries: integration.customQueries + } + }; + }, {}); + +export { registerIntegrations }; diff --git a/packages/core/middleware/tsconfig.json b/packages/core/middleware/tsconfig.json new file mode 100644 index 0000000000..5f31c268ff --- /dev/null +++ b/packages/core/middleware/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "es5", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "lib": ["es6", "es7", "dom"], + "strict": false, + }, + "exclude": ["node_modules", "**/*.spec.ts"] +} diff --git a/packages/core/nuxt-module/.gitignore b/packages/core/nuxt-module/.gitignore new file mode 100644 index 0000000000..c434d59719 --- /dev/null +++ b/packages/core/nuxt-module/.gitignore @@ -0,0 +1,2 @@ +node_modules +!lib diff --git a/packages/core/nuxt-module/lib/helpers/isProduction.js b/packages/core/nuxt-module/lib/helpers/isProduction.js new file mode 100644 index 0000000000..e71474aecb --- /dev/null +++ b/packages/core/nuxt-module/lib/helpers/isProduction.js @@ -0,0 +1 @@ +module.exports = (options) => process.env.NODE_ENV === 'production' || options.coreDevelopment; \ No newline at end of file diff --git a/packages/core/nuxt-module/lib/helpers/log.js b/packages/core/nuxt-module/lib/helpers/log.js new file mode 100644 index 0000000000..63caa034d2 --- /dev/null +++ b/packages/core/nuxt-module/lib/helpers/log.js @@ -0,0 +1,9 @@ +const consola = require('consola'); +const chalk = require('chalk'); + +module.exports = Object.freeze({ + info: (message) => consola.info(chalk.bold('VSF'), message), + success: (message) => consola.success(chalk.bold('VSF'), message), + warning: (message) => consola.warning(chalk.bold('VSF'), message), + error: (message) => consola.error(chalk.bold('VSF'), message) +}); diff --git a/packages/core/nuxt-module/lib/helpers/merge.js b/packages/core/nuxt-module/lib/helpers/merge.js new file mode 100644 index 0000000000..4d1599b6f7 --- /dev/null +++ b/packages/core/nuxt-module/lib/helpers/merge.js @@ -0,0 +1,7 @@ +const { mergeWith, isArray } = require('lodash'); + +module.exports = (source, destination) => mergeWith(source, destination, (objValue, srcValue) => { + if (isArray(objValue)) { + return objValue.concat(srcValue); + } +}); diff --git a/packages/core/nuxt-module/lib/helpers/resolveDependency.js b/packages/core/nuxt-module/lib/helpers/resolveDependency.js new file mode 100644 index 0000000000..6d1474a007 --- /dev/null +++ b/packages/core/nuxt-module/lib/helpers/resolveDependency.js @@ -0,0 +1,7 @@ +module.exports = function resolveDependency (name) { + try { + return require.resolve(name, { paths: [ process.cwd() ] }) + } catch (error) { + return false; + } +}; diff --git a/packages/core/nuxt-module/lib/module.js b/packages/core/nuxt-module/lib/module.js new file mode 100644 index 0000000000..4bdbad5ab9 --- /dev/null +++ b/packages/core/nuxt-module/lib/module.js @@ -0,0 +1,97 @@ +// TODO proper bundling, for now it's just to experiment with nuxt modules api +const fs = require('fs'); +const path = require('path'); +const log = require('./helpers/log'); +const merge = require('./helpers/merge'); +const resolveDependency = require('./helpers/resolveDependency'); +const performanceModule = require('./modules/performance'); +const storefrontUiModule = require('./modules/storefront-ui'); +const rawSourcesModule = require('./modules/raw-sources-loader'); + +module.exports = function VueStorefrontNuxtModule (moduleOptions) { + const defaultOptions = { + coreDevelopment: false, + performance : { + httpPush: true, + purgeCSS: { + enabled: false, + paths: [ + '**/*.vue' + ] + } + }, + useRawSource: { + dev: [], + prod: [] + } + }; + + const options = merge(defaultOptions, moduleOptions); + + // Add meta data + this.options.head.meta.push({ + name: 'generator', + content: 'Vue Storefront 2' + }); + + log.info('Starting Vue Storefront Nuxt Module'); + + // Enable HTTP/2 push for JS files + if (options.performance.httpPush) { + this.options.render = merge(this.options.render, { + http2: { + push: true, + pushAssets: (request, response, publicPath, preloadFiles) => { + return preloadFiles + .filter(({ asType }) => asType === 'script') + .map(({ file, asType }) => `<${publicPath}${file}>; rel=preload; as=${asType}`); + } + } + }); + } + + // Context plugin + this.addPlugin(path.resolve(__dirname, 'plugins/context.js')) + log.success('Installed Vue Storefront Context plugin'); + + // SSR plugin + this.addPlugin(path.resolve(__dirname, 'plugins/ssr.js')); + log.success('Installed Vue Storefront SSR plugin'); + + // Logger plugin + this.addPlugin({ + src: path.resolve(__dirname, 'plugins/logger.js'), + options: moduleOptions.logger || {} + }); + log.success('Installed VSF Logger plugin'); + + // Context plugin + this.addPlugin(path.resolve(__dirname, 'plugins/e2e-testing.js')) + log.success('Installed Vue Storefront E2E testing plugin'); + + // i18n-cookies plugin + this.addPlugin({ + src: path.resolve(__dirname, 'plugins/i18n-cookies.js'), + options: this.options.i18n + }); + log.success('Installed Internationalization Cookies plugin'); + + // Composition API plugin + this.addModule('@nuxtjs/composition-api'); + log.success('Installed nuxt Composition API Module'); + + // StorefrontUI module + if (fs.existsSync(resolveDependency('@storefront-ui/vue'))) { + storefrontUiModule.call(this, options); + log.success('Installed StorefrontUI Module'); + } + + // Performance module + performanceModule.call(this, options); + log.success('Installed Performance Module'); + + // Raw sources loader + rawSourcesModule.call(this, options); +} + +module.exports.meta = require('../package.json') diff --git a/packages/core/nuxt-module/lib/modules/performance.js b/packages/core/nuxt-module/lib/modules/performance.js new file mode 100644 index 0000000000..905e5b478c --- /dev/null +++ b/packages/core/nuxt-module/lib/modules/performance.js @@ -0,0 +1,31 @@ +const { merge } = require('lodash'); + +function pushScripts() { + this.options.render = merge(this.options.render, { + http2: { + push: true, + pushAssets: (request, response, publicPath, preloadFiles) => { + return preloadFiles + .filter(({ asType }) => asType === 'script') + .map(({ file, asType }) => `<${publicPath}${file}>; rel=preload; as=${asType}`); + } + } + }); +} + +function loadPurgeCss(options) { + // PurgeCSS module should be installed after all other modules + this.nuxt.hook('modules:done', moduleContainer => moduleContainer.addModule([ 'nuxt-purgecss', options ])); +} + +module.exports = function VueStorefrontPerformanceModule (options) { + const { httpPush, purgeCSS } = options.performance; + + if (httpPush) { + pushScripts.call(this); + } + + if (purgeCSS && purgeCSS.enabled) { + loadPurgeCss.call(this, purgeCSS); + } +}; diff --git a/packages/core/nuxt-module/lib/modules/raw-sources-loader.js b/packages/core/nuxt-module/lib/modules/raw-sources-loader.js new file mode 100644 index 0000000000..9414e90559 --- /dev/null +++ b/packages/core/nuxt-module/lib/modules/raw-sources-loader.js @@ -0,0 +1,21 @@ +const log = require('../helpers/log'); +const isProduction = require('../helpers/isProduction'); +const resolveDependency = require('../helpers/resolveDependency'); + +module.exports = function VueStorefrontPerformanceModule (options) { + const useRawSource = (package) => { + const pkgPath = resolveDependency(`${package}/package.json`); + const pkg = require(pkgPath); + + if (pkg.module) { + this.extendBuild(config => { + config.resolve.alias[pkg.name + '$'] = resolveDependency(`${package}/${pkg.module}`); + }); + } + + this.options.build.transpile.push(package); + log.info(`Using raw source/ESM for ${pkg.name}`); + }; + + options.useRawSource[isProduction(options) ? 'prod' : 'dev'].map(package => useRawSource(package)); +}; diff --git a/packages/core/nuxt-module/lib/modules/storefront-ui.js b/packages/core/nuxt-module/lib/modules/storefront-ui.js new file mode 100644 index 0000000000..8a405c7f87 --- /dev/null +++ b/packages/core/nuxt-module/lib/modules/storefront-ui.js @@ -0,0 +1,18 @@ +const merge = require('../helpers/merge'); + +// TODO: Create a separate nuxt module for storefront ui +function loadStorefrontRawSources (options) { + const rawSources = [ + '@storefront-ui/vue', + '@storefront-ui/shared' + ]; + + options.useRawSource = merge(options.useRawSource, { + dev: rawSources, + prod: rawSources + }); +} + +module.exports = function VueStorefrontPerformanceModule (options) { + loadStorefrontRawSources.call(this, options); +}; diff --git a/packages/core/nuxt-module/lib/plugins/composition-api.js b/packages/core/nuxt-module/lib/plugins/composition-api.js new file mode 100644 index 0000000000..ca35865e01 --- /dev/null +++ b/packages/core/nuxt-module/lib/plugins/composition-api.js @@ -0,0 +1,4 @@ +import Vue from 'vue'; +import VueCompositionApi from '@vue/composition-api'; + +Vue.use(VueCompositionApi); diff --git a/packages/core/nuxt-module/lib/plugins/context.js b/packages/core/nuxt-module/lib/plugins/context.js new file mode 100644 index 0000000000..d61aab14b5 --- /dev/null +++ b/packages/core/nuxt-module/lib/plugins/context.js @@ -0,0 +1,18 @@ + +import { configureContext } from '@vue-storefront/core' +import { useContext as useBaseContext } from '@nuxtjs/composition-api'; + +const contextPlugin = (ctx, inject) => { + const sharedMap = new Map(); + + const useVSFContext = () => { + const { $vsf, ...context } = useBaseContext(); + + return { $vsf, ...context, ...$vsf } + } + + configureContext({ useVSFContext }); + inject('sharedRefsMap', sharedMap) +}; + +export default contextPlugin; diff --git a/packages/core/nuxt-module/lib/plugins/e2e-testing.js b/packages/core/nuxt-module/lib/plugins/e2e-testing.js new file mode 100644 index 0000000000..5d9b74bc2c --- /dev/null +++ b/packages/core/nuxt-module/lib/plugins/e2e-testing.js @@ -0,0 +1,13 @@ +import Vue from 'vue'; + +const e2eTestingPlugin = (ctx) => { + Vue.directive('e2e', { + bind: (element, binding) => { + const enabled = ctx.isDev || ctx.env.NUXT_ENV_E2E === true.toString(); + + return enabled && element.setAttribute(`data-e2e`, binding.value); + } + }); +}; + +export default e2eTestingPlugin; \ No newline at end of file diff --git a/packages/core/nuxt-module/lib/plugins/i18n-cookies.js b/packages/core/nuxt-module/lib/plugins/i18n-cookies.js new file mode 100644 index 0000000000..e6c423dae0 --- /dev/null +++ b/packages/core/nuxt-module/lib/plugins/i18n-cookies.js @@ -0,0 +1,34 @@ +const { VSF_CURRENCY_COOKIE, VSF_COUNTRY_COOKIE } = require('@vue-storefront/core'); + +const i18nCookiesPlugin = ({ $cookies }) => { + const i18n = <%= serialize(options) %>; + + const settings = { + defaultLocale: i18n.defaultLocale || (i18n.locales.length && i18n.locales[0].code), + currency: i18n.currency || (i18n.currencies.length && i18n.currencies[0].name), + country: i18n.country || (i18n.countries.length && i18n.countries[0].name) + }; + + const missingFields = Object + .entries(settings) + .reduce((carry, [name, value]) => { + !value && carry.push(name); + + return carry; + }, []); + + if (missingFields.length) { + throw new Error(`Following fields are missing in the i18n configuration: ${ missingFields.join(', ') }`); + } + + const cookieOptions = { + path: '/', + sameSite: 'lax', + expires: new Date(new Date().setFullYear(new Date().getFullYear() + 1)) // Year from now + }; + + !$cookies.get(VSF_CURRENCY_COOKIE) && $cookies.set(VSF_CURRENCY_COOKIE, settings.currency, cookieOptions); + !$cookies.get(VSF_COUNTRY_COOKIE) && $cookies.set(VSF_COUNTRY_COOKIE, settings.country, cookieOptions); +}; + +export default i18nCookiesPlugin; \ No newline at end of file diff --git a/packages/core/nuxt-module/lib/plugins/logger.js b/packages/core/nuxt-module/lib/plugins/logger.js new file mode 100644 index 0000000000..2cfd887c10 --- /dev/null +++ b/packages/core/nuxt-module/lib/plugins/logger.js @@ -0,0 +1,8 @@ +import { registerLogger } from '@vue-storefront/core' + +const loggerPlugin = (ctx) => { + const { verbosity, customLogger, ...args } = <%= serialize(options) %>; + registerLogger(customLogger || args, verbosity || 'error') +}; + +export default loggerPlugin; diff --git a/packages/core/nuxt-module/lib/plugins/ssr.js b/packages/core/nuxt-module/lib/plugins/ssr.js new file mode 100644 index 0000000000..889f0e9311 --- /dev/null +++ b/packages/core/nuxt-module/lib/plugins/ssr.js @@ -0,0 +1,31 @@ +import { configureSSR } from '@vue-storefront/core' +import { ssrRef, getCurrentInstance, onServerPrefetch } from '@nuxtjs/composition-api'; + +const hasRouteChanged = (ctx) => { + const { from } = ctx.$router.app.context; + const { current } = ctx.$router.history + + if (!from) { + return false + } + + return from.fullPath !== current.fullPath +} + +const ssrPlugin = () => { + configureSSR({ + vsfRef: ssrRef, + onSSR: (fn) => { + onServerPrefetch(fn); + if (typeof window !== 'undefined') { + const vm = getCurrentInstance(); + + if (hasRouteChanged(vm)) { + fn(); + } + } + } + }); +}; + +export default ssrPlugin; diff --git a/packages/core/nuxt-module/package.json b/packages/core/nuxt-module/package.json new file mode 100644 index 0000000000..856b637cac --- /dev/null +++ b/packages/core/nuxt-module/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vue-storefront/nuxt", + "version": "2.4.1", + "description": "", + "main": "lib/module.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Vue Storefront", + "license": "MIT", + "dependencies": { + "@nuxt/typescript-build": "^2.0.0", + "@vue/composition-api": "1.0.0-beta.21", + "@nuxtjs/composition-api": "0.17.0", + "@nuxtjs/style-resources": "^1.0.0", + "chalk": "^2.4.2", + "chokidar": "^3.3.1", + "consola": "^2.10.1", + "lodash": "^4.17.15", + "nuxt-purgecss": "^1.0.0" + }, + "devDependencies": { + "@nuxt/types": "^0.7.9" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/core/nuxt-theme-module/README.md b/packages/core/nuxt-theme-module/README.md new file mode 100644 index 0000000000..66c8e8b136 --- /dev/null +++ b/packages/core/nuxt-theme-module/README.md @@ -0,0 +1,49 @@ +# Nuxt Theme Module + +This module is basically our default theme on steroids. It contains additional utilities that make it easier to work with the theme (in projects and as core developers) and allows to disable parts of its features: +- By default, it adds eCommerce [routes](https://github.com/DivanteLtd/vue-storefront/blob/next/packages/core/nuxt-theme-module/routes.js) to your app. +- If you set `generate` in the config, it will copy, merge & watch for changes in agnostic and integration theme. + +## How to install +Add dependency: +```sh +yarn add @vue-storefront/nuxt-theme +``` +Add it to `buildModules` in your `nuxt.config.js`: +```js +['@vue-storefront/nuxt-theme'], +``` + +## Configuration details +### `routes` +If you want to disable autoadding routes you can do it: +```js +['@vue-storefront/nuxt-theme', { + routes: false +}], +``` +### `generate` +To properly configure `generate` property you have to provide data that will be replaced in EJS templates. This property applies only to the core development: +```js +['@vue-storefront/nuxt-theme', { + generate: { + replace: { + apiClient: '@vue-storefront/commercetools-api', + composables: '@vue-storefront/commercetools' + } + } +}], +``` +Example EJS template where it will be pasted: +```js +import { useCategory } from '<%= options.generate.replace.composables %>'; +``` + +If you want to change the target directory where agnostic and integration themes will be merged and copied during core development change the `generate.path` property (default: `_theme`): +```js +['@vue-storefront/nuxt-theme', { + generate: { + path: '.custom-dir' + } +}], +``` diff --git a/packages/core/nuxt-theme-module/defaultConfig.js b/packages/core/nuxt-theme-module/defaultConfig.js new file mode 100644 index 0000000000..97fdb6c403 --- /dev/null +++ b/packages/core/nuxt-theme-module/defaultConfig.js @@ -0,0 +1,3 @@ +export default { + routes: true +}; diff --git a/packages/core/nuxt-theme-module/generate.js b/packages/core/nuxt-theme-module/generate.js new file mode 100644 index 0000000000..f8e5461d8a --- /dev/null +++ b/packages/core/nuxt-theme-module/generate.js @@ -0,0 +1,89 @@ +const path = require('path'); +const fs = require('fs'); +const chokidar = require('chokidar'); +const chalk = require('chalk'); +const compileTemplate = require('./scripts/compileTemplate'); +const { copyThemeFile, copyThemeFiles } = require('./scripts/copyThemeFiles'); +const getAllFilesFromDir = require('./scripts/getAllFilesFromDir'); +const getAllSubDirs = require('./scripts/getAllSubDirs'); + +export default async function ({ + log, + moduleOptions, + projectLocalThemeDir, + targetDirectory +}) { + + const agnosticThemeDir = path.join(__dirname, 'theme'); + const compileAgnosticTemplate = (filePath) => { + return compileTemplate( + path.join(__dirname, filePath), + this.options.buildDir.split('.nuxt').pop() + targetDirectory + path.sep + filePath.split('theme' + path.sep).pop(), + { + generate: { + replace: { + apiClient: moduleOptions.generate.replace.apiClient, + composables: moduleOptions.generate.replace.composables + } + } + }); + }; + + const agnosticThemeFiles = getAllFilesFromDir(agnosticThemeDir).filter(file => !file.includes(path.sep + 'static' + path.sep)); + + log.info('Adding theme files...'); + + const themeDirectoriesPaths = getAllSubDirs(this.options.rootDir, [targetDirectory, '.nuxt', 'node_modules', 'test']) + .map(directory => path.join(this.options.rootDir, directory)); + + await Promise.all(agnosticThemeFiles.map(path => compileAgnosticTemplate(path))); + await Promise.all(themeDirectoriesPaths.map(absolutePath => copyThemeFiles(absolutePath))); + + log.success(`Added ${agnosticThemeFiles.length} theme file(s) to ${chalk.bold(targetDirectory)} folder`); + + this.options.dir = { + ...this.options.dir, + ...{ + middleware: `${targetDirectory}/middleware`, + layouts: `${targetDirectory}/layouts`, + assets: `${targetDirectory}/assets`, + pages: `${targetDirectory}/pages` + } + }; + + this.extendBuild(config => { + delete config.resolve.alias['~']; + config.resolve.alias['~/middleware'] = path.join(projectLocalThemeDir, path.sep + 'middleware'); + config.resolve.alias['~/components'] = path.join(projectLocalThemeDir, path.sep + 'components'); + config.resolve.alias['~/assets'] = path.join(projectLocalThemeDir, path.sep + 'assets'); + config.resolve.alias['~'] = path.join(projectLocalThemeDir); + }); + + chokidar.watch(agnosticThemeDir, { ignoreInitial: true }).on('all', (event, baseFilePath) => { + const overwriteFilePath = baseFilePath.replace(agnosticThemeDir, this.options.rootDir); + + if (event === 'add' || event === 'change') { + if (!fs.existsSync(overwriteFilePath)) { + compileAgnosticTemplate(baseFilePath.replace(__dirname, '')); + } + } else if (event === 'unlink') { + if (!fs.existsSync(overwriteFilePath)) { + fs.unlinkSync(baseFilePath.replace(agnosticThemeDir, projectLocalThemeDir)); + } + } + }); + + chokidar.watch(themeDirectoriesPaths, { ignoreInitial: true }) + .on('all', (event, filePath) => { + if (event === 'unlink') { + const baseFilePath = filePath.replace(this.options.rootDir, agnosticThemeDir); + if (fs.existsSync(baseFilePath)) { + compileAgnosticTemplate(baseFilePath.replace(__dirname, '')); + } else { + fs.unlinkSync(filePath.replace(this.options.rootDir, projectLocalThemeDir)); + } + } else if (event === 'add' || event === 'change') { + copyThemeFile(filePath); + } + }); +} diff --git a/packages/core/nuxt-theme-module/index.js b/packages/core/nuxt-theme-module/index.js new file mode 100644 index 0000000000..be520314d4 --- /dev/null +++ b/packages/core/nuxt-theme-module/index.js @@ -0,0 +1,42 @@ + +const consola = require('consola'); +const merge = require('lodash.merge'); +const defaultConfig = require('./defaultConfig').default; +const getRoutes = require('./routes'); +const chalk = require('chalk'); + +const log = { + info: (message) => consola.info(chalk.bold('VSF'), message), + success: (message) => consola.success(chalk.bold('VSF'), message), + warning: (message) => consola.warn(chalk.bold('VSF'), message), + error: (message) => consola.error(chalk.bold('VSF'), message) +}; + +module.exports = async function DefaultThemeModule(moduleOptions) { + log.info(chalk.green('Starting Theme Module')); + + moduleOptions = merge(defaultConfig, moduleOptions); + const targetDirectory = moduleOptions.generate && moduleOptions.generate.path + ? moduleOptions.generate.path + : '_theme'; + const projectLocalThemeDir = this.options.buildDir.replace('.nuxt', targetDirectory); + + if (moduleOptions.routes) { + this.extendRoutes((routes) => { + getRoutes(moduleOptions.generate ? projectLocalThemeDir : this.options.rootDir).forEach(route => routes.unshift(route)); + }); + } + + if (moduleOptions.generate) { + log.info('Watching changes in @vue-storefront/nuxt-theme-module and used platform theme directory'); + // eslint-disable-next-line global-require + const generate = require('./generate').default; + + generate.call(this, { + log, + moduleOptions, + projectLocalThemeDir, + targetDirectory + }); + } +}; diff --git a/packages/core/nuxt-theme-module/package.json b/packages/core/nuxt-theme-module/package.json new file mode 100644 index 0000000000..e5aa419ddb --- /dev/null +++ b/packages/core/nuxt-theme-module/package.json @@ -0,0 +1,29 @@ +{ + "name": "@vue-storefront/nuxt-theme", + "version": "2.4.1", + "description": "", + "main": "lib/module.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Vue Storefront", + "license": "MIT", + "devDependencies": { + "@vue/composition-api": "1.0.0-beta.21", + "chalk": "^2.4.2", + "chokidar": "^3.3.1", + "consola": "^2.10.1", + "loglevel": "^1.6.7", + "ts-loader": "^8.0.3", + "vue": "^2.6.11" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "ejs": "^3.0.2", + "lodash.merge": "^4.6.2", + "lodash.debounce": "^4.0.8", + "vue-lazy-hydration": "^2.0.0-beta.4" + } +} diff --git a/packages/core/nuxt-theme-module/routes.js b/packages/core/nuxt-theme-module/routes.js new file mode 100644 index 0000000000..28da6561b1 --- /dev/null +++ b/packages/core/nuxt-theme-module/routes.js @@ -0,0 +1,57 @@ +const path = require('path'); + +module.exports = function getRoutes(themeDir = __dirname) { + return [{ + name: 'home', + path: '/', + component: path.resolve(themeDir, 'pages/Home.vue') + }, + { + name: 'product', + path: '/p/:id/:slug/', + component: path.resolve(themeDir, 'pages/Product.vue') + }, + { + name: 'category', + path: '/c/:slug_1/:slug_2?/:slug_3?/:slug_4?/:slug_5?', + component: path.resolve(themeDir, 'pages/Category.vue') + }, + { + name: 'my-account', + path: '/my-account/:pageName?', + component: path.resolve(themeDir, 'pages/MyAccount.vue') + }, + { + name: 'checkout', + path: '/checkout', + component: path.resolve(themeDir, 'pages/Checkout.vue'), + children: [ + { + path: 'shipping', + name: 'shipping', + component: path.resolve(themeDir, 'pages/Checkout/Shipping.vue') + }, + { + path: 'billing', + name: 'billing', + component: path.resolve(themeDir, 'pages/Checkout/Billing.vue') + }, + { + path: 'payment', + name: 'payment', + component: path.resolve(themeDir, 'pages/Checkout/Payment.vue') + }, + { + path: 'thank-you', + name: 'thank-you', + component: path.resolve(themeDir, 'pages/Checkout/ThankYou.vue') + } + ] + }, + { + name: 'reset-password', + path: '/reset-password', + component: path.resolve(themeDir, 'pages/ResetPassword.vue') + } + ]; +}; diff --git a/packages/core/nuxt-theme-module/scripts/compileTemplate.js b/packages/core/nuxt-theme-module/scripts/compileTemplate.js new file mode 100644 index 0000000000..e4987931a9 --- /dev/null +++ b/packages/core/nuxt-theme-module/scripts/compileTemplate.js @@ -0,0 +1,15 @@ +const ejs = require('ejs'); +const fs = require('fs'); +const ensureDirectoryExists = require('./ensureDirectoryExists'); + +module.exports = async function compileTemplate(templatePath, outputPath, options) { + const data = fs.readFileSync(templatePath, 'utf8'); + const compiledTemplate = ejs.render( + data, + { options }, + { filename: templatePath } + ); + + ensureDirectoryExists(outputPath); + return fs.writeFileSync(outputPath, compiledTemplate); +}; diff --git a/packages/core/nuxt-theme-module/scripts/copyThemeFiles.js b/packages/core/nuxt-theme-module/scripts/copyThemeFiles.js new file mode 100644 index 0000000000..30fd410f12 --- /dev/null +++ b/packages/core/nuxt-theme-module/scripts/copyThemeFiles.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const path = require('path'); +const isImage = require('./isImage'); +const getAllFilesFromDir = require('./getAllFilesFromDir.js'); +const ensureDirectoryExists = require('./ensureDirectoryExists'); + +async function copyFile(fileDir, outDir) { + const data = fs.readFileSync(fileDir, !isImage(fileDir) ? 'utf8' : undefined); + ensureDirectoryExists(outDir); + return fs.writeFileSync(outDir, data); +} + +function copyThemeFile(themePath) { + return copyFile(themePath, themePath.replace(path.sep + 'theme' + path.sep, path.sep + 'theme' + path.sep + '_theme' + path.sep)); +} + +function copyThemeFiles(filesDir) { + return Promise.all(getAllFilesFromDir(filesDir).map( + file => copyThemeFile(file) + )); +} + +module.exports = { + copyFile, + copyThemeFile, + copyThemeFiles +}; diff --git a/packages/core/nuxt-theme-module/scripts/ensureDirectoryExists.js b/packages/core/nuxt-theme-module/scripts/ensureDirectoryExists.js new file mode 100644 index 0000000000..28363a6a03 --- /dev/null +++ b/packages/core/nuxt-theme-module/scripts/ensureDirectoryExists.js @@ -0,0 +1,11 @@ +const fs = require('fs'); +const path = require('path'); + +module.exports = function ensureDirectoryExists (filePath) { + const dirname = path.dirname(filePath); + if (fs.existsSync(dirname)) { + return true; + } + ensureDirectoryExists(dirname); + fs.mkdirSync(dirname); +}; diff --git a/packages/core/nuxt-theme-module/scripts/getAllFilesFromDir.js b/packages/core/nuxt-theme-module/scripts/getAllFilesFromDir.js new file mode 100644 index 0000000000..df3172991a --- /dev/null +++ b/packages/core/nuxt-theme-module/scripts/getAllFilesFromDir.js @@ -0,0 +1,24 @@ +const fs = require('fs'); +const path = require('path'); +const parentDirectory = path.resolve(__dirname, '..'); + +/** + * + * @param {string} dirPath + * @param {Array} arrayOfFiles + * @returns {Array} + */ +const getAllFilesFromDir = (dirPath, arrayOfFiles = []) => { + const files = fs.readdirSync(dirPath); + files.forEach((file) => { + if (fs.statSync(dirPath + path.sep + file).isDirectory()) { + arrayOfFiles = getAllFilesFromDir(dirPath + path.sep + file, arrayOfFiles); + } else { + arrayOfFiles.push((dirPath + path.sep + file).split(parentDirectory + path.sep).pop()); + } + }); + + return arrayOfFiles; +}; + +module.exports = getAllFilesFromDir; diff --git a/packages/core/nuxt-theme-module/scripts/getAllSubDirs.js b/packages/core/nuxt-theme-module/scripts/getAllSubDirs.js new file mode 100644 index 0000000000..51fac84e93 --- /dev/null +++ b/packages/core/nuxt-theme-module/scripts/getAllSubDirs.js @@ -0,0 +1,14 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * + * @param {string} directoryPath + * @param {Array} omitDirectories + * @returns {Array} + */ +const getAllSubDirs = (directoryPath, omitDirectories = []) => fs.readdirSync(directoryPath).filter( + file => fs.statSync(path.join(directoryPath, file)).isDirectory() && !omitDirectories.includes(file) +); + +module.exports = getAllSubDirs; diff --git a/packages/core/nuxt-theme-module/scripts/isImage.js b/packages/core/nuxt-theme-module/scripts/isImage.js new file mode 100644 index 0000000000..91ebfaa59e --- /dev/null +++ b/packages/core/nuxt-theme-module/scripts/isImage.js @@ -0,0 +1,125 @@ +const imageExtensions = [ + 'ase', + 'art', + 'bmp', + 'blp', + 'cd5', + 'cit', + 'cpt', + 'cr2', + 'cut', + 'dds', + 'dib', + 'djvu', + 'egt', + 'exif', + 'gif', + 'gpl', + 'grf', + 'icns', + 'ico', + 'iff', + 'jng', + 'jpeg', + 'jpg', + 'jfif', + 'jp2', + 'jps', + 'lbm', + 'max', + 'miff', + 'mng', + 'msp', + 'nitf', + 'ota', + 'pbm', + 'pc1', + 'pc2', + 'pc3', + 'pcf', + 'pcx', + 'pdn', + 'pgm', + 'PI1', + 'PI2', + 'PI3', + 'pict', + 'pct', + 'pnm', + 'pns', + 'ppm', + 'psb', + 'psd', + 'pdd', + 'psp', + 'px', + 'pxm', + 'pxr', + 'qfx', + 'raw', + 'rle', + 'sct', + 'sgi', + 'rgb', + 'int', + 'bw', + 'tga', + 'tiff', + 'tif', + 'vtf', + 'xbm', + 'xcf', + 'xpm', + '3dv', + 'amf', + 'ai', + 'awg', + 'cgm', + 'cdr', + 'cmx', + 'dxf', + 'e2d', + 'egt', + 'eps', + 'fs', + 'gbr', + 'odg', + 'svg', + 'stl', + 'vrml', + 'x3d', + 'sxd', + 'v2d', + 'vnd', + 'wmf', + 'emf', + 'art', + 'xar', + 'png', + 'webp', + 'jxr', + 'hdp', + 'wdp', + 'cur', + 'ecw', + 'iff', + 'lbm', + 'liff', + 'nrrd', + 'pam', + 'pcx', + 'pgf', + 'sgi', + 'rgb', + 'rgba', + 'bw', + 'int', + 'inta', + 'sid', + 'ras', + 'sun', + 'tga' +]; + +const path = require('path'); +module.exports = (filePath) => imageExtensions.includes(path.extname(filePath).slice(1).toLowerCase()); diff --git a/packages/core/nuxt-theme-module/theme/components/AppFooter.vue b/packages/core/nuxt-theme-module/theme/components/AppFooter.vue new file mode 100644 index 0000000000..89a0aabe3b --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/AppFooter.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/AppHeader.vue b/packages/core/nuxt-theme-module/theme/components/AppHeader.vue new file mode 100644 index 0000000000..1b0c68935d --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/AppHeader.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/BottomNavigation.vue b/packages/core/nuxt-theme-module/theme/components/BottomNavigation.vue new file mode 100644 index 0000000000..b85d2ab91b --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/BottomNavigation.vue @@ -0,0 +1,70 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/CartSidebar.vue b/packages/core/nuxt-theme-module/theme/components/CartSidebar.vue new file mode 100644 index 0000000000..4c3edf744d --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/CartSidebar.vue @@ -0,0 +1,268 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/Checkout/CartPreview.vue b/packages/core/nuxt-theme-module/theme/components/Checkout/CartPreview.vue new file mode 100644 index 0000000000..14c08cf887 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/Checkout/CartPreview.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/Checkout/VsfPaymentProvider.vue b/packages/core/nuxt-theme-module/theme/components/Checkout/VsfPaymentProvider.vue new file mode 100644 index 0000000000..cefdc3997c --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/Checkout/VsfPaymentProvider.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/Checkout/VsfShippingProvider.vue b/packages/core/nuxt-theme-module/theme/components/Checkout/VsfShippingProvider.vue new file mode 100644 index 0000000000..40389ae10c --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/Checkout/VsfShippingProvider.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/HeaderNavigation.vue b/packages/core/nuxt-theme-module/theme/components/HeaderNavigation.vue new file mode 100644 index 0000000000..4f935fb437 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/HeaderNavigation.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/InstagramFeed.vue b/packages/core/nuxt-theme-module/theme/components/InstagramFeed.vue new file mode 100644 index 0000000000..a3746eaede --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/InstagramFeed.vue @@ -0,0 +1,101 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/LocaleSelector.vue b/packages/core/nuxt-theme-module/theme/components/LocaleSelector.vue new file mode 100644 index 0000000000..50f18dd8c9 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/LocaleSelector.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/LoginModal.vue b/packages/core/nuxt-theme-module/theme/components/LoginModal.vue new file mode 100644 index 0000000000..2a121bcac2 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/LoginModal.vue @@ -0,0 +1,386 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/MobileStoreBanner.vue b/packages/core/nuxt-theme-module/theme/components/MobileStoreBanner.vue new file mode 100644 index 0000000000..8765a266b8 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/MobileStoreBanner.vue @@ -0,0 +1,81 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/MyAccount/BillingAddressForm.vue b/packages/core/nuxt-theme-module/theme/components/MyAccount/BillingAddressForm.vue new file mode 100644 index 0000000000..8807fc62b6 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/MyAccount/BillingAddressForm.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/MyAccount/PasswordResetForm.vue b/packages/core/nuxt-theme-module/theme/components/MyAccount/PasswordResetForm.vue new file mode 100644 index 0000000000..1ff617fdff --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/MyAccount/PasswordResetForm.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/MyAccount/ProfileUpdateForm.vue b/packages/core/nuxt-theme-module/theme/components/MyAccount/ProfileUpdateForm.vue new file mode 100644 index 0000000000..9658e251fd --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/MyAccount/ProfileUpdateForm.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/MyAccount/ShippingAddressForm.vue b/packages/core/nuxt-theme-module/theme/components/MyAccount/ShippingAddressForm.vue new file mode 100644 index 0000000000..c24aee5c83 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/MyAccount/ShippingAddressForm.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/NewsletterModal.vue b/packages/core/nuxt-theme-module/theme/components/NewsletterModal.vue new file mode 100644 index 0000000000..4ac601f12c --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/NewsletterModal.vue @@ -0,0 +1,116 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/Notification.vue b/packages/core/nuxt-theme-module/theme/components/Notification.vue new file mode 100644 index 0000000000..9d3301de88 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/Notification.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/RelatedProducts.vue b/packages/core/nuxt-theme-module/theme/components/RelatedProducts.vue new file mode 100644 index 0000000000..287fc16477 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/RelatedProducts.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/SearchResults.vue b/packages/core/nuxt-theme-module/theme/components/SearchResults.vue new file mode 100644 index 0000000000..67fc77b775 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/SearchResults.vue @@ -0,0 +1,249 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/TopBar.vue b/packages/core/nuxt-theme-module/theme/components/TopBar.vue new file mode 100644 index 0000000000..83a78e2697 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/TopBar.vue @@ -0,0 +1,37 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/components/UserBillingAddress.vue b/packages/core/nuxt-theme-module/theme/components/UserBillingAddress.vue new file mode 100644 index 0000000000..12648317c3 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/UserBillingAddress.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/UserShippingAddress.vue b/packages/core/nuxt-theme-module/theme/components/UserShippingAddress.vue new file mode 100644 index 0000000000..fbd08c2d53 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/UserShippingAddress.vue @@ -0,0 +1,9 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/components/WishlistSidebar.vue b/packages/core/nuxt-theme-module/theme/components/WishlistSidebar.vue new file mode 100644 index 0000000000..e1346bc40f --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/components/WishlistSidebar.vue @@ -0,0 +1,204 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/composables/index.ts b/packages/core/nuxt-theme-module/theme/composables/index.ts new file mode 100644 index 0000000000..1d414b562d --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/composables/index.ts @@ -0,0 +1,9 @@ +import useUiHelpers from './useUiHelpers'; +import useUiState from './useUiState'; +import useUiNotification from './useUiNotification'; + +export { + useUiHelpers, + useUiState, + useUiNotification +}; diff --git a/packages/core/nuxt-theme-module/theme/composables/useUiHelpers/index.ts b/packages/core/nuxt-theme-module/theme/composables/useUiHelpers/index.ts new file mode 100644 index 0000000000..7ab524d15e --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/composables/useUiHelpers/index.ts @@ -0,0 +1,72 @@ + +const useUiHelpers = () => { + const getFacetsFromURL = () => { + console.warn('[VSF] please implement useUiHelpers.getFacets.'); + + return { + categorySlug: null, + page: 1 + } as any; + }; + + // eslint-disable-next-line + const getCatLink = (category): string => { + console.warn('[VSF] please implement useUiHelpers.getCatLink.'); + + return '/'; + }; + + // eslint-disable-next-line + const changeSorting = (sort) => { + console.warn('[VSF] please implement useUiHelpers.changeSorting.'); + + return 'latest'; + }; + + // eslint-disable-next-line + const changeFilters = (filters) => { + console.warn('[VSF] please implement useUiHelpers.changeFilters.'); + }; + + // eslint-disable-next-line + const changeItemsPerPage = (itemsPerPage) => { + console.warn('[VSF] please implement useUiHelpers.changeItemsPerPage.'); + }; + + // eslint-disable-next-line + const setTermForUrl = (term: string) => { + console.warn('[VSF] please implement useUiHelpers.changeSearchTerm.'); + }; + + // eslint-disable-next-line + const isFacetColor = (facet): boolean => { + console.warn('[VSF] please implement useUiHelpers.isFacetColor.'); + + return false; + }; + + // eslint-disable-next-line + const isFacetCheckbox = (facet): boolean => { + console.warn('[VSF] please implement useUiHelpers.isFacetCheckbox.'); + + return false; + }; + + const getSearchTermFromUrl = () => { + console.warn('[VSF] please implement useUiHelpers.getSearchTermFromUrl.'); + }; + + return { + getFacetsFromURL, + getCatLink, + changeSorting, + changeFilters, + changeItemsPerPage, + setTermForUrl, + isFacetColor, + isFacetCheckbox, + getSearchTermFromUrl + }; +}; + +export default useUiHelpers; diff --git a/packages/core/nuxt-theme-module/theme/composables/useUiNotification/index.ts b/packages/core/nuxt-theme-module/theme/composables/useUiNotification/index.ts new file mode 100644 index 0000000000..1a5b75d0de --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/composables/useUiNotification/index.ts @@ -0,0 +1,53 @@ +import { computed, reactive } from '@vue/composition-api'; + +interface UiNotification { + message: string; + action: { text: string; onClick: (...args: any) => void }; + type: 'danger' | 'success' | 'info'; + icon: string; + persist: boolean; + id: symbol; + dismiss: () => void; +} + +interface Notifications { + notifications: Array; +} + +const state = reactive({ + notifications: [] +}); +const maxVisibleNotifications = 3; +const timeToLive = 3000; + +const useUiNotification = () => { + const send = (notification: UiNotification) => { + const id = Symbol(); + + const dismiss = () => { + const index = state.notifications.findIndex(notification => notification.id === id); + + if (index !== -1) state.notifications.splice(index, 1); + }; + + const newNotification = { + ...notification, + id, + dismiss + }; + + state.notifications.push(newNotification); + if (state.notifications.length > maxVisibleNotifications) state.notifications.shift(); + + if (!notification.persist) { + setTimeout(dismiss, timeToLive); + } + }; + + return { + send, + notifications: computed(() => state.notifications) + }; +}; + +export default useUiNotification; diff --git a/packages/core/nuxt-theme-module/theme/composables/useUiState.ts b/packages/core/nuxt-theme-module/theme/composables/useUiState.ts new file mode 100644 index 0000000000..37bbb5e151 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/composables/useUiState.ts @@ -0,0 +1,78 @@ +import Vue from 'vue'; +import VueCompositionAPI, { reactive, computed } from '@vue/composition-api'; + +// We need to register it again because of Vue instance instantiation issues +Vue.use(VueCompositionAPI); + +const state = reactive({ + isCartSidebarOpen: false, + isWishlistSidebarOpen: false, + isLoginModalOpen: false, + isNewsletterModalOpen: false, + isCategoryGridView: true, + isFilterSidebarOpen: false, + isMobileMenuOpen: false +}); + +const useUiState = () => { + const isMobileMenuOpen = computed(() => state.isMobileMenuOpen); + const toggleMobileMenu = () => { + state.isMobileMenuOpen = !state.isMobileMenuOpen; + }; + + const isCartSidebarOpen = computed(() => state.isCartSidebarOpen); + const toggleCartSidebar = () => { + if (state.isMobileMenuOpen) toggleMobileMenu(); + state.isCartSidebarOpen = !state.isCartSidebarOpen; + }; + + const isWishlistSidebarOpen = computed(() => state.isWishlistSidebarOpen); + const toggleWishlistSidebar = () => { + if (state.isMobileMenuOpen) toggleMobileMenu(); + state.isWishlistSidebarOpen = !state.isWishlistSidebarOpen; + }; + + const isLoginModalOpen = computed(() => state.isLoginModalOpen); + const toggleLoginModal = () => { + if (state.isMobileMenuOpen) toggleMobileMenu(); + state.isLoginModalOpen = !state.isLoginModalOpen; + }; + + const isNewsletterModalOpen = computed(() => state.isNewsletterModalOpen); + const toggleNewsletterModal = () => { + state.isNewsletterModalOpen = !state.isNewsletterModalOpen; + }; + + const isCategoryGridView = computed(() => state.isCategoryGridView); + const changeToCategoryGridView = () => { + state.isCategoryGridView = true; + }; + const changeToCategoryListView = () => { + state.isCategoryGridView = false; + }; + + const isFilterSidebarOpen = computed(() => state.isFilterSidebarOpen); + const toggleFilterSidebar = () => { + state.isFilterSidebarOpen = !state.isFilterSidebarOpen; + }; + + return { + isCartSidebarOpen, + isWishlistSidebarOpen, + isLoginModalOpen, + isNewsletterModalOpen, + isCategoryGridView, + isFilterSidebarOpen, + isMobileMenuOpen, + toggleCartSidebar, + toggleWishlistSidebar, + toggleLoginModal, + toggleNewsletterModal, + changeToCategoryGridView, + changeToCategoryListView, + toggleFilterSidebar, + toggleMobileMenu + }; +}; + +export default useUiState; diff --git a/packages/core/nuxt-theme-module/theme/helpers/cacheControl.js b/packages/core/nuxt-theme-module/theme/helpers/cacheControl.js new file mode 100644 index 0000000000..ecfe60d7ba --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/helpers/cacheControl.js @@ -0,0 +1,11 @@ +const cacheControl = (values) => ({ res }) => { + if (!process.server) return; + + const cacheControlValue = Object.entries(values) + .map(([key, value]) => `${key}=${value}`) + .join(','); + + res.setHeader('Cache-Control', cacheControlValue); +}; + +export default cacheControl; diff --git a/packages/core/nuxt-theme-module/theme/helpers/category/getCategoryPath.js b/packages/core/nuxt-theme-module/theme/helpers/category/getCategoryPath.js new file mode 100644 index 0000000000..e8ac2f5de5 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/helpers/category/getCategoryPath.js @@ -0,0 +1,3 @@ +export function getCategoryPath(category, context = this) { + return `/c/${context.$route.params.slug_1}/${category.slug}`; +} diff --git a/packages/core/nuxt-theme-module/theme/helpers/category/getCategorySearchParameters.js b/packages/core/nuxt-theme-module/theme/helpers/category/getCategorySearchParameters.js new file mode 100644 index 0000000000..ef12a45a6a --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/helpers/category/getCategorySearchParameters.js @@ -0,0 +1,6 @@ +export const getCategorySearchParameters = (context) => { + const { path } = context.root.$route; + const slug = path.replace(/^\/c\//, ''); + + return { slug }; +}; diff --git a/packages/core/nuxt-theme-module/theme/helpers/category/index.js b/packages/core/nuxt-theme-module/theme/helpers/category/index.js new file mode 100644 index 0000000000..1e86aa177b --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/helpers/category/index.js @@ -0,0 +1,8 @@ +import { getCategorySearchParameters } from './getCategorySearchParameters'; +import { getCategoryPath } from './getCategoryPath'; + +// TODO: remove, use faceting instead +export { + getCategorySearchParameters, + getCategoryPath +}; diff --git a/packages/core/nuxt-theme-module/theme/lang/de.js b/packages/core/nuxt-theme-module/theme/lang/de.js new file mode 100644 index 0000000000..8d83e80a01 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/lang/de.js @@ -0,0 +1,155 @@ +/* eslint-disable */ + +export default { + 'Categories': 'Kategorien', + 'Filters': 'Filters', + 'Sort by': 'Sortieren nach', + 'Products found': 'Produkte gefunden', + 'About us': 'Über uns', + 'Who we are': 'Wer wir sind', + 'Quality in the details': 'Qualität im Detail', + 'Customer Reviews': 'Kundenbewertungen', + 'Departments': 'Abteilungen', + 'Women fashion': 'Damenmode', + 'Men fashion': 'Herrenmode', + 'Kidswear': 'Kinderkleidung', + 'Home': 'Zuhause', + 'Help': 'Hilfe', + 'Customer service': 'Kundendienst', + 'Size guide': 'Größentabelle', + 'Contact us': 'Kontaktiere uns', + 'Payment & Delivery': 'Zahlung & Lieferung', + 'Purchase terms': 'Kaufbedingungen', + 'Guarantee': 'Garantie', + 'Description': 'Beschreibung', + 'Read reviews': 'Bewertungen lesen', + 'Additional Information': 'Zusätzliche Information', + 'Save for later': 'Für später speichern', + 'Add to compare': 'Hinzufügen zum vergleichen', + 'Match it with': 'Kombiniere es mit', + 'Share your look': 'Teile deinen Look', + 'Product description': 'Das Karissa V-Neck Tee hat eine halb taillierte Form schmeichelhaft für jede Figur. Sie können mit ins Fitnessstudio gehen Vertrauen, während es Kurven umarmt und häufiges "Problem" verbirgt Bereiche. Finden Sie atemberaubende Cocktailkleider für Frauen und feiern Sie Kleider.', + 'Brand': 'Marke', + 'Instruction1': 'Um mich kümmern', + 'Instruction2': 'Nur hier für die Pflegehinweise?', + 'Instruction3': 'Ja, das haben wir uns gedacht', + 'Items': 'Gegenstände', + 'View': 'Ansicht', + 'Show on page': 'Auf Seite anzeigen', + 'Done': 'Fertig', + 'Clear all': 'Alles löschen', + 'Empty': 'Sieht so aus, als hätten Sie der Tasche noch keine Artikel hinzugefügt. Beginnen Sie mit dem Einkaufen, um es auszufüllen.', + 'Help & FAQs': 'Hilfe & FAQs', + 'Download': 'Laden Sie unsere Anwendung herunter', + 'Find out more': 'Finde mehr heraus', + 'Login': 'Anmeldung', + 'Forgotten password?': 'Passwort vergessen?', + 'No account': `Sie haben noch keinen Account?`, + 'Register today': 'Melde dich noch heute an', + 'Go to checkout': 'Zur Kasse gehen', + 'Go back shopping': 'Zurück einkaufen', + 'Personal details': 'Persönliche Daten', + 'Edit': 'Bearbeiten', + 'Shipping details': 'Versanddetails', + 'Billing address': 'Rechnungsadresse', + 'Same as shipping address': 'Wie Versandadresse', + 'Payment method': 'Zahlungsmethode', + 'Apply': 'Übernehmen', + 'Update password': 'Passwort aktualisieren', + 'Update personal data': 'Persönliche Daten aktualisieren', + 'Item': 'Artikel', + 'Go back': 'Go back', + 'Continue to shipping': 'Weiter zum Versand', + 'I agree to': 'Ich stimme zu', + 'Terms and conditions': 'Allgemeine Geschäftsbedingungen', + 'Pay for order': 'Für Bestellung bezahlen', + 'Log into your account': 'In dein Konto einloggen', + 'or fill the details below': 'oder füllen Sie die Details unten', + 'Enjoy your free account': 'Enjoy these perks with your free account!', + 'Continue to payment': 'Weiter zur Zahlung', + 'Order No.': 'Bestellnummer', + 'Successful placed order': 'Sie haben die Bestellung erfolgreich aufgegeben. Sie können den Status Ihres Bestellen Sie über unsere Lieferstatusfunktion. Sie erhalten eine Bestellung Bestätigungs-E-Mail mit Details Ihrer Bestellung und einem Link zum Verfolgen der Bestellung Fortschritt.', + 'Info after order': 'Sie können sich mit E-Mail und definiertem Passwort in Ihrem Konto anmelden vorhin. Überprüfen Sie in Ihrem Konto Ihre Profildaten Transaktionsverlauf, Abonnement für Newsletter bearbeiten.', + 'Allow order notifications': 'Bestellbenachrichtigungen zulassen', + 'Feedback': 'Ihr Feedback ist uns wichtig. Lassen Sie uns wissen, was wir verbessern können.', + 'Send my feedback': 'Senden Sie mein Feedback', + 'Go back to shop': 'Zurück zum Einkaufen', + 'Read all reviews': 'Alle Bewertungen lesen', + 'Color': 'Farbe', + 'Contact details updated': 'Halten Sie Ihre Adressen und Kontaktdaten auf dem neuesten Stand.', + 'Manage billing addresses': 'Alle gewünschten Rechnungsadressen verwalten (Arbeitsplatz, Privatadresse ...) Auf diese Weise müssen Sie die Rechnungsadresse nicht bei jeder Bestellung manuell eingeben.', + 'Change': 'Änderungsänderung', + 'Delete': 'Löschen', + 'Add new address': 'Neue Adresse hinzufügen', + 'Set up newsletter': 'Richten Sie Ihren Newsletter ein und wir senden Ihnen wöchentlich Informationen zu neuen Produkten und Trends aus den von Ihnen ausgewählten Bereichen', + 'Sections that interest you': 'Abschnitte, die Sie interessieren', + 'Save changes': 'Änderungen speichern', + 'Read and understand': 'Ich habe das gelesen und verstanden', + 'Privacy': 'Datenschutz', + 'Cookies Policy': 'Cookie-Richtlinie', + 'Commercial information': 'und erklären sich damit einverstanden, personalisierte Handelsinformationen vom Markennamen per E-Mail zu erhalten', + 'Feel free to edit': 'Fühlen Sie sich frei, Ihre unten stehenden Daten zu bearbeiten, damit Ihr Konto immer auf dem neuesten Stand ist', + 'Use your personal data': 'Bei Markennamen legen wir großen Wert auf Datenschutzfragen und verpflichten uns, die persönlichen Daten unserer Benutzer zu schützen. Erfahren Sie mehr darüber, wie wir Ihre persönlichen Daten pflegen und verwenden', + 'Privacy Policy': 'Datenschutzrichtlinie', + 'Change password your account': 'Wenn Sie das Passwort ändern möchten, um auf Ihr Konto zuzugreifen, geben Sie die folgenden Informationen ein', + 'Your current email address is': 'Ihre aktuelle E-Mail-Adresse lautet', + 'Product': 'Produkt', + 'Details and status orders': 'Überprüfen Sie die Details und den Status Ihrer Bestellungen im Online-Shop. Sie können Ihre Bestellung auch stornieren oder eine Rücksendung anfordern. ', + 'You currently have no orders': 'Sie haben derzeit keine Bestellungen', + 'Start shopping': 'Einkaufen starten', + 'Download': 'Herunterladen', + 'Download all': 'Alle herunterladen', + 'View details': 'Details anzeigen', + 'Manage shipping addresses': 'Alle gewünschten Versandadressen verwalten (Arbeitsplatz, Privatadresse ...) Auf diese Weise müssen Sie die Versandadresse nicht bei jeder Bestellung manuell eingeben.', + 'Quantity': 'Menge', + 'Price': 'Preis', + 'Back to homepage': 'Zurück zur Homepage', + 'Select shipping method': 'Versandart auswählen', + 'Review my order': 'Meine Bestellung überprüfen', + 'Select payment method': 'Zahlungsmethode auswählen', + 'Make an order': 'Bestellung aufgeben', + 'or': 'oder', + 'login in to your account': 'Anmelden bei Ihrem Konto', + 'Create an account': 'Konto erstellen', + 'Your bag is empty': 'Ihre Tasche ist leer', + 'Cancel': 'Abbrechen', + 'See all results': 'Alle Ergebnisse anzeigen', + 'You haven’t searched for items yet': 'Sie haben noch nicht nach Artikeln gesucht.', + 'Let’s start now – we’ll help you': 'Fangen wir jetzt an - wir helfen Ihnen.', + 'Search results': 'Suchergebnisse', + 'Product suggestions': 'Produktvorschläge', + 'Search for items': 'Nach Artikeln suchen', + 'Enter promo code': 'Geben Sie den Promo-Code ein', + 'Shipping method': 'Versandart', + 'Continue to billing': 'Weiter zur Abrechnung', + 'Payment methods': 'Zahlungsmethoden', + 'Shipping address': 'Lieferanschrift', + 'Subtotal': 'Zwischensumme', + 'Shipping': 'Versand', + 'Total price': 'Gesamtpreis', + 'Payment': 'Zahlung', + 'Order summary': 'Bestellübersicht', + 'Products': 'Produkte', + 'Total': 'Gesamt', + 'Reset Password': 'Passwort Zurücksetzen', + 'Save Password': 'Passwort Speichern', + 'Back to home': 'Zurück Zur Startseite', + 'Forgot Password': 'Wenn Sie Ihr Passwort vergessen haben, können Sie es zurücksetzen.', + 'Thank You Inbox': 'Wenn die Nachricht nicht in Ihrem Posteingang ankommt, versuchen Sie es mit einer anderen E-Mail-Adresse, mit der Sie sich möglicherweise registriert haben.', + 'Sign in': 'Einloggen', + 'Register': 'Registrieren', + 'Password Changed': 'Passwort erfolgreich geändert. Sie können nun zur Startseite zurückkehren und sich anmelden.', + 'Password': 'Passwort', + 'Repeat Password': 'Wiederhole das Passwort', + 'Forgot Password Modal Email': 'E-Mail, mit der Sie sich anmelden:', + forgotPasswordConfirmation: 'Vielen Dank! Wenn ein Konto mit der E-Mail-Adresse {0} registriert ist, finden Sie in Ihrem Posteingang eine Nachricht mit einem Link zum Zurücksetzen des Passworts.', + subscribeToNewsletterModalContent: + 'Wenn Sie sich für den Newsletter angemeldet haben, erhalten Sie spezielle Angebote und Nachrichten von VSF per E-Mail. Wir werden Ihre E-Mail zu keinem Zeitpunkt an Dritte verkaufen oder weitergeben. Bitte beachten Sie unsere {0}.', + 'Subscribe': 'Abonnieren', + 'Subscribe to newsletter': 'Anmeldung zum Newsletter', + 'Email address': 'E-Mail Adresse', + 'I confirm subscription': 'Ich bestätige das Abonnement', + 'You can unsubscribe at any time': 'Sie können sich jederzeit abmelden', + 'show more': 'mehr anzeigen', + 'hide': 'ausblenden' +}; diff --git a/packages/core/nuxt-theme-module/theme/lang/en.js b/packages/core/nuxt-theme-module/theme/lang/en.js new file mode 100644 index 0000000000..482677f5a8 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/lang/en.js @@ -0,0 +1,155 @@ +/* eslint-disable */ + +export default { + 'Categories': 'Categories', + 'Filters': 'Filter', + 'Sort by': 'Sort by', + 'Products found': 'Products found', + 'About us': 'About us', + 'Who we are': 'Who we are', + 'Quality in the details': 'Quality in the details', + 'Customer Reviews': 'Customer Reviews', + 'Departments': 'Departments', + 'Women fashion': 'Women fashion', + 'Men fashion': 'Men fashion', + 'Kidswear': 'Kidswear', + 'Home': 'Home', + 'Help': 'Help', + 'Customer service': 'Customer service', + 'Size guide': 'Size guide', + 'Contact us': 'Contact us', + 'Payment & Delivery': 'Payment & Delivery', + 'Purchase terms': 'Purchase terms', + 'Guarantee': 'Guarantee', + 'Description': 'Description', + 'Read reviews': 'Read reviews', + 'Additional Information': 'Additional Information', + 'Save for later': 'Save for later', + 'Add to compare': 'Add to compare', + 'Match it with': 'Match it with', + 'Share your look': 'Share your look', + 'Product description': `The Karissa V-Neck Tee features a semi-fitted shape that's flattering for every figure. You can hit the gym with confidence while it hugs curves and hides common "problem" areas. Find stunning women's cocktail dresses and party dresses.`, + 'Brand': 'Brand', + 'Instruction1': 'Take care of me', + 'Instruction2': 'Just here for the care instructions?', + 'Instruction3': 'Yeah, we thought so', + 'Items': 'Items', + 'View': 'View', + 'Show on page': 'Show on page', + 'Done': 'Done', + 'Clear all': 'Clear all', + 'Empty': 'Looks like you haven’t added any items to the bag yet. Start shopping to fill it in.', + 'Help & FAQs': 'Help & FAQs', + 'Download': 'Download our application.', + 'Find out more': 'Find out more', + 'Login': 'Login', + 'Forgotten password?': 'Forgotten password?', + 'No account': `Don't have an account yet?`, + 'Register today': 'Register today', + 'Go to checkout': 'Go to checkout', + 'Go back shopping': 'Go back shopping', + 'Personal details': 'Personal details', + 'Edit': 'Edit', + 'Shipping details': 'Shipping details', + 'Billing address': 'Billing address', + 'Same as shipping address': 'Same as shipping address', + 'Payment method': 'Payment method', + 'Apply': 'Apply', + 'Update password': 'Update password', + 'Update personal data': 'Update personal data', + 'Item': 'Item', + 'Go back': 'Go back', + 'Continue to shipping': 'Continue to shipping', + 'I agree to': 'I agree to', + 'Terms and conditions': 'Terms and conditions', + 'Pay for order': 'Pay for order', + 'Log into your account': 'Log into your account', + 'or fill the details below': 'or fill the details below', + 'Enjoy your free account': 'Enjoy these perks with your free account!', + 'Continue to payment': 'Continue to payment', + 'Order No.': 'Order No.', + 'Successful placed order': 'You have successfully placed the order. You can check status of your order by using our delivery status feature. You will receive an order confirmation e-mail with details of your order and a link to track its progress.', + 'Info after order': 'You can log to your account using e-mail and password defined earlier. On your account you can edit your profile data, check history of transactions, edit subscription to newsletter.', + 'Allow order notifications': 'Allow order notifications', + 'Feedback': 'Your feedback is important to us. Let us know what we could improve.', + 'Send my feedback': 'Send my feedback', + 'Go back to shop': 'Go back to shop', + 'Read all reviews': 'Read all reviews', + 'Color': 'Color', + 'Contact details updated': 'Keep your addresses and contact details updated.', + 'Manage billing addresses': 'Manage all the billing addresses you want (work place, home address...) This way you won"t have to enter the billing address manually with each order.', + 'Change': 'Change', + 'Delete': 'Delete', + 'Add new address': 'Add new address', + 'Set up newsletter': 'Set up your newsletter and we will send you information about new products and trends from the sections you selected every week.', + 'Sections that interest you': 'Sections that interest you', + 'Save changes': 'Save changes', + 'Read and understand': 'I have read and understand the', + 'Privacy': 'Privacy', + 'Cookies Policy': 'Cookies Policy', + 'Commercial information': 'and agree to receive personalized commercial information from Brand name by email', + 'Feel free to edit': 'Feel free to edit any of your details below so your account is always up to date', + 'Use your personal data': 'At Brand name, we attach great importance to privacy issues and are committed to protecting the personal data of our users. Learn more about how we care and use your personal data in the', + 'Privacy Policy': 'Privacy Policy', + 'Change password your account': 'If you want to change the password to access your account, enter the following information', + 'Your current email address is': 'Your current email address is', + 'Product': 'Product', + 'Details and status orders': 'Check the details and status of your orders in the online store. You can also cancel your order or request a return.', + 'You currently have no orders': 'You currently have no orders', + 'Start shopping': 'Start shopping', + 'Download': 'Download', + 'Download all': 'Download all', + 'View details': 'View details', + 'Manage shipping addresses': 'Manage all the shipping addresses you want (work place, home address...) This way you won"t have to enter the shipping address manually with each order.', + 'Quantity': 'Quantity', + 'Price': 'Price', + 'Back to homepage': 'Back to homepage', + 'Select shipping method': 'Select shipping method', + 'Review my order': 'Review my order', + 'Select payment method': 'Select payment method', + 'Make an order': 'Make an order', + 'or': 'or', + 'login in to your account': 'login in to your account', + 'Create an account': 'Create an account', + 'Your bag is empty': 'Your bag is empty', + 'Cancel': 'Cancel', + 'See all results': 'See all results', + 'You haven’t searched for items yet': 'You haven’t searched for items yet.', + 'Let’s start now – we’ll help you': 'Let’s start now – we’ll help you.', + 'Search results': 'Search results', + 'Product suggestions': 'Product suggestions', + 'Search for items': 'Search for items', + 'Enter promo code': 'Enter promo code', + 'Shipping method': 'Shipping method', + 'Continue to billing': 'Continue to billing', + 'Payment methods': 'Payment methods', + 'Shipping address': 'Shipping address', + 'Subtotal': 'Subtotal', + 'Shipping': 'Shipping', + 'Total price': 'Total price', + 'Payment': 'Payment', + 'Order summary': 'Order summary', + 'Products': 'Products', + 'Total': 'Total', + 'Reset Password': 'Reset Password', + 'Save Password': 'Save Password', + 'Back to home': 'Back to home', + 'Forgot Password': 'If you can’t remember your password, you can reset it.', + 'Thank You Inbox': 'If the message is not arriving in your inbox, try another email address you might’ve used to register.', + 'Sign in': 'Sign in', + 'Register': 'Register', + 'Password Changed': 'Password successfuly changed. You can now go back to homepage and sign in.', + 'Password': 'Password', + 'Repeat Password': 'Repeat Password', + 'Forgot Password Modal Email': 'Email you are using to sign in:', + forgotPasswordConfirmation: 'Thanks! If there is an account registered with the {0} email, you will find message with a password reset link in your inbox.', + subscribeToNewsletterModalContent: + 'After signing up for the newsletter, you will receive special offers and messages from VSF via email. We will not sell or distribute your email to any third party at any time. Please see our {0}.', + 'Subscribe': 'Subscribe', + 'Subscribe to newsletter': 'Subscribe to newsletter', + 'Email address': 'Email address', + 'I confirm subscription': 'I confirm subscription', + 'You can unsubscribe at any time': 'You can unsubscribe at any time', + 'show more': 'show more', + 'hide': 'hide' +}; diff --git a/packages/core/nuxt-theme-module/theme/layouts/account.vue b/packages/core/nuxt-theme-module/theme/layouts/account.vue new file mode 100644 index 0000000000..e8772433e2 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/layouts/account.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/layouts/blank.vue b/packages/core/nuxt-theme-module/theme/layouts/blank.vue new file mode 100644 index 0000000000..a72d5bc269 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/layouts/blank.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/layouts/default.vue b/packages/core/nuxt-theme-module/theme/layouts/default.vue new file mode 100644 index 0000000000..43583c0a50 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/layouts/default.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/layouts/error.vue b/packages/core/nuxt-theme-module/theme/layouts/error.vue new file mode 100644 index 0000000000..ba7b7d9fdf --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/layouts/error.vue @@ -0,0 +1,91 @@ + + + diff --git a/packages/core/nuxt-theme-module/theme/middleware/checkout.js b/packages/core/nuxt-theme-module/theme/middleware/checkout.js new file mode 100644 index 0000000000..4f061091f1 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/middleware/checkout.js @@ -0,0 +1,5 @@ +import { Logger } from '@vue-storefront/core'; + +export default () => { + Logger.error('Please implement vendor-specific checkout.js middleware in the \'middleware\' directory to block access to checkout steps when customer did not yet complete previous steps'); +}; diff --git a/packages/core/nuxt-theme-module/theme/middleware/is-authenticated.js b/packages/core/nuxt-theme-module/theme/middleware/is-authenticated.js new file mode 100644 index 0000000000..b435393582 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/middleware/is-authenticated.js @@ -0,0 +1,5 @@ +import { Logger } from '@vue-storefront/core'; + +export default () => { + Logger.error('Please implement vendor-specific is-authenticated.js middleware in the \'middleware\' directory to block guests from accessing user profile routes'); +}; diff --git a/packages/core/nuxt-theme-module/theme/mockedSearchProducts.json b/packages/core/nuxt-theme-module/theme/mockedSearchProducts.json new file mode 100644 index 0000000000..0ea8a4b327 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/mockedSearchProducts.json @@ -0,0 +1,166 @@ +{ + "products": [ + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + }, + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + }, + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + }, + { + "_id": "38143c0c-c9b0-448c-93cd-60eb90d8da57", + "_slug": "aspesi-shirt-h805-white", + "_name": "Shirt Aspesi white M", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/081223_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 16625 + } + }, + "_rating": { + "averageRating": 4 + } + }, + { + "_id": "9c1881eb-139d-4d29-bbb4-01b1ff51de03", + "_slug": "moschino-tshirt-b02033717-black", + "_name": "T-Shirt Moschino Cheap And Chic black", + "images": [ + { + "url": "https://s3-eu-west-1.amazonaws.com/commercetools-maximilian/products/072750_1_large.jpg" + } + ], + "price": { + "value": { + "centAmount": 37250 + } + }, + "_rating": { + "averageRating": 2.5 + } + } + ], + "categories": [ + { + "label": "Men", + "slug": "men" + }, + { + "label": "Women", + "slug": "women" + }, + { + "label": "Sale", + "slug": "sale" + }, + { + "label": "New", + "slug": "new" + } + ] +} diff --git a/packages/core/nuxt-theme-module/theme/pages/Category.vue b/packages/core/nuxt-theme-module/theme/pages/Category.vue new file mode 100644 index 0000000000..9511628c32 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Category.vue @@ -0,0 +1,819 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/Checkout.vue b/packages/core/nuxt-theme-module/theme/pages/Checkout.vue new file mode 100644 index 0000000000..e8f8a1e4db --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Checkout.vue @@ -0,0 +1,108 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/Checkout/Billing.vue b/packages/core/nuxt-theme-module/theme/pages/Checkout/Billing.vue new file mode 100644 index 0000000000..1125d465ad --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Checkout/Billing.vue @@ -0,0 +1,381 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/Checkout/Payment.vue b/packages/core/nuxt-theme-module/theme/pages/Checkout/Payment.vue new file mode 100644 index 0000000000..7d13b7b18f --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Checkout/Payment.vue @@ -0,0 +1,279 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/Checkout/Shipping.vue b/packages/core/nuxt-theme-module/theme/pages/Checkout/Shipping.vue new file mode 100644 index 0000000000..3325df3092 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Checkout/Shipping.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/Checkout/ThankYou.vue b/packages/core/nuxt-theme-module/theme/pages/Checkout/ThankYou.vue new file mode 100644 index 0000000000..32c5e92b02 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Checkout/ThankYou.vue @@ -0,0 +1,264 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/Home.vue b/packages/core/nuxt-theme-module/theme/pages/Home.vue new file mode 100644 index 0000000000..c5acca7149 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Home.vue @@ -0,0 +1,396 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount.vue new file mode 100644 index 0000000000..c740ab7f5f --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount.vue @@ -0,0 +1,159 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount/BillingDetails.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount/BillingDetails.vue new file mode 100644 index 0000000000..4554265436 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount/BillingDetails.vue @@ -0,0 +1,214 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount/LoyaltyCard.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount/LoyaltyCard.vue new file mode 100644 index 0000000000..a7dc1210d6 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount/LoyaltyCard.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyNewsletter.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyNewsletter.vue new file mode 100644 index 0000000000..b40cbef86a --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyNewsletter.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyProfile.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyProfile.vue new file mode 100644 index 0000000000..e2683b64d7 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyProfile.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyReviews.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyReviews.vue new file mode 100644 index 0000000000..092180b9a8 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount/MyReviews.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount/OrderHistory.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount/OrderHistory.vue new file mode 100644 index 0000000000..e6607d63e9 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount/OrderHistory.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/MyAccount/ShippingDetails.vue b/packages/core/nuxt-theme-module/theme/pages/MyAccount/ShippingDetails.vue new file mode 100644 index 0000000000..957abe1160 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/MyAccount/ShippingDetails.vue @@ -0,0 +1,213 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/Product.vue b/packages/core/nuxt-theme-module/theme/pages/Product.vue new file mode 100644 index 0000000000..9f7fce13c0 --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/Product.vue @@ -0,0 +1,494 @@ + + + + diff --git a/packages/core/nuxt-theme-module/theme/pages/ResetPassword.vue b/packages/core/nuxt-theme-module/theme/pages/ResetPassword.vue new file mode 100644 index 0000000000..50ecf4850c --- /dev/null +++ b/packages/core/nuxt-theme-module/theme/pages/ResetPassword.vue @@ -0,0 +1,167 @@ + + + + diff --git a/packages/core/rfcs/20200417-products-filters.md b/packages/core/rfcs/20200417-products-filters.md new file mode 100644 index 0000000000..dad72e73ea --- /dev/null +++ b/packages/core/rfcs/20200417-products-filters.md @@ -0,0 +1,46 @@ +# Filtering products + +Filtering products is an important part of any products listing page. To ensure the best developer experience and consistency between integrations (because filters have to be delivered to the theme in a unified form) we need to provide a common way to retrieve available filters. + +## New APIs + +### Modify `useProduct` composable + +Since there are several factors that the filters depend on (product's type, category, available products, their attributes) and the set of filters might be dynamic, this feature should introduce a new field in the `useProduct` composable, `availableFilters`, like in the example below. + +**New interface**: + +```ts +export interface UseProduct { + products: ComputedProperty; + availableFilters: ComputedProperty; + search: (params: { + perPage?: number; + page?: number; + sort?: any; + term?: any; + filters?: PRODUCT_FILTERS; + [x: string]: any; + }) => Promise; + loading: ComputedProperty; + [x: string]: any; +} +``` + +**`UseProductFactoryParams` interface** + +Loading filters, their possible values, counts, etc. will require an integration-specific logic that will be part of the search function, so the factory params interface needs a new interface for search function result: + +```ts +export interface ProductsSearchResult { + data: PRODUCT[], + total: number, + availableFilters?: PRODUCT_FILTERS; +} +``` + +## Migration process + +- Add `PRODUCT_FILTER` type to `useProduct` on integration side. +- Implement getting available filters in `searchProducts` factory param (optional). +- Implement handling filters in products search (optional). diff --git a/packages/core/rfcs/merge-core-packages.md b/packages/core/rfcs/merge-core-packages.md new file mode 100644 index 0000000000..4704a5cec8 --- /dev/null +++ b/packages/core/rfcs/merge-core-packages.md @@ -0,0 +1,19 @@ +# Merging core packages + +## Motivation + +Currently there are 6 core packages: +- `docs` +- `factories` +- `interfaces` +- `nuxt-module` +- `theme-module` +- `utils` + +While this division was making sense long time ago at the beginning of creating this repo now after many changes and shifts it doesn't work so well. We shifted from interface-only core to factories and utility functions. Because some of these packages depends on each other and they will be used together anyway in the integration it makes sense to merge them to avoid desynchronization and complexity. + +Specifically I suggest merging: `factories`, `interfaces` and `utils` into single `core` and rename the `core` folder into `shared` as it's basically containing shared resources ;) + +## Migration process + +Prety straightforward, only replacing pathes . \ No newline at end of file diff --git a/packages/core/rfcs/order-getters-product-data.md b/packages/core/rfcs/order-getters-product-data.md new file mode 100644 index 0000000000..902e4ba4ff --- /dev/null +++ b/packages/core/rfcs/order-getters-product-data.md @@ -0,0 +1,26 @@ +# Add more data to the Order getters + +## Motivation + +In case of showing completed order items, we need a getter for its items any possibility to get additional information about each product: +- name +- quantity bought +- price +- sku + +In this RFC is also proposed price as an agnostic unit + +## Proposed interface +```TS +export interface UserOrderGetters { + getDate: (order: ORDER) => string; + getId: (order: ORDER) => string; + getStatus: (order: ORDER) => string; + getPrice: (order: ORDER) => number; + getItems: (order: ORDER) => ORDER_ITEM[]; + getItemSku: (item: ORDER_ITEM) => string; + getItemName: (item: ORDER_ITEM) => string; + getItemQty: (item: ORDER_ITEM) => number; + [getterName: string]: (element: any, options?: any) => unknown; +} +``` \ No newline at end of file diff --git a/packages/core/rfcs/setting-composable-internal-fields.md b/packages/core/rfcs/setting-composable-internal-fields.md new file mode 100644 index 0000000000..9c51634261 --- /dev/null +++ b/packages/core/rfcs/setting-composable-internal-fields.md @@ -0,0 +1,55 @@ +# Setting composable internal fields + +## Motivation + +Sometimes when we create a new composable (using factory) we need to override some fields that are inside. +For example, when you use `useProduct` you want to reload `cart` property from the `useCart` composable. +To do this our factories can return setter function instead of just composable instance: + +### Factory: +```js +const useCartFactory = () => { + const cart = ref({}); + + const setCart = (newCart) => { + cart.value = newCart; + } + + const useCart = () => { + return { cart } + }; + + return { setCart, useCart }; +} + +export default useCartFactory +``` + +### Integration: +```js +import { useCartFactory } from '@vue-storefront/factories'; + +export default useCartFactory({ ... }) +``` + +now usage (example with user): + +```js +import { setCart } from './useCart'; + +const factoryParams = { + login: async () => { + const response = await authenticate(); + setCart(response.cart); + + return response.customer; + } +} + +export default useUserFactory(factoryParams); +``` + + +## Migration process + +You have to change all of imports of the composables as the factories will return object instead of composable function. diff --git a/packages/jest.base.config.js b/packages/jest.base.config.js new file mode 100644 index 0000000000..a79e60212e --- /dev/null +++ b/packages/jest.base.config.js @@ -0,0 +1,16 @@ +// For a detailed explanation regarding each configuration property, visit: +// https://jestjs.io/docs/en/configuration.html + +module.exports = { + transform: { + '^.+\\.[jt]s$': 'ts-jest' + }, + coverageDirectory: './coverage/', + coverageReporters: ['html', 'lcov', 'text'], + collectCoverageFrom: [ + 'src/**/*.ts' + ], + moduleFileExtensions: ['ts', 'js', 'json'], + watchPathIgnorePatterns: ['**/node_modules'], + testMatch: ['/**/__tests__/**/*spec.[jt]s?(x)'] +}; diff --git a/packages/rollup.base.config.js b/packages/rollup.base.config.js new file mode 100644 index 0000000000..1321c2b127 --- /dev/null +++ b/packages/rollup.base.config.js @@ -0,0 +1,40 @@ +import nodeResolve from '@rollup/plugin-node-resolve'; +import typescript from 'rollup-plugin-typescript2'; +import { terser } from 'rollup-plugin-terser'; + +const extensions = ['.ts', '.js']; + +export function generateBaseConfig(pkg, useTerser = false) { + const plugins = [ + nodeResolve({ + extensions + }), + typescript({ + // eslint-disable-next-line global-require + typescript: require('typescript') + }) + ]; + + if (useTerser) plugins.push(terser()); + + return { + input: 'src/index.ts', + output: [ + { + file: pkg.main, + format: 'cjs', + sourcemap: true + }, + { + file: pkg.module, + format: 'es', + sourcemap: true + } + ], + external: [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ], + plugins + }; +} diff --git a/packages/tsconfig.base.json b/packages/tsconfig.base.json new file mode 100644 index 0000000000..302f7b4f28 --- /dev/null +++ b/packages/tsconfig.base.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "esModuleInterop": true, + "target": "es5", + "module": "ES2015", + "moduleResolution": "node", + "importHelpers": true, + "noEmitHelpers": true, + "sourceMap": true, + "declaration": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "baseUrl": "./", + "lib": ["es6", "es7", "ES2017", "ES2018", "ES2019", "dom"], + "strict": false, + "preserveSymlinks": true, + "allowJs": true + }, + "exclude": ["node_modules", "**/*.spec.ts"] +} diff --git a/shims.d.ts b/shims.d.ts deleted file mode 100644 index 771cb22bd9..0000000000 --- a/shims.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module '*.vue' { - import Vue from 'vue' - export default Vue -} - -declare module 'vue-router' { - import VueRouter, { RouteConfig } from 'node_modules/vue-router' - export * from 'node_modules/vue-router' - export default class extends VueRouter { - public addRoutes (routes: RouteConfig[], useRouteQueue?: boolean, priority?: number): void; - } -} diff --git a/src/index.amp.template.html b/src/index.amp.template.html deleted file mode 100755 index 5735afdfcf..0000000000 --- a/src/index.amp.template.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - {{{ meta.inject().title.text() }}} - {{{ meta.inject().meta.text() }}} - - {{{ meta.inject().link.text() }}} - {{{ output.appendHead() }}} - {{{ renderStyles().replace(" - - - - diff --git a/src/index.basic.template.html b/src/index.basic.template.html deleted file mode 100755 index d0d917e6f4..0000000000 --- a/src/index.basic.template.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - {{{ meta.inject().title.text() }}} - {{{ meta.inject().meta.text() }}} - - {{{ meta.inject().link.text() }}} - {{{ renderStyles() }}} - {{{ output.appendHead() }}} - - - - - diff --git a/src/index.minimal.template.html b/src/index.minimal.template.html deleted file mode 100755 index 722d1ef381..0000000000 --- a/src/index.minimal.template.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - {{{ meta.inject().title.text() }}} - {{{ meta.inject().meta.text() }}} - - {{{ meta.inject().link.text() }}} - {{{ output.appendHead() }}} - - - - - diff --git a/src/index.template.html b/src/index.template.html deleted file mode 100755 index 711961f9e2..0000000000 --- a/src/index.template.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - {{{ meta.inject().title.text() }}} - {{{ meta.inject().meta.text() }}} - - {{{ meta.inject().link.text() }}} - {{{ renderResourceHints() }}} - {{{ renderStyles() }}} - {{{ output.appendHead() }}} - - - - {{{ renderState() }}} - {{{ renderScripts() }}} - - diff --git a/src/modules/amp-renderer/index.ts b/src/modules/amp-renderer/index.ts deleted file mode 100644 index f3bdf71469..0000000000 --- a/src/modules/amp-renderer/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import moduleRoutes from './router' -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { setupMultistoreRoutes } from '@vue-storefront/core/lib/multistore' -import config from 'config' - -const ampRendererStore = { - namespaced: true, - state: { - key: null - } -} - -export const AmpRendererModule: StorefrontModule = function ({ store, router }) { - store.registerModule('amp-renderer', ampRendererStore) - setupMultistoreRoutes(config, router, moduleRoutes, 10) -} diff --git a/src/modules/amp-renderer/router.ts b/src/modules/amp-renderer/router.ts deleted file mode 100644 index ceda4742eb..0000000000 --- a/src/modules/amp-renderer/router.ts +++ /dev/null @@ -1,12 +0,0 @@ -import config from 'config' - -let AmpThemeRouting - -try { - const themeName = config.theme.replace('@vue-storefront/theme-', '') - AmpThemeRouting = require(`src/themes/${themeName}-amp/router`) -} catch (err) { - AmpThemeRouting = null -} - -export default AmpThemeRouting diff --git a/src/modules/client.ts b/src/modules/client.ts deleted file mode 100644 index 2e62808b51..0000000000 --- a/src/modules/client.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { VueStorefrontModule } from '@vue-storefront/core/lib/module' -import { CatalogModule } from '@vue-storefront/core/modules/catalog' -import { CatalogNextModule } from '@vue-storefront/core/modules/catalog-next' -import { CartModule } from '@vue-storefront/core/modules/cart' -import { CheckoutModule } from '@vue-storefront/core/modules/checkout' -import { CompareModule } from '@vue-storefront/core/modules/compare' -import { WishlistModule } from '@vue-storefront/core/modules/wishlist' -import { NotificationModule } from '@vue-storefront/core/modules/notification' -import { UrlModule } from '@vue-storefront/core/modules/url' -import { BreadcrumbsModule } from '@vue-storefront/core/modules/breadcrumbs' -import { UserModule } from '@vue-storefront/core/modules/user' -import { CmsModule } from '@vue-storefront/core/modules/cms' -// import { GoogleTagManagerModule } from './google-tag-manager'; -// import { AmpRendererModule } from './amp-renderer'; -import { PaymentBackendMethodsModule } from './payment-backend-methods' -import { PaymentCashOnDeliveryModule } from './payment-cash-on-delivery' -import { NewsletterModule } from '@vue-storefront/core/modules/newsletter' -import { InitialResourcesModule } from '@vue-storefront/core/modules/initial-resources' - -// import { DeviceModule } from './device/index'; -import { registerModule } from '@vue-storefront/core/lib/modules' - -// TODO:distributed across proper pages BEFORE 1.11 -export function registerClientModules () { - registerModule(UrlModule) - registerModule(CatalogModule) - registerModule(CheckoutModule) // To Checkout - registerModule(CartModule) - registerModule(PaymentBackendMethodsModule) - registerModule(PaymentCashOnDeliveryModule) - registerModule(WishlistModule) // Trigger on wishlist icon click - registerModule(NotificationModule) - registerModule(UserModule) // Trigger on user icon click - registerModule(CatalogNextModule) - registerModule(CompareModule) - registerModule(BreadcrumbsModule) - // registerModule(GoogleTagManagerModule) - // registerModule(AmpRendererModule) - registerModule(CmsModule) - registerModule(NewsletterModule) - registerModule(InitialResourcesModule) - // registerModule(DeviceModule) -} - -// Deprecated API, will be removed in 2.0 -export const registerModules: VueStorefrontModule[] = [ - // Example -] diff --git a/src/modules/compress/server.ts b/src/modules/compress/server.ts deleted file mode 100644 index 3ca3054bc7..0000000000 --- a/src/modules/compress/server.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { serverHooks } from '@vue-storefront/core/server/hooks' - -const compression = require('compression') -serverHooks.afterApplicationInitialized(({ app, isProd }) => { - if (isProd) { - console.log('Output Compression is enabled') - app.use(compression({ enabled: isProd })) - } -}) diff --git a/src/modules/device/README.md b/src/modules/device/README.md deleted file mode 100644 index efb1b5d014..0000000000 --- a/src/modules/device/README.md +++ /dev/null @@ -1,32 +0,0 @@ -### Device - -Based on: https://github.com/nuxt-community/device-module - -It provides as some logic helpers based on UserAgent. List of tests: -``` -isMobile, -isMobileOrTablet, -isTablet, -isDesktop, -isDesktopOrTablet, -isIos, -isWindows, -isMacOS -``` - -They are accessible by, e.g:: -``` -this.$device.isMobile -``` -Or in asyncData by (you need to import Vue): -``` -Vue.prototype.$device.isMobile -``` - -We could totally disable this feature by setting `config.device.appendToInstance` to false and the small library will not by imported. - -In addition when we are using installer script. I've added multiselect so we could pick which tests we want to have. - -I've tested it with `curl -A "some user agent" http://localhost:3000 and it worked. - -That's obvious we should use media queries whenever we can. However, sometimes we have more advanced structure and we are ending with hidden useless vue instance. diff --git a/src/modules/device/index.ts b/src/modules/device/index.ts deleted file mode 100644 index 0ca15e1b4b..0000000000 --- a/src/modules/device/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { isServer } from '@vue-storefront/core/helpers/index'; -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import Vue from 'vue' - -const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.39 Safari/537.36' - -export const DeviceModule: StorefrontModule = async function ({ app, appConfig }) { - let headersOrUserAgent - if (isServer) { - headersOrUserAgent = Vue.prototype.$ssrRequestContext.userAgent || DEFAULT_USER_AGENT - } else { - headersOrUserAgent = window.navigator.userAgent || DEFAULT_USER_AGENT - } - - if (appConfig.device && appConfig.device.appendToInstance && appConfig.device.tests && appConfig.device.tests.length) { - const deviceLibrary: any = await import(/* webpackChunkName: "device" */ './logic') - let userAgent = typeof headersOrUserAgent === 'string' - ? headersOrUserAgent - : headersOrUserAgent['user-agent'] - - Vue.prototype.$device = deviceLibrary.default(userAgent, appConfig.device.tests) - if (userAgent === 'Amazon CloudFront') { - if (headersOrUserAgent['cloudfront-is-mobile-viewer'] === 'true') { - Vue.prototype.$device.isMobile = true - Vue.prototype.$device.isMobileOrTablet = true - } - if (headersOrUserAgent['cloudfront-is-tablet-viewer'] === 'true') { - Vue.prototype.$device.isMobile = false - Vue.prototype.$device.isMobileOrTablet = true - } - } - (app as any).device = Vue.prototype.$device - } -} diff --git a/src/modules/device/logic/index.ts b/src/modules/device/logic/index.ts deleted file mode 100644 index 14c4281cb7..0000000000 --- a/src/modules/device/logic/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -// these regular expressions are borrowed from below page. -// https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser - -// eslint-disable-next-line -const REGEX_MOBILE1 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i - -// eslint-disable-next-line -const REGEX_MOBILE2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i - -// eslint-disable-next-line -const REGEX_MOBILE_OR_TABLET1 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i -// eslint-disable-next-line -const REGEX_MOBILE_OR_TABLET2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i - -const testers = { - isMobile (a) { - return REGEX_MOBILE1.test(a) || REGEX_MOBILE2.test(a.substr(0, 4)) - }, - - isMobileOrTablet (a) { - return REGEX_MOBILE_OR_TABLET1.test(a) || REGEX_MOBILE_OR_TABLET2.test(a.substr(0, 4)) - }, - - isIos (a) { - return /iPad|iPhone|iPod/.test(a) - }, - - isWindows (a) { - return /Windows/.test(a) - }, - - isMacOS (a) { - return /Mac OS X/.test(a) - } -} - -interface DeviceTests { - isMobile?: boolean, - isMobileOrTablet?: boolean, - isTablet?: boolean, - isDesktop?: boolean, - isDesktopOrTablet?: boolean, - isIos?: boolean, - isWindows?: boolean, - isMacOS?: boolean -} - -export default (userAgent: string, tests: string[]): DeviceTests => { - const deviceTests: DeviceTests = {} - - for (let test of tests) { - if (testers[test]) { - deviceTests[test] = testers[test](userAgent) - } else { - switch (test) { - case 'isTablet': - deviceTests[test] = !testers['isMobile'](userAgent) && !testers['isMobileOrTablet'](userAgent); - break; - case 'isDesktop': - deviceTests[test] = !testers['isMobileOrTablet'](userAgent) - break; - case 'isDesktopOrTablet': - deviceTests[test] = !testers['isMobile'](userAgent) - break; - } - } - } - - return deviceTests -} diff --git a/src/modules/fastly/README.md b/src/modules/fastly/README.md deleted file mode 100644 index 50293a731d..0000000000 --- a/src/modules/fastly/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# VSF Cache Fastly -This module extends default caching docs/guide/basics/ssr-cache.md to allow using fastly as cache provider. - -## How to install -Add to config: -```json -"fastly": { - "enabled": true, - "serviceId": "xyz", // (https://docs.fastly.com/en/guides/finding-and-managing-your-account-info#finding-your-service-id) - "token": "xyz" // fastly api token (https://docs.fastly.com/api/auth#tokens) -} -``` - -Change those values in `server` section: -```json -"useOutputCacheTagging": true, -"useOutputCache": true -``` - -## How to purge cache? -Open: -``` -http://localhost:3000/invalidate?key=aeSu7aip&tag=home -``` diff --git a/src/modules/fastly/server.ts b/src/modules/fastly/server.ts deleted file mode 100644 index 474c5525a6..0000000000 --- a/src/modules/fastly/server.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { serverHooks } from '@vue-storefront/core/server/hooks' -import fetch from 'isomorphic-fetch' -import config from 'config' - -const chunk = require('lodash/chunk') - -serverHooks.beforeOutputRenderedResponse(({ output, res, context }) => { - if (!config.get('fastly.enabled')) { - return output - } - - const tagsArray = Array.from(context.output.cacheTags) - const cacheTags = tagsArray.join(' ') - res.setHeader('Surrogate-Key', cacheTags) - - return output -}) - -serverHooks.beforeCacheInvalidated(async ({ tags }) => { - if (!config.get('fastly.enabled') || !config.get('server.useOutputCache') || !config.get('server.useOutputCacheTagging')) { - return - } - - console.log('Invalidating Fastly Surrogate-Key') - const tagsChunks = chunk(tags.filter((tag) => - config.server.availableCacheTags.indexOf(tag) >= 0 || - config.server.availableCacheTags.find(t => tag.indexOf(t) === 0) - ), 256) // we can send maximum 256 keys per request, more info https://docs.fastly.com/api/purge#purge_db35b293f8a724717fcf25628d713583 - - for (const tagsChunk of tagsChunks) { - const response = await fetch(`https://api.fastly.com/service/${config.get('fastly.serviceId')}/purge`, { - method: 'POST', - headers: { 'Fastly-Key': config.get('fastly.token') }, - body: JSON.stringify({ surrogate_keys: tagsChunk }) - }) - const text = await response.text() - console.log(text) - } -}) diff --git a/src/modules/google-analytics/index.ts b/src/modules/google-analytics/index.ts deleted file mode 100644 index 5a20b46fd7..0000000000 --- a/src/modules/google-analytics/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -import VueAnalytics from 'vue-analytics' -import { Logger } from '@vue-storefront/core/lib/logger' -import { once, isServer } from '@vue-storefront/core/helpers' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import Vue from 'vue'; - -const googleAnalyticsStore = { - namespaced: true, - state: { - key: null - } -} - -export const GoogleAnalyticsModule: StorefrontModule = function ({ store, router, appConfig }) { - if (appConfig.analytics.id && !isServer) { - once('__VUE_EXTEND_ANALYTICS__', () => { - Vue.use(VueAnalytics, { - id: appConfig.analytics.id, - router, - ecommerce: { - enabled: true - } - }) - }) - } else { - Logger.warn( - 'Google Analytics extension is not working. Ensure Google Analytics account ID is defined in config', - 'GA' - )() - } - - store.registerModule('google-analytics', googleAnalyticsStore) - - if (appConfig.analytics.id && !isServer) { - Vue.prototype.$bus.$on('order-after-placed', event => { - const order = event.order - const ecommerce = (Vue as any).$ga.ecommerce - - order.products.forEach(product => { - ecommerce.addItem({ - id: product.id.toString(), - name: product.name, - sku: product.sku, - category: product.category ? product.category[0].name : '', - price: product.price.toString(), - quantity: product.qty.toString() - }) - }) - ecommerce.send() - }) - } -} diff --git a/src/modules/google-cloud-trace/package.json b/src/modules/google-cloud-trace/package.json deleted file mode 100644 index 3c21d5eb25..0000000000 --- a/src/modules/google-cloud-trace/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "google-cloud-tracing", - "version": "1.0.1", - "main": "index.js", - "license": "MIT", - "private": true, - "dependencies": { - "@google-cloud/trace-agent": "^4.2.5" - } -} diff --git a/src/modules/google-cloud-trace/server.ts b/src/modules/google-cloud-trace/server.ts deleted file mode 100644 index 5e7db50b0b..0000000000 --- a/src/modules/google-cloud-trace/server.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { serverHooks } from '@vue-storefront/core/server/hooks' - -serverHooks.afterProcessStarted((config) => { - let trace = require('@google-cloud/trace-agent') - if (config.has('trace') && config.get('trace.enabled')) { - trace.start(config.get('trace.config')) - } -}) diff --git a/src/modules/google-tag-manager/hooks/afterRegistration.ts b/src/modules/google-tag-manager/hooks/afterRegistration.ts deleted file mode 100644 index 2e5c85ac5b..0000000000 --- a/src/modules/google-tag-manager/hooks/afterRegistration.ts +++ /dev/null @@ -1,110 +0,0 @@ -import Vue from 'vue' -import VueGtm from 'vue-gtm' -import { Store } from 'vuex' -import { currentStoreView } from '@vue-storefront/core/lib/multistore' -import { isServer } from '@vue-storefront/core/helpers' - -export const isEnabled = (gtmId: string | null) => { - return typeof gtmId === 'string' && gtmId.length > 0 && !isServer -} - -export function afterRegistration (config, store: Store) { - if (isEnabled(config.googleTagManager.id)) { - const GTM: VueGtm = (Vue as any).gtm - - const storeView = currentStoreView() - const currencyCode = storeView.i18n.currencyCode - - const getProduct = (item) => { - let product = {} - - const attributeMap: string[]|Record[] = config.googleTagManager.product_attributes - attributeMap.forEach(attribute => { - const isObject = typeof attribute === 'object' - let attributeField = isObject ? Object.keys(attribute)[0] : attribute - let attributeName = isObject ? Object.values(attribute)[0] : attribute - - if (item.hasOwnProperty(attributeField) || product.hasOwnProperty(attributeName)) { - const value = item[attributeField] || product[attributeName] - if (value) { - product[attributeName] = value - } - } - }) - - const { category } = item - if (category && category.length > 0) { - product['category'] = category.slice(-1)[0].name - } - - return product - } - - store.subscribe(({ type, payload }, state) => { - // Adding a Product to a Shopping Cart - if (type === 'cart/cart/ADD') { - GTM.trackEvent({ - event: 'addToCart', - ecommerce: { - currencyCode: currencyCode, - add: { - products: [getProduct(payload.product)] - } - } - }); - } - - // Removing a Product from a Shopping Cart - if (type === 'cart/cart/DEL') { - GTM.trackEvent({ - event: 'removeFromCart', - ecommerce: { - remove: { - products: [getProduct(payload.product)] - } - } - }); - } - - // Measuring Views of Product Details - if (type === 'product/product/SET_PRODUCT_CURRENT') { - GTM.trackEvent({ - ecommerce: { - detail: { - 'actionField': { 'list': '' }, // 'detail' actions have an optional list property. - 'products': [getProduct(payload)] - } - } - }); - } - - // Measuring Purchases - if (type === 'order/orders/LAST_ORDER_CONFIRMATION') { - const orderId = payload.confirmation.backendOrderId - const products = payload.order.products.map(product => getProduct(product)) - store.dispatch( - 'user/getOrdersHistory', - { refresh: true, useCache: false } - ).then(() => { - const orderHistory = state.user.orders_history - const order = state.user.orders_history ? orderHistory.items.find((order) => order['entity_id'].toString() === orderId) : null - GTM.trackEvent({ - 'ecommerce': { - 'purchase': { - 'actionField': { - 'id': orderId, - 'affiliation': order ? order.store_name : '', - 'revenue': order ? order.total_due : state.cart.platformTotals && state.cart.platformTotals.base_grand_total ? state.cart.platformTotals.base_grand_total : '', - 'tax': order ? order.total_due : state.cart.platformTotals && state.cart.platformTotals.base_tax_amount ? state.cart.platformTotals.base_tax_amount : '', - 'shipping': order ? order.total_due : state.cart.platformTotals && state.cart.platformTotals.base_shipping_amount ? state.cart.platformTotals.base_shipping_amount : '', - 'coupon': '' - }, - 'products': products - } - } - }) - }) - } - }) - } -} diff --git a/src/modules/google-tag-manager/index.ts b/src/modules/google-tag-manager/index.ts deleted file mode 100644 index 236c80ef15..0000000000 --- a/src/modules/google-tag-manager/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import Vue from 'vue' -import VueGtm from 'vue-gtm' - -import { once, isServer } from '@vue-storefront/core/helpers' -import { StorefrontModule } from '@vue-storefront/core/lib/modules' -import { Logger } from '@vue-storefront/core/lib/logger' - -import { googleTagManagerModule } from './store' -import { afterRegistration, isEnabled } from './hooks/afterRegistration' - -export const KEY = 'google-tag-manager' - -export const GoogleTagManagerModule: StorefrontModule = function ({ store, router, appConfig }) { - if (isEnabled(appConfig.googleTagManager.id)) { - once('__VUE_EXTEND_GTM__', () => { - Vue.use(VueGtm, { - enabled: true, - id: appConfig.googleTagManager.id, - debug: appConfig.googleTagManager.debug, - vueRouter: router - }) - }) - } else { - Logger.warn('Google Tag Manager extensions is not working. Ensure Google Tag Manager container ID is defined in config', 'GTM')() - } - - store.registerModule(KEY, googleTagManagerModule) - - afterRegistration(appConfig, store) -} diff --git a/src/modules/google-tag-manager/store/index.ts b/src/modules/google-tag-manager/store/index.ts deleted file mode 100644 index 5a29ae581f..0000000000 --- a/src/modules/google-tag-manager/store/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from 'vuex' -import GoogleTagManagerState from '../types/GoogleTagManagerState' - -export const googleTagManagerModule: Module = { - namespaced: true, - state: { - key: null - } -} diff --git a/src/modules/google-tag-manager/types/GoogleTagManagerState.ts b/src/modules/google-tag-manager/types/GoogleTagManagerState.ts deleted file mode 100644 index 6a6c6e26a6..0000000000 --- a/src/modules/google-tag-manager/types/GoogleTagManagerState.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default interface GoogleTagManagerState { - key?: null|string -} diff --git a/src/modules/hotjar/index.ts b/src/modules/hotjar/index.ts deleted file mode 100644 index 850aba3d31..0000000000 --- a/src/modules/hotjar/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import { isServer } from '@vue-storefront/core/helpers' - -const hotjarStore = { - namespaced: true, - state: { - key: null - } -}; - -const hotjarSnippet = (hjid) => (function (h, o, t, j, a, r) { - h.hj = - h.hj || - function () { - (h.hj.q = h.hj.q || []).push(arguments); - }; - h._hjSettings = { hjid, hjsv: 6 }; - a = o.getElementsByTagName('head')[0]; - r = o.createElement('script'); - r.async = 1; - r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv; - a.appendChild(r); -})(window as any, document, '//static.hotjar.com/c/hotjar-', '.js?sv='); - -export const HotjarModule: StorefrontModule = function ({ store, appConfig }) { - store.registerModule('hotjar', hotjarStore) - - if (!isServer && appConfig.hotjar && appConfig.hotjar.id) { - hotjarSnippet(appConfig.hotjar.id); - } -} diff --git a/src/modules/instant-checkout/components/InstantCheckout.vue b/src/modules/instant-checkout/components/InstantCheckout.vue deleted file mode 100644 index 084316296f..0000000000 --- a/src/modules/instant-checkout/components/InstantCheckout.vue +++ /dev/null @@ -1,320 +0,0 @@ - - - diff --git a/src/modules/instant-checkout/index.ts b/src/modules/instant-checkout/index.ts deleted file mode 100644 index d19416d5ac..0000000000 --- a/src/modules/instant-checkout/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; - -export const InstantCheckoutModule: StorefrontModule = function () {} diff --git a/src/modules/payment-backend-methods/index.ts b/src/modules/payment-backend-methods/index.ts deleted file mode 100644 index ab1617afc8..0000000000 --- a/src/modules/payment-backend-methods/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as types from './store/mutation-types' -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import { isServer } from '@vue-storefront/core/helpers' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' - -const PaymentBackendMethodsStore = { - namespaced: true, - state: { - methods: null - }, - mutations: { - [types.SET_BACKEND_PAYMENT_METHODS] (state, paymentMethods) { - state.methods = paymentMethods - } - } -} - -export const PaymentBackendMethodsModule: StorefrontModule = function ({ store }) { - store.registerModule('payment-backend-methods', PaymentBackendMethodsStore) - - let correctPaymentMethod = false - - // Place the order. Payload is empty as we don't have any specific info to add for this payment method '{}' - const placeOrder = () => { - if (correctPaymentMethod) { - EventBus.$emit('checkout-do-placeOrder', {}) - } - } - - if (!isServer) { - // Update the methods - EventBus.$on('set-unique-payment-methods', methods => { - store.commit('payment-backend-methods/' + types.SET_BACKEND_PAYMENT_METHODS, methods) - }) - - EventBus.$on('checkout-before-placeOrder', placeOrder) - - // Mount the info component when required - EventBus.$on('checkout-payment-method-changed', (paymentMethodCode) => { - let methods = store.state['payment-backend-methods'].methods - if (methods !== null && methods.find(item => (item.code === paymentMethodCode && item.is_server_method === true))) { - correctPaymentMethod = true - } else { - correctPaymentMethod = false - } - }) - } -} diff --git a/src/modules/payment-backend-methods/store/mutation-types.ts b/src/modules/payment-backend-methods/store/mutation-types.ts deleted file mode 100644 index ecef191506..0000000000 --- a/src/modules/payment-backend-methods/store/mutation-types.ts +++ /dev/null @@ -1 +0,0 @@ -export const SET_BACKEND_PAYMENT_METHODS = 'SET_BACKEND_PAYMENT_METHODS' diff --git a/src/modules/payment-cash-on-delivery/components/Info.vue b/src/modules/payment-cash-on-delivery/components/Info.vue deleted file mode 100644 index 32d3328025..0000000000 --- a/src/modules/payment-cash-on-delivery/components/Info.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/src/modules/payment-cash-on-delivery/index.ts b/src/modules/payment-cash-on-delivery/index.ts deleted file mode 100644 index d9645a46d0..0000000000 --- a/src/modules/payment-cash-on-delivery/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { StorefrontModule } from '@vue-storefront/core/lib/modules'; -import { isServer } from '@vue-storefront/core/helpers' -import Vue from 'vue'; -import InfoComponent from './components/Info.vue' -import EventBus from '@vue-storefront/core/compatibility/plugins/event-bus' - -export const PaymentCashOnDeliveryModule: StorefrontModule = function ({ store }) { - // Place the order. Payload is empty as we don't have any specific info to add for this payment method '{}' - let correctPaymentMethod = false - const placeOrder = () => { - if (correctPaymentMethod) { - EventBus.$emit('checkout-do-placeOrder', {}) - } - } - // Update the methods - let paymentMethodConfig = { - 'title': 'Cash on delivery', - 'code': 'cashondelivery', - 'cost': 0, - 'costInclTax': 0, - 'default': true, - 'offline': true, - 'is_server_method': false - } - store.dispatch('checkout/addPaymentMethod', paymentMethodConfig) - if (!isServer) { - // Update the methods - let paymentMethodConfig = { - 'title': 'Cash on delivery', - 'code': 'cashondelivery', - 'cost': 0, - 'cost_incl_tax': 0, - 'default': true, - 'offline': true, - 'is_server_method': false - } - store.dispatch('checkout/addPaymentMethod', paymentMethodConfig) - - EventBus.$on('checkout-before-placeOrder', placeOrder) - - // Mount the info component when required. - EventBus.$on('checkout-payment-method-changed', (paymentMethodCode) => { - let methods = store.state['payment-backend-methods'].methods - if (methods) { - let method = methods.find(item => (item.code === paymentMethodCode)) - if (paymentMethodCode === 'cashondelivery' && ((typeof method !== 'undefined' && !method.is_server_method) || typeof method === 'undefined') /* otherwise it could be a `payment-backend-methods` module */) { - correctPaymentMethod = true - - // Dynamically inject a component into the order review section (optional) - const Component = Vue.extend(InfoComponent) - const componentInstance = (new Component()) - componentInstance.$mount('#checkout-order-review-additional') - } else { - correctPaymentMethod = false - } - } - }) - } -} diff --git a/src/modules/robots/server.ts b/src/modules/robots/server.ts deleted file mode 100644 index f245ca1240..0000000000 --- a/src/modules/robots/server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { serverHooks } from '@vue-storefront/core/server/hooks' - -serverHooks.afterApplicationInitialized(({ app }) => { - app.get('/robots.txt', (req, res) => { - res.end('User-agent: *\nDisallow: ') - }) -}) diff --git a/src/modules/vsf-cache-nginx b/src/modules/vsf-cache-nginx deleted file mode 160000 index c2c07879ca..0000000000 --- a/src/modules/vsf-cache-nginx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c2c07879ca261b88c7cc76c45eaa49519bfcfc87 diff --git a/src/modules/vsf-cache-varnish b/src/modules/vsf-cache-varnish deleted file mode 160000 index 09cc48f65e..0000000000 --- a/src/modules/vsf-cache-varnish +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 09cc48f65ee174dc39eec809cb5f816fd3f039fa diff --git a/src/search/adapter/graphql/gqlQuery.js b/src/search/adapter/graphql/gqlQuery.js deleted file mode 100644 index 80595a9f7d..0000000000 --- a/src/search/adapter/graphql/gqlQuery.js +++ /dev/null @@ -1,104 +0,0 @@ -function processNestedFieldFilter (filter) { - let processedFilter = {} - let filterAttributes = filter.attribute.split('.') - if (filterAttributes.length > 1) { - let nestedFilter = filter.value - for (let i = filterAttributes.length - 1; i >= 0; i--) { - nestedFilter = { [filterAttributes[i]]: nestedFilter } - } - processedFilter = nestedFilter - } else { - processedFilter[filter.attribute] = filter.value - } - return processedFilter -} - -export function prepareQueryVars (Request) { - let queryVariables = {} - const filters = typeof Request.searchQuery.getAppliedFilters !== 'undefined' ? Request.searchQuery.getAppliedFilters() : {} - - queryVariables.search = Request.searchQuery.getSearchText() - queryVariables.sort = {} - queryVariables.filter = {} - queryVariables._sourceInclude = {} - queryVariables._sourceExclude = {} - - // Add aggregations for filters - const allFilters = Request.searchQuery.getAvailableFilters() - if (allFilters.length > 0) { - for (let attrToFilter of allFilters) { - queryVariables.filter[attrToFilter.field] = {} - if (typeof attrToFilter.scope !== 'undefined') { - queryVariables.filter[attrToFilter.field]['scope'] = attrToFilter.scope - } - } - } - - for (let _filter of filters) { - if (_filter.scope) { - _filter.value['scope'] = _filter.scope - delete (_filter.scope) - } - let processedFilter = processNestedFieldFilter(_filter) - queryVariables.filter = Object.assign(queryVariables.filter, processedFilter) - } - - if (Request.sort !== '') { - const sortParse = Request.sort.split(':') - if (sortParse[1] !== undefined) { - queryVariables.sort[sortParse[0]] = sortParse[1].toUpperCase() - } else { - if (sortParse[0] === '_score') { - queryVariables.sort[sortParse[0]] = 'DESC' - } else { - queryVariables.sort[sortParse[0]] = 'ASC' - } - } - } - - queryVariables.pageSize = Request.size - queryVariables.currentPage = Request.from / Request.size + 1 - queryVariables.attributes = queryVariables.filter - queryVariables._sourceInclude = Request._sourceInclude - queryVariables._sourceExclude = Request._sourceExclude - - return queryVariables -} - -export function prepareGraphQlBody (Request) { - // @TODO Create graphQl query builder uses gqlQuery.body params - // below is a simple demo test products search query - - let query = `` - let queryVariables = prepareQueryVars(Request) - switch (Request.type) { - case 'product': - query = require('./queries/products.gql') - break - case 'attribute': - query = require('./queries/customAttributeMetadata.gql') - break - case 'category': - query = require('./queries/categories.gql') - break - case 'taxrule': - query = require('./queries/taxrule.gql') - break - case 'cms_page': - query = require('./queries/cmsPage.gql') - break - case 'cms_block': - query = require('./queries/cmsBlock.gql') - break - case 'cms_hierarhy': - query = require('./queries/cmsHierarchy.gql') - break - } - - const body = JSON.stringify({ - query, - variables: queryVariables - }) - - return body -} diff --git a/src/search/adapter/graphql/processor/processType.ts b/src/search/adapter/graphql/processor/processType.ts deleted file mode 100644 index 3909d7a773..0000000000 --- a/src/search/adapter/graphql/processor/processType.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { SearchResponse } from '@vue-storefront/core/types/search/SearchResponse' -import map from 'lodash-es/map' -import { slugify } from '@vue-storefront/core/helpers' -import config from 'config' - -export function processESResponseType (resp, start, size): SearchResponse { - const response = { - items: map(resp.hits.hits, hit => { - return Object.assign(hit._source, { - _score: hit._score, - slug: hit._source.slug ? hit._source.slug : ((hit._source.hasOwnProperty('url_key') && config.products.useMagentoUrlKeys) - ? hit._source.url_key - : (hit._source.hasOwnProperty('name') ? slugify(hit._source.name) + '-' + hit._source.id : '')) - }) // TODO: assign slugs server side - }), // TODO: add scoring information - total: resp.hits.total, - start: start, - perPage: size, - aggregations: resp.aggregations, - suggestions: resp.suggest - } - - return response -} - -export function processProductsType (resp, start, size): SearchResponse { - const response = { - items: map(resp.items, item => { - let options = {} - if (item._score) { - options['_score'] = item._score - delete item._score - } - options['slug'] = item.slug ? item.slug : ((item.url_key && - config.products.useMagentoUrlKeys) - ? item.url_key : (item.name - ? slugify(item.name) + '-' + item.id : '')) - - return Object.assign(item, options) // TODO: assign slugs server side - }), // TODO: add scoring information - total: resp.total_count, - start: start, - perPage: size, - aggregations: resp.aggregations, - suggestions: resp.suggest - } - - return response -} - -export function processCmsType (resp, start, size): SearchResponse { - const response = { - items: resp.items, - total: resp.total_count, - start: start, - perPage: size, - aggregations: resp.aggregations, - suggestions: resp.suggest - } - - return response -} diff --git a/src/search/adapter/graphql/queries/categories.gql b/src/search/adapter/graphql/queries/categories.gql deleted file mode 100644 index f1e244420d..0000000000 --- a/src/search/adapter/graphql/queries/categories.gql +++ /dev/null @@ -1,19 +0,0 @@ -query CategoryList ( - $filter: CategoryFilterInput, - $pageSize: Int, - $currentPage: Int, - $sort: CategorySortInput, - $_sourceInclude: [String] - ) { - categories( - filter: $filter - pageSize: $pageSize - currentPage: $currentPage - sort: $sort - _sourceInclude: $_sourceInclude - ) - { - hits - suggest - } -} \ No newline at end of file diff --git a/src/search/adapter/graphql/queries/cmsBlock.gql b/src/search/adapter/graphql/queries/cmsBlock.gql deleted file mode 100644 index e9cdb49214..0000000000 --- a/src/search/adapter/graphql/queries/cmsBlock.gql +++ /dev/null @@ -1,14 +0,0 @@ -query cmsBlocks ($filter: CmsInput) { - cmsBlocks( - filter: $filter - ) - { - items { - title - id - identifier - content - creation_time - } - } -} diff --git a/src/search/adapter/graphql/queries/cmsHierarchy.gql b/src/search/adapter/graphql/queries/cmsHierarchy.gql deleted file mode 100644 index b411c108d8..0000000000 --- a/src/search/adapter/graphql/queries/cmsHierarchy.gql +++ /dev/null @@ -1,14 +0,0 @@ -query cmsHierarchies ($filter: CmsInput) { - cmsHierarchies( - filter: $filter - ) - { - items { - page_id - label - xpath - request_url - parent_node_id - } - } -} \ No newline at end of file diff --git a/src/search/adapter/graphql/queries/cmsPage.gql b/src/search/adapter/graphql/queries/cmsPage.gql deleted file mode 100644 index c06627b2d4..0000000000 --- a/src/search/adapter/graphql/queries/cmsPage.gql +++ /dev/null @@ -1,14 +0,0 @@ -query cmsPages ($filter: CmsInput) { - cmsPages( - filter: $filter - ) - { - items { - title - identifier - content - meta_description - meta_keywords - } - } -} \ No newline at end of file diff --git a/src/search/adapter/graphql/queries/customAttributeMetadata.gql b/src/search/adapter/graphql/queries/customAttributeMetadata.gql deleted file mode 100644 index da60894716..0000000000 --- a/src/search/adapter/graphql/queries/customAttributeMetadata.gql +++ /dev/null @@ -1,9 +0,0 @@ -query customAttributeMetadata ($attributes: AttributeInput!, $_sourceInclude: [String]) { - customAttributeMetadata( - attributes: $attributes - _sourceInclude: $_sourceInclude - ) - { - hits - } -} \ No newline at end of file diff --git a/src/search/adapter/graphql/queries/products.gql b/src/search/adapter/graphql/queries/products.gql deleted file mode 100644 index bec0befbac..0000000000 --- a/src/search/adapter/graphql/queries/products.gql +++ /dev/null @@ -1,33 +0,0 @@ -query ProductListFilters ( - $filter: ProductFilterInput, - $search: String!, - $pageSize: Int, - $currentPage: Int, - $sort: ProductSortInput, - $_sourceInclude: [String], - $_sourceExclude: [String] - ) { - products( - filter: $filter - search: $search - pageSize: $pageSize - currentPage: $currentPage - sort: $sort - _sourceInclude: $_sourceInclude - _sourceExclude: $_sourceExclude - ) - { - items - total_count - aggregations - sort_fields{ - options { - value - } - } - page_info{ - page_size - current_page - } - } -} \ No newline at end of file diff --git a/src/search/adapter/graphql/queries/reviews.gql b/src/search/adapter/graphql/queries/reviews.gql deleted file mode 100644 index a9fc5113a3..0000000000 --- a/src/search/adapter/graphql/queries/reviews.gql +++ /dev/null @@ -1,15 +0,0 @@ -query ReviewList ( - $filter: ReviewFilterInput, - $pageSize: Int, - $currentPage: Int - ) { - reviews( - filter: $filter - pageSize: $pageSize - currentPage: $currentPage - ) - { - hits - suggest - } -} \ No newline at end of file diff --git a/src/search/adapter/graphql/queries/taxrule.gql b/src/search/adapter/graphql/queries/taxrule.gql deleted file mode 100644 index 24d7c365c9..0000000000 --- a/src/search/adapter/graphql/queries/taxrule.gql +++ /dev/null @@ -1,8 +0,0 @@ -query taxrule ($filter: TaxRuleInput) { - taxrule( - filter: $filter - ) - { - hits - } -} \ No newline at end of file diff --git a/src/search/adapter/graphql/searchAdapter.ts b/src/search/adapter/graphql/searchAdapter.ts deleted file mode 100644 index 86640a1bb3..0000000000 --- a/src/search/adapter/graphql/searchAdapter.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { prepareQueryVars } from './gqlQuery' -import { currentStoreView, prepareStoreView } from '@vue-storefront/core/lib/multistore' -import fetch from 'isomorphic-fetch' -import { processESResponseType, processProductsType, processCmsType } from './processor/processType' -import { SearchQuery } from 'storefront-query-builder' -import config from 'config' -import { isServer } from '@vue-storefront/core/helpers' -import getApiEndpointUrl from '@vue-storefront/core/helpers/getApiEndpointUrl'; - -export class SearchAdapter { - public entities: any - - public constructor () { - this.entities = [] - this.initBaseTypes() - } - /** - * register entit type using registerEntityTypeByQuery - * @param {Request} Request request object - * @return {Promise} - */ - public async search (Request) { - if (!(Request.searchQuery instanceof SearchQuery)) { - throw new Error('SearchQuery instance has wrong class required to process with graphQl request.') - } - - if (!this.entities[Request.type]) { - throw new Error('No entity type registered for ' + Request.type) - } - - const storeView = (Request.store === null) ? currentStoreView() : await prepareStoreView(Request.store) - if (storeView.storeCode === undefined || storeView.storeCode == null || !Request.type) { - throw new Error('Store and SearchRequest.type are required arguments for executing Graphql query') - } - - const gqlQueryVars = prepareQueryVars(Request) - const query = this.entities[Request.type].query - - const gqlQueryBody = JSON.stringify({ - query, - variables: gqlQueryVars - }) - - // define GraphQL url from searchAdapter entity or use default graphQl host with storeCode param - let urlGql = '' - if (getApiEndpointUrl(this.entities[Request.type], 'url')) { - urlGql = getApiEndpointUrl(this.entities[Request.type], 'url') - } else { - const serverProtocol = isServer ? getApiEndpointUrl(config.server, 'protocol') : config.server.protocol - const host = isServer ? getApiEndpointUrl(config.graphql, 'host') : config.graphql.host - const port = isServer ? getApiEndpointUrl(config.graphql, 'port') : config.graphql.port - urlGql = serverProtocol + '://' + host + ':' + port + '/graphql' - const urlStoreCode = (storeView.storeCode !== '') ? encodeURIComponent(storeView.storeCode) + '/' : '' - urlGql = urlGql + '/' + urlStoreCode - } - - return fetch(urlGql, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }, - body: gqlQueryBody - }) - .then(resp => { - return resp.json() - }) - .catch(error => { - throw new Error('FetchError in request to ES: ' + error.toString()) - }) - } - - /** - * register entit type using registerEntityTypeByQuery - * @param {string} gql gql file path - * @param {String} url server URL - * @param {function} queryProcessor some function which can update query if needed - * @param {function} resultProcessor process results of response - * @return {Object} - */ - public registerEntityType (entityType, { url = '', url_ssr = '', gql, queryProcessor, resultProcessor }) { - this.entities[entityType] = { - query: require(`${gql}`), - queryProcessor: queryProcessor, - resultProcessor: resultProcessor - } - if (url !== '') { - this.entities[entityType]['url'] = url - } - if (url_ssr !== '') { - this.entities[entityType]['url_ssr'] = url_ssr - } - return this - } - - /** - * register entit type using registerEntityTypeByQuery - * @param {graphQl} query is the GraphQL query - * @param {String} url server URL - * @param {function} queryProcessor some function which can update query if needed - * @param {function} resultProcessor process results of response - * @return {Object} - */ - public registerEntityTypeByQuery (entityType, { url = '', url_ssr = '', query, queryProcessor, resultProcessor }) { - this.entities[entityType] = { - query: query, - queryProcessor: queryProcessor, - resultProcessor: resultProcessor - } - if (url !== '') { - this.entities[entityType]['url'] = url - } - if (url_ssr !== '') { - this.entities[entityType]['url_ssr'] = url_ssr - } - return this - } - - // initialise default entitypes - public initBaseTypes () { - this.registerEntityType('product', { - gql: './queries/products.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processProductsType(resp.data.products, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'product\'') - } - } - } - }) - - this.registerEntityType('attribute', { - gql: './queries/customAttributeMetadata.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processESResponseType(resp.data.customAttributeMetadata, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'attribute\'') - } - } - } - }) - this.registerEntityType('review', { - gql: './queries/reviews.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processESResponseType(resp.data.reviews, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'review\'') - } - } - } - }) - this.registerEntityType('category', { - gql: './queries/categories.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processESResponseType(resp.data.categories, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'category\'') - } - } - } - }) - - this.registerEntityType('taxrule', { - gql: './queries/taxrule.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processESResponseType(resp.data.taxrule, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'taxrule\'') - } - } - } - }) - - this.registerEntityType('cms_page', { - gql: './queries/cmsPage.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processCmsType(resp.data.cmsPages, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'cmsPage\'') - } - } - } - }) - - this.registerEntityType('cms_block', { - gql: './queries/cmsBlock.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processCmsType(resp.data.cmsBlocks, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'cmsBlock\'') - } - } - } - }) - - this.registerEntityType('cms_hierarchy', { - gql: './queries/cmsHierarchy.gql', - queryProcessor: (query) => { - // function that can modify the query each time before it's being executed - return query - }, - resultProcessor: (resp, start, size) => { - if (resp === null) { - throw new Error('Invalid graphQl result - null not exepcted') - } - if (resp.hasOwnProperty('data')) { - return processCmsType(resp.data.cmsHierarchies, start, size) - } else { - if (resp.error) { - throw new Error(JSON.stringify(resp.error)) - } else { - throw new Error('Unknown error with graphQl result in resultProcessor for entity type \'cmsHierarchy\'') - } - } - } - }) - } -} diff --git a/test/e2e/integration/add-to-cart.js b/test/e2e/integration/add-to-cart.js deleted file mode 100644 index 796e17ba9f..0000000000 --- a/test/e2e/integration/add-to-cart.js +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint no-undef: 0 */ -describe('add to cart', () => { - it('verify that the configurable product is added to cart', () => { - cy.visit('/p/WS01/gwyn-endurance-tee-1577/WS01'); - cy.get('[data-testid=productName]').contains('Gwyn Endurance Tee') - cy.wait(1000) - cy.get('[aria-label="Select color Green"]').click(); - cy.get('[aria-label="Select size L"]').click(); - cy.get('[data-testid=addToCart]').click(); - cy.get('[data-testid=notificationMessage]').should('contain', - 'Product has been added to the cart!'); - cy.get('[data-testid=openMicrocart]').click({ force: true }); - cy.get('[data-testid=productSku]').contains('WS01-L-Green'); - cy.get('[data-testid=closeMicrocart]').click(); - }); - - it('verify that quantity is updated', () => { - cy.get('[data-testid=addToCart]').click(); - cy.get('[data-testid=notificationMessage]').should('contain', - 'Product quantity has been updated!' - ); - cy.get('[data-testid=openMicrocart]').click({ force: true }); - cy.get('input[type=number]').should('have.value', '2'); - }); - - it('verify that the bundle product is added to cart', () => { - cy.visit('/p/24-WG080/sprite-yoga-companion-kit-45'); - cy.get('[data-testid=addToCart]').click(); - cy.get('[data-testid=notificationMessage]', { timeout: 10000 }).contains( - 'This product is out of stock.' - ); - cy.get('[data-testid=notificationAction1]').click(); - - cy.get('[data-testid=bundle-options]').children().as('allOptions') - cy.get('@allOptions').first().find('[data-testid=bundle-single-option]').its(1).as('ballOptions') - .find('input[type=radio]').click({ force: true }) - cy.get('#bundleOptionQty_1') - .clear() - .type('2'); - cy.get('@ballOptions').should(($ballOptions) => { - const text = $ballOptions.text() - expect(text).to.include('Sprite Stasis Ball 65') - }) - cy.get('@allOptions').eq(-2).find('[data-testid=custom-single-option]').its(2).as('strapOptions') - .find('input[type=radio]').click({ force: true }) - cy.get('@strapOptions').should(($strapOptions) => { - const text = $strapOptions.text() - expect(text).to.include('Sprite Yoga Strap 10 foot') - }) - cy.get('#bundleOptionQty_4') - .clear() - .type('3'); - cy.get('[data-testid=addToCart]').click(); - cy.get('[data-testid=notificationMessage]').contains( - 'Product has been added to the cart!' - ); - cy.get('[data-testid=notificationAction1]').click(); - cy.get('[data-testid=openMicrocart]').click({ force: true }); - cy.get('[class=prices]').contains('168.51'); - cy.get('[data-testid="productLink"]').should('contain', 'Sprite Yoga Companion Kit') - cy.get('[data-testid=closeMicrocart]').click(); - }); -}); diff --git a/test/e2e/integration/add-to-compare.js b/test/e2e/integration/add-to-compare.js deleted file mode 100644 index 7590dc354b..0000000000 --- a/test/e2e/integration/add-to-compare.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint no-undef: 0 */ -describe('add to compare', () => { - it('Two products should be added to comparison table', () => { - cy.visit('/c/jackets-23'); - cy.get('[data-testid="productLink"]').eq(1).as('firstProduct') - cy.get('@firstProduct').click().should('have.attr', 'href').and('include', 'olivia-14-zip-light-jacket') - cy.get('[data-testid="addToCompare"]').click(); - cy.go('back').wait(1000) - cy.get('[data-testid="addToCompare"]').eq(2).click() - cy.scrollTo('top'); - cy.get('[data-testid="compare-list-icon"]').click(); - cy.get('[data-testid="comparedProduct"]').should('have.length', 2) - }); -}); diff --git a/test/e2e/integration/basic-client-path.js b/test/e2e/integration/basic-client-path.js deleted file mode 100644 index d946fcbd66..0000000000 --- a/test/e2e/integration/basic-client-path.js +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint no-undef: 0 */ -describe('basic client path', () => { - it('should go through basic user flow', () => { - cy.visit('/'); - cy.get('[data-testid=productLink]') - .eq(7) - .click({ force: true }); - cy.get('[data-testid=addToCart]').click({ force: true }); - cy.get('[data-testid=notificationMessage]').contains( - 'This product is out of stock.' - ); - cy.get('[data-testid=notificationAction1]').click(); - cy.get('[aria-label="Select color Red"]').click(); - cy.get('[aria-label="Select size S"]').click(); - cy.wait(1000); - cy.get('[data-testid=addToCart]').click({ force: true }); - cy.get('[data-testid=notificationAction2]').click(); - cy.get('[name=first-name]').type('Firstname'); - cy.get('[name=last-name]').type('Lastname'); - cy.get('[name=email-address]').type('e2e@vuestorefront.io'); - cy.get('[data-testid=personalDetailsSubmit]').click({ force: true }); - cy.get('[name=street-address]').type('Streetname'); - cy.get('[name=apartment-number]').type('28'); - cy.get('[name=city]').type('Wroclaw'); - cy.get('[name=state]').type('Lowersilesian'); - cy.get('[name=zip-code]').type('50-000'); - cy.get('[name="countries"]').select('PL'); - cy.get('[name=phone-number]').type('111 222 333'); - cy.get('[data-testid=shippingSubmit]').click({ force: true }); - cy.get('#sendToShippingAddressCheckbox').check({ force: true }); - cy.get('[data-testid=paymentSubmit]').click(); - cy.get('#acceptTermsCheckbox').check({ force: true }); - cy.get('[data-testid="errorMessage"]').should('not.exist'); - }); -}); diff --git a/test/e2e/integration/category-page.js b/test/e2e/integration/category-page.js deleted file mode 100644 index e72a3864ea..0000000000 --- a/test/e2e/integration/category-page.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint no-undef: 0 */ -describe('Category page', () => { - it('verification of filters in the Women category', () => { - cy.visit('/'); - cy.get('[data-testid=menuButton]').click(); - cy.get('[data-testid=categoryButton]') - .contains('Women') - .click({ force: true }); - cy.get('[data-testid=categoryLink][href="/women/women-20"]').click(); - cy.url().should('include', '/women/women-20'); - cy.get('[aria-label="Select color Red"]') - .first() - .click() - .should('have.class', 'active'); - cy.wait(500); - cy.get('[data-testid=productImage]', { timeout: 10000 }) - .first() - .find('img') - .eq(2) - .should('have.attr', 'src') - .and('include', 'red'); - cy.get('[aria-label="Select size S"]') - .first() - .click() - .contains('S'); - cy.get('[aria-label="Select size 30"]') - .first() - .click() - .should('have.class', 'active'); - cy.get('[aria-label="Select size XL"]') - .first() - .click() - .contains('XL'); - cy.get('[data-testid=productImage]').should('have.length', 2); - cy.get('[aria-label="Price < $50"]') - .first() - .click() - .should('have.class', 'active'); - cy.get('[aria-label="Price > $150"]') - .first() - .click() - .should('have.class', 'active'); - cy.get('[data-testid=noProductsInfo]').contains('No products found!'); - cy.get('[aria-label="Price > $150"]') - .first() - .click(); - cy.get('[data-testid=noProductsInfo]').should('not.exist'); - }); -}); diff --git a/test/e2e/integration/checkout-page.js b/test/e2e/integration/checkout-page.js deleted file mode 100644 index 6ae75d48f9..0000000000 --- a/test/e2e/integration/checkout-page.js +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint no-undef: 0 */ -describe('checkout page', () => { - it('Default shipping/billing address should be changed', () => { - cy.visit('/'); - cy.get('[data-testid=accountButton]').click(); - cy.get('[name=email]').type('logintest@user.co'); - cy.get('[name=password]').type('123qwe!@#'); - cy.get('#remember').check({ force: true }); - cy.get('[data-testid=loginSubmit]').click(); - cy.wait(500); - cy.get('[data-testid=notificationMessage]').contains('You are logged in!'); - cy.get('[data-testid=productLink]') - .eq(3) - .click(); - cy.get('[aria-label="Select size L"]').click(); - cy.get('[data-testid=addToCart]').click(); - cy.get('[data-testid=notificationAction2]').click(); - cy.get('[data-testid=personalDetailsSubmit]').click(); - cy.get('#shipToMyAddressCheckbox').check({ force: true }); - cy.get('[name=street-address]') - .clear() - .type('Dmowskiego street'); - cy.get('[name=apartment-number]') - .clear() - .type('17'); - cy.get('[name=city]') - .clear() - .type('Wroclaw'); - cy.get('[name=zip-code]') - .clear() - .type('50-555'); - cy.get('[data-testid=shippingSubmit]').click(); - cy.get('[data-testid=shippingAddressSummary]').contains('Wroclaw'); - cy.get('#sendToShippingAddressCheckbox').check({ force: true }); - cy.get('[value=checkmo]').check(); - cy.get('[data-testid=paymentSubmit]').click(); - cy.get('#acceptTermsCheckbox').check({ force: true }); - cy.get('[data-testid="errorMessage"]').should('not.exist'); - cy.get('[data-testid=orderReviewSubmit]').click(); - cy.url().should('include', '/checkout#orderReview'); - cy.get('.category-title').contains('Order confirmation'); - }); -}); diff --git a/test/e2e/integration/home-page.js b/test/e2e/integration/home-page.js deleted file mode 100644 index ce7da19516..0000000000 --- a/test/e2e/integration/home-page.js +++ /dev/null @@ -1,24 +0,0 @@ -/* eslint no-undef: 0 */ -describe('home page+privacy', () => { - it('verify the content of the homepage', () => { - cy.visit('/'); - cy.get('[data-testid=mainSliderTitle]') - .first() - .contains('Walk the walk.'); - cy.get('.header') - .should('be.visible') - .find('[data-testid="accountButton"]'); - cy.get('[title="See details"]').click(); - cy.get('.static-content').contains('Luma Privacy Policy'); - cy.get('[title="Home Page"]').click(); - cy.get('[data-testid=closeCookieButton]').click(); - cy.get('[data-testid=bottomLinks').should('be.visible'); - cy.get('[data-testid=openNewsletterButton') - .click() - .contains('Subscribe'); - cy.get('[data-testid=subscribeSubmit]').contains('Subscribe'); - cy.get('[data-testid=closeModalButton]').click(); - cy.get('.new-collection').should('be.visible'); - cy.scrollTo(0, 0); - }); -}); diff --git a/test/e2e/integration/local-storage.js b/test/e2e/integration/local-storage.js deleted file mode 100644 index f89f110767..0000000000 --- a/test/e2e/integration/local-storage.js +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint no-undef: 0 */ -describe('local-storage', () => { - it('Items added to the cart should be kept.', () => { - cy.visit('p/WS11/diva-gym-tee-1545/WS11') - cy.wait(1000) - cy.get('[aria-label="Select color Yellow"]').click().should('have.class', 'active') - cy.get('[aria-label="Select size S"]').click() - cy.get('[data-testid=variantsLabel]').first().contains('Yellow') - cy.get('[data-testid=variantsLabel]').last().contains('S') - cy.get('[data-testid=addToCart]').click() - cy.get('[data-testid=notificationMessage]').contains( - 'Product has been added to the cart!') - cy.get('[data-testid=openMicrocart]').click({ force: true }) - cy.get('[data-testid=microcart]').should('be.visible') - cy.reload().wait(500) - cy.get('[data-testid=openMicrocart]').click({ force: true }) - cy.get('[data-testid=productSku]').contains('WS11-S-Yellow') - cy.get('input[type=number]').should('have.value', '1'); - cy.get('.col-xs > .actions > :nth-child(1)').click() - cy.get('#input_164').clear({ force: true }).type(2).blur() - cy.get('.pb15 > [data-testid=subscribeSubmit]').click() - cy.wait(500) - cy.reload() - cy.get('[data-testid=openMicrocart]').click({ force: true }) - cy.get('input[type=number]').should('have.value', '2'); - cy.get('[data-testid=closeMicrocart]').click() - cy.get('[data-testid=minicartCount]').contains('2') - }) -}) diff --git a/test/e2e/integration/login-path.js b/test/e2e/integration/login-path.js deleted file mode 100644 index 8099799e45..0000000000 --- a/test/e2e/integration/login-path.js +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint no-undef: 0 */ -describe('login path', () => { - it('not existing user', () => { - cy.visit('/') - cy.get('[data-testid=accountButton]').click() - cy.get('[name=email]').type('test@false') - cy.get('[name=password]').type('Password123') - cy.get('#remember').check({ force: true }) - cy.get('[data-testid="errorMessage"]').should('exist').contains( - 'Please provide valid e-mail address.') - cy.get('[data-testid=loginSubmit]').click() - cy.get(`[data-testid=notificationMessage]`).should('exist').contains( - 'Please fix the validation errors') - }) - it('successfull login', () => { - cy.visit('/') - cy.get('[data-testid=accountButton]').click() - cy.get('[name=email]').type('test@vue.co') - cy.get('[name=password]').type('Password123') - cy.get('#remember').check({ force: true }) - cy.get('[data-testid="errorMessage"]').should('not.exist') - cy.get('[data-testid=loginSubmit]').click() - cy.get(`[data-testid=notificationMessage]`).should('exist').contains( - 'You are logged in!') - }) -}) diff --git a/test/e2e/integration/product-page.js b/test/e2e/integration/product-page.js deleted file mode 100644 index 02328db80f..0000000000 --- a/test/e2e/integration/product-page.js +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint no-undef: 0 */ -describe('product page', () => { - it('should verify that all information are visible', () => { - cy.visit('/p/WS01/gwyn-endurance-tee-1577/WS01') - cy.get('[data-testid=productName]').contains('Gwyn Endurance Tee').wait(1000) - cy.get('.product-image__thumb').eq(1).should( - 'have.attr', - 'src', - 'https://next.storefrontcloud.io/img/600/744/resize/w/s/ws01-black_main.jpg' - ) - cy.get('[aria-label="Select color Green"]') - .click() - .should('have.class', 'active') - cy.get('[data-testid=variantsLabel]').first().contains('Green') - cy.get('[aria-label="Select color Yellow"]') - .click() - .should('have.class', 'active') - cy.get('[data-testid=variantsLabel]').first().contains('Yellow') - cy.get('[aria-label="Select size M"]') - .click() - .should('have.class', 'active') - cy.get('[data-testid=variantsLabel]').last().contains('M') - cy.get('[aria-label="Select size L"]') - .click() - .should('have.class', 'active') - cy.get('[data-testid=variantsLabel]').last().contains('L') - }) - - it('should add and remove product from wishlist', () => { - cy.get('[data-testid=addToWishlist]').first().click() - cy.get('[data-testid=notificationMessage]').contains( - 'Product Gwyn Endurance Tee has been added to wishlist!' - ) - cy.get('[data-testid=notificationAction1]').click() - cy.get('[data-testid=addToWishlist]').first().click() - cy.get('[data-testid=notificationMessage]').contains( - 'Product Gwyn Endurance Tee has been removed from wishlist!' - ) - cy.get('[data-testid=notificationAction1]').click() - }) - - it('should add and remove product from compare', () => { - cy.get('[data-testid=addToCompare]').first().click() - cy.get('[data-testid=notificationMessage]').contains( - 'Product Gwyn Endurance Tee has been added to the compare!' - ) - cy.get('[data-testid=notificationAction1]').click() - cy.get('[data-testid=addToCompare]').first().click() - cy.get('[data-testid=notificationMessage]').contains( - 'Product Gwyn Endurance Tee has been removed from compare!' - ) - cy.get('[data-testid=notificationAction1]').click() - }) - - it('should add product to cart', () => { - cy.get('[data-testid=addToCart]').click() - cy.wait(500) - cy.get('[data-testid=notificationMessage]').contains( - 'Product has been added to the cart!' - ) - cy.get('[data-testid=notificationAction1]').click() - cy.get('[data-testid=openMicrocart]').click({ force: true }) - cy.get('[data-testid=microcart').contains('Gwyn Endurance Tee') - }) -}) diff --git a/test/e2e/integration/register-path.js b/test/e2e/integration/register-path.js deleted file mode 100644 index e015a0e689..0000000000 --- a/test/e2e/integration/register-path.js +++ /dev/null @@ -1,14 +0,0 @@ -describe('register path', () => { - it('should register user', () => { - cy.visit('/') - cy.get('[data-testid=accountButton]').click() - cy.get('[data-testid=registerLink]').click() - cy.get('[name=email]').type('test@test.com') - cy.get('[name=first-name]').type('Firstname') - cy.get('[name=last-name]').type('Lastname') - cy.get('[name=password]').type('Password123') - cy.get('[name=password-confirm]').type('Password123') - cy.get('#terms').check({ force: true }) - cy.get('[data-testid="errorMessage"]').should('not.exist') - }) -}) diff --git a/test/e2e/integration/search-results.js b/test/e2e/integration/search-results.js deleted file mode 100644 index d2f2e63876..0000000000 --- a/test/e2e/integration/search-results.js +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint no-undef: 0 */ -describe('Search results', () => { - it('verification of the search results', () => { - cy.visit('/') - cy.get('[data-testid=openSearchPanel]').first().should('not.be.visible') - cy.get('[data-testid=openSearchPanel]').last().click() - cy.get('#search').should('be.visible').type('Didi Sport Watch') - cy.wait(200) - cy.get('[data-testid=searchPanel] [data-testid=productLink]').first().as('firstResult') - cy.get('@firstResult') - .should('be.visible') - .find('.product-image__thumb').eq(1) - .should( - 'have.attr', - 'src', - 'https://next.storefrontcloud.io/img/310/300/resize/w/g/wg02-bk-0.jpg' - ) - cy.get('@firstResult').contains('Didi Sport Watch') - cy.get('#search').clear() - cy.get('[data-testid=closeSearchPanel]').click() - cy.get('[data-testid=searchPanel]').should('not.be.visible') - - // check mobile viewport - cy.viewport(320, 480) - cy.get('[data-testid=openSearchPanel]').last().should('not.be.visible') - cy.get('[data-testid=openSearchPanel]').first().click() - cy.get('#search').should('be.visible').type('Didi Sport Watch') - cy.wait(200) - cy.get('[data-testid=searchPanel] [data-testid=productLink]').first().as('firstResult') - cy.get('@firstResult') - .should('be.visible') - .find('.product-image__thumb').eq(1) - .should( - 'have.attr', - 'src', - 'https://next.storefrontcloud.io/img/310/300/resize/w/g/wg02-bk-0.jpg' - ) - cy.get('@firstResult').contains('Didi Sport Watch') - cy.get('[data-testid=closeSearchPanel]').click() - cy.get('[data-testid=searchPanel]').should('not.be.visible') - }) -}) diff --git a/test/e2e/plugins/index.js b/test/e2e/plugins/index.js deleted file mode 100644 index fd170fba69..0000000000 --- a/test/e2e/plugins/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -} diff --git a/test/e2e/support/commands.js b/test/e2e/support/commands.js deleted file mode 100644 index 31735e082b..0000000000 --- a/test/e2e/support/commands.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint no-undef: 0 */ - -Cypress.Commands.add('internalNavigation', (selector) => { - // create random numbre which will be checked after page change - cy.window().then(win => { - // add random number to window object - win.e2eInternalNavigationKey = 'test-key' - }) - // change page by clicking link - cy.get(selector).click() - cy.window() - .then(win => { - if (!win.e2eInternalNavigationKey) { - cy.log(`After clicking on link ${selector} page was reloaded. Please use 'router-link' or 'nuxt-link' instead of native link`) - } - // check if page was reloaded, if it wasn't reloaded then it should pass, because window should be still the same - cy.wrap({ e2eInternalNavigationKey: win.e2eInternalNavigationKey }).should('have.property', 'e2eInternalNavigationKey', 'test-key') - cy.log(`Internal page navigation was successfull`) - }) -}) -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add("login", (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This is will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/test/e2e/support/index.js b/test/e2e/support/index.js deleted file mode 100644 index 1a5a601eed..0000000000 --- a/test/e2e/support/index.js +++ /dev/null @@ -1,38 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands' - -// Alternatively you can use CommonJS syntax: -// require('./commands') - -before(() => { - return window.caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - return window.caches.delete(cacheName); - }) - ); - }) -}) - -beforeEach(() => { - cy.setCookie('shop/claims/onboardingAccepted', 'test') - indexedDB.deleteDatabase('shop') - indexedDB.deleteDatabase('carts') - cy.clearLocalStorage() -}) - diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json deleted file mode 100644 index 756a92b355..0000000000 --- a/test/e2e/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "baseUrl": "../node_modules", - "types": [ - "cypress" - ] - }, - "include": [ - "**/*.*" - ] - } \ No newline at end of file diff --git a/test/unit/cssStub.js b/test/unit/cssStub.js deleted file mode 100644 index 8e76a5e5c6..0000000000 --- a/test/unit/cssStub.js +++ /dev/null @@ -1 +0,0 @@ -export default { } diff --git a/test/unit/jest.conf.js b/test/unit/jest.conf.js deleted file mode 100644 index 538927ee98..0000000000 --- a/test/unit/jest.conf.js +++ /dev/null @@ -1,42 +0,0 @@ -const config = require('config') - -module.exports = { - rootDir: '../../', - moduleFileExtensions: [ - 'js', - 'ts', - 'json', - 'vue' - ], - testMatch: [ - '/src/modules/**/test/unit/**/*.spec.(js|ts)', - `/src/themes/**/*.spec.(js|ts)`, - '/core/**/test/unit/**/*.spec.(js|ts)' - ], - transform: { - '^.+\\.js$': '/node_modules/babel-jest', - '^.+\\.ts$': '/node_modules/ts-jest', - '.*\\.(vue)$': '/node_modules/vue-jest' - }, - snapshotSerializers: ['/node_modules/jest-serializer-vue'], - coverageDirectory: '/test/unit/coverage', - collectCoverageFrom: [ - 'src/**/*.{js,ts,vue}', - 'core/**/*.{js,ts,vue}', - '!src/**/types/*.{js,ts}', - '!core/**/types/*.{js,ts}' - ], - moduleNameMapper: { - '^src(.*)$': '/src$1', - '^theme(.*)$': `/node_modules/${config.theme}$1`, - '^.+\\.(css|less)$': '/test/unit/cssStub.js' - }, - transformIgnorePatterns: [ - '(.*)storefront-query-builder/node_modules/(.*)', - '/node_modules/(?!lodash)', - '/node_modules/(?!lodash-es/.*)' - ], - setupFiles: [ - '/test/unit/setupTestEnvironment.ts' - ] -} diff --git a/test/unit/package.json b/test/unit/package.json deleted file mode 100644 index f5437e5975..0000000000 --- a/test/unit/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@vue-storefront/unit-tests", - "private": true, - "version": "1.12.2" -} diff --git a/test/unit/setupTestEnvironment.ts b/test/unit/setupTestEnvironment.ts deleted file mode 100644 index 1c969ed4ac..0000000000 --- a/test/unit/setupTestEnvironment.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@babel/polyfill' -import { GlobalWithFetchMock } from 'jest-fetch-mock'; - -const customGlobal: GlobalWithFetchMock = (global as unknown) as GlobalWithFetchMock; -customGlobal.fetch = require('jest-fetch-mock'); -customGlobal.fetchMock = customGlobal.fetch; diff --git a/test/unit/utils/index.ts b/test/unit/utils/index.ts deleted file mode 100644 index 6c923a8509..0000000000 --- a/test/unit/utils/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import Vuex from 'vuex' -import { shallowMount, createLocalVue, Wrapper, ThisTypedShallowMountOptions } from '@vue/test-utils'; -import Vue, { ComponentOptions } from 'vue'; - -export const mountMixin = ( - component: ComponentOptions, - mountOptions: ThisTypedShallowMountOptions = {}, - template = '
' -): Wrapper => { - const localVue = createLocalVue(); - - localVue.use(Vuex); - - return shallowMount({ - template, - mixins: [component] - }, { - localVue, - ...mountOptions - }) -}; - -export const mountMixinWithStore = ( - component: ComponentOptions, - storeOptions: object = {}, - mountOptions: ThisTypedShallowMountOptions = {}, - template = '
' -): Wrapper => { - const localVue = createLocalVue(); - - localVue.use(Vuex); - - const store = new Vuex.Store({ - ...storeOptions - }); - - return shallowMount({ - template, - mixins: [component] - }, { - store, - localVue, - ...mountOptions - }) -}; - -export const createContextMock = (props = {}) => ({ - // @ts-ignore - commit: jest.fn(), - // @ts-ignore - dispatch: jest.fn(), - // @ts-ignore - ...props -}) diff --git a/tsconfig-build.json b/tsconfig-build.json deleted file mode 100644 index 151c2420c1..0000000000 --- a/tsconfig-build.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "esModuleInterop": true - }, - "exclude": [ - "**/*.spec.ts" - ] -} diff --git a/tsconfig.json b/tsconfig.json index b569ee03a7..5f1dcbc1dc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,43 +1,10 @@ { - "compilerOptions": { - "target": "es5", - "module": "esnext", - "strict": false, - "allowJs": true, - "importHelpers": false, - "moduleResolution": "node", - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "esModuleInterop": true, - "resolveJsonModule": false, - "sourceMap": true, - "downlevelIteration": true, - "baseUrl": ".", - "types": [ - "reflect-metadata", - "node", - "jest" - ], - "paths": { - "core/*": ["core/*"], - "theme/*": ["src/themes/default/*"], - "core/modules/*": ["./core/modules/*"], - "@vue-storefront/*": ["./"] - }, - "lib": ["es2017", "dom"] - }, - "files": ["shims.d.ts"], + "extends": "./packages/tsconfig.base.json", "include": [ - "src/**/*.ts", - "src/**/*.vue", - "core/**/*.ts", - "core/**/*.vue", - "test/**/*.ts", - "module/**/*.ts", - "packages/**/*.ts" + "packages" ], "exclude": [ - "node_modules", - "**/*.spec.ts" + "dev", + "node_modules" ] } diff --git a/yarn.lock b/yarn.lock index 94dec14ac4..5ec5077b00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,1475 +2,1085 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== - dependencies: - "@babel/highlight" "^7.8.3" +"@apollo/client@^3.2.5", "@apollo/client@^3.2.9", "@apollo/client@^3.3.21": + version "3.4.9" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.4.9.tgz#78d0decf8371b768f97c721d1b71c96260bac4db" + integrity sha512-1AlYjRJ/ktDApEUEP2DqHI38tqSyhSlsF/Q3fFb/aCbLHQfcSZ1dCv7ZlC9UXRyDwQYc0w23gYJ7wZde6W8P4A== + dependencies: + "@graphql-typed-document-node/core" "^3.0.0" + "@wry/context" "^0.6.0" + "@wry/equality" "^0.5.0" + "@wry/trie" "^0.3.0" + graphql-tag "^2.12.3" + hoist-non-react-statics "^3.3.2" + optimism "^0.16.1" + prop-types "^15.7.2" + symbol-observable "^4.0.0" + ts-invariant "^0.9.0" + tslib "^2.3.0" + zen-observable-ts "^1.1.0" + +"@apollo/client@~3.2.5 || ~3.3.0": + version "3.3.21" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.3.21.tgz#2862baa4e1ced8c5e89ebe6fc52877fc64a726aa" + integrity sha512-RAmZReFuKCKx0Rs5C0nVJwKomAHUHn+gGP/YvbEsXQWu0sXoncEUZa71UqlfCPVXa/0MkYOIbCXSQdOcuRrHgw== + dependencies: + "@graphql-typed-document-node/core" "^3.0.0" + "@types/zen-observable" "^0.8.0" + "@wry/context" "^0.6.0" + "@wry/equality" "^0.5.0" + fast-json-stable-stringify "^2.0.0" + graphql-tag "^2.12.0" + hoist-non-react-statics "^3.3.2" + optimism "^0.16.0" + prop-types "^15.7.2" + symbol-observable "^4.0.0" + ts-invariant "^0.8.0" + tslib "^1.10.0" + zen-observable "^0.8.14" -"@babel/code-frame@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" - integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== +"@ardatan/aggregate-error@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz#fe6924771ea40fc98dc7a7045c2e872dc8527609" + integrity sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ== dependencies: - "@babel/highlight" "^7.10.1" + tslib "~2.0.1" -"@babel/compat-data@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.1.tgz#b1085ffe72cd17bf2c0ee790fc09f9626011b2db" - integrity sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw== +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: - browserslist "^4.12.0" - invariant "^2.2.4" - semver "^5.5.0" + "@babel/highlight" "^7.10.4" -"@babel/compat-data@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.6.tgz#7eeaa0dfa17e50c7d9c0832515eee09b56f04e35" - integrity sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== dependencies: - browserslist "^4.8.5" - invariant "^2.2.4" - semver "^5.5.0" + "@babel/highlight" "^7.14.5" + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.0", "@babel/compat-data@^7.14.7", "@babel/compat-data@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" + integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== -"@babel/core@^7.1.0", "@babel/core@^7.7.5", "@babel/core@^7.8.4": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.6.tgz#27d7df9258a45c2e686b6f18b6c659e563aa4636" - integrity sha512-Sheg7yEJD51YHAvLEV/7Uvw95AeWqYPL3Vk3zGujJKIhJ+8oLw2ALaf3hbucILhKsgSoADOvtKRJuNVdcJkOrg== +"@babel/core@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" + integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA== dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.6" - "@babel/helpers" "^7.8.4" - "@babel/parser" "^7.8.6" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" - convert-source-map "^1.7.0" + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.4" + "@babel/helpers" "^7.4.4" + "@babel/parser" "^7.4.5" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.5" + "@babel/types" "^7.4.4" + convert-source-map "^1.1.0" debug "^4.1.0" - gensync "^1.0.0-beta.1" json5 "^2.1.0" - lodash "^4.17.13" + lodash "^4.17.11" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" - integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.0" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helpers" "^7.9.0" - "@babel/parser" "^7.9.0" - "@babel/template" "^7.8.6" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" +"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.10.5", "@babel/core@^7.11.0", "@babel/core@^7.14.0", "@babel/core@^7.7.2", "@babel/core@^7.7.5", "@babel/core@^7.8.4": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.0.tgz#749e57c68778b73ad8082775561f67f5196aafa8" + integrity sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.15.0" + "@babel/helper-compilation-targets" "^7.15.0" + "@babel/helper-module-transforms" "^7.15.0" + "@babel/helpers" "^7.14.8" + "@babel/parser" "^7.15.0" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" convert-source-map "^1.7.0" debug "^4.1.0" - gensync "^1.0.0-beta.1" + gensync "^1.0.0-beta.2" json5 "^2.1.2" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/generator@^7.10.1": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9" - integrity sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA== - dependencies: - "@babel/types" "^7.10.2" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - -"@babel/generator@^7.4.0", "@babel/generator@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.6.tgz#57adf96d370c9a63c241cd719f9111468578537a" - integrity sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg== - dependencies: - "@babel/types" "^7.8.6" - jsesc "^2.5.1" - lodash "^4.17.13" + semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.9.0", "@babel/generator@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" - integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== +"@babel/generator@^7.12.13", "@babel/generator@^7.15.0", "@babel/generator@^7.4.0", "@babel/generator@^7.4.4", "@babel/generator@^7.5.0", "@babel/generator@^7.7.2": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15" + integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ== dependencies: - "@babel/types" "^7.9.5" + "@babel/types" "^7.15.0" jsesc "^2.5.1" - lodash "^4.17.13" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268" - integrity sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-annotate-as-pure@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" - integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz#0ec7d9be8174934532661f87783eb18d72290059" - integrity sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" - integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-call-delegate@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz#de82619898aa605d409c42be6ffb8d7204579692" - integrity sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A== - dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-compilation-targets@^7.10.2": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz#a17d9723b6e2c750299d2a14d4637c76936d8285" - integrity sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA== - dependencies: - "@babel/compat-data" "^7.10.1" - browserslist "^4.12.0" - invariant "^2.2.4" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/helper-compilation-targets@^7.8.4", "@babel/helper-compilation-targets@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.6.tgz#015b85db69e3a34240d5c2b761fc53eb9695f09c" - integrity sha512-UrJdk27hKVJSnibFcUWYLkCL0ZywTUoot8yii1lsHJcvwrypagmYKjHLMWivQPm4s6GdyygCL8fiH5EYLxhQwQ== - dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.8.5" - invariant "^2.2.4" - levenary "^1.1.1" - semver "^5.5.0" - -"@babel/helper-create-class-features-plugin@^7.10.1": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz#7474295770f217dbcf288bf7572eb213db46ee67" - integrity sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ== - dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/helper-member-expression-to-functions" "^7.10.1" - "@babel/helper-optimise-call-expression" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" - -"@babel/helper-create-class-features-plugin@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0" - integrity sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg== - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - -"@babel/helper-create-regexp-features-plugin@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz#1b8feeab1594cbcfbf3ab5a3bbcabac0468efdbd" - integrity sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-regex" "^7.10.1" - regexpu-core "^4.7.0" - -"@babel/helper-create-regexp-features-plugin@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz#7fa040c97fb8aebe1247a5c645330c32d083066b" - integrity sha512-bPyujWfsHhV/ztUkwGHz/RPV1T1TDEsSZDsN42JPehndA+p1KKTh3npvTadux0ZhCrytx9tvjpWNowKby3tM6A== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-regex" "^7.8.3" - regexpu-core "^4.6.0" - -"@babel/helper-define-map@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz#5e69ee8308648470dd7900d159c044c10285221d" - integrity sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg== - dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/types" "^7.10.1" - lodash "^4.17.13" - -"@babel/helper-define-map@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" - integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== - dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/types" "^7.8.3" - lodash "^4.17.13" - -"@babel/helper-explode-assignable-expression@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz#e9d76305ee1162ca467357ae25df94f179af2b7e" - integrity sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg== - dependencies: - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-explode-assignable-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" - integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== - dependencies: - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-function-name@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4" - integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ== - dependencies: - "@babel/helper-get-function-arity" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" - integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-function-name@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" - integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== - dependencies: - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/types" "^7.9.5" - -"@babel/helper-get-function-arity@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d" - integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-get-function-arity@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" - integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-hoist-variables@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz#7e77c82e5dcae1ebf123174c385aaadbf787d077" - integrity sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-hoist-variables@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" - integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-member-expression-to-functions@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15" - integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-member-expression-to-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" - integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" - integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-module-imports@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876" - integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-module-transforms@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622" - integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg== - dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" - "@babel/helper-simple-access" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" - lodash "^4.17.13" - -"@babel/helper-module-transforms@^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz#6a13b5eecadc35692047073a64e42977b97654a4" - integrity sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.8.6" - lodash "^4.17.13" - -"@babel/helper-module-transforms@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" - integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-simple-access" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/template" "^7.8.6" - "@babel/types" "^7.9.0" - lodash "^4.17.13" - -"@babel/helper-optimise-call-expression@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543" - integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg== +"@babel/helper-annotate-as-pure@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61" + integrity sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA== dependencies: - "@babel/types" "^7.10.1" + "@babel/types" "^7.14.5" -"@babel/helper-optimise-call-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" - integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.14.5.tgz#b939b43f8c37765443a19ae74ad8b15978e0a191" + integrity sha512-YTA/Twn0vBXDVGJuAX6PwW7x5zQei1luDDo2Pl6q1qZ7hVNl0RZrhHCQG/ArGpR29Vl7ETiB8eJyrvpuRp300w== dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" - integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== - -"@babel/helper-plugin-utils@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127" - integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA== - -"@babel/helper-regex@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.1.tgz#021cf1a7ba99822f993222a001cc3fec83255b96" - integrity sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g== - dependencies: - lodash "^4.17.13" - -"@babel/helper-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" - integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== - dependencies: - lodash "^4.17.13" - -"@babel/helper-remap-async-to-generator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz#bad6aaa4ff39ce8d4b82ccaae0bfe0f7dbb5f432" - integrity sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-wrap-function" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-remap-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" - integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-wrap-function" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-replace-supers@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d" - integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.10.1" - "@babel/helper-optimise-call-expression" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" - integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.6" - -"@babel/helper-simple-access@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e" - integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw== - dependencies: - "@babel/template" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/helper-simple-access@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" - integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== - dependencies: - "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" - -"@babel/helper-split-export-declaration@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f" - integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g== - dependencies: - "@babel/types" "^7.10.1" - -"@babel/helper-split-export-declaration@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" - integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== - dependencies: - "@babel/types" "^7.8.3" - -"@babel/helper-validator-identifier@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5" - integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw== - -"@babel/helper-validator-identifier@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" - integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== + "@babel/helper-explode-assignable-expression" "^7.14.5" + "@babel/types" "^7.14.5" -"@babel/helper-wrap-function@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz#956d1310d6696257a7afd47e4c42dfda5dfcedc9" - integrity sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.16", "@babel/helper-compilation-targets@^7.14.5", "@babel/helper-compilation-targets@^7.15.0", "@babel/helper-compilation-targets@^7.9.6": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818" + integrity sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A== dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/template" "^7.10.1" - "@babel/traverse" "^7.10.1" - "@babel/types" "^7.10.1" + "@babel/compat-data" "^7.15.0" + "@babel/helper-validator-option" "^7.14.5" + browserslist "^4.16.6" + semver "^6.3.0" -"@babel/helper-wrap-function@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" - integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== +"@babel/helper-create-class-features-plugin@^7.14.5", "@babel/helper-create-class-features-plugin@^7.3.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.0.tgz#c9a137a4d137b2d0e2c649acf536d7ba1a76c0f7" + integrity sha512-MdmDXgvTIi4heDVX/e9EFfeGpugqm9fobBVg/iioE8kueXrOHdRDe36FAY7SnE9xXLVeYCoJR/gdrBEIHRC83Q== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-member-expression-to-functions" "^7.15.0" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/helper-replace-supers" "^7.15.0" + "@babel/helper-split-export-declaration" "^7.14.5" -"@babel/helpers@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.4.tgz#754eb3ee727c165e0a240d6c207de7c455f36f73" - integrity sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w== +"@babel/helper-create-regexp-features-plugin@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz#c7d5ac5e9cf621c26057722fb7a8a4c5889358c4" + integrity sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A== dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.4" - "@babel/types" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.14.5" + regexpu-core "^4.7.1" -"@babel/helpers@^7.9.0": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" - integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== +"@babel/helper-define-polyfill-provider@^0.2.2": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz#0525edec5094653a282688d34d846e4c75e9c0b6" + integrity sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew== dependencies: - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.9.0" - "@babel/types" "^7.9.0" - -"@babel/highlight@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" - integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-explode-assignable-expression@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz#8aa72e708205c7bb643e45c73b4386cdf2a1f645" + integrity sha512-Htb24gnGJdIGT4vnRKMdoXiOIlqOLmdiUYpAQ0mYfgVT/GDm8GOYhgi4GL+hMKrkiPRohO4ts34ELFsGAPQLDQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-function-name@^7.12.13", "@babel/helper-function-name@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4" + integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ== dependencies: - "@babel/helper-validator-identifier" "^7.10.1" - chalk "^2.0.0" - js-tokens "^4.0.0" + "@babel/helper-get-function-arity" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/types" "^7.14.5" -"@babel/highlight@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" - integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== - dependencies: +"@babel/helper-get-function-arity@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815" + integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-hoist-variables@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d" + integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-member-expression-to-functions@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b" + integrity sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg== + dependencies: + "@babel/types" "^7.15.0" + +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12", "@babel/helper-module-imports@^7.14.5", "@babel/helper-module-imports@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3" + integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-module-transforms@^7.14.5", "@babel/helper-module-transforms@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz#679275581ea056373eddbe360e1419ef23783b08" + integrity sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg== + dependencies: + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-replace-supers" "^7.15.0" + "@babel/helper-simple-access" "^7.14.8" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.9" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" + +"@babel/helper-optimise-call-expression@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c" + integrity sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + +"@babel/helper-remap-async-to-generator@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.14.5.tgz#51439c913612958f54a987a4ffc9ee587a2045d6" + integrity sha512-rLQKdQU+HYlxBwQIj8dk4/0ENOUEhA/Z0l4hN8BexpvmSMN9oA9EagjnhnDpNsRdWCfjwa4mn/HyBXO9yhQP6A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-wrap-function" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helper-replace-supers@^7.14.5", "@babel/helper-replace-supers@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz#ace07708f5bf746bf2e6ba99572cce79b5d4e7f4" + integrity sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.15.0" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" + +"@babel/helper-simple-access@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.8.tgz#82e1fec0644a7e775c74d305f212c39f8fe73924" + integrity sha512-TrFN4RHh9gnWEU+s7JloIho2T76GPwRHhdzOWLqTrMnlas8T9O7ec+oEDNsRXndOmru9ymH9DFrEOxpzPoSbdg== + dependencies: + "@babel/types" "^7.14.8" + +"@babel/helper-skip-transparent-expression-wrappers@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz#96f486ac050ca9f44b009fbe5b7d394cab3a0ee4" + integrity sha512-dmqZB7mrb94PZSAOYtr+ZN5qt5owZIAgqtoTuqiFbHFtxgEcmQlRJVI+bO++fciBunXtB6MK7HrzrfcAzIz2NQ== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-split-export-declaration@^7.12.13", "@babel/helper-split-export-declaration@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a" + integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA== + dependencies: + "@babel/types" "^7.14.5" + +"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== + +"@babel/helper-validator-option@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== + +"@babel/helper-wrap-function@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.14.5.tgz#5919d115bf0fe328b8a5d63bcb610f51601f2bff" + integrity sha512-YEdjTCq+LNuNS1WfxsDCNpgXkJaIyqco6DAelTUjT4f2KIWC1nBcaCaSdHTBqQVLnTBexBcVcFhLSU1KnYuePQ== + dependencies: + "@babel/helper-function-name" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/helpers@^7.14.8", "@babel/helpers@^7.4.4": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.3.tgz#c96838b752b95dcd525b4e741ed40bb1dc2a1357" + integrity sha512-HwJiz52XaS96lX+28Tnbu31VeFSQJGOeKHJeaEPQlTl7PnlhFElWPj8tUXtqFIzeN86XxXoBr+WFAyK2PPVz6g== + dependencies: + "@babel/template" "^7.14.5" + "@babel/traverse" "^7.15.0" + "@babel/types" "^7.15.0" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" chalk "^2.0.0" - esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.6.tgz#ba5c9910cddb77685a008e3c587af8d27b67962c" - integrity sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g== - -"@babel/parser@^7.10.1": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0" - integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ== +"@babel/parser@7.12.16": + version "7.12.16" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.16.tgz#cc31257419d2c3189d394081635703f549fc1ed4" + integrity sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw== -"@babel/parser@^7.9.0": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.14.5", "@babel/parser@^7.14.9", "@babel/parser@^7.15.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.5", "@babel/parser@^7.7.2", "@babel/parser@^7.9.6": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.3.tgz#3416d9bea748052cfcb63dbcc27368105b1ed862" + integrity sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA== -"@babel/plugin-proposal-async-generator-functions@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz#6911af5ba2e615c4ff3c497fe2f47b35bf6d7e55" - integrity sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz#4b467302e1548ed3b1be43beae2cc9cf45e0bb7e" + integrity sha512-ZoJS2XCKPBfTmL122iP6NM9dOg+d4lc9fFk3zxc8iDjvt8Pk4+TlsHSKhIPf6X+L5ORCdBzqMZDjL/WHj7WknQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-remap-async-to-generator" "^7.10.1" - "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" + "@babel/plugin-proposal-optional-chaining" "^7.14.5" -"@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" - integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - -"@babel/plugin-proposal-class-properties@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01" - integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-proposal-class-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" - integrity sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-proposal-decorators@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz#2156860ab65c5abf068c3f67042184041066543e" - integrity sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w== +"@babel/plugin-proposal-async-generator-functions@^7.14.9", "@babel/plugin-proposal-async-generator-functions@^7.2.0": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.9.tgz#7028dc4fa21dc199bbacf98b39bab1267d0eaf9a" + integrity sha512-d1lnh+ZnKrFKwtTYdw320+sQWCTwgkB9fmUhNXRADA4akR6wLjaruSGnIEUjpt9HCOwTr4ynFTKu19b7rFRpmw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-decorators" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-remap-async-to-generator" "^7.14.5" + "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-dynamic-import@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0" - integrity sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA== +"@babel/plugin-proposal-class-properties@7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd" + integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - -"@babel/plugin-proposal-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" - integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - -"@babel/plugin-proposal-json-strings@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz#b1e691ee24c651b5a5e32213222b2379734aff09" - integrity sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-json-strings" "^7.8.0" - -"@babel/plugin-proposal-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" - integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.0" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz#02dca21673842ff2fe763ac253777f235e9bbf78" - integrity sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" - integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/helper-create-class-features-plugin" "^7.3.0" + "@babel/helper-plugin-utils" "^7.0.0" -"@babel/plugin-proposal-numeric-separator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz#a9a38bc34f78bdfd981e791c27c6fdcec478c123" - integrity sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA== +"@babel/plugin-proposal-class-properties@^7.0.0", "@babel/plugin-proposal-class-properties@^7.13.0", "@babel/plugin-proposal-class-properties@^7.14.5", "@babel/plugin-proposal-class-properties@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.14.5.tgz#40d1ee140c5b1e31a350f4f5eed945096559b42e" + integrity sha512-q/PLpv5Ko4dVc1LYMpCY7RVAAO4uk55qPwrIuJ5QJ8c6cVuAmhu7I/49JOppXL6gXf7ZHzpRVEUZdYoPLM04Gg== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-numeric-separator" "^7.10.1" + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-proposal-object-rest-spread@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz#cba44908ac9f142650b4a65b8aa06bf3478d5fb6" - integrity sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ== +"@babel/plugin-proposal-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.5.tgz#158e9e10d449c3849ef3ecde94a03d9f1841b681" + integrity sha512-KBAH5ksEnYHCegqseI5N9skTdxgJdmDoAOc0uXa+4QMYKeZD0w5IARh4FMlTNtaHhbB8v+KzMdTgxMMzsIy6Yg== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.10.1" + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb" - integrity sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA== +"@babel/plugin-proposal-decorators@^7.13.15", "@babel/plugin-proposal-decorators@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.14.5.tgz#59bc4dfc1d665b5a6749cf798ff42297ed1b2c1d" + integrity sha512-LYz5nvQcvYeRVjui1Ykn28i+3aUiXwQ/3MGoEy0InTaz1pJo/lAzmIDXX+BQny/oufgHzJ6vnEEiXQ8KZjEVFg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-decorators" "^7.14.5" -"@babel/plugin-proposal-optional-catch-binding@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz#c9f86d99305f9fa531b568ff5ab8c964b8b223d2" - integrity sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA== +"@babel/plugin-proposal-dynamic-import@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.5.tgz#0c6617df461c0c1f8fff3b47cd59772360101d2c" + integrity sha512-ExjiNYc3HDN5PXJx+bwC50GIx/KKanX2HiggnIUAYedbARdImiCU4RhhHfdf0Kd7JNXGpsBBBCOm+bBVy3Gb0g== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" - integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== +"@babel/plugin-proposal-export-namespace-from@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.5.tgz#dbad244310ce6ccd083072167d8cea83a52faf76" + integrity sha512-g5POA32bXPMmSBu5Dx/iZGLGnKmKPc5AiY7qfZgurzrCYgIztDlHFbznSNCoQuv57YQLnQfaDi7dxCtLDIdXdA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz#15f5d6d22708629451a91be28f8facc55b0e818c" - integrity sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA== +"@babel/plugin-proposal-json-strings@^7.14.5", "@babel/plugin-proposal-json-strings@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.5.tgz#38de60db362e83a3d8c944ac858ddf9f0c2239eb" + integrity sha512-NSq2fczJYKVRIsUJyNxrVUMhB27zb7N7pOFGQOhBKJrChbGcgEAqyZrmZswkPk18VMurEeJAaICbfm57vUeTbQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543" - integrity sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg== +"@babel/plugin-proposal-logical-assignment-operators@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.5.tgz#6e6229c2a99b02ab2915f82571e0cc646a40c738" + integrity sha512-YGn2AvZAo9TwyhlLvCCWxD90Xq8xJ4aSgaX3G5D/8DW94L8aaT+dS5cSP+Z06+rCJERGSr9GxMBZ601xoc2taw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-private-methods@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598" - integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.5.tgz#ee38589ce00e2cc59b299ec3ea406fcd3a0fdaf6" + integrity sha512-gun/SOnMqjSb98Nkaq2rTKMwervfdAoz6NphdY0vTfuzMfryj+tDGb2n6UkDKwez+Y8PZDhE3D143v6Gepp4Hg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-unicode-property-regex@^7.10.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz#dc04feb25e2dd70c12b05d680190e138fa2c0c6f" - integrity sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ== +"@babel/plugin-proposal-numeric-separator@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.5.tgz#83631bf33d9a51df184c2102a069ac0c58c05f18" + integrity sha512-yiclALKe0vyZRZE0pS6RXgjUOt87GWv6FYa5zqj15PvhOGFO69R5DusPlgK/1K5dVnCtegTiWu9UaBSrLLJJBg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-unicode-property-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz#b646c3adea5f98800c9ab45105ac34d06cd4a47f" - integrity sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ== +"@babel/plugin-proposal-object-rest-spread@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz#6d1859882d4d778578e41f82cc5d7bf3d5daf6c1" + integrity sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-async-generators@^7.8.0": + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + +"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.14.7", "@babel/plugin-proposal-object-rest-spread@^7.4.4": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.7.tgz#5920a2b3df7f7901df0205974c0641b13fd9d363" + integrity sha512-082hsZz+sVabfmDWo1Oct1u1AgbKbUAyVgmX4otIc7bdsRgHBXwTwb3DpDmD4Eyyx6DNiuz5UAATT655k+kL5g== + dependencies: + "@babel/compat-data" "^7.14.7" + "@babel/helper-compilation-targets" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.14.5" + +"@babel/plugin-proposal-optional-catch-binding@^7.14.5", "@babel/plugin-proposal-optional-catch-binding@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.5.tgz#939dd6eddeff3a67fdf7b3f044b5347262598c3c" + integrity sha512-3Oyiixm0ur7bzO5ybNcZFlmVsygSIQgdOa7cTfOYCMY+wEPAYhZAJxi3mixKFCTCKUhQXuCTtQ1MzrpL3WT8ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.5.tgz#fa83651e60a360e3f13797eef00b8d519695b603" + integrity sha512-ycz+VOzo2UbWNI1rQXxIuMOzrDdHGrI23fRiz/Si2R4kv2XZQ1BK8ccdHwehMKBlcH/joGW/tzrUmo67gbJHlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.13.0", "@babel/plugin-proposal-private-methods@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.14.5.tgz#37446495996b2945f30f5be5b60d5e2aa4f5792d" + integrity sha512-838DkdUA1u+QTCplatfq4B7+1lnDa/+QMI89x5WZHBcnNv+47N8QEj2k9I2MUU9xIv8XJ4XvPCviM/Dj7Uwt9g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-proposal-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.5.tgz#9f65a4d0493a940b4c01f8aa9d3f1894a587f636" + integrity sha512-62EyfyA3WA0mZiF2e2IV9mc9Ghwxcg8YTu8BS4Wss4Y3PY725OmS9M0qLORbJwLqFtGh+jiE4wAmocK2CTUK2Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-create-class-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.14.5", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz#0f95ee0e757a5d647f378daa0eca7e93faa8bbe8" + integrity sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-async-generators@^7.2.0", "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-bigint@^7.0.0": +"@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5" - integrity sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ== +"@babel/plugin-syntax-class-properties@^7.0.0", "@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-syntax-decorators@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.8.3.tgz#8d2c15a9f1af624b0025f961682a9d53d3001bda" - integrity sha512-8Hg4dNNT9/LcA1zQlfwuKR8BUc/if7Q7NkTam9sGTcJphLwpf2g4S42uhspQrIrR+dpzE0dtTqBVFoHl8GtnnQ== +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-dynamic-import@^7.8.0", "@babel/plugin-syntax-dynamic-import@^7.8.3": +"@babel/plugin-syntax-decorators@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.14.5.tgz#eafb9c0cbe09c8afeb964ba3a7bbd63945a72f20" + integrity sha512-c4sZMRWL4GSvP1EXy0woIP7m4jkVcEuG8R1TOZxPBPtp4FSM/kiPZub9UIs/Jrb5ZAOzvTUSGYrWsrSu1JvoPw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-json-strings@^7.8.0": +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-flow@^7.0.0", "@babel/plugin-syntax-flow@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.14.5.tgz#2ff654999497d7d7d142493260005263731da180" + integrity sha512-9WK5ZwKCdWHxVuU13XNT6X73FGmutAXeor5lGFq6qhOFtMFUF4jkbijuyUdZZlpYq6E2hZeZf/u3959X9wsv0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.2.0", "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" - integrity sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A== +"@babel/plugin-syntax-jsx@^7.0.0", "@babel/plugin-syntax-jsx@^7.14.5", "@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz#000e2e25d8673cce49300517a3eda44c263e4201" + integrity sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz#25761ee7410bc8cf97327ba741ee94e4a61b7d99" - integrity sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg== +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0": +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.8.0": +"@babel/plugin-syntax-optional-catch-binding@^7.2.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-chaining@^7.8.0": +"@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz#8b8733f8c57397b3eaa47ddba8841586dcaef362" - integrity sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ== +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" - integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== +"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-arrow-functions@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz#cb5ee3a36f0863c06ead0b409b4cc43a889b295b" - integrity sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA== +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz#b82c6ce471b165b5ce420cf92914d6fb46225716" + integrity sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-arrow-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" - integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== +"@babel/plugin-transform-arrow-functions@^7.0.0", "@babel/plugin-transform-arrow-functions@^7.14.5", "@babel/plugin-transform-arrow-functions@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.14.5.tgz#f7187d9588a768dd080bf4c9ffe117ea62f7862a" + integrity sha512-KOnO0l4+tD5IfOdi4x8C1XmEIRWUjNRV8wc6K2vz/3e8yAOoZZvsRXRRIF/yo/MAOFb4QjtAw9xSxMXbSMRy8A== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-async-to-generator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz#e5153eb1a3e028f79194ed8a7a4bf55f862b2062" - integrity sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg== +"@babel/plugin-transform-async-to-generator@^7.14.5", "@babel/plugin-transform-async-to-generator@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.14.5.tgz#72c789084d8f2094acb945633943ef8443d39e67" + integrity sha512-szkbzQ0mNk0rpu76fzDdqSyPu0MuvpXgC+6rz5rpMb5OIRxdmHfQxrktL8CYolL2d8luMCZTR0DpIMIdL27IjA== dependencies: - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-remap-async-to-generator" "^7.10.1" + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-remap-async-to-generator" "^7.14.5" -"@babel/plugin-transform-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" - integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== +"@babel/plugin-transform-block-scoped-functions@^7.0.0", "@babel/plugin-transform-block-scoped-functions@^7.14.5", "@babel/plugin-transform-block-scoped-functions@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.14.5.tgz#e48641d999d4bc157a67ef336aeb54bc44fd3ad4" + integrity sha512-dtqWqdWZ5NqBX3KzsVCWfQI3A53Ft5pWFCT2eCVUftWZgjc5DpDponbIF1+c+7cSGk2wN0YK7HGL/ezfRbpKBQ== dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-block-scoped-functions@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz#146856e756d54b20fff14b819456b3e01820b85d" - integrity sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q== +"@babel/plugin-transform-block-scoping@^7.0.0", "@babel/plugin-transform-block-scoping@^7.14.5", "@babel/plugin-transform-block-scoping@^7.4.4": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.15.3.tgz#94c81a6e2fc230bcce6ef537ac96a1e4d2b3afaf" + integrity sha512-nBAzfZwZb4DkaGtOes1Up1nOAp9TDRRFw4XBzBBSG9QK7KVFmYzgj9o9sbPv7TX5ofL4Auq4wZnxCoPnI/lz2Q== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-block-scoped-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" - integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-block-scoping@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz#47092d89ca345811451cd0dc5d91605982705d5e" - integrity sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw== +"@babel/plugin-transform-classes@^7.0.0", "@babel/plugin-transform-classes@^7.14.9", "@babel/plugin-transform-classes@^7.4.4": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.9.tgz#2a391ffb1e5292710b00f2e2c210e1435e7d449f" + integrity sha512-NfZpTcxU3foGWbl4wxmZ35mTsYJy8oQocbeIMoDAGGFarAmSQlL+LWMkDx/tj6pNotpbX3rltIA4dprgAPOq5A== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - lodash "^4.17.13" - -"@babel/plugin-transform-block-scoping@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" - integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - lodash "^4.17.13" - -"@babel/plugin-transform-classes@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz#6e11dd6c4dfae70f540480a4702477ed766d733f" - integrity sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-define-map" "^7.10.1" - "@babel/helper-function-name" "^7.10.1" - "@babel/helper-optimise-call-expression" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-optimise-call-expression" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" globals "^11.1.0" -"@babel/plugin-transform-classes@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz#77534447a477cbe5995ae4aee3e39fbc8090c46d" - integrity sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz#59aa399064429d64dce5cf76ef9b90b7245ebd07" - integrity sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-computed-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" - integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-destructuring@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz#abd58e51337815ca3a22a336b85f62b998e71907" - integrity sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-destructuring@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz#20ddfbd9e4676906b1056ee60af88590cc7aaa0b" - integrity sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-dotall-regex@^7.10.1", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz#920b9fec2d78bb57ebb64a644d5c2ba67cc104ee" - integrity sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA== +"@babel/plugin-transform-computed-properties@^7.0.0", "@babel/plugin-transform-computed-properties@^7.14.5", "@babel/plugin-transform-computed-properties@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.14.5.tgz#1b9d78987420d11223d41195461cc43b974b204f" + integrity sha512-pWM+E4283UxaVzLb8UBXv4EIxMovU4zxT1OPnpHJcmnvyY9QbPPTKZfEj31EUvG3/EQRbYAGaYEUZ4yWOBC2xg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-dotall-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" - integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== +"@babel/plugin-transform-destructuring@^7.0.0", "@babel/plugin-transform-destructuring@^7.14.7", "@babel/plugin-transform-destructuring@^7.4.4": + version "7.14.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.7.tgz#0ad58ed37e23e22084d109f185260835e5557576" + integrity sha512-0mDE99nK+kVh3xlc5vKwB6wnP9ecuSj+zQCa/n0voENtP/zymdT4HH6QEb65wjjcbqr1Jb/7z9Qp7TF5FtwYGw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-duplicate-keys@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz#c900a793beb096bc9d4d0a9d0cde19518ffc83b9" - integrity sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA== +"@babel/plugin-transform-dotall-regex@^7.14.5", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz#2f6bf76e46bdf8043b4e7e16cf24532629ba0c7a" + integrity sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-create-regexp-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-duplicate-keys@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" - integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== +"@babel/plugin-transform-duplicate-keys@^7.14.5", "@babel/plugin-transform-duplicate-keys@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.14.5.tgz#365a4844881bdf1501e3a9f0270e7f0f91177954" + integrity sha512-iJjbI53huKbPDAsJ8EmVmvCKeeq21bAze4fu9GBQtSLqfvzj2oRuHVx4ZkDwEhg1htQ+5OBZh/Ab0XDf5iBZ7A== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-exponentiation-operator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz#279c3116756a60dd6e6f5e488ba7957db9c59eb3" - integrity sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA== +"@babel/plugin-transform-exponentiation-operator@^7.14.5", "@babel/plugin-transform-exponentiation-operator@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.14.5.tgz#5154b8dd6a3dfe6d90923d61724bd3deeb90b493" + integrity sha512-jFazJhMBc9D27o9jDnIE5ZErI0R0m7PbKXVq77FFvqFbzvTMuv8jaAwLZ5PviOLSFttqKIW0/wxNSDbjLk0tYA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-exponentiation-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" - integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== +"@babel/plugin-transform-flow-strip-types@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.14.5.tgz#0dc9c1d11dcdc873417903d6df4bed019ef0f85e" + integrity sha512-KhcolBKfXbvjwI3TV7r7TkYm8oNXHNBqGOy6JDVwtecFaRoKYsUUqJdS10q0YDKW1c6aZQgO+Ys3LfGkox8pXA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-flow" "^7.14.5" -"@babel/plugin-transform-for-of@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz#ff01119784eb0ee32258e8646157ba2501fcfda5" - integrity sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w== +"@babel/plugin-transform-for-of@^7.0.0", "@babel/plugin-transform-for-of@^7.14.5", "@babel/plugin-transform-for-of@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.14.5.tgz#dae384613de8f77c196a8869cbf602a44f7fc0eb" + integrity sha512-CfmqxSUZzBl0rSjpoQSFoR9UEj3HzbGuGNL21/iFTmjb5gFggJp3ph0xR1YBhexmLoKRHzgxuFvty2xdSt6gTA== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-for-of@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz#a051bd1b402c61af97a27ff51b468321c7c2a085" - integrity sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw== +"@babel/plugin-transform-function-name@^7.0.0", "@babel/plugin-transform-function-name@^7.14.5", "@babel/plugin-transform-function-name@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.14.5.tgz#e81c65ecb900746d7f31802f6bed1f52d915d6f2" + integrity sha512-vbO6kv0fIzZ1GpmGQuvbwwm+O4Cbm2NrPzwlup9+/3fdkuzo1YqOZcXw26+YUJB84Ja7j9yURWposEHLYwxUfQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-function-name@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz#4ed46fd6e1d8fde2a2ec7b03c66d853d2c92427d" - integrity sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw== +"@babel/plugin-transform-literals@^7.0.0", "@babel/plugin-transform-literals@^7.14.5", "@babel/plugin-transform-literals@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.14.5.tgz#41d06c7ff5d4d09e3cf4587bd3ecf3930c730f78" + integrity sha512-ql33+epql2F49bi8aHXxvLURHkxJbSmMKl9J5yHqg4PLtdE6Uc48CH1GS6TQvZ86eoB/ApZXwm7jlA+B3kra7A== dependencies: - "@babel/helper-function-name" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" - integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== +"@babel/plugin-transform-member-expression-literals@^7.0.0", "@babel/plugin-transform-member-expression-literals@^7.14.5", "@babel/plugin-transform-member-expression-literals@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.14.5.tgz#b39cd5212a2bf235a617d320ec2b48bcc091b8a7" + integrity sha512-WkNXxH1VXVTKarWFqmso83xl+2V3Eo28YY5utIkbsmXoItO8Q3aZxN4BTS2k0hz9dGUloHK26mJMyQEYfkn/+Q== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-literals@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz#5794f8da82846b22e4e6631ea1658bce708eb46a" - integrity sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw== +"@babel/plugin-transform-modules-amd@^7.14.5", "@babel/plugin-transform-modules-amd@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.5.tgz#4fd9ce7e3411cb8b83848480b7041d83004858f7" + integrity sha512-3lpOU8Vxmp3roC4vzFpSdEpGUWSMsHFreTWOMMLzel2gNGfHE5UWIh/LN6ghHs2xurUp4jRFYMUIZhuFbody1g== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" - integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-member-expression-literals@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz#90347cba31bca6f394b3f7bd95d2bbfd9fce2f39" - integrity sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-member-expression-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" - integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-modules-amd@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz#65950e8e05797ebd2fe532b96e19fc5482a1d52a" - integrity sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw== - dependencies: - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-amd@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5" - integrity sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ== +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.15.0", "@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.4.4": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.0.tgz#3305896e5835f953b5cdb363acd9e8c2219a5281" + integrity sha512-3H/R9s8cXcOGE8kgMlmjYYC9nqr5ELiPkJn4q0mypBrjhYQoc+5/Maq69vV4xRPWnkzZuwJPf5rArxpB/35Cig== dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-commonjs@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz#d5ff4b4413ed97ffded99961056e1fb980fb9301" - integrity sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg== - dependencies: - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-simple-access" "^7.10.1" + "@babel/helper-module-transforms" "^7.15.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-simple-access" "^7.14.8" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz#df251706ec331bd058a34bdd72613915f82928a5" - integrity sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg== +"@babel/plugin-transform-modules-systemjs@^7.14.5", "@babel/plugin-transform-modules-systemjs@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.14.5.tgz#c75342ef8b30dcde4295d3401aae24e65638ed29" + integrity sha512-mNMQdvBEE5DcMQaL5LbzXFMANrQjd2W7FPzg34Y4yEz7dBgdaC+9B84dSO+/1Wba98zoDbInctCDo4JGxz1VYA== dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-simple-access" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" - -"@babel/plugin-transform-modules-systemjs@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz#9962e4b0ac6aaf2e20431ada3d8ec72082cbffb6" - integrity sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA== - dependencies: - "@babel/helper-hoist-variables" "^7.10.1" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-hoist-variables" "^7.14.5" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-validator-identifier" "^7.14.5" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz#d8bbf222c1dbe3661f440f2f00c16e9bb7d0d420" - integrity sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg== +"@babel/plugin-transform-modules-umd@^7.14.5", "@babel/plugin-transform-modules-umd@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.5.tgz#fb662dfee697cce274a7cda525190a79096aa6e0" + integrity sha512-RfPGoagSngC06LsGUYyM9QWSXZ8MysEjDJTAea1lqRjNECE3y0qIJF/qbvJxc4oA4s99HumIMdXOrd+TdKaAAA== dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + "@babel/helper-module-transforms" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-modules-umd@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz#ea080911ffc6eb21840a5197a39ede4ee67b1595" - integrity sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.14.9", "@babel/plugin-transform-named-capturing-groups-regex@^7.4.5": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.14.9.tgz#c68f5c5d12d2ebaba3762e57c2c4f6347a46e7b2" + integrity sha512-l666wCVYO75mlAtGFfyFwnWmIXQm3kSH0C3IRnJqWcZbWkoihyAdDhFm2ZWaxWTqvBvhVFfJjMRQ0ez4oN1yYA== dependencies: - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-create-regexp-features-plugin" "^7.14.5" -"@babel/plugin-transform-modules-umd@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz#592d578ce06c52f5b98b02f913d653ffe972661a" - integrity sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw== +"@babel/plugin-transform-new-target@^7.14.5", "@babel/plugin-transform-new-target@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.14.5.tgz#31bdae8b925dc84076ebfcd2a9940143aed7dbf8" + integrity sha512-Nx054zovz6IIRWEB49RDRuXGI4Gy0GMgqG0cII9L3MxqgXz/+rgII+RU58qpo4g7tNEx1jG7rRVH4ihZoP4esQ== dependencies: - "@babel/helper-module-transforms" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" - integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== +"@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.14.5", "@babel/plugin-transform-object-super@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.14.5.tgz#d0b5faeac9e98597a161a9cf78c527ed934cdc45" + integrity sha512-MKfOBWzK0pZIrav9z/hkRqIk/2bTv9qvxHzPQc12RcVkMOzpIKnFCNYJip00ssKWYkd8Sf5g0Wr7pqJ+cmtuFg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-replace-supers" "^7.14.5" -"@babel/plugin-transform-new-target@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz#6ee41a5e648da7632e22b6fb54012e87f612f324" - integrity sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw== +"@babel/plugin-transform-parameters@^7.0.0", "@babel/plugin-transform-parameters@^7.14.5", "@babel/plugin-transform-parameters@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.5.tgz#49662e86a1f3ddccac6363a7dfb1ff0a158afeb3" + integrity sha512-Tl7LWdr6HUxTmzQtzuU14SqbgrSKmaR77M0OKyq4njZLQTPfOvzblNKyNkGwOfEFCEx7KeYHQHDI0P3F02IVkA== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-new-target@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" - integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== +"@babel/plugin-transform-property-literals@^7.0.0", "@babel/plugin-transform-property-literals@^7.14.5", "@babel/plugin-transform-property-literals@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.14.5.tgz#0ddbaa1f83db3606f1cdf4846fa1dfb473458b34" + integrity sha512-r1uilDthkgXW8Z1vJz2dKYLV1tuw2xsbrp3MrZmD99Wh9vsfKoob+JTgri5VUb/JqyKRXotlOtwgu4stIYCmnw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-object-super@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz#2e3016b0adbf262983bf0d5121d676a5ed9c4fde" - integrity sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw== +"@babel/plugin-transform-react-display-name@^7.0.0": + version "7.15.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.15.1.tgz#6aaac6099f1fcf6589d35ae6be1b6e10c8c602b9" + integrity sha512-yQZ/i/pUCJAHI/LbtZr413S3VT26qNrEm0M5RRxQJA947/YNYwbZbBaXGDrq6CG5QsZycI1VIP6d7pQaBfP+8Q== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-object-super@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" - integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.3" - -"@babel/plugin-transform-parameters@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz#b25938a3c5fae0354144a720b07b32766f683ddd" - integrity sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg== - dependencies: - "@babel/helper-get-function-arity" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-parameters@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz#1d5155de0b65db0ccf9971165745d3bb990d77d3" - integrity sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA== +"@babel/plugin-transform-react-jsx-self@^7.0.0": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.14.9.tgz#33041e665453391eb6ee54a2ecf3ba1d46bd30f4" + integrity sha512-Fqqu0f8zv9W+RyOnx29BX/RlEsBRANbOf5xs5oxb2aHP4FKbLXxIaVPUiCti56LAR1IixMH4EyaixhUsKqoBHw== dependencies: - "@babel/helper-call-delegate" "^7.8.3" - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-property-literals@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz#cffc7315219230ed81dc53e4625bf86815b6050d" - integrity sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA== +"@babel/plugin-transform-react-jsx-source@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.14.5.tgz#79f728e60e6dbd31a2b860b0bf6c9765918acf1d" + integrity sha512-1TpSDnD9XR/rQ2tzunBVPThF5poaYT9GqP+of8fAtguYuI/dm2RkrMBDemsxtY0XBzvW7nXjYM0hRyKX9QYj7Q== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-property-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" - integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.9.tgz#3314b2163033abac5200a869c4de242cd50a914c" + integrity sha512-30PeETvS+AeD1f58i1OVyoDlVYQhap/K20ZrMjLmmzmC2AYR/G43D4sdJAaDAqCD3MYpSWbmrz3kES158QSLjw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.14.5" + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/plugin-syntax-jsx" "^7.14.5" + "@babel/types" "^7.14.9" -"@babel/plugin-transform-regenerator@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz#10e175cbe7bdb63cc9b39f9b3f823c5c7c5c5490" - integrity sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw== +"@babel/plugin-transform-regenerator@^7.14.5", "@babel/plugin-transform-regenerator@^7.4.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.14.5.tgz#9676fd5707ed28f522727c5b3c0aa8544440b04f" + integrity sha512-NVIY1W3ITDP5xQl50NgTKlZ0GrotKtLna08/uGY6ErQt6VEQZXla86x/CTddm5gZdcr+5GSsvMeTmWA5Ii6pkg== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-regenerator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz#b31031e8059c07495bf23614c97f3d9698bc6ec8" - integrity sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA== +"@babel/plugin-transform-reserved-words@^7.14.5", "@babel/plugin-transform-reserved-words@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.14.5.tgz#c44589b661cfdbef8d4300dcc7469dffa92f8304" + integrity sha512-cv4F2rv1nD4qdexOGsRQXJrOcyb5CrgjUH9PKrrtyhSDBNWGxd0UIitjyJiWagS+EbUGjG++22mGH1Pub8D6Vg== dependencies: - regenerator-transform "^0.14.0" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-reserved-words@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz#0fc1027312b4d1c3276a57890c8ae3bcc0b64a86" - integrity sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ== +"@babel/plugin-transform-runtime@7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz#566bc43f7d0aedc880eaddbd29168d0f248966ea" + integrity sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-reserved-words@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" - integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-runtime@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.8.3.tgz#c0153bc0a5375ebc1f1591cb7eea223adea9f169" - integrity sha512-/vqUt5Yh+cgPZXXjmaG9NT8aVfThKk7G4OqkVhrXqwsC5soMn/qTCxs36rZ2QFhpfTJcjw4SNDIZ4RUb8OL4jQ== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" resolve "^1.8.1" semver "^5.5.1" -"@babel/plugin-transform-shorthand-properties@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3" - integrity sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-shorthand-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" - integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-spread@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz#0c6d618a0c4461a274418460a28c9ccf5239a7c8" - integrity sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw== - dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - -"@babel/plugin-transform-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" - integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-transform-sticky-regex@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz#90fc89b7526228bed9842cff3588270a7a393b00" - integrity sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA== +"@babel/plugin-transform-runtime@^7.11.0", "@babel/plugin-transform-runtime@^7.13.15": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.0.tgz#d3aa650d11678ca76ce294071fda53d7804183b3" + integrity sha512-sfHYkLGjhzWTq6xsuQ01oEsUYjkHRux9fW1iUA68dC7Qd8BS1Unq4aZ8itmQp95zUzIcyR2EbNMTzAicFj+guw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-regex" "^7.10.1" - -"@babel/plugin-transform-sticky-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" - integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-regex" "^7.8.3" + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + babel-plugin-polyfill-corejs2 "^0.2.2" + babel-plugin-polyfill-corejs3 "^0.2.2" + babel-plugin-polyfill-regenerator "^0.2.2" + semver "^6.3.0" -"@babel/plugin-transform-template-literals@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz#914c7b7f4752c570ea00553b4284dad8070e8628" - integrity sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg== +"@babel/plugin-transform-shorthand-properties@^7.0.0", "@babel/plugin-transform-shorthand-properties@^7.14.5", "@babel/plugin-transform-shorthand-properties@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz#97f13855f1409338d8cadcbaca670ad79e091a58" + integrity sha512-xLucks6T1VmGsTB+GWK5Pl9Jl5+nRXD1uoFdA5TSO6xtiNjtXTjKkmPdFXVLGlK5A2/or/wQMKfmQ2Y0XJfn5g== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-template-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" - integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== +"@babel/plugin-transform-spread@^7.0.0", "@babel/plugin-transform-spread@^7.14.6", "@babel/plugin-transform-spread@^7.2.0": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.14.6.tgz#6bd40e57fe7de94aa904851963b5616652f73144" + integrity sha512-Zr0x0YroFJku7n7+/HH3A2eIrGMjbmAIbJSVv0IZ+t3U2WUQUA64S/oeied2e+MaGSjmt4alzBCsK9E8gh+fag== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.14.5" -"@babel/plugin-transform-typeof-symbol@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz#60c0239b69965d166b80a84de7315c1bc7e0bb0e" - integrity sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g== +"@babel/plugin-transform-sticky-regex@^7.14.5", "@babel/plugin-transform-sticky-regex@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.14.5.tgz#5b617542675e8b7761294381f3c28c633f40aeb9" + integrity sha512-Z7F7GyvEMzIIbwnziAZmnSNpdijdr4dWt+FJNBnBLz5mwDFkqIXU9wmBcWWad3QeJF5hMTkRe4dAq2sUZiG+8A== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-typeof-symbol@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" - integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== +"@babel/plugin-transform-template-literals@^7.0.0", "@babel/plugin-transform-template-literals@^7.14.5", "@babel/plugin-transform-template-literals@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.14.5.tgz#a5f2bc233937d8453885dc736bdd8d9ffabf3d93" + integrity sha512-22btZeURqiepOfuy/VkFr+zStqlujWaarpMErvay7goJS6BWwdd6BY9zQyDLDa4x2S3VugxFb162IZ4m/S/+Gg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-unicode-escapes@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz#add0f8483dab60570d9e03cecef6c023aa8c9940" - integrity sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw== +"@babel/plugin-transform-typeof-symbol@^7.14.5", "@babel/plugin-transform-typeof-symbol@^7.2.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.14.5.tgz#39af2739e989a2bd291bf6b53f16981423d457d4" + integrity sha512-lXzLD30ffCWseTbMQzrvDWqljvZlHkXU+CnseMhkMNqU1sASnCsz3tSzAaH3vCUXb9PHeUb90ZT1BdFTm1xxJw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-unicode-regex@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz#6b58f2aea7b68df37ac5025d9c88752443a6b43f" - integrity sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw== +"@babel/plugin-transform-unicode-escapes@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.14.5.tgz#9d4bd2a681e3c5d7acf4f57fa9e51175d91d0c6b" + integrity sha512-crTo4jATEOjxj7bt9lbYXcBAM3LZaUrbP2uUdxb6WIorLmjNKSpHfIybgY4B8SRpbf8tEVIWH3Vtm7ayCrKocA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-transform-unicode-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" - integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/polyfill@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.8.3.tgz#2333fc2144a542a7c07da39502ceeeb3abe4debd" - integrity sha512-0QEgn2zkCzqGIkSWWAEmvxD7e00Nm9asTtQvi7HdlYvMhjy/J38V/1Y9ode0zEJeIuxAI0uftiAzqc7nVeWUGg== +"@babel/plugin-transform-unicode-regex@^7.14.5", "@babel/plugin-transform-unicode-regex@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.14.5.tgz#4cd09b6c8425dd81255c7ceb3fb1836e7414382e" + integrity sha512-UygduJpC5kHeCiRw/xDVzC+wj8VaYSoKl5JNVmbP7MadpNinAm3SvZCxZ42H37KZBKztz46YC73i9yV34d0Tzw== dependencies: - core-js "^2.6.5" - regenerator-runtime "^0.13.2" + "@babel/helper-create-regexp-features-plugin" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" -"@babel/preset-env@^7.8.4": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.6.tgz#2a0773b08589ecba4995fc71b1965e4f531af40b" - integrity sha512-M5u8llV9DIVXBFB/ArIpqJuvXpO+ymxcJ6e8ZAmzeK3sQeBNOD1y+rHvHCGG4TlEmsNpIrdecsHGHT8ZCoOSJg== +"@babel/preset-env@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58" + integrity sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w== dependencies: - "@babel/compat-data" "^7.8.6" - "@babel/helper-compilation-targets" "^7.8.6" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.8.3" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.8.3" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.8.6" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.8.6" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.8.3" - "@babel/plugin-transform-modules-commonjs" "^7.8.3" - "@babel/plugin-transform-modules-systemjs" "^7.8.3" - "@babel/plugin-transform-modules-umd" "^7.8.3" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.4" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.3" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" - "@babel/types" "^7.8.6" - browserslist "^4.8.5" - core-js-compat "^3.6.2" + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-json-strings" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread" "^7.4.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-arrow-functions" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.4.4" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.4.4" + "@babel/plugin-transform-classes" "^7.4.4" + "@babel/plugin-transform-computed-properties" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/plugin-transform-duplicate-keys" "^7.2.0" + "@babel/plugin-transform-exponentiation-operator" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.4.4" + "@babel/plugin-transform-function-name" "^7.4.4" + "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-member-expression-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.2.0" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-modules-systemjs" "^7.4.4" + "@babel/plugin-transform-modules-umd" "^7.2.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5" + "@babel/plugin-transform-new-target" "^7.4.4" + "@babel/plugin-transform-object-super" "^7.2.0" + "@babel/plugin-transform-parameters" "^7.4.4" + "@babel/plugin-transform-property-literals" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.4.5" + "@babel/plugin-transform-reserved-words" "^7.2.0" + "@babel/plugin-transform-shorthand-properties" "^7.2.0" + "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-sticky-regex" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.4.4" + "@babel/plugin-transform-typeof-symbol" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.4.4" + "@babel/types" "^7.4.4" + browserslist "^4.6.0" + core-js-compat "^3.1.1" invariant "^2.2.2" - levenary "^1.1.1" + js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/preset-env@^7.9.0": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.2.tgz#715930f2cf8573b0928005ee562bed52fb65fdfb" - integrity sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA== - dependencies: - "@babel/compat-data" "^7.10.1" - "@babel/helper-compilation-targets" "^7.10.2" - "@babel/helper-module-imports" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-proposal-async-generator-functions" "^7.10.1" - "@babel/plugin-proposal-class-properties" "^7.10.1" - "@babel/plugin-proposal-dynamic-import" "^7.10.1" - "@babel/plugin-proposal-json-strings" "^7.10.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1" - "@babel/plugin-proposal-numeric-separator" "^7.10.1" - "@babel/plugin-proposal-object-rest-spread" "^7.10.1" - "@babel/plugin-proposal-optional-catch-binding" "^7.10.1" - "@babel/plugin-proposal-optional-chaining" "^7.10.1" - "@babel/plugin-proposal-private-methods" "^7.10.1" - "@babel/plugin-proposal-unicode-property-regex" "^7.10.1" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.10.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.10.1" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.10.1" - "@babel/plugin-transform-arrow-functions" "^7.10.1" - "@babel/plugin-transform-async-to-generator" "^7.10.1" - "@babel/plugin-transform-block-scoped-functions" "^7.10.1" - "@babel/plugin-transform-block-scoping" "^7.10.1" - "@babel/plugin-transform-classes" "^7.10.1" - "@babel/plugin-transform-computed-properties" "^7.10.1" - "@babel/plugin-transform-destructuring" "^7.10.1" - "@babel/plugin-transform-dotall-regex" "^7.10.1" - "@babel/plugin-transform-duplicate-keys" "^7.10.1" - "@babel/plugin-transform-exponentiation-operator" "^7.10.1" - "@babel/plugin-transform-for-of" "^7.10.1" - "@babel/plugin-transform-function-name" "^7.10.1" - "@babel/plugin-transform-literals" "^7.10.1" - "@babel/plugin-transform-member-expression-literals" "^7.10.1" - "@babel/plugin-transform-modules-amd" "^7.10.1" - "@babel/plugin-transform-modules-commonjs" "^7.10.1" - "@babel/plugin-transform-modules-systemjs" "^7.10.1" - "@babel/plugin-transform-modules-umd" "^7.10.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.10.1" - "@babel/plugin-transform-object-super" "^7.10.1" - "@babel/plugin-transform-parameters" "^7.10.1" - "@babel/plugin-transform-property-literals" "^7.10.1" - "@babel/plugin-transform-regenerator" "^7.10.1" - "@babel/plugin-transform-reserved-words" "^7.10.1" - "@babel/plugin-transform-shorthand-properties" "^7.10.1" - "@babel/plugin-transform-spread" "^7.10.1" - "@babel/plugin-transform-sticky-regex" "^7.10.1" - "@babel/plugin-transform-template-literals" "^7.10.1" - "@babel/plugin-transform-typeof-symbol" "^7.10.1" - "@babel/plugin-transform-unicode-escapes" "^7.10.1" - "@babel/plugin-transform-unicode-regex" "^7.10.1" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.10.2" - browserslist "^4.12.0" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" +"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.14.1": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.15.0.tgz#e2165bf16594c9c05e52517a194bf6187d6fe464" + integrity sha512-FhEpCNFCcWW3iZLg0L2NPE9UerdtsCR6ZcsGHUX6Om6kbCQeL5QZDqFDmeNHC6/fy6UH3jEge7K4qG5uC9In0Q== + dependencies: + "@babel/compat-data" "^7.15.0" + "@babel/helper-compilation-targets" "^7.15.0" + "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-validator-option" "^7.14.5" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.14.5" + "@babel/plugin-proposal-async-generator-functions" "^7.14.9" + "@babel/plugin-proposal-class-properties" "^7.14.5" + "@babel/plugin-proposal-class-static-block" "^7.14.5" + "@babel/plugin-proposal-dynamic-import" "^7.14.5" + "@babel/plugin-proposal-export-namespace-from" "^7.14.5" + "@babel/plugin-proposal-json-strings" "^7.14.5" + "@babel/plugin-proposal-logical-assignment-operators" "^7.14.5" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.5" + "@babel/plugin-proposal-numeric-separator" "^7.14.5" + "@babel/plugin-proposal-object-rest-spread" "^7.14.7" + "@babel/plugin-proposal-optional-catch-binding" "^7.14.5" + "@babel/plugin-proposal-optional-chaining" "^7.14.5" + "@babel/plugin-proposal-private-methods" "^7.14.5" + "@babel/plugin-proposal-private-property-in-object" "^7.14.5" + "@babel/plugin-proposal-unicode-property-regex" "^7.14.5" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.14.5" + "@babel/plugin-transform-async-to-generator" "^7.14.5" + "@babel/plugin-transform-block-scoped-functions" "^7.14.5" + "@babel/plugin-transform-block-scoping" "^7.14.5" + "@babel/plugin-transform-classes" "^7.14.9" + "@babel/plugin-transform-computed-properties" "^7.14.5" + "@babel/plugin-transform-destructuring" "^7.14.7" + "@babel/plugin-transform-dotall-regex" "^7.14.5" + "@babel/plugin-transform-duplicate-keys" "^7.14.5" + "@babel/plugin-transform-exponentiation-operator" "^7.14.5" + "@babel/plugin-transform-for-of" "^7.14.5" + "@babel/plugin-transform-function-name" "^7.14.5" + "@babel/plugin-transform-literals" "^7.14.5" + "@babel/plugin-transform-member-expression-literals" "^7.14.5" + "@babel/plugin-transform-modules-amd" "^7.14.5" + "@babel/plugin-transform-modules-commonjs" "^7.15.0" + "@babel/plugin-transform-modules-systemjs" "^7.14.5" + "@babel/plugin-transform-modules-umd" "^7.14.5" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.14.9" + "@babel/plugin-transform-new-target" "^7.14.5" + "@babel/plugin-transform-object-super" "^7.14.5" + "@babel/plugin-transform-parameters" "^7.14.5" + "@babel/plugin-transform-property-literals" "^7.14.5" + "@babel/plugin-transform-regenerator" "^7.14.5" + "@babel/plugin-transform-reserved-words" "^7.14.5" + "@babel/plugin-transform-shorthand-properties" "^7.14.5" + "@babel/plugin-transform-spread" "^7.14.6" + "@babel/plugin-transform-sticky-regex" "^7.14.5" + "@babel/plugin-transform-template-literals" "^7.14.5" + "@babel/plugin-transform-typeof-symbol" "^7.14.5" + "@babel/plugin-transform-unicode-escapes" "^7.14.5" + "@babel/plugin-transform-unicode-regex" "^7.14.5" + "@babel/preset-modules" "^0.1.4" + "@babel/types" "^7.15.0" + babel-plugin-polyfill-corejs2 "^0.2.2" + babel-plugin-polyfill-corejs3 "^0.2.2" + babel-plugin-polyfill-regenerator "^0.2.2" + core-js-compat "^3.16.0" + semver "^6.3.0" -"@babel/preset-modules@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" - integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== +"@babel/preset-modules@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" + integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" @@ -1478,101 +1088,85 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" - integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== - dependencies: - regenerator-runtime "^0.13.2" - -"@babel/template@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" - integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig== - dependencies: - "@babel/code-frame" "^7.10.1" - "@babel/parser" "^7.10.1" - "@babel/types" "^7.10.1" - -"@babel/template@^7.4.0", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" - integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== +"@babel/preset-react@7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0" + integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w== dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.4", "@babel/traverse@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff" - integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.6" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27" - integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ== - dependencies: - "@babel/code-frame" "^7.10.1" - "@babel/generator" "^7.10.1" - "@babel/helper-function-name" "^7.10.1" - "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/parser" "^7.10.1" - "@babel/types" "^7.10.1" +"@babel/runtime@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" + integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA== + dependencies: + regenerator-runtime "^0.12.0" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.8.4": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.0.0", "@babel/template@^7.14.5", "@babel/template@^7.3.3", "@babel/template@^7.4.0", "@babel/template@^7.4.4": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" + integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/parser" "^7.14.5" + "@babel/types" "^7.14.5" + +"@babel/traverse@7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.13.tgz#689f0e4b4c08587ad26622832632735fb8c4e0c0" + integrity sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.12.13" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.12.13" + "@babel/types" "^7.12.13" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.9.0": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" - integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.5" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.5" + lodash "^4.17.19" + +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.5", "@babel/traverse@^7.14.9", "@babel/traverse@^7.15.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98" + integrity sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.15.0" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-hoist-variables" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/parser" "^7.15.0" + "@babel/types" "^7.15.0" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.13" - -"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.6.tgz#629ecc33c2557fcde7126e58053127afdb3e6d01" - integrity sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA== - dependencies: - esutils "^2.0.2" - lodash "^4.17.13" - to-fast-properties "^2.0.0" -"@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.4.4": - version "7.10.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d" - integrity sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng== +"@babel/types@7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.13.tgz#8be1aa8f2c876da11a9cf650c0ecf656913ad611" + integrity sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ== dependencies: - "@babel/helper-validator-identifier" "^7.10.1" - lodash "^4.17.13" + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" to-fast-properties "^2.0.0" -"@babel/types@^7.9.0", "@babel/types@^7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" - integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== +"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.14.9", "@babel/types@^7.15.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0", "@babel/types@^7.4.4": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" + integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== dependencies: - "@babel/helper-validator-identifier" "^7.9.5" - lodash "^4.17.13" + "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -1580,6 +1174,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cld-apis/utils@^0.1.0": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@cld-apis/utils/-/utils-0.1.3.tgz#a12d26bef6955d4c3e501fb84ad4bf544f07aa10" + integrity sha512-e50vj4qE6PLYrado/ypwH+AVHwMRCevvpAz8sqDhRMow9MPiWNTuY9+jnLU8K/ewmX7rgdBsxxqBpMtF/OwhIw== + dependencies: + lodash.snakecase "^4.1.1" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1588,7 +1189,198 @@ exec-sh "^0.3.2" minimist "^1.2.0" -"@cypress/listr-verbose-renderer@0.4.1": +"@commercetools/sdk-auth@^3.0.1", "@commercetools/sdk-auth@^3.0.12": + version "3.0.12" + resolved "https://registry.yarnpkg.com/@commercetools/sdk-auth/-/sdk-auth-3.0.12.tgz#6b6930f697f2311beeb909677df08bd5c7f523b8" + integrity sha512-t7F71oNK+A23hLGzlEYnjTtqhxDUFST8f4l9YAf7mmVCA9Qeu668pDvJ/kf5gVLMQVazEQM8Vjy4JyyeQctEkQ== + dependencies: + "@commercetools/sdk-middleware-http" "^6.0.11" + lodash.defaultsdeep "^4.6.0" + qss "2.0.3" + +"@commercetools/sdk-middleware-http@^6.0.11": + version "6.0.11" + resolved "https://registry.yarnpkg.com/@commercetools/sdk-middleware-http/-/sdk-middleware-http-6.0.11.tgz#0ca16cefe881b68c1d2b77ddbd3a48733a5ee062" + integrity sha512-9Keb5rv6fvdA9qdehBEjk/JMrAzlBbg76TodsvhCZZZteaO0+ybjFgtV0ekdGyI4awxOxgsiPDZrTmQNvnI5Wg== + +"@commitlint/cli@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-13.1.0.tgz#3608bb24dbef41aaa0729ffe65c7f9b57409626a" + integrity sha512-xN/uNYWtGTva5OMSd+xA6e6/c2jk8av7MUbdd6w2cw89u6z3fAWoyiH87X0ewdSMNYmW/6B3L/2dIVGHRDID5w== + dependencies: + "@commitlint/format" "^13.1.0" + "@commitlint/lint" "^13.1.0" + "@commitlint/load" "^13.1.0" + "@commitlint/read" "^13.1.0" + "@commitlint/types" "^13.1.0" + lodash "^4.17.19" + resolve-from "5.0.0" + resolve-global "1.0.0" + yargs "^17.0.0" + +"@commitlint/config-conventional@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/config-conventional/-/config-conventional-13.1.0.tgz#f02871d50c73db0a31b777231f49203b964d9d59" + integrity sha512-zukJXqdr6jtMiVRy3tTHmwgKcUMGfqKDEskRigc5W3k2aYF4gBAtCEjMAJGZgSQE4DMcHeok0pEV2ANmTpb0cw== + dependencies: + conventional-changelog-conventionalcommits "^4.3.1" + +"@commitlint/config-lerna-scopes@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/config-lerna-scopes/-/config-lerna-scopes-13.1.0.tgz#c30006ff60a2a625184b9c37bc2ba8ce44ff0d60" + integrity sha512-/OTFM75GYHFRm5m1TKOpHsLvAUuGL6GYnA5xJDT4fe2vPIGqgBYVL9hEolKYTJfDtviBq4fEkEWZQjQF5e4KgA== + dependencies: + globby "^11.0.1" + import-from "4.0.0" + resolve-pkg "2.0.0" + semver "7.3.5" + +"@commitlint/ensure@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-13.1.0.tgz#057a325b54f104cbeed2a26bacb5eec29298e7d5" + integrity sha512-NRGyjOdZQnlYwm9it//BZJ2Vm+4x7G9rEnHpLCvNKYY0c6RA8Qf7hamLAB8dWO12RLuFt06JaOpHZoTt/gHutA== + dependencies: + "@commitlint/types" "^13.1.0" + lodash "^4.17.19" + +"@commitlint/execute-rule@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-13.0.0.tgz#7823303b82b5d86dac46e67cfa005f4433476981" + integrity sha512-lBz2bJhNAgkkU/rFMAw3XBNujbxhxlaFHY3lfKB/MxpAa+pIfmWB3ig9i1VKe0wCvujk02O0WiMleNaRn2KJqw== + +"@commitlint/format@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-13.1.0.tgz#915570d958d83bae5fa645de6b1e6c9dd1362ec0" + integrity sha512-n46rYvzf+6Sm99TJjTLjJBkjm6JVcklt31lDO5Q+pCIV0NnJ4qIUcwa6wIL9a9Vqb1XzlMgtp27E0zyYArkvSg== + dependencies: + "@commitlint/types" "^13.1.0" + chalk "^4.0.0" + +"@commitlint/is-ignored@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-13.1.0.tgz#88a5dfbc8f9ea91e860323af6681aa131322b0c4" + integrity sha512-P6zenLE5Tn3FTNjRzmL9+/KooTXEI0khA2TmUbuei9KiycemeO4q7Xk7w7aXwFPNAbN0O9oI7z3z7cFpzKJWmQ== + dependencies: + "@commitlint/types" "^13.1.0" + semver "7.3.5" + +"@commitlint/lint@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-13.1.0.tgz#ea56ce0970f9b75ffe7bd2c9968f4f1d4461ba3a" + integrity sha512-qH9AYSQDDTaSWSdtOvB3G1RdPpcYSgddAdFYqpFewlKQ1GJj/L+sM7vwqCG7/ip6AiM04Sry1sgmFzaEoFREUA== + dependencies: + "@commitlint/is-ignored" "^13.1.0" + "@commitlint/parse" "^13.1.0" + "@commitlint/rules" "^13.1.0" + "@commitlint/types" "^13.1.0" + +"@commitlint/load@>6.1.1", "@commitlint/load@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-13.1.0.tgz#d6c9b547551f2216586d6c1964d93f92e7b04277" + integrity sha512-zlZbjJCWnWmBOSwTXis8H7I6pYk6JbDwOCuARA6B9Y/qt2PD+NCo0E/7EuaaFoxjHl+o56QR5QttuMBrf+BJzg== + dependencies: + "@commitlint/execute-rule" "^13.0.0" + "@commitlint/resolve-extends" "^13.0.0" + "@commitlint/types" "^13.1.0" + chalk "^4.0.0" + cosmiconfig "^7.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + +"@commitlint/message@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-13.0.0.tgz#4f8d56b59e9cee8b37b8db6b48c26d7faf33762f" + integrity sha512-W/pxhesVEk8747BEWJ+VGQ9ILHmCV27/pEwJ0hGny1wqVquUR8SxvScRCbUjHCB1YtWX4dEnOPXOS9CLH/CX7A== + +"@commitlint/parse@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-13.1.0.tgz#b88764be36527a468531e1b8dd2d95693ff9ba34" + integrity sha512-xFybZcqBiKVjt6vTStvQkySWEUYPI0AcO4QQELyy29o8EzYZqWkhUfrb7K61fWiHsplWL1iL6F3qCLoxSgTcrg== + dependencies: + "@commitlint/types" "^13.1.0" + conventional-changelog-angular "^5.0.11" + conventional-commits-parser "^3.0.0" + +"@commitlint/read@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-13.1.0.tgz#ccb65426b1228b8a598ed36966722d19756eea41" + integrity sha512-NrVe23GMKyL6i1yDJD8IpqCBzhzoS3wtLfDj8QBzc01Ov1cYBmDojzvBklypGb+MLJM1NbzmRM4PR5pNX0U/NQ== + dependencies: + "@commitlint/top-level" "^13.0.0" + "@commitlint/types" "^13.1.0" + fs-extra "^10.0.0" + git-raw-commits "^2.0.0" + +"@commitlint/resolve-extends@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-13.0.0.tgz#a38fcd2474483bf9ec6e1e901b27b8a23abe7d73" + integrity sha512-1SyaE+UOsYTkQlTPUOoj4NwxQhGFtYildVS/d0TJuK8a9uAJLw7bhCLH2PEeH5cC2D1do4Eqhx/3bLDrSLH3hg== + dependencies: + import-fresh "^3.0.0" + lodash "^4.17.19" + resolve-from "^5.0.0" + resolve-global "^1.0.0" + +"@commitlint/rules@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-13.1.0.tgz#04f5aaf952884364ebf4e899ec440e3985f0e580" + integrity sha512-b6F+vBqEXsHVghrhomG0Y6YJimHZqkzZ0n5QEpk03dpBXH2OnsezpTw5e+GvbyYCc7PutGbYVQkytuv+7xCxYA== + dependencies: + "@commitlint/ensure" "^13.1.0" + "@commitlint/message" "^13.0.0" + "@commitlint/to-lines" "^13.0.0" + "@commitlint/types" "^13.1.0" + execa "^5.0.0" + +"@commitlint/to-lines@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/to-lines/-/to-lines-13.0.0.tgz#5937dd287e3a4f984580ea94bdb994132169a780" + integrity sha512-mzxWwCio1M4/kG9/69TTYqrraQ66LmtJCYTzAZdZ2eJX3I5w52pSjyP/DJzAUVmmJCYf2Kw3s+RtNVShtnZ+Rw== + +"@commitlint/top-level@^13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@commitlint/top-level/-/top-level-13.0.0.tgz#f8e1d1425240cd72c600e4da5716418c4ea0bda2" + integrity sha512-baBy3MZBF28sR93yFezd4a5TdHsbXaakeladfHK9dOcGdXo9oQe3GS5hP3BmlN680D6AiQSN7QPgEJgrNUWUCg== + dependencies: + find-up "^5.0.0" + +"@commitlint/types@^13.1.0": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-13.1.0.tgz#12cfb6e932372b1816af8900e2d10694add28191" + integrity sha512-zcVjuT+OfKt8h91vhBxt05RMcTGEx6DM7Q9QZeuMbXFk6xgbsSEDMMapbJPA1bCZ81fa/1OQBijSYPrKvtt06g== + dependencies: + chalk "^4.0.0" + +"@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + +"@cypress/browserify-preprocessor@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@cypress/browserify-preprocessor/-/browserify-preprocessor-3.0.1.tgz#ab86335b0c061d11f5ad7df03f06b1877b836f71" + integrity sha512-sErmFSEr5287bLMRl0POGnyFtJCs/lSk5yxrUIJUIHZ8eDvtTEr0V93xRgLjJVG54gJU4MbpHy1mRPA9VZbtQA== + dependencies: + "@babel/core" "7.4.5" + "@babel/plugin-proposal-class-properties" "7.3.0" + "@babel/plugin-proposal-object-rest-spread" "7.3.2" + "@babel/plugin-transform-runtime" "7.2.0" + "@babel/preset-env" "7.4.5" + "@babel/preset-react" "7.0.0" + "@babel/runtime" "7.3.1" + babel-plugin-add-module-exports "1.0.2" + babelify "10.0.0" + bluebird "3.5.3" + browserify "16.2.3" + coffeeify "3.0.1" + coffeescript "1.12.7" + debug "4.1.1" + fs-extra "9.0.0" + lodash.clonedeep "4.5.0" + through2 "^2.0.0" + watchify "3.11.1" + +"@cypress/listr-verbose-renderer@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= @@ -1598,7 +1390,45 @@ date-fns "^1.27.2" figures "^1.7.0" -"@cypress/xvfb@1.2.4": +"@cypress/mount-utils@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@cypress/mount-utils/-/mount-utils-1.0.2.tgz#afbc4f8c350b7cd86edc5ad0db0cbe1e0181edc8" + integrity sha512-Fn3fdTiyayHoy8Ol0RSu4MlBH2maQ2ZEXeEVKl/zHHXEQpld5HX3vdNLhK5YLij8cLynA4DxOT/nO9iEnIiOXw== + +"@cypress/request@^2.88.5": + version "2.88.6" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9" + integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^8.3.2" + +"@cypress/vue@^2.0.1": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@cypress/vue/-/vue-2.2.3.tgz#81822914019c47b5a6011652e50c5aad6041c6cc" + integrity sha512-KgrUjiLVyoiU5xb5JAhsdFLDzyh7Njhm4TMuINFsoBZkt4PJptGCsMzdaI7lk1XX20NGvg8MPIFbbQi+L0L8qQ== + dependencies: + "@cypress/mount-utils" "1.0.2" + "@vue/test-utils" "^1.1.3" + +"@cypress/xvfb@^1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a" integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== @@ -1606,6 +1436,21 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.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" @@ -1680,68 +1525,365 @@ unique-filename "^1.1.1" which "^1.3.1" -"@google-cloud/common@^2.0.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@google-cloud/common/-/common-2.4.0.tgz#2783b7de8435024a31453510f2dab5a6a91a4c82" - integrity sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg== +"@fullhuman/postcss-purgecss@^2.0.5": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-2.3.0.tgz#50a954757ec78696615d3e118e3fee2d9291882e" + integrity sha512-qnKm5dIOyPGJ70kPZ5jiz0I9foVOic0j+cOzNDoo8KoCf6HjicIZ99UfO2OmE7vCYSKAAepEwJtNzpiiZAh9xw== dependencies: - "@google-cloud/projectify" "^1.0.0" - "@google-cloud/promisify" "^1.0.0" - arrify "^2.0.0" - duplexify "^3.6.0" - ent "^2.2.0" - extend "^3.0.2" - google-auth-library "^5.5.0" - retry-request "^4.0.0" - teeny-request "^6.0.0" + postcss "7.0.32" + purgecss "^2.3.0" -"@google-cloud/projectify@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-1.0.4.tgz#28daabebba6579ed998edcadf1a8f3be17f3b5f0" - integrity sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg== +"@gar/promisify@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" + integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== -"@google-cloud/promisify@^1.0.0": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-1.0.4.tgz#ce86ffa94f9cfafa2e68f7b3e4a7fad194189723" - integrity sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ== +"@glidejs/glide@^3.3.0": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@glidejs/glide/-/glide-3.4.1.tgz#8ad21f3169076e32bbfd75ed0753807662c25e90" + integrity sha512-C34AEcK1HjSyxilRToUL54I6KAoodojUbeRlXoruobZuG0eGm8xfDL+3kgkWj7AJK4EZtunSOYfoqMp70eDtwg== -"@google-cloud/trace-agent@^4.2.5": - version "4.2.5" - resolved "https://registry.yarnpkg.com/@google-cloud/trace-agent/-/trace-agent-4.2.5.tgz#443bf19f08aaceb8cf20bd7591f10efbdebf995a" - integrity sha512-gVx+7NC5bwVtIZ/Z7jUbnvytwFS1RhF3iWiMyPRKWDohmy5Ixs/QK0fJNgr4Ugs12nstrILG7VfC09q8FOsK4g== - dependencies: - "@google-cloud/common" "^2.0.0" - "@opencensus/propagation-stackdriver" "0.0.19" - builtin-modules "^3.0.0" - console-log-level "^1.4.0" - continuation-local-storage "^3.2.1" - extend "^3.0.2" - gcp-metadata "^3.0.0" - hex2dec "^1.0.1" - is "^3.2.0" - methods "^1.1.1" - require-in-the-middle "^5.0.0" - semver "^7.0.0" - shimmer "^1.2.0" - source-map-support "^0.5.16" - uuid "^3.0.1" +"@graphql-tools/batch-delegate@^7.0.0": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/batch-delegate/-/batch-delegate-7.0.2.tgz#e18bfe3f545c60c03b0bc079fe4bfa8f208b1631" + integrity sha512-Dn1LKcbZfgttfxW1V7E3JYrLGCWOhtPC+HkxBRrvzxkesxqDbgaOH8X4rsx3Po8AQD49J0eLdg9KXa0yF20VCA== + dependencies: + "@graphql-tools/delegate" "^7.0.10" + dataloader "2.0.0" + tslib "~2.1.0" + +"@graphql-tools/batch-execute@^7.0.0", "@graphql-tools/batch-execute@^7.1.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/batch-execute/-/batch-execute-7.1.2.tgz#35ba09a1e0f80f34f1ce111d23c40f039d4403a0" + integrity sha512-IuR2SB2MnC2ztA/XeTMTfWcA0Wy7ZH5u+nDkDNLAdX+AaSyDnsQS35sCmHqG0VOGTl7rzoyBWLCKGwSJplgtwg== + dependencies: + "@graphql-tools/utils" "^7.7.0" + dataloader "2.0.0" + tslib "~2.2.0" + value-or-promise "1.0.6" + +"@graphql-tools/code-file-loader@^6.2.5": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/code-file-loader/-/code-file-loader-6.3.1.tgz#42dfd4db5b968acdb453382f172ec684fa0c34ed" + integrity sha512-ZJimcm2ig+avgsEOWWVvAaxZrXXhiiSZyYYOJi0hk9wh5BxZcLUNKkTp6EFnZE/jmGUwuos3pIjUD3Hwi3Bwhg== + dependencies: + "@graphql-tools/graphql-tag-pluck" "^6.5.1" + "@graphql-tools/utils" "^7.0.0" + tslib "~2.1.0" + +"@graphql-tools/delegate@^7.0.1", "@graphql-tools/delegate@^7.0.10", "@graphql-tools/delegate@^7.1.0", "@graphql-tools/delegate@^7.1.4", "@graphql-tools/delegate@^7.1.5": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-7.1.5.tgz#0b027819b7047eff29bacbd5032e34a3d64bd093" + integrity sha512-bQu+hDd37e+FZ0CQGEEczmRSfQRnnXeUxI/0miDV+NV/zCbEdIJj5tYFNrKT03W6wgdqx8U06d8L23LxvGri/g== + dependencies: + "@ardatan/aggregate-error" "0.0.6" + "@graphql-tools/batch-execute" "^7.1.2" + "@graphql-tools/schema" "^7.1.5" + "@graphql-tools/utils" "^7.7.1" + dataloader "2.0.0" + tslib "~2.2.0" + value-or-promise "1.0.6" + +"@graphql-tools/git-loader@^6.2.5": + version "6.2.6" + resolved "https://registry.yarnpkg.com/@graphql-tools/git-loader/-/git-loader-6.2.6.tgz#c2226f4b8f51f1c05c9ab2649ba32d49c68cd077" + integrity sha512-ooQTt2CaG47vEYPP3CPD+nbA0F+FYQXfzrB1Y1ABN9K3d3O2RK3g8qwslzZaI8VJQthvKwt0A95ZeE4XxteYfw== + dependencies: + "@graphql-tools/graphql-tag-pluck" "^6.2.6" + "@graphql-tools/utils" "^7.0.0" + tslib "~2.1.0" + +"@graphql-tools/github-loader@^6.2.5": + version "6.2.5" + resolved "https://registry.yarnpkg.com/@graphql-tools/github-loader/-/github-loader-6.2.5.tgz#460dff6f5bbaa26957a5ea3be4f452b89cc6a44b" + integrity sha512-DLuQmYeNNdPo8oWus8EePxWCfCAyUXPZ/p1PWqjrX/NGPyH2ZObdqtDAfRHztljt0F/qkBHbGHCEk2TKbRZTRw== + dependencies: + "@graphql-tools/graphql-tag-pluck" "^6.2.6" + "@graphql-tools/utils" "^7.0.0" + cross-fetch "3.0.6" + tslib "~2.0.1" + +"@graphql-tools/graphql-file-loader@^6.2.5": + version "6.2.7" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.2.7.tgz#d3720f2c4f4bb90eb2a03a7869a780c61945e143" + integrity sha512-5k2SNz0W87tDcymhEMZMkd6/vs6QawDyjQXWtqkuLTBF3vxjxPD1I4dwHoxgWPIjjANhXybvulD7E+St/7s9TQ== + dependencies: + "@graphql-tools/import" "^6.2.6" + "@graphql-tools/utils" "^7.0.0" + tslib "~2.1.0" + +"@graphql-tools/graphql-tag-pluck@^6.2.6", "@graphql-tools/graphql-tag-pluck@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.5.1.tgz#5fb227dbb1e19f4b037792b50f646f16a2d4c686" + integrity sha512-7qkm82iFmcpb8M6/yRgzjShtW6Qu2OlCSZp8uatA3J0eMl87TxyJoUmL3M3UMMOSundAK8GmoyNVFUrueueV5Q== + dependencies: + "@babel/parser" "7.12.16" + "@babel/traverse" "7.12.13" + "@babel/types" "7.12.13" + "@graphql-tools/utils" "^7.0.0" + tslib "~2.1.0" + +"@graphql-tools/import@^6.2.4", "@graphql-tools/import@^6.2.6": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/import/-/import-6.3.1.tgz#731c47ab6c6ac9f7994d75c76b6c2fa127d2d483" + integrity sha512-1szR19JI6WPibjYurMLdadHKZoG9C//8I/FZ0Dt4vJSbrMdVNp8WFxg4QnZrDeMG4MzZc90etsyF5ofKjcC+jw== + dependencies: + resolve-from "5.0.0" + tslib "~2.2.0" + +"@graphql-tools/json-file-loader@^6.2.5": + version "6.2.6" + resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-6.2.6.tgz#830482cfd3721a0799cbf2fe5b09959d9332739a" + integrity sha512-CnfwBSY5926zyb6fkDBHnlTblHnHI4hoBALFYXnrg0Ev4yWU8B04DZl/pBRUc459VNgO2x8/mxGIZj2hPJG1EA== + dependencies: + "@graphql-tools/utils" "^7.0.0" + tslib "~2.0.1" + +"@graphql-tools/links@^7.0.4": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/links/-/links-7.1.0.tgz#239eaf4832a9871d490fec272766916688d6e7fc" + integrity sha512-8cJLs3ko0Zq0agJiFiHuAZ27OXbfgRF5JtVtIx8q2RfjVN0sss9QeetrTBjc2XfTj5HYZr6BHqqlyMMA4OXp7A== + dependencies: + "@graphql-tools/delegate" "^7.1.0" + "@graphql-tools/utils" "^7.7.0" + apollo-upload-client "14.1.3" + cross-fetch "3.1.2" + form-data "4.0.0" + is-promise "4.0.0" + tslib "~2.1.0" + +"@graphql-tools/load-files@^6.2.4": + version "6.3.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/load-files/-/load-files-6.3.2.tgz#c4e84394e5b95b96452c22e960e2595ac9154648" + integrity sha512-3mgwEKZ8yy7CD/uVs9yeXR3r+GwjlTKRG5bC75xdJFN8WbzbcHjIJiTXfWSAYqbfSTam0hWnRdWghagzFSo5kQ== + dependencies: + globby "11.0.3" + tslib "~2.1.0" + unixify "1.0.0" + +"@graphql-tools/load@^6.2.5": + version "6.2.8" + resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-6.2.8.tgz#16900fb6e75e1d075cad8f7ea439b334feb0b96a" + integrity sha512-JpbyXOXd8fJXdBh2ta0Q4w8ia6uK5FHzrTNmcvYBvflFuWly2LDTk2abbSl81zKkzswQMEd2UIYghXELRg8eTA== + dependencies: + "@graphql-tools/merge" "^6.2.12" + "@graphql-tools/utils" "^7.5.0" + globby "11.0.3" + import-from "3.0.0" + is-glob "4.0.1" + p-limit "3.1.0" + tslib "~2.2.0" + unixify "1.0.0" + valid-url "1.0.9" + +"@graphql-tools/merge@^6.2.11", "@graphql-tools/merge@^6.2.12", "@graphql-tools/merge@^6.2.14": + version "6.2.17" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-6.2.17.tgz#4dedf87d8435a5e1091d7cc8d4f371ed1e029f1f" + integrity sha512-G5YrOew39fZf16VIrc49q3c8dBqQDD0ax5LYPiNja00xsXDi0T9zsEWVt06ApjtSdSF6HDddlu5S12QjeN8Tow== + dependencies: + "@graphql-tools/schema" "^8.0.2" + "@graphql-tools/utils" "8.0.2" + tslib "~2.3.0" + +"@graphql-tools/merge@^8.0.2": + version "8.0.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.0.3.tgz#56c844bc5d7d833456695c8e5eda4f1a0d5be873" + integrity sha512-lVMyW9cREs+nQYbUvMaaqSl+pRCezl2RafNMFi/04akjvOtjVefdi7n3pArpSqPhLHPJDyQRlI8CK8cmOZ9jTA== + dependencies: + "@graphql-tools/utils" "^8.1.2" + tslib "~2.3.0" + +"@graphql-tools/mock@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/mock/-/mock-7.0.0.tgz#b43858f47fedfbf7d8bbbf7d33e6acb64b8b7da7" + integrity sha512-ShO8D9HudgnhqoWeKb3iejGtPV8elFqSO1U0O70g3FH3W/CBW2abXfuyodBUevXVGIjyqzfkNzVtpIE0qiOVVQ== + dependencies: + "@graphql-tools/schema" "^7.0.0" + "@graphql-tools/utils" "^7.0.0" + tslib "~2.0.1" + +"@graphql-tools/module-loader@^6.2.5": + version "6.2.7" + resolved "https://registry.yarnpkg.com/@graphql-tools/module-loader/-/module-loader-6.2.7.tgz#66ab9468775fac8079ca46ea9896ceea76e4ef69" + integrity sha512-ItAAbHvwfznY9h1H9FwHYDstTcm22Dr5R9GZtrWlpwqj0jaJGcBxsMB9jnK9kFqkbtFYEe4E/NsSnxsS4/vViQ== + dependencies: + "@graphql-tools/utils" "^7.5.0" + tslib "~2.1.0" + +"@graphql-tools/optimize@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/optimize/-/optimize-1.0.1.tgz#9933fffc5a3c63f95102b1cb6076fb16ac7bb22d" + integrity sha512-cRlUNsbErYoBtzzS6zXahXeTBZGPVlPHXCpnEZ0XiK/KY/sQL96cyzak0fM/Gk6qEI9/l32MYEICjasiBQrl5w== + dependencies: + tslib "~2.0.1" + +"@graphql-tools/relay-operation-optimizer@^6.2.5": + version "6.3.7" + resolved "https://registry.yarnpkg.com/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.3.7.tgz#16c874a091a1a37bc308136d87277443cebe5056" + integrity sha512-7UYnxPvIUDrdEKFAYrNF/YsoVBYMj6l3rwwuNs1jZyzAVZh8uq3TdvaFIIlcYvRychj45BEsg1jvRBvmhTaj3Q== + dependencies: + "@graphql-tools/utils" "^8.1.1" + relay-compiler "11.0.2" + tslib "~2.3.0" + +"@graphql-tools/resolvers-composition@^6.2.5": + version "6.3.5" + resolved "https://registry.yarnpkg.com/@graphql-tools/resolvers-composition/-/resolvers-composition-6.3.5.tgz#846856247c31f73381e4e9221971c41a1e429c08" + integrity sha512-bN2ztDSkcA5MIL9pCxmBCDQDaVYSuUbs3Hi1QWEMRSor3QaMbm6I3Ir1wFNaLgXNe9XrGxugZ5jUD78a768+dQ== + dependencies: + "@graphql-tools/utils" "^8.1.1" + lodash "4.17.21" + micromatch "^4.0.4" + tslib "~2.3.0" + +"@graphql-tools/schema@^7.0.0", "@graphql-tools/schema@^7.1.4", "@graphql-tools/schema@^7.1.5": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-7.1.5.tgz#07b24e52b182e736a6b77c829fc48b84d89aa711" + integrity sha512-uyn3HSNSckf4mvQSq0Q07CPaVZMNFCYEVxroApOaw802m9DcZPgf9XVPy/gda5GWj9AhbijfRYVTZQgHnJ4CXA== + dependencies: + "@graphql-tools/utils" "^7.1.2" + tslib "~2.2.0" + value-or-promise "1.0.6" + +"@graphql-tools/schema@^8.0.2": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-8.1.2.tgz#913879da1a7889a9488e9b7dc189e7c83eff74be" + integrity sha512-rX2pg42a0w7JLVYT+f/yeEKpnoZL5PpLq68TxC3iZ8slnNBNjfVfvzzOn8Q8Q6Xw3t17KP9QespmJEDfuQe4Rg== + dependencies: + "@graphql-tools/merge" "^8.0.2" + "@graphql-tools/utils" "^8.1.1" + tslib "~2.3.0" + value-or-promise "1.0.10" + +"@graphql-tools/stitch@^7.3.0": + version "7.5.3" + resolved "https://registry.yarnpkg.com/@graphql-tools/stitch/-/stitch-7.5.3.tgz#1b339942ebb93ea4e9da248439b8cf06660688cc" + integrity sha512-0oBdNFkviWRs0OkQiGow6THjRtDbiSOljCxCtkwF+C+bB/yLEPCNatTMo3ZR51twWBRPFgMwHZbSVbhRlK51kw== + dependencies: + "@graphql-tools/batch-delegate" "^7.0.0" + "@graphql-tools/delegate" "^7.1.4" + "@graphql-tools/merge" "^6.2.11" + "@graphql-tools/schema" "^7.1.4" + "@graphql-tools/utils" "^7.7.0" + "@graphql-tools/wrap" "^7.0.6" + tslib "~2.2.0" + +"@graphql-tools/url-loader@^6.3.2": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-6.10.1.tgz#dc741e4299e0e7ddf435eba50a1f713b3e763b33" + integrity sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw== + dependencies: + "@graphql-tools/delegate" "^7.0.1" + "@graphql-tools/utils" "^7.9.0" + "@graphql-tools/wrap" "^7.0.4" + "@microsoft/fetch-event-source" "2.0.1" + "@types/websocket" "1.0.2" + abort-controller "3.0.0" + cross-fetch "3.1.4" + extract-files "9.0.0" + form-data "4.0.0" + graphql-ws "^4.4.1" + is-promise "4.0.0" + isomorphic-ws "4.0.1" + lodash "4.17.21" + meros "1.1.4" + subscriptions-transport-ws "^0.9.18" + sync-fetch "0.3.0" + tslib "~2.2.0" + valid-url "1.0.9" + ws "7.4.5" + +"@graphql-tools/utils@8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.0.2.tgz#795a8383cdfdc89855707d62491c576f439f3c51" + integrity sha512-gzkavMOgbhnwkHJYg32Adv6f+LxjbQmmbdD5Hty0+CWxvaiuJq+nU6tzb/7VSU4cwhbNLx/lGu2jbCPEW1McZQ== + dependencies: + tslib "~2.3.0" + +"@graphql-tools/utils@^7.0.0", "@graphql-tools/utils@^7.0.1", "@graphql-tools/utils@^7.1.2", "@graphql-tools/utils@^7.5.0", "@graphql-tools/utils@^7.7.0", "@graphql-tools/utils@^7.7.1", "@graphql-tools/utils@^7.8.1", "@graphql-tools/utils@^7.9.0": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-7.10.0.tgz#07a4cb5d1bec1ff1dc1d47a935919ee6abd38699" + integrity sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w== + dependencies: + "@ardatan/aggregate-error" "0.0.6" + camel-case "4.1.2" + tslib "~2.2.0" + +"@graphql-tools/utils@^8.1.1", "@graphql-tools/utils@^8.1.2": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-8.1.2.tgz#a376259fafbca7532fda657e3abeec23b545e5d3" + integrity sha512-3G+NIBR5mHjPm78jAD0l07JRE0XH+lr9m7yL/wl69jAzK0Jr/H+/Ok4ljEolI70iglz+ZhIShVPAwyesF6rnFg== + dependencies: + tslib "~2.3.0" + +"@graphql-tools/wrap@^7.0.0", "@graphql-tools/wrap@^7.0.4", "@graphql-tools/wrap@^7.0.6": + version "7.0.8" + resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-7.0.8.tgz#ad41e487135ca3ea1ae0ea04bb3f596177fb4f50" + integrity sha512-1NDUymworsOlb53Qfh7fonDi2STvqCtbeE68ntKY9K/Ju/be2ZNxrFSbrBHwnxWcN9PjISNnLcAyJ1L5tCUyhg== + dependencies: + "@graphql-tools/delegate" "^7.1.5" + "@graphql-tools/schema" "^7.1.5" + "@graphql-tools/utils" "^7.8.1" + tslib "~2.2.0" + value-or-promise "1.0.6" + +"@graphql-typed-document-node/core@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950" + integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg== + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + 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== + +"@intlify/shared@^9.0.0": + version "9.1.7" + resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.1.7.tgz#e7d8bc90cb59dc17dd7b4c85a73db16fcb7891fc" + integrity sha512-zt0zlUdalumvT9AjQNxPXA36UgOndUyvBMplh8uRZU0fhWHAwhnJTcf0NaG9Qvr8I1n3HPSs96+kLb/YdwTavQ== + +"@intlify/vue-i18n-extensions@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-extensions/-/vue-i18n-extensions-1.0.2.tgz#ab7f8507f7d423c368e44fa21d6dece700261fca" + integrity sha512-rnfA0ScyBXyp9xsSD4EAMGeOh1yv/AE7fhqdAdSOr5X8N39azz257umfRtzNT9sHXAKSSzpCVhIbMAkp5c/gjQ== + dependencies: + "@babel/parser" "^7.9.6" + +"@intlify/vue-i18n-loader@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-loader/-/vue-i18n-loader-1.1.0.tgz#eecc6460823676f533784b3641665c5a609eccf0" + integrity sha512-9LXiztMtYKTE8t/hRwwGUp+ofrwU0sxLQLzFEOZ38zvn0DonUIQmZUj1cfz5p1Lu8BllxKbCrn6HnsRJ+LYA6g== + dependencies: + "@intlify/shared" "^9.0.0" + js-yaml "^3.13.1" + json5 "^2.1.1" "@istanbuljs/load-nyc-config@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" - integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg== + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" find-up "^4.1.0" + get-package-type "^0.1.0" js-yaml "^3.13.1" resolve-from "^5.0.0" "@istanbuljs/schema@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" - integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^24.9.0": +"@jest/console@^24.7.1", "@jest/console@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== @@ -1750,58 +1892,106 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/console@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.1.0.tgz#1fc765d44a1e11aec5029c08e798246bd37075ab" - integrity sha512-3P1DpqAMK/L07ag/Y9/Jup5iDEG9P4pRAuZiMQnU0JB3UOvCyYCjCoxr7sIA80SeyUCUKrr24fKAxVpmBgQonA== +"@jest/console@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.0.6.tgz#3eb72ea80897495c3d73dd97aab7f26770e2260f" + integrity sha512-fMlIBocSHPZ3JxgWiDNW/KPj6s+YRd0hicb33IrmelCcjXo/pXPwvuiKFmZz+XuqI/1u7nbUK10zSsWL/1aegg== dependencies: - "@jest/source-map" "^25.1.0" - chalk "^3.0.0" - jest-util "^25.1.0" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.0.6" + jest-util "^27.0.6" slash "^3.0.0" -"@jest/core@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.1.0.tgz#3d4634fc3348bb2d7532915d67781cdac0869e47" - integrity sha512-iz05+NmwCmZRzMXvMo6KFipW7nzhbpEawrKrkkdJzgytavPse0biEnCNr2wRlyCsp3SmKaEY+SGv7YWYQnIdig== +"@jest/core@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4" + integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== + dependencies: + "@jest/console" "^24.7.1" + "@jest/reporters" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-changed-files "^24.9.0" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-resolve-dependencies "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + jest-watcher "^24.9.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + realpath-native "^1.1.0" + rimraf "^2.5.4" + slash "^2.0.0" + strip-ansi "^5.0.0" + +"@jest/core@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.0.6.tgz#c5f642727a0b3bf0f37c4b46c675372d0978d4a1" + integrity sha512-SsYBm3yhqOn5ZLJCtccaBcvD/ccTLCeuDv8U41WJH/V1MW5eKUkeMHT9U+Pw/v1m1AIWlnIW/eM2XzQr0rEmow== dependencies: - "@jest/console" "^25.1.0" - "@jest/reporters" "^25.1.0" - "@jest/test-result" "^25.1.0" - "@jest/transform" "^25.1.0" - "@jest/types" "^25.1.0" + "@jest/console" "^27.0.6" + "@jest/reporters" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" ansi-escapes "^4.2.1" - chalk "^3.0.0" + chalk "^4.0.0" + emittery "^0.8.1" exit "^0.1.2" - graceful-fs "^4.2.3" - jest-changed-files "^25.1.0" - jest-config "^25.1.0" - jest-haste-map "^25.1.0" - jest-message-util "^25.1.0" - jest-regex-util "^25.1.0" - jest-resolve "^25.1.0" - jest-resolve-dependencies "^25.1.0" - jest-runner "^25.1.0" - jest-runtime "^25.1.0" - jest-snapshot "^25.1.0" - jest-util "^25.1.0" - jest-validate "^25.1.0" - jest-watcher "^25.1.0" - micromatch "^4.0.2" + graceful-fs "^4.2.4" + jest-changed-files "^27.0.6" + jest-config "^27.0.6" + jest-haste-map "^27.0.6" + jest-message-util "^27.0.6" + jest-regex-util "^27.0.6" + jest-resolve "^27.0.6" + jest-resolve-dependencies "^27.0.6" + jest-runner "^27.0.6" + jest-runtime "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" + jest-watcher "^27.0.6" + micromatch "^4.0.4" p-each-series "^2.1.0" - realpath-native "^1.1.0" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.1.0.tgz#4a97f64770c9d075f5d2b662b5169207f0a3f787" - integrity sha512-cTpUtsjU4cum53VqBDlcW0E4KbQF03Cn0jckGPW/5rrE9tb+porD3+hhLtHAwhthsqfyF+bizyodTlsRA++sHg== +"@jest/environment@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18" + integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== + dependencies: + "@jest/fake-timers" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + +"@jest/environment@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.0.6.tgz#ee293fe996db01d7d663b8108fa0e1ff436219d2" + integrity sha512-4XywtdhwZwCpPJ/qfAkqExRsERW+UaoSRStSHCCiQTUpoYdLukj+YJbQSFrZjhlUDRZeNiU9SFH0u7iNimdiIg== dependencies: - "@jest/fake-timers" "^25.1.0" - "@jest/types" "^25.1.0" - jest-mock "^25.1.0" + "@jest/fake-timers" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + jest-mock "^27.0.6" "@jest/fake-timers@^24.9.0": version "24.9.0" @@ -1812,51 +2002,85 @@ jest-message-util "^24.9.0" jest-mock "^24.9.0" -"@jest/fake-timers@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.1.0.tgz#a1e0eff51ffdbb13ee81f35b52e0c1c11a350ce8" - integrity sha512-Eu3dysBzSAO1lD7cylZd/CVKdZZ1/43SF35iYBNV1Lvvn2Undp3Grwsv8PrzvbLhqwRzDd4zxrY4gsiHc+wygQ== +"@jest/fake-timers@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.0.6.tgz#cbad52f3fe6abe30e7acb8cd5fa3466b9588e3df" + integrity sha512-sqd+xTWtZ94l3yWDKnRTdvTeZ+A/V7SSKrxsrOKSqdyddb9CeNRF8fbhAU0D7ZJBpTTW2nbp6MftmKJDZfW2LQ== + dependencies: + "@jest/types" "^27.0.6" + "@sinonjs/fake-timers" "^7.0.2" + "@types/node" "*" + jest-message-util "^27.0.6" + jest-mock "^27.0.6" + jest-util "^27.0.6" + +"@jest/globals@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.0.6.tgz#48e3903f99a4650673d8657334d13c9caf0e8f82" + integrity sha512-DdTGCP606rh9bjkdQ7VvChV18iS7q0IMJVP1piwTWyWskol4iqcVwthZmoJEf7obE1nc34OpIyoVGPeqLC+ryw== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/types" "^27.0.6" + expect "^27.0.6" + +"@jest/reporters@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43" + integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== dependencies: - "@jest/types" "^25.1.0" - jest-message-util "^25.1.0" - jest-mock "^25.1.0" - jest-util "^25.1.0" - lolex "^5.0.0" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.2" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.1" + istanbul-reports "^2.2.6" + jest-haste-map "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" + node-notifier "^5.4.2" + slash "^2.0.0" + source-map "^0.6.0" + string-length "^2.0.0" -"@jest/reporters@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.1.0.tgz#9178ecf136c48f125674ac328f82ddea46e482b0" - integrity sha512-ORLT7hq2acJQa8N+NKfs68ZtHFnJPxsGqmofxW7v7urVhzJvpKZG9M7FAcgh9Ee1ZbCteMrirHA3m5JfBtAaDg== +"@jest/reporters@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.0.6.tgz#91e7f2d98c002ad5df94d5b5167c1eb0b9fd5b00" + integrity sha512-TIkBt09Cb2gptji3yJXb3EE+eVltW6BjO7frO7NEfjI9vSIYoISi5R3aI3KpEDXlB1xwB+97NXIqz84qYeYsfA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^25.1.0" - "@jest/environment" "^25.1.0" - "@jest/test-result" "^25.1.0" - "@jest/transform" "^25.1.0" - "@jest/types" "^25.1.0" - chalk "^3.0.0" + "@jest/console" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" + graceful-fs "^4.2.4" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^4.0.3" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.0" - jest-haste-map "^25.1.0" - jest-resolve "^25.1.0" - jest-runtime "^25.1.0" - jest-util "^25.1.0" - jest-worker "^25.1.0" + istanbul-reports "^3.0.2" + jest-haste-map "^27.0.6" + jest-resolve "^27.0.6" + jest-util "^27.0.6" + jest-worker "^27.0.6" slash "^3.0.0" source-map "^0.6.0" - string-length "^3.1.0" + string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^4.0.1" - optionalDependencies: - node-notifier "^6.0.0" + v8-to-istanbul "^8.0.0" -"@jest/source-map@^24.9.0": +"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== @@ -1865,13 +2089,13 @@ graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/source-map@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.1.0.tgz#b012e6c469ccdbc379413f5c1b1ffb7ba7034fb0" - integrity sha512-ohf2iKT0xnLWcIUhL6U6QN+CwFWf9XnrM2a6ybL9NXxJjgYijjLSitkYHIdzkd8wFliH73qj/+epIpTiWjRtAA== +"@jest/source-map@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.0.6.tgz#be9e9b93565d49b0548b86e232092491fb60551f" + integrity sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g== dependencies: callsites "^3.0.0" - graceful-fs "^4.2.3" + graceful-fs "^4.2.4" source-map "^0.6.0" "@jest/test-result@^24.9.0": @@ -1883,26 +2107,35 @@ "@jest/types" "^24.9.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-result@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.1.0.tgz#847af2972c1df9822a8200457e64be4ff62821f7" - integrity sha512-FZzSo36h++U93vNWZ0KgvlNuZ9pnDnztvaM7P/UcTx87aPDotG18bXifkf1Ji44B7k/eIatmMzkBapnAzjkJkg== +"@jest/test-result@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.0.6.tgz#3fa42015a14e4fdede6acd042ce98c7f36627051" + integrity sha512-ja/pBOMTufjX4JLEauLxE3LQBPaI2YjGFtXexRAjt1I/MbfNlMx0sytSX3tn5hSLzQsR3Qy2rd0hc1BWojtj9w== dependencies: - "@jest/console" "^25.1.0" - "@jest/transform" "^25.1.0" - "@jest/types" "^25.1.0" + "@jest/console" "^27.0.6" + "@jest/types" "^27.0.6" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.1.0.tgz#4df47208542f0065f356fcdb80026e3c042851ab" - integrity sha512-WgZLRgVr2b4l/7ED1J1RJQBOharxS11EFhmwDqknpknE0Pm87HLZVS2Asuuw+HQdfQvm2aXL2FvvBLxOD1D0iw== +"@jest/test-sequencer@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" + integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== + dependencies: + "@jest/test-result" "^24.9.0" + jest-haste-map "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + +"@jest/test-sequencer@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.0.6.tgz#80a913ed7a1130545b1cd777ff2735dd3af5d34b" + integrity sha512-bISzNIApazYOlTHDum9PwW22NOyDa6VI31n6JucpjTVM0jD6JDgqEZ9+yn575nDdPF0+4csYDxNNW13NvFQGZA== dependencies: - "@jest/test-result" "^25.1.0" - jest-haste-map "^25.1.0" - jest-runner "^25.1.0" - jest-runtime "^25.1.0" + "@jest/test-result" "^27.0.6" + graceful-fs "^4.2.4" + jest-haste-map "^27.0.6" + jest-runtime "^27.0.6" "@jest/transform@^24.9.0": version "24.9.0" @@ -1926,24 +2159,23 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/transform@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.1.0.tgz#221f354f512b4628d88ce776d5b9e601028ea9da" - integrity sha512-4ktrQ2TPREVeM+KxB4zskAT84SnmG1vaz4S+51aTefyqn3zocZUnliLLm5Fsl85I3p/kFPN4CRp1RElIfXGegQ== +"@jest/transform@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.0.6.tgz#189ad7107413208f7600f4719f81dd2f7278cc95" + integrity sha512-rj5Dw+mtIcntAUnMlW/Vju5mr73u8yg+irnHwzgtgoeI6cCPOvUwQ0D1uQtc/APmWgvRweEb1g05pkUpxH3iCA== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^25.1.0" + "@jest/types" "^27.0.6" babel-plugin-istanbul "^6.0.0" - chalk "^3.0.0" + chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.3" - jest-haste-map "^25.1.0" - jest-regex-util "^25.1.0" - jest-util "^25.1.0" - micromatch "^4.0.2" + graceful-fs "^4.2.4" + jest-haste-map "^27.0.6" + jest-regex-util "^27.0.6" + jest-util "^27.0.6" + micromatch "^4.0.4" pirates "^4.0.1" - realpath-native "^1.1.0" slash "^3.0.0" source-map "^0.6.1" write-file-atomic "^3.0.0" @@ -1957,24 +2189,41 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jest/types@^25.1.0": - version "25.1.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395" - integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA== +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" "@types/yargs" "^15.0.0" - chalk "^3.0.0" + chalk "^4.0.0" -"@lerna/add@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.20.0.tgz#bea7edf36fc93fb72ec34cb9ba854c48d4abf309" - integrity sha512-AnH1oRIEEg/VDa3SjYq4x1/UglEAvrZuV0WssHUMN81RTZgQk3we+Mv3qZNddrZ/fBcZu2IAdN/EQ3+ie2JxKQ== +"@jest/types@^27.0.6": + version "27.0.6" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.6.tgz#9a992bc517e0c49f035938b8549719c2de40706b" + integrity sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@juggle/resize-observer@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0" + integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== + +"@lerna/add@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b" + integrity sha512-vhUXXF6SpufBE1EkNEXwz1VLW03f177G9uMOFMQkp6OJ30/PWg4Ekifuz9/3YfgB2/GH8Tu4Lk3O51P2Hskg/A== dependencies: "@evocateur/pacote" "^9.6.3" - "@lerna/bootstrap" "3.20.0" - "@lerna/command" "3.18.5" + "@lerna/bootstrap" "3.21.0" + "@lerna/command" "3.21.0" "@lerna/filter-options" "3.20.0" "@lerna/npm-conf" "3.16.0" "@lerna/validation-error" "3.13.0" @@ -1983,12 +2232,12 @@ p-map "^2.1.0" semver "^6.2.0" -"@lerna/bootstrap@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.20.0.tgz#635d71046830f208e851ab429a63da1747589e37" - integrity sha512-Wylullx3uthKE7r4izo09qeRGL20Y5yONlQEjPCfnbxCC2Elu+QcPu4RC6kqKQ7b+g7pdC3OOgcHZjngrwr5XQ== +"@lerna/bootstrap@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/bootstrap/-/bootstrap-3.21.0.tgz#bcd1b651be5b0970b20d8fae04c864548123aed6" + integrity sha512-mtNHlXpmvJn6JTu0KcuTTPl2jLsDNud0QacV/h++qsaKbhAaJr/FElNZ5s7MwZFUM3XaDmvWzHKaszeBMHIbBw== dependencies: - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/filter-options" "3.20.0" "@lerna/has-npm-version" "3.16.5" "@lerna/npm-install" "3.16.5" @@ -2012,13 +2261,13 @@ read-package-tree "^5.1.6" semver "^6.2.0" -"@lerna/changed@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.20.0.tgz#66b97ebd6c8f8d207152ee524a0791846a9097ae" - integrity sha512-+hzMFSldbRPulZ0vbKk6RD9f36gaH3Osjx34wrrZ62VB4pKmjyuS/rxVYkCA3viPLHoiIw2F8zHM5BdYoDSbjw== +"@lerna/changed@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/changed/-/changed-3.21.0.tgz#108e15f679bfe077af500f58248c634f1044ea0b" + integrity sha512-hzqoyf8MSHVjZp0gfJ7G8jaz+++mgXYiNs9iViQGA8JlN/dnWLI5sWDptEH3/B30Izo+fdVz0S0s7ydVE3pWIw== dependencies: "@lerna/collect-updates" "3.20.0" - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/listable" "3.18.5" "@lerna/output" "3.13.0" @@ -2040,12 +2289,12 @@ execa "^1.0.0" strong-log-transformer "^2.0.0" -"@lerna/clean@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.20.0.tgz#ba777e373ddeae63e57860df75d47a9e5264c5b2" - integrity sha512-9ZdYrrjQvR5wNXmHfDsfjWjp0foOkCwKe3hrckTzkAeQA1ibyz5llGwz5e1AeFrV12e2/OLajVqYfe+qdkZUgg== +"@lerna/clean@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/clean/-/clean-3.21.0.tgz#c0b46b5300cc3dae2cda3bec14b803082da3856d" + integrity sha512-b/L9l+MDgE/7oGbrav6rG8RTQvRiZLO1zTcG17zgJAAuhlsPxJExMlh2DFwJEVi2les70vMhHfST3Ue1IMMjpg== dependencies: - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/filter-options" "3.20.0" "@lerna/prompt" "3.18.5" "@lerna/pulse-till-done" "3.13.0" @@ -2085,14 +2334,14 @@ npmlog "^4.1.2" slash "^2.0.0" -"@lerna/command@3.18.5": - version "3.18.5" - resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.18.5.tgz#14c6d2454adbfd365f8027201523e6c289cd3cd9" - integrity sha512-36EnqR59yaTU4HrR1C9XDFti2jRx0BgpIUBeWn129LZZB8kAB3ov1/dJNa1KcNRKp91DncoKHLY99FZ6zTNpMQ== +"@lerna/command@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/command/-/command-3.21.0.tgz#9a2383759dc7b700dacfa8a22b2f3a6e190121f7" + integrity sha512-T2bu6R8R3KkH5YoCKdutKv123iUgUbW8efVjdGCDnCMthAQzoentOJfDeodBwn0P2OqCl3ohsiNVtSn9h78fyQ== dependencies: "@lerna/child-process" "3.16.5" "@lerna/package-graph" "3.18.5" - "@lerna/project" "3.18.0" + "@lerna/project" "3.21.0" "@lerna/validation-error" "3.13.0" "@lerna/write-log-file" "3.13.0" clone-deep "^4.0.1" @@ -2101,10 +2350,10 @@ is-ci "^2.0.0" npmlog "^4.1.2" -"@lerna/conventional-commits@3.18.5": - version "3.18.5" - resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.18.5.tgz#08efd2e5b45acfaf3f151a53a3ec7ecade58a7bc" - integrity sha512-qcvXIEJ3qSgalxXnQ7Yxp5H9Ta5TVyai6vEor6AAEHc20WiO7UIdbLDCxBtiiHMdGdpH85dTYlsoYUwsCJu3HQ== +"@lerna/conventional-commits@3.22.0": + version "3.22.0" + resolved "https://registry.yarnpkg.com/@lerna/conventional-commits/-/conventional-commits-3.22.0.tgz#2798f4881ee2ef457bdae027ab7d0bf0af6f1e09" + integrity sha512-z4ZZk1e8Mhz7+IS8NxHr64wyklHctCJyWpJKEZZPJiLFJ8yKto/x38O80R10pIzC0rr8Sy/OsjSH4bl0TbbgqA== dependencies: "@lerna/validation-error" "3.13.0" conventional-changelog-angular "^5.0.3" @@ -2127,14 +2376,14 @@ fs-extra "^8.1.0" npmlog "^4.1.2" -"@lerna/create@3.18.5": - version "3.18.5" - resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.18.5.tgz#11ac539f069248eaf7bc4c42e237784330f4fc47" - integrity sha512-cHpjocbpKmLopCuZFI7cKEM3E/QY8y+yC7VtZ4FQRSaLU8D8i2xXtXmYaP1GOlVNavji0iwoXjuNpnRMInIr2g== +"@lerna/create@3.22.0": + version "3.22.0" + resolved "https://registry.yarnpkg.com/@lerna/create/-/create-3.22.0.tgz#d6bbd037c3dc5b425fe5f6d1b817057c278f7619" + integrity sha512-MdiQQzCcB4E9fBF1TyMOaAEz9lUjIHp1Ju9H7f3lXze5JK6Fl5NYkouAvsLgY6YSIhXMY8AHW2zzXeBDY4yWkw== dependencies: "@evocateur/pacote" "^9.6.3" "@lerna/child-process" "3.16.5" - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/npm-conf" "3.16.0" "@lerna/validation-error" "3.13.0" camelcase "^5.0.0" @@ -2159,23 +2408,23 @@ "@lerna/child-process" "3.16.5" npmlog "^4.1.2" -"@lerna/diff@3.18.5": - version "3.18.5" - resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.18.5.tgz#e9e2cb882f84d5b84f0487c612137305f07accbc" - integrity sha512-u90lGs+B8DRA9Z/2xX4YaS3h9X6GbypmGV6ITzx9+1Ga12UWGTVlKaCXBgONMBjzJDzAQOK8qPTwLA57SeBLgA== +"@lerna/diff@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/diff/-/diff-3.21.0.tgz#e6df0d8b9916167ff5a49fcb02ac06424280a68d" + integrity sha512-5viTR33QV3S7O+bjruo1SaR40m7F2aUHJaDAC7fL9Ca6xji+aw1KFkpCtVlISS0G8vikUREGMJh+c/VMSc8Usw== dependencies: "@lerna/child-process" "3.16.5" - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/validation-error" "3.13.0" npmlog "^4.1.2" -"@lerna/exec@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.20.0.tgz#29f0c01aee2340eb46f90706731fef2062a49639" - integrity sha512-pS1mmC7kzV668rHLWuv31ClngqeXjeHC8kJuM+W2D6IpUVMGQHLcCTYLudFgQsuKGVpl0DGNYG+sjLhAPiiu6A== +"@lerna/exec@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/exec/-/exec-3.21.0.tgz#17f07533893cb918a17b41bcc566dc437016db26" + integrity sha512-iLvDBrIE6rpdd4GIKTY9mkXyhwsJ2RvQdB9ZU+/NhR3okXfqKc6py/24tV111jqpXTtZUW6HNydT4dMao2hi1Q== dependencies: "@lerna/child-process" "3.16.5" - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/filter-options" "3.20.0" "@lerna/profiler" "3.20.0" "@lerna/run-topologically" "3.18.5" @@ -2218,13 +2467,13 @@ ssri "^6.0.1" tar "^4.4.8" -"@lerna/github-client@3.16.5": - version "3.16.5" - resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.16.5.tgz#2eb0235c3bf7a7e5d92d73e09b3761ab21f35c2e" - integrity sha512-rHQdn8Dv/CJrO3VouOP66zAcJzrHsm+wFuZ4uGAai2At2NkgKH+tpNhQy2H1PSC0Ezj9LxvdaHYrUzULqVK5Hw== +"@lerna/github-client@3.22.0": + version "3.22.0" + resolved "https://registry.yarnpkg.com/@lerna/github-client/-/github-client-3.22.0.tgz#5d816aa4f76747ed736ae64ff962b8f15c354d95" + integrity sha512-O/GwPW+Gzr3Eb5bk+nTzTJ3uv+jh5jGho9BOqKlajXaOkMYGBELEAqV5+uARNGWZFvYAiF4PgqHb6aCUu7XdXg== dependencies: "@lerna/child-process" "3.16.5" - "@octokit/plugin-enterprise-rest" "^3.6.1" + "@octokit/plugin-enterprise-rest" "^6.0.1" "@octokit/rest" "^16.28.4" git-url-parse "^11.1.2" npmlog "^4.1.2" @@ -2251,13 +2500,13 @@ "@lerna/child-process" "3.16.5" semver "^6.2.0" -"@lerna/import@3.18.5": - version "3.18.5" - resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.18.5.tgz#a9c7d8601870729851293c10abd18b3707f7ba5e" - integrity sha512-PH0WVLEgp+ORyNKbGGwUcrueW89K3Iuk/DDCz8mFyG2IG09l/jOF0vzckEyGyz6PO5CMcz4TI1al/qnp3FrahQ== +"@lerna/import@3.22.0": + version "3.22.0" + resolved "https://registry.yarnpkg.com/@lerna/import/-/import-3.22.0.tgz#1a5f0394f38e23c4f642a123e5e1517e70d068d2" + integrity sha512-uWOlexasM5XR6tXi4YehODtH9Y3OZrFht3mGUFFT3OIl2s+V85xIGFfqFGMTipMPAGb2oF1UBLL48kR43hRsOg== dependencies: "@lerna/child-process" "3.16.5" - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/prompt" "3.18.5" "@lerna/pulse-till-done" "3.13.0" "@lerna/validation-error" "3.13.0" @@ -2265,43 +2514,43 @@ fs-extra "^8.1.0" p-map-series "^1.0.0" -"@lerna/info@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/info/-/info-3.20.0.tgz#3a5212f3029f2bc6255f9533bdf4bcb120ef329a" - integrity sha512-Rsz+KQF9mczbGUbPTrtOed1N0C+cA08Qz0eX/oI+NNjvsryZIju/o7uedG4I3P55MBiAioNrJI88fHH3eTgYug== +"@lerna/info@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/info/-/info-3.21.0.tgz#76696b676fdb0f35d48c83c63c1e32bb5e37814f" + integrity sha512-0XDqGYVBgWxUquFaIptW2bYSIu6jOs1BtkvRTWDDhw4zyEdp6q4eaMvqdSap1CG+7wM5jeLCi6z94wS0AuiuwA== dependencies: - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/output" "3.13.0" envinfo "^7.3.1" -"@lerna/init@3.18.5": - version "3.18.5" - resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.18.5.tgz#86dd0b2b3290755a96975069b5cb007f775df9f5" - integrity sha512-oCwipWrha98EcJAHm8AGd2YFFLNI7AW9AWi0/LbClj1+XY9ah+uifXIgYGfTk63LbgophDd8936ZEpHMxBsbAg== +"@lerna/init@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/init/-/init-3.21.0.tgz#1e810934dc8bf4e5386c031041881d3b4096aa5c" + integrity sha512-6CM0z+EFUkFfurwdJCR+LQQF6MqHbYDCBPyhu/d086LRf58GtYZYj49J8mKG9ktayp/TOIxL/pKKjgLD8QBPOg== dependencies: "@lerna/child-process" "3.16.5" - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" fs-extra "^8.1.0" p-map "^2.1.0" write-json-file "^3.2.0" -"@lerna/link@3.18.5": - version "3.18.5" - resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.18.5.tgz#f24347e4f0b71d54575bd37cfa1794bc8ee91b18" - integrity sha512-xTN3vktJpkT7Nqc3QkZRtHO4bT5NvuLMtKNIBDkks0HpGxC9PRyyqwOoCoh1yOGbrWIuDezhfMg3Qow+6I69IQ== +"@lerna/link@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/link/-/link-3.21.0.tgz#8be68ff0ccee104b174b5bbd606302c2f06e9d9b" + integrity sha512-tGu9GxrX7Ivs+Wl3w1+jrLi1nQ36kNI32dcOssij6bg0oZ2M2MDEFI9UF2gmoypTaN9uO5TSsjCFS7aR79HbdQ== dependencies: - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/package-graph" "3.18.5" "@lerna/symlink-dependencies" "3.17.0" p-map "^2.1.0" slash "^2.0.0" -"@lerna/list@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.20.0.tgz#7e67cc29c5cf661cfd097e8a7c2d3dcce7a81029" - integrity sha512-fXTicPrfioVnRzknyPawmYIVkzDRBaQqk9spejS1S3O1DOidkihK0xxNkr8HCVC0L22w6f92g83qWDp2BYRUbg== +"@lerna/list@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/list/-/list-3.21.0.tgz#42f76fafa56dea13b691ec8cab13832691d61da2" + integrity sha512-KehRjE83B1VaAbRRkRy6jLX1Cin8ltsrQ7FHf2bhwhRHK0S54YuA6LOoBnY/NtA8bHDX/Z+G5sMY78X30NS9tg== dependencies: - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/filter-options" "3.20.0" "@lerna/listable" "3.18.5" "@lerna/output" "3.13.0" @@ -2447,10 +2696,10 @@ npmlog "^4.1.2" upath "^1.2.0" -"@lerna/project@3.18.0": - version "3.18.0" - resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.18.0.tgz#56feee01daeb42c03cbdf0ed8a2a10cbce32f670" - integrity sha512-+LDwvdAp0BurOAWmeHE3uuticsq9hNxBI0+FMHiIai8jrygpJGahaQrBYWpwbshbQyVLeQgx3+YJdW2TbEdFWA== +"@lerna/project@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/project/-/project-3.21.0.tgz#5d784d2d10c561a00f20320bcdb040997c10502d" + integrity sha512-xT1mrpET2BF11CY32uypV2GPtPVm6Hgtha7D81GQP9iAitk9EccrdNjYGt5UBYASl4CIDXBRxwmTTVGfrCx82A== dependencies: "@lerna/package" "3.16.0" "@lerna/validation-error" "3.13.0" @@ -2473,10 +2722,10 @@ inquirer "^6.2.0" npmlog "^4.1.2" -"@lerna/publish@3.20.2": - version "3.20.2" - resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.20.2.tgz#a45d29813099b3249657ea913d0dc3f8ebc5cc2e" - integrity sha512-N7Y6PdhJ+tYQPdI1tZum8W25cDlTp4D6brvRacKZusweWexxaopbV8RprBaKexkEX/KIbncuADq7qjDBdQHzaA== +"@lerna/publish@3.22.1": + version "3.22.1" + resolved "https://registry.yarnpkg.com/@lerna/publish/-/publish-3.22.1.tgz#b4f7ce3fba1e9afb28be4a1f3d88222269ba9519" + integrity sha512-PG9CM9HUYDreb1FbJwFg90TCBQooGjj+n/pb3gw/eH5mEDq0p8wKdLFe0qkiqUkm/Ub5C8DbVFertIo0Vd0zcw== dependencies: "@evocateur/libnpmaccess" "^3.1.2" "@evocateur/npm-registry-fetch" "^4.0.0" @@ -2484,7 +2733,7 @@ "@lerna/check-working-tree" "3.16.5" "@lerna/child-process" "3.16.5" "@lerna/collect-updates" "3.20.0" - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/describe-ref" "3.16.5" "@lerna/log-packed" "3.16.0" "@lerna/npm-conf" "3.16.0" @@ -2499,7 +2748,7 @@ "@lerna/run-lifecycle" "3.16.2" "@lerna/run-topologically" "3.18.5" "@lerna/validation-error" "3.13.0" - "@lerna/version" "3.20.2" + "@lerna/version" "3.22.1" figgy-pudding "^3.5.1" fs-extra "^8.1.0" npm-package-arg "^6.1.0" @@ -2562,12 +2811,12 @@ figgy-pudding "^3.5.1" p-queue "^4.0.0" -"@lerna/run@3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.20.0.tgz#a479f7c42bdf9ebabb3a1e5a2bdebb7a8d201151" - integrity sha512-9U3AqeaCeB7KsGS9oyKNp62s9vYoULg/B4cqXTKZkc+OKL6QOEjYHYVSBcMK9lUXrMjCjDIuDSX3PnTCPxQ2Dw== +"@lerna/run@3.21.0": + version "3.21.0" + resolved "https://registry.yarnpkg.com/@lerna/run/-/run-3.21.0.tgz#2a35ec84979e4d6e42474fe148d32e5de1cac891" + integrity sha512-fJF68rT3veh+hkToFsBmUJ9MHc9yGXA7LSDvhziAojzOb0AI/jBDp6cEcDQyJ7dbnplba2Lj02IH61QUf9oW0Q== dependencies: - "@lerna/command" "3.18.5" + "@lerna/command" "3.21.0" "@lerna/filter-options" "3.20.0" "@lerna/npm-run-script" "3.16.5" "@lerna/output" "3.13.0" @@ -2612,17 +2861,17 @@ dependencies: npmlog "^4.1.2" -"@lerna/version@3.20.2": - version "3.20.2" - resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.20.2.tgz#3709141c0f537741d9bc10cb24f56897bcb30428" - integrity sha512-ckBJMaBWc+xJen0cMyCE7W67QXLLrc0ELvigPIn8p609qkfNM0L0CF803MKxjVOldJAjw84b8ucNWZLvJagP/Q== +"@lerna/version@3.22.1": + version "3.22.1" + resolved "https://registry.yarnpkg.com/@lerna/version/-/version-3.22.1.tgz#9805a9247a47ee62d6b81bd9fa5fb728b24b59e2" + integrity sha512-PSGt/K1hVqreAFoi3zjD0VEDupQ2WZVlVIwesrE5GbrL2BjXowjCsTDPqblahDUPy0hp6h7E2kG855yLTp62+g== dependencies: "@lerna/check-working-tree" "3.16.5" "@lerna/child-process" "3.16.5" "@lerna/collect-updates" "3.20.0" - "@lerna/command" "3.18.5" - "@lerna/conventional-commits" "3.18.5" - "@lerna/github-client" "3.16.5" + "@lerna/command" "3.21.0" + "@lerna/conventional-commits" "3.22.0" + "@lerna/github-client" "3.22.0" "@lerna/gitlab-client" "3.15.0" "@lerna/output" "3.13.0" "@lerna/prerelease-id-from-version" "3.16.0" @@ -2652,6 +2901,66 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" +"@microsoft/api-documenter@^7.13.7": + version "7.13.41" + resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.13.41.tgz#91d305e5ff6890a0b8dfe548172f819ef6f10a5a" + integrity sha512-ql4ALuhITlDh9uewwiUyICRUJXjF1uo4UFNHreuI7MOlsc5WSQ9IlDMSixKn9nNaf3zFOCJ/YeS0BfqhP3G27A== + dependencies: + "@microsoft/api-extractor-model" "7.13.5" + "@microsoft/tsdoc" "0.13.2" + "@rushstack/node-core-library" "3.40.0" + "@rushstack/ts-command-line" "4.9.0" + colors "~1.2.1" + js-yaml "~3.13.1" + resolve "~1.17.0" + +"@microsoft/api-extractor-model@7.13.5": + version "7.13.5" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.13.5.tgz#7836a81ba47b9a654062ed0361e4eee69afae51e" + integrity sha512-il6AebNltYo5hEtqXZw4DMvrwBPn6+F58TxwqmsLY+U+sSJNxaYn2jYksArrjErXVPR3gUgRMqD6zsdIkg+WEQ== + dependencies: + "@microsoft/tsdoc" "0.13.2" + "@microsoft/tsdoc-config" "~0.15.2" + "@rushstack/node-core-library" "3.40.0" + +"@microsoft/api-extractor@^7.18.3": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.18.7.tgz#851d2413a3c5d696f7cc914eb59de7a7882b2e8b" + integrity sha512-JhtV8LoyLuIecbgCPyZQg08G1kngIRWpai2UzwNil9mGVGYiDZVeeKx8c2phmlPcogmMDm4oQROxyuiYt5sJiw== + dependencies: + "@microsoft/api-extractor-model" "7.13.5" + "@microsoft/tsdoc" "0.13.2" + "@microsoft/tsdoc-config" "~0.15.2" + "@rushstack/node-core-library" "3.40.0" + "@rushstack/rig-package" "0.3.0" + "@rushstack/ts-command-line" "4.9.0" + colors "~1.2.1" + lodash "~4.17.15" + resolve "~1.17.0" + semver "~7.3.0" + source-map "~0.6.1" + typescript "~4.3.5" + +"@microsoft/fetch-event-source@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d" + integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== + +"@microsoft/tsdoc-config@~0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.15.2.tgz#eb353c93f3b62ab74bdc9ab6f4a82bcf80140f14" + integrity sha512-mK19b2wJHSdNf8znXSMYVShAHktVr/ib0Ck2FA3lsVBSEhSI/TfXT7DJQkAYgcztTuwazGcg58ZjYdk0hTCVrA== + dependencies: + "@microsoft/tsdoc" "0.13.2" + ajv "~6.12.6" + jju "~1.4.0" + resolve "~1.19.0" + +"@microsoft/tsdoc@0.13.2": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz#3b0efb6d3903bd49edb073696f60e90df08efb26" + integrity sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg== + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -2660,31 +2969,466 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/fs@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.0.0.tgz#589612cfad3a6ea0feafcb901d29c63fd52db09f" + integrity sha512-8ltnOpRR/oJbOp8vaGUnipOi3bqkcW+sLHFlyXIr08OGHmVJLB1Hn7QtGXbYcpVtH1gAYZTlmDXtE4YV0+AMMQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@nuxt/babel-preset-app@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/babel-preset-app/-/babel-preset-app-2.15.8.tgz#c78eb8c47c1cafec1c5aba6a52385a3ce877b968" + integrity sha512-z23bY5P7dLTmIbk0ZZ95mcEXIEER/mQCOqEp2vxnzG2nurks+vq6tNcUAXqME1Wl6aXWTXlqky5plBe7RQHzhQ== + dependencies: + "@babel/compat-data" "^7.14.0" + "@babel/core" "^7.14.0" + "@babel/helper-compilation-targets" "^7.13.16" + "@babel/helper-module-imports" "^7.13.12" + "@babel/plugin-proposal-class-properties" "^7.13.0" + "@babel/plugin-proposal-decorators" "^7.13.15" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8" + "@babel/plugin-proposal-optional-chaining" "^7.13.12" + "@babel/plugin-proposal-private-methods" "^7.13.0" + "@babel/plugin-transform-runtime" "^7.13.15" + "@babel/preset-env" "^7.14.1" + "@babel/runtime" "^7.14.0" + "@vue/babel-preset-jsx" "^1.2.4" + core-js "^2.6.5" + core-js-compat "^3.12.1" + regenerator-runtime "^0.13.7" + +"@nuxt/builder@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/builder/-/builder-2.15.8.tgz#66ead4be0a2ce6932a2b7e521cfe1621e49290e7" + integrity sha512-WVhN874LFMdgRiJqpxmeKI+vh5lhCUBVOyR9PhL1m1V/GV3fb+Dqc1BKS6XgayrWAWavPLveCJmQ/FID0puOfQ== + dependencies: + "@nuxt/devalue" "^1.2.5" + "@nuxt/utils" "2.15.8" + "@nuxt/vue-app" "2.15.8" + "@nuxt/webpack" "2.15.8" + chalk "^4.1.1" + chokidar "^3.5.1" + consola "^2.15.3" + fs-extra "^9.1.0" + glob "^7.1.7" + hash-sum "^2.0.0" + ignore "^5.1.8" + lodash "^4.17.21" + pify "^5.0.0" + serialize-javascript "^5.0.1" + upath "^2.0.1" + +"@nuxt/cli@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/cli/-/cli-2.15.8.tgz#3b946ee08c7b5b3223c8952873c65727e775ec30" + integrity sha512-KcGIILW/dAjBKea1DHsuLCG1sNzhzETShwT23DhXWO304qL8ljf4ndYKzn2RenzauGRGz7MREta80CbJCkLSHw== + dependencies: + "@nuxt/config" "2.15.8" + "@nuxt/utils" "2.15.8" + boxen "^5.0.1" + chalk "^4.1.1" + compression "^1.7.4" + connect "^3.7.0" + consola "^2.15.3" + crc "^3.8.0" + defu "^4.0.1" + destr "^1.1.0" + execa "^5.0.0" + exit "^0.1.2" + fs-extra "^9.1.0" + globby "^11.0.3" + hable "^3.0.0" + lodash "^4.17.21" + minimist "^1.2.5" + opener "1.5.2" + pretty-bytes "^5.6.0" + semver "^7.3.5" + serve-static "^1.14.1" + std-env "^2.3.0" + upath "^2.0.1" + wrap-ansi "^7.0.0" + +"@nuxt/components@^2.1.8": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@nuxt/components/-/components-2.2.1.tgz#49c4442ac5a0ef49f49ef7d9960f4376fc3e7c78" + integrity sha512-r1LHUzifvheTnJtYrMuA+apgsrEJbxcgFKIimeXKb+jl8TnPWdV3egmrxBCaDJchrtY/wmHyP47tunsft7AWwg== + dependencies: + chalk "^4.1.2" + chokidar "^3.5.2" + glob "^7.1.7" + globby "^11.0.4" + scule "^0.2.1" + semver "^7.3.5" + upath "^2.0.1" + vue-template-compiler "^2.6.14" + +"@nuxt/config@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/config/-/config-2.15.8.tgz#56cc1b052871072a26f76c6d3b69d9b53808ce52" + integrity sha512-KMQbjmUf9RVHeTZEf7zcuFnh03XKZioYhok6GOCY+leu3g5n/UhyPvLnTsgTfsLWohqoRoOm94u4A+tNYwn9VQ== + dependencies: + "@nuxt/utils" "2.15.8" + consola "^2.15.3" + defu "^4.0.1" + destr "^1.1.0" + dotenv "^9.0.2" + lodash "^4.17.21" + rc9 "^1.2.0" + std-env "^2.3.0" + ufo "^0.7.4" + +"@nuxt/core@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/core/-/core-2.15.8.tgz#443d13da9edc5c4ae47d7902f1d6504a8cce27a2" + integrity sha512-31pipWRvwHiyB5VDqffgSO7JtmHxyzgshIzuZzSinxMbVmK3BKsOwacD/51oEyELgrPlUgLqcY9dg+RURgmHGQ== + dependencies: + "@nuxt/config" "2.15.8" + "@nuxt/server" "2.15.8" + "@nuxt/utils" "2.15.8" + consola "^2.15.3" + fs-extra "^9.1.0" + hable "^3.0.0" + hash-sum "^2.0.0" + lodash "^4.17.21" + +"@nuxt/devalue@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@nuxt/devalue/-/devalue-1.2.5.tgz#8d95e3e74b3332d3eb713342c5c4d18096047d66" + integrity sha512-Tg86C7tqzvZtZli2BQVqgzZN136mZDTgauvJXagglKkP2xt5Kw3NUIiJyjX0Ww/IZy2xVmD0LN+CEPpij4dB2g== + dependencies: + consola "^2.9.0" + +"@nuxt/friendly-errors-webpack-plugin@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@nuxt/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-2.5.1.tgz#3ab815c31eb43859a239848a85481157aaf7b07e" + integrity sha512-mKN0Mbb1PjJYBzrswsyWvSEZw5Jxi0fQZPMA0ssrTmkz9lvtxtXq4luhX31OpULUvbc0jLaBu/SL0ExlxIbTlw== + dependencies: + chalk "^2.3.2" + consola "^2.6.0" + error-stack-parser "^2.0.0" + string-width "^2.0.0" + +"@nuxt/generator@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/generator/-/generator-2.15.8.tgz#d6bd4a677edf14f34d516e13bcb70d62cdd4c5b4" + integrity sha512-hreLdYbBIe3SWcP8LsMG7OlDTx2ZVucX8+f8Vrjft3Q4r8iCwLMYC1s1N5etxeHAZfS2kZiLmF92iscOdfbgMQ== + dependencies: + "@nuxt/utils" "2.15.8" + chalk "^4.1.1" + consola "^2.15.3" + defu "^4.0.1" + devalue "^2.0.1" + fs-extra "^9.1.0" + html-minifier "^4.0.0" + node-html-parser "^3.2.0" + ufo "^0.7.4" + +"@nuxt/loading-screen@^2.0.3": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@nuxt/loading-screen/-/loading-screen-2.0.4.tgz#756abd861f77c57001be4d21d47534723afb4f3a" + integrity sha512-xpEDAoRu75tLUYCkUJCIvJkWJSuwr8pqomvQ+fkXpSrkxZ/9OzlBFjAbVdOAWTMj4aV/LVQso4vcEdircKeFIQ== + dependencies: + connect "^3.7.0" + defu "^5.0.0" + get-port-please "^2.2.0" + node-res "^5.0.1" + serve-static "^1.14.1" + +"@nuxt/opencollective@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.3.2.tgz#83cb70cdb2bac5fad6f8c93529e7b11187d49c02" + integrity sha512-XG7rUdXG9fcafu9KTDIYjJSkRO38EwjlKYIb5TQ/0WDbiTUTtUtgncMscKOYzfsY86kGs05pAuMOR+3Fi0aN3A== + dependencies: + chalk "^4.1.0" + consola "^2.15.0" + node-fetch "^2.6.1" + +"@nuxt/server@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/server/-/server-2.15.8.tgz#ec733897de78f858ae0eebd174e8549f247c4e99" + integrity sha512-E4EtXudxtWQBUHMHOxFwm5DlPOkJbW+iF1+zc0dGmXLscep1KWPrlP+4nrpZj8/UKzpupamE8ZTS9I4IbnExVA== + dependencies: + "@nuxt/utils" "2.15.8" + "@nuxt/vue-renderer" "2.15.8" + "@nuxtjs/youch" "^4.2.3" + compression "^1.7.4" + connect "^3.7.0" + consola "^2.15.3" + etag "^1.8.1" + fresh "^0.5.2" + fs-extra "^9.1.0" + ip "^1.1.5" + launch-editor-middleware "^2.2.1" + on-headers "^1.0.2" + pify "^5.0.0" + serve-placeholder "^1.2.3" + serve-static "^1.14.1" + server-destroy "^1.0.1" + ufo "^0.7.4" + +"@nuxt/telemetry@^1.3.3": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@nuxt/telemetry/-/telemetry-1.3.6.tgz#a27a27b2f56a2ad4ef2c1bef82e12f0bc0dc40ac" + integrity sha512-sZpLf/rU3cvN8/alR1HpJIl3mHPA1GOg41GKdOOrtw7Gi/lCEVk4hK+lpXgYInZ2n6i1JyknpKhM9YzX2RU33w== + dependencies: + arg "^5.0.0" + chalk "^4.1.1" + ci-info "^3.1.1" + consola "^2.15.3" + create-require "^1.1.1" + defu "^5.0.0" + destr "^1.1.0" + dotenv "^9.0.2" + fs-extra "^8.1.0" + git-url-parse "^11.4.4" + inquirer "^7.3.3" + is-docker "^2.2.1" + jiti "^1.9.2" + nanoid "^3.1.23" + node-fetch "^2.6.1" + parse-git-config "^3.0.0" + rc9 "^1.2.0" + std-env "^2.3.0" + +"@nuxt/types@^0.7.9": + version "0.7.9" + resolved "https://registry.yarnpkg.com/@nuxt/types/-/types-0.7.9.tgz#a6bc6911672a812063cbb17726304cb536f58a3d" + integrity sha512-AsKj+iVV+XlCKKdpfYuLjUCUUNgL+7SzaQPp6/Z6WMqndIXegVMXn2Iu9Ixo+Qd7Y4U1Cvy4e3LYHMj0QNH+Lg== + dependencies: + "@types/autoprefixer" "^9.7.2" + "@types/babel__core" "^7.1.8" + "@types/compression" "^1.7.0" + "@types/connect" "^3.4.33" + "@types/etag" "^1.8.0" + "@types/file-loader" "^4.2.0" + "@types/html-minifier" "^3.5.3" + "@types/less" "^3.0.1" + "@types/node" "^12.12.47" + "@types/node-sass" "^4.11.1" + "@types/optimize-css-assets-webpack-plugin" "^5.0.1" + "@types/pug" "^2.0.4" + "@types/serve-static" "^1.13.4" + "@types/terser-webpack-plugin" "^2.2.1" + "@types/webpack" "^4.41.17" + "@types/webpack-bundle-analyzer" "^3.8.0" + "@types/webpack-dev-middleware" "^3.7.1" + "@types/webpack-hot-middleware" "^2.25.3" + +"@nuxt/typescript-build@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nuxt/typescript-build/-/typescript-build-2.1.0.tgz#191fe60e942ce84a01468ba6e255744e01c7c538" + integrity sha512-7TLMpfzgOckf3cBkzoPFns6Xl8FzY6MoFfm/5HUE47QeTWAdOG9ZFxMrVhHWieZHYUuV+k6byRtaRv4S/3R8zA== + dependencies: + consola "^2.15.3" + fork-ts-checker-webpack-plugin "^6.1.1" + ts-loader "^8.0.17" + typescript "~4.2" + +"@nuxt/utils@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/utils/-/utils-2.15.8.tgz#0c3594f01be63ab521583904cafd32215b719d4c" + integrity sha512-e0VBarUbPiQ4ZO1T58puoFIuXme7L5gk1QfwyxOONlp2ryE7aRyZ8X/mryuOiIeyP64c4nwSUtN7q9EUWRb7Lg== + dependencies: + consola "^2.15.3" + create-require "^1.1.1" + fs-extra "^9.1.0" + hash-sum "^2.0.0" + jiti "^1.9.2" + lodash "^4.17.21" + proper-lockfile "^4.1.2" + semver "^7.3.5" + serialize-javascript "^5.0.1" + signal-exit "^3.0.3" + ua-parser-js "^0.7.28" + ufo "^0.7.4" + +"@nuxt/vue-app@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/vue-app/-/vue-app-2.15.8.tgz#46b7ec8fc93f8d1f4cdf4f6b04134cb40ceb7c4a" + integrity sha512-FJf9FSMPsWT3BqkS37zEuPTxLKzSg2EIwp1sP8Eou25eE08qxRfe2PwTVA8HnXUPNdpz2uk/T9DlNw+JraiFRQ== + dependencies: + node-fetch "^2.6.1" + ufo "^0.7.4" + unfetch "^4.2.0" + vue "^2.6.12" + vue-client-only "^2.0.0" + vue-meta "^2.4.0" + vue-no-ssr "^1.1.1" + vue-router "^3.5.1" + vue-template-compiler "^2.6.12" + vuex "^3.6.2" + +"@nuxt/vue-renderer@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/vue-renderer/-/vue-renderer-2.15.8.tgz#1cd781de18724a98e27655e89bfe64cd5521491e" + integrity sha512-54I/k+4G6axP9XVYYdtH6M1S6T49OIkarpF6/yIJj0yi3S/2tdJ9eUyfoLZ9EbquZFDDRHBxSswTtr2l/eakPw== + dependencies: + "@nuxt/devalue" "^1.2.5" + "@nuxt/utils" "2.15.8" + consola "^2.15.3" + defu "^4.0.1" + fs-extra "^9.1.0" + lodash "^4.17.21" + lru-cache "^5.1.1" + ufo "^0.7.4" + vue "^2.6.12" + vue-meta "^2.4.0" + vue-server-renderer "^2.6.12" + +"@nuxt/webpack@2.15.8": + version "2.15.8" + resolved "https://registry.yarnpkg.com/@nuxt/webpack/-/webpack-2.15.8.tgz#6169b4b8a13ee2cdb4987df6c5a401e18c412ef1" + integrity sha512-CzJYFed23Ow/UK0+cI1FVthDre1p2qc8Q97oizG39d3/SIh3aUHjgj8c60wcR+RSxVO0FzZMXkmq02NmA7vWJg== + dependencies: + "@babel/core" "^7.14.0" + "@nuxt/babel-preset-app" "2.15.8" + "@nuxt/friendly-errors-webpack-plugin" "^2.5.1" + "@nuxt/utils" "2.15.8" + babel-loader "^8.2.2" + cache-loader "^4.1.0" + caniuse-lite "^1.0.30001228" + consola "^2.15.3" + css-loader "^4.3.0" + cssnano "^4.1.11" + eventsource-polyfill "^0.9.6" + extract-css-chunks-webpack-plugin "^4.9.0" + file-loader "^6.2.0" + glob "^7.1.7" + hard-source-webpack-plugin "^0.13.1" + hash-sum "^2.0.0" + html-webpack-plugin "^4.5.1" + lodash "^4.17.21" + memory-fs "^0.5.0" + optimize-css-assets-webpack-plugin "^5.0.4" + pify "^5.0.0" + pnp-webpack-plugin "^1.6.4" + postcss "^7.0.32" + postcss-import "^12.0.1" + postcss-import-resolver "^2.0.0" + postcss-loader "^3.0.0" + postcss-preset-env "^6.7.0" + postcss-url "^8.0.0" + semver "^7.3.5" + std-env "^2.3.0" + style-resources-loader "^1.4.1" + terser-webpack-plugin "^4.2.3" + thread-loader "^3.0.4" + time-fix-plugin "^2.0.7" + ufo "^0.7.4" + url-loader "^4.1.1" + vue-loader "^15.9.7" + vue-style-loader "^4.1.3" + vue-template-compiler "^2.6.12" + webpack "^4.46.0" + webpack-bundle-analyzer "^4.4.1" + webpack-dev-middleware "^4.2.0" + webpack-hot-middleware "^2.25.0" + webpack-node-externals "^3.0.0" + webpackbar "^4.0.0" + +"@nuxtjs/composition-api@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@nuxtjs/composition-api/-/composition-api-0.17.0.tgz#533402f82dc860cbed17d9612ac8b41333789b26" + integrity sha512-hc/1amRpu1sr4/0jkb4T0N37YgvM00jcBTQeur5ymdUd52eJExKiOu+EwbWvTPRFz8IU29x6PhdLZ5LcJT6FgA== + dependencies: + "@vue/composition-api" "1.0.0-beta.21" + defu "^3.2.2" + normalize-path "^3.0.0" + +"@nuxtjs/pwa@^3.2.2": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@nuxtjs/pwa/-/pwa-3.3.5.tgz#db7c905536ebe8a464a347b6ae3215810642c044" + integrity sha512-8tTmW8DBspWxlJwTimOHTkwfkwPpL9wIcGmy75Gcmin+c9YtX2Ehxmhgt/TLFOC9XsLAqojqynw3/Agr/9OE1w== + dependencies: + clone-deep "^4.0.1" + defu "^3.2.2" + execa "^5.0.0" + fs-extra "^9.1.0" + hasha "^5.2.2" + jimp-compact "^0.16.1" + lodash.template "^4.5.0" + serve-static "^1.14.1" + workbox-cdn "^5.1.4" + +"@nuxtjs/style-resources@^1.0.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@nuxtjs/style-resources/-/style-resources-1.2.1.tgz#9a2b6580b2ed9b06e930bee488a56b8376a263de" + integrity sha512-sOp71gCBNuGK2jchybTtVab83yB7jnSr+hw6DAKDgAGX/jrMYUyxRc9tiFxe+8YDSnqghTgQrkEkqPsfS4D4sg== + dependencies: + consola "^2.15.3" + glob-all "^3.2.1" + sass-resources-loader "^2.2.4" + +"@nuxtjs/youch@^4.2.3": + version "4.2.3" + resolved "https://registry.yarnpkg.com/@nuxtjs/youch/-/youch-4.2.3.tgz#36f8b22df5a0efaa81373109851e1d857aca6bed" + integrity sha512-XiTWdadTwtmL/IGkNqbVe+dOlT+IMvcBu7TvKI7plWhVQeBCQ9iKhk3jgvVWFyiwL2yHJDlEwOM5v9oVES5Xmw== + dependencies: + cookie "^0.3.1" + mustache "^2.3.0" + stack-trace "0.0.10" + "@octokit/auth-token@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.0.tgz#b64178975218b99e4dfe948253f0673cbbb59d9f" - integrity sha512-eoOVMjILna7FVQf96iWc3+ZtE/ZT6y8ob8ZzcqKY1ibSQCnu4O/B7pJvzMx5cyZ/RjAff6DAdEb0O0Cjcxidkg== + version "2.4.5" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" + integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== dependencies: - "@octokit/types" "^2.0.0" + "@octokit/types" "^6.0.3" -"@octokit/endpoint@^5.5.0": - version "5.5.3" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.5.3.tgz#0397d1baaca687a4c8454ba424a627699d97c978" - integrity sha512-EzKwkwcxeegYYah5ukEeAI/gYRLv2Y9U5PpIsseGSFDk+G3RbipQGBs8GuYS1TLCtQaqoO66+aQGtITPalxsNQ== +"@octokit/endpoint@^6.0.1": + version "6.0.12" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.12.tgz#3b4d47a4b0e79b1027fb8d75d4221928b2d05658" + integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== dependencies: - "@octokit/types" "^2.0.0" - is-plain-object "^3.0.0" - universal-user-agent "^5.0.0" + "@octokit/types" "^6.0.3" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" -"@octokit/plugin-enterprise-rest@^3.6.1": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-3.6.2.tgz#74de25bef21e0182b4fa03a8678cd00a4e67e561" - integrity sha512-3wF5eueS5OHQYuAEudkpN+xVeUsg8vYEMMenEzLphUZ7PRZ8OJtDcsreL3ad9zxXmBbaFWzLmFcdob5CLyZftA== +"@octokit/openapi-types@^9.5.0": + version "9.7.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-9.7.0.tgz#9897cdefd629cd88af67b8dbe2e5fb19c63426b2" + integrity sha512-TUJ16DJU8mekne6+KVcMV5g6g/rJlrnIKn7aALG9QrNpnEipFc1xjoarh0PKaAWf2Hf+HwthRKYt+9mCm5RsRg== + +"@octokit/plugin-enterprise-rest@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" + integrity sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw== "@octokit/plugin-paginate-rest@^1.1.1": version "1.1.2" @@ -2694,9 +3438,9 @@ "@octokit/types" "^2.0.1" "@octokit/plugin-request-log@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" - integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== "@octokit/plugin-rest-endpoint-methods@2.4.0": version "2.4.0" @@ -2706,7 +3450,7 @@ "@octokit/types" "^2.0.1" deprecation "^2.3.1" -"@octokit/request-error@^1.0.1", "@octokit/request-error@^1.0.2": +"@octokit/request-error@^1.0.2": version "1.2.1" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801" integrity sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA== @@ -2715,24 +3459,31 @@ deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.2.0": - version "5.3.2" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.3.2.tgz#1ca8b90a407772a1ee1ab758e7e0aced213b9883" - integrity sha512-7NPJpg19wVQy1cs2xqXjjRq/RmtSomja/VSWnptfYwuBxLdbYh2UjhGi0Wx7B1v5Iw5GKhfFDQL7jM7SSp7K2g== +"@octokit/request-error@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.1.0.tgz#9e150357831bfc788d13a4fd4b1913d60c74d677" + integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== dependencies: - "@octokit/endpoint" "^5.5.0" - "@octokit/request-error" "^1.0.1" - "@octokit/types" "^2.0.0" + "@octokit/types" "^6.0.3" deprecation "^2.0.0" - is-plain-object "^3.0.0" - node-fetch "^2.3.0" once "^1.4.0" - universal-user-agent "^5.0.0" + +"@octokit/request@^5.2.0": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.6.1.tgz#f97aff075c37ab1d427c49082fefeef0dba2d8ce" + integrity sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ== + dependencies: + "@octokit/endpoint" "^6.0.1" + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.16.1" + is-plain-object "^5.0.0" + node-fetch "^2.6.1" + universal-user-agent "^6.0.0" "@octokit/rest@^16.28.4": - version "16.43.1" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.1.tgz#3b11e7d1b1ac2bbeeb23b08a17df0b20947eda6b" - integrity sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw== + version "16.43.2" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b" + integrity sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ== dependencies: "@octokit/auth-token" "^2.4.0" "@octokit/plugin-paginate-rest" "^1.1.1" @@ -2752,36 +3503,114 @@ universal-user-agent "^4.0.0" "@octokit/types@^2.0.0", "@octokit/types@^2.0.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.3.1.tgz#40cd61c125a6161cfb3bfabc75805ac7a54213b4" - integrity sha512-rvJP1Y9A/+Cky2C3var1vsw3Lf5Rjn/0sojNl2AjCX+WbpIHYccaJ46abrZoIxMYnOToul6S9tPytUVkFI7CXQ== + version "2.16.2" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2" + integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q== dependencies: "@types/node" ">= 8" -"@opencensus/core@^0.0.19": - version "0.0.19" - resolved "https://registry.yarnpkg.com/@opencensus/core/-/core-0.0.19.tgz#058a2885179bb9f89db7fa93b366a73e51c0c8dc" - integrity sha512-Y5QXa7vggMU0+jveLcworfX9jNnztix7x1NraAV0uGkTp4y46HrFl0DnNcnNxUDvBu/cYeWRwlmhiWlr9+adOQ== +"@octokit/types@^6.0.3", "@octokit/types@^6.16.1": + version "6.25.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.25.0.tgz#c8e37e69dbe7ce55ed98ee63f75054e7e808bf1a" + integrity sha512-bNvyQKfngvAd/08COlYIN54nRgxskmejgywodizQNyiKoXmWRAjKup2/LYwm+T9V0gsKH6tuld1gM0PzmOiB4Q== dependencies: - continuation-local-storage "^3.2.1" - log-driver "^1.2.7" - semver "^6.0.0" - shimmer "^1.2.0" - uuid "^3.2.1" + "@octokit/openapi-types" "^9.5.0" -"@opencensus/propagation-stackdriver@0.0.19": - version "0.0.19" - resolved "https://registry.yarnpkg.com/@opencensus/propagation-stackdriver/-/propagation-stackdriver-0.0.19.tgz#7940b7546c0d2379cb653668377aea0e850aebce" - integrity sha512-TTL9KIOkvTpd+DT2gj3R3JP7XqOAf69ab/wzxIwpBlFqfRiIFBkOALyC/Gy4pKooAe5DemDhXZuRtIa0PgfoZQ== +"@polka/url@^1.0.0-next.19": + version "1.0.0-next.20" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.20.tgz#111b5db0f501aa89b05076fa31f0ea0e0c292cd3" + integrity sha512-88p7+M0QGxKpmnkfXjS4V26AnoC/eiqZutE8GLdaI5X12NY75bXSdTY9NkmYb2Xyk1O+MmkuO6Frmsj84V6I8Q== + +"@rollup/plugin-babel@^5.1.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" + integrity sha512-9uIC8HZOnVLrLHxayq/PTzw+uS25E14KPUBh5ktF+18Mjo5yK0ToMMx6epY0uEgkjwJw0aBW4x2horYXh8juWw== dependencies: - "@opencensus/core" "^0.0.19" - hex2dec "^1.0.1" - uuid "^3.2.1" + "@babel/helper-module-imports" "^7.10.4" + "@rollup/pluginutils" "^3.1.0" -"@samverschueren/stream-to-observable@^0.3.0": +"@rollup/plugin-graphql@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-graphql/-/plugin-graphql-1.0.0.tgz#5d5f168ec7d7c2180d75d0a67be31eb3c9379498" + integrity sha512-AxeXpotdweLUh9M8p8Cr4x0SQOCJHogntW5NmZeIQ0KT1Bj7SI88aokagn3tjBCr+uTkoeLnsSH0qE4hKydpMg== + dependencies: + "@rollup/pluginutils" "^4.0.0" + graphql-tag "^2.2.2" + +"@rollup/plugin-node-resolve@^13.0.0": + version "13.0.4" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.4.tgz#b10222f4145a019740acb7738402130d848660c0" + integrity sha512-eYq4TFy40O8hjeDs+sIxEH/jc9lyuI2k9DM557WN6rO5OpnC2qXMBNj4IKH1oHrnAazL49C5p0tgP0/VpqJ+/w== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + builtin-modules "^3.1.0" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.19.0" + +"@rollup/plugin-replace@^2.3.3": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz#a2d539314fbc77c244858faa523012825068510a" + integrity sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg== + dependencies: + "@rollup/pluginutils" "^3.1.0" + magic-string "^0.25.7" + +"@rollup/pluginutils@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + +"@rollup/pluginutils@^4.0.0", "@rollup/pluginutils@^4.1.0": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.1.tgz#1d4da86dd4eded15656a57d933fda2b9a08d47ec" + integrity sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + +"@rushstack/node-core-library@3.40.0": + version "3.40.0" + resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.40.0.tgz#2551915ea34e34ec2abb7172b9d7f4546144d9d4" + integrity sha512-P6uMPI7cqTdawLSPAG5BQrBu1MHlGRPqecp7ruIRgyukIEzkmh0QAnje4jAL/l1r3hw0qe4e+Dz5ZSnukT/Egg== + dependencies: + "@types/node" "10.17.13" + colors "~1.2.1" + fs-extra "~7.0.1" + import-lazy "~4.0.0" + jju "~1.4.0" + resolve "~1.17.0" + semver "~7.3.0" + timsort "~0.3.0" + z-schema "~3.18.3" + +"@rushstack/rig-package@0.3.0": version "0.3.0" - resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" - integrity sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg== + resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.3.0.tgz#334ad2846797861361b3445d4cc9ae9164b1885c" + integrity sha512-Lj6noF7Q4BBm1hKiBDw94e6uZvq1xlBwM/d2cBFaPqXeGdV+G6r3qaCWfRiSXK0pcHpGGpV5Tb2MdfhVcO6G/g== + dependencies: + resolve "~1.17.0" + strip-json-comments "~3.1.1" + +"@rushstack/ts-command-line@4.9.0": + version "4.9.0" + resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.9.0.tgz#781ba42cff73cae097b6d5241b6441e7cc2fe6e0" + integrity sha512-kmT8t+JfnvphISF1C5WwY56RefjwgajhSjs9J4ckvAFXZDXR6F5cvF5/RTh7fGCzIomg8esy2PHO/b52zFoZvA== + dependencies: + "@types/argparse" "1.0.38" + argparse "~1.0.9" + colors "~1.2.1" + string-argv "~0.3.1" + +"@samverschueren/stream-to-observable@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301" + integrity sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ== dependencies: any-observable "^0.3.0" @@ -2791,12 +3620,68 @@ integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== "@sinonjs/commons@^1.7.0": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1" - integrity sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ== + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== dependencies: type-detect "4.0.8" +"@sinonjs/fake-timers@^7.0.2": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5" + integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@storefront-ui/shared@0.10.2": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@storefront-ui/shared/-/shared-0.10.2.tgz#144d3a7d5508563ad97c1882fbd291abb0166349" + integrity sha512-EWwct/pMYcXhMKGEE2h7blw0XIvcVokhzdzfl5VROSajtflJkJ11hfaOxFWkB5Zgd6IdS9iwR2ayzVjunVRuEA== + +"@storefront-ui/shared@0.10.71": + version "0.10.71" + resolved "https://registry.yarnpkg.com/@storefront-ui/shared/-/shared-0.10.71.tgz#210665f54f04b62e000c97ce2ce9aa973ce95197" + integrity sha512-fLv7iAEk1YEq7yoZkvWkQNGMH6XqpqqJWjrJ5Hcr51gUvzk0vCLGebOp8iOYbSHmMwRhOkCxUVOFK9oo3WKdAQ== + +"@storefront-ui/vue@0.10.3": + version "0.10.3" + resolved "https://registry.yarnpkg.com/@storefront-ui/vue/-/vue-0.10.3.tgz#949b69a0888f85aa0f17fcdaf396972e5c210016" + integrity sha512-IPwlFfuQiNDKcN2OzwKvBWpaKKqwKr7Up2ZhnnE7j1rTqI+fI9rK97fj4ST5CquoKWnRr1or/nuy+x6d0gXtZQ== + dependencies: + "@glidejs/glide" "^3.3.0" + "@storefront-ui/shared" "0.10.2" + body-scroll-lock "^3.0.1" + cloudinary-build-url "^0.1.1" + core-js "^3.6.5" + hammerjs "^2.0.8" + leaflet "^1.5.1" + node-sass "^4.13.1" + sass-loader "^8.0.2" + simplebar-vue "^1.4.0" + vue-drag-drop "^1.1.4" + vue-fragment "^1.5.1" + vue2-leaflet "^2.5.2" + +"@storefront-ui/vue@^0.10.3": + version "0.10.71" + resolved "https://registry.yarnpkg.com/@storefront-ui/vue/-/vue-0.10.71.tgz#18c512d2234db4ba009e49e4391d5062ec05b63f" + integrity sha512-cWdml1ZPx3g1i/kyhktd0cMjQNDgnKi6h3e5+VZS21gJEjmnXyhIilc0gQffRJ852w06fhQ3KXgC3TF6mFEauQ== + dependencies: + "@cypress/vue" "^2.0.1" + "@glidejs/glide" "^3.3.0" + "@storefront-ui/shared" "0.10.71" + body-scroll-lock "^3.0.1" + cloudinary-build-url "^0.1.1" + core-js "^3.6.5" + hammerjs "^2.0.8" + leaflet "^1.5.1" + node-sass "^4.13.1" + nouislider "^15.2.0" + sass-loader "^8.0.2" + simplebar-vue "^1.4.0" + vue "^2.6.12" + vue2-leaflet "^2.5.2" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -2805,19 +3690,27 @@ defer-to-connect "^1.0.1" "@tootallnate/once@1": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.0.0.tgz#9c13c2574c92d4503b005feca8f2e16cc1611506" - integrity sha512-KYyTT/T6ALPkIRd2Ge080X/BsXvy9O0hcWTtMWkPvwAwF99+vn6Dv4GzrFT/Nn1LePr+FFDbRXXlqmsy9lw2zA== + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== +"@types/argparse@1.0.38": + version "1.0.38" + resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" + integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA== + +"@types/autoprefixer@^9.7.2": + version "9.7.2" + resolved "https://registry.yarnpkg.com/@types/autoprefixer/-/autoprefixer-9.7.2.tgz#64b3251c9675feef5a631b7dd34cfea50a8fdbcc" + integrity sha512-QX7U7YW3zX3ex6MECtWO9folTGsXeP4b8bSjTq3I1ODM+H+sFHwGKuof+T+qBcDClGlCGtDb3SVfiTVfmcxw4g== + dependencies: + "@types/browserslist" "*" + postcss "7.x.x" -"@types/babel__core@^7.1.0": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" - integrity sha512-tTnhWszAqvXnhW7m5jQU9PomXSiKXk2sFxpahXvI20SZKu9ylPi8WtIxueZ6ehDWikPT0jeFujMj3X4ZHuf3Tg== +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.0", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.8": + version "7.1.15" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024" + integrity sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -2826,111 +3719,145 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" - integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== + version "7.6.3" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" + integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" - integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.9.tgz#be82fab304b141c3eee81a4ce3b034d0eba1590a" - integrity sha512-jEFQ8L1tuvPjOI8lnpaf73oCJe+aoxL6ygqSy6c8LcW98zaC+4mzWuQIRCEvKeCOu+lbqdXcg4Uqmm1S8AP1tw== +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.14.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== dependencies: "@babel/types" "^7.3.0" "@types/body-parser@*": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" - integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c" + integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg== dependencies: "@types/connect" "*" "@types/node" "*" -"@types/color-name@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/browserslist@*": + version "4.15.0" + resolved "https://registry.yarnpkg.com/@types/browserslist/-/browserslist-4.15.0.tgz#ba0265b33003a2581df1fc5f483321a30205f2d2" + integrity sha512-h9LyKErRGZqMsHh9bd+FE8yCIal4S0DxKTOeui56VgVXqa66TKiuaIUxCAI7c1O0LjaUzOTcsMyOpO9GetozRA== + dependencies: + browserslist "*" -"@types/connect-history-api-fallback@*": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.3.tgz#4772b79b8b53185f0f4c9deab09236baf76ee3b4" - integrity sha512-7SxFCd+FLlxCfwVwbyPxbR4khL9aNikJhrorw8nUIOqeuooc9gifBuDQOJw5kzN7i6i3vLn9G8Wde/4QDihpYw== +"@types/clean-css@*": + version "4.2.5" + resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.5.tgz#69ce62cc13557c90ca40460133f672dc52ceaf89" + integrity sha512-NEzjkGGpbs9S9fgC4abuBvTpVwE3i+Acu9BBod3PUyjDVZcNsGx61b8r2PphR61QGPnn0JHVs5ey6/I4eTrkxw== + dependencies: + "@types/node" "*" + source-map "^0.6.0" + +"@types/compression@^1.7.0": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@types/compression/-/compression-1.7.1.tgz#d935a1cd6f5cd9a4dc8b0d8aadf2ee91a2975acc" + integrity sha512-d6K1bU3qIjtfB2u+A1N0WDf62LpewRjrvbqY79qlPwk2otgQ4mWB4+LzPCWTvGmcuVwo+zAroEhsNlJavRcFvg== dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" + "@types/express" "*" -"@types/connect@*": - version "3.4.33" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546" - integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A== +"@types/connect@*", "@types/connect@^3.4.33": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== dependencies: "@types/node" "*" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/cookie@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" + integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== -"@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/estree@*": + version "0.0.50" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" + integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== + +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/etag@^1.8.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@types/etag/-/etag-1.8.1.tgz#593ca8ddb43acb3db049bd0955fd64d281ab58b9" + integrity sha512-bsKkeSqN7HYyYntFRAmzcwx/dKW4Wa+KVMTInANlI72PWLQmOpZu96j0OqHZGArW4VQwCmJPteQlXaUDeOB0WQ== + dependencies: + "@types/node" "*" -"@types/express-serve-static-core@*": - version "4.17.2" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf" - integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg== +"@types/express-serve-static-core@^4.17.18": + version "4.17.24" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07" + integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA== dependencies: "@types/node" "*" + "@types/qs" "*" "@types/range-parser" "*" -"@types/express@*": - version "4.17.3" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9" - integrity sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg== +"@types/express@*", "@types/express@^4.11.1": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== dependencies: "@types/body-parser" "*" - "@types/express-serve-static-core" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" "@types/serve-static" "*" +"@types/file-loader@^4.2.0": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@types/file-loader/-/file-loader-4.2.1.tgz#f41154dc90d578002df8ae94db80c315e844720e" + integrity sha512-ImtIwnIEEMgyE7DK1JduhiDv+8WzfRWb3BPuf6RiBD1ySz05vyDRhGiKvIcuUPxUzMNBRZHN0pB+bWXSX3+t1w== + dependencies: + "@types/webpack" "^4" + "@types/glob@^7.1.1": - version "7.1.1" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" - integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + version "7.1.4" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.4.tgz#ea59e21d2ee5c517914cb4bc8e4153b99e566672" + integrity sha512-w+LsMxKyYQm347Otw+IfBXOv9UWVjpHpCDdbBMt8Kz/xbvCYNjP+0qPh91Km3iKfSRLBB0P7fAMf0KHrPu+MyA== dependencies: - "@types/events" "*" "@types/minimatch" "*" "@types/node" "*" -"@types/http-proxy-middleware@*": - version "0.19.3" - resolved "https://registry.yarnpkg.com/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz#b2eb96fbc0f9ac7250b5d9c4c53aade049497d03" - integrity sha512-lnBTx6HCOUeIJMLbI/LaL5EmdKLhczJY5oeXZpX/cXE4rRqb3RmV7VcMpiEfYkmTjipv3h7IAyIINe4plEv7cA== +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== dependencies: - "@types/connect" "*" - "@types/http-proxy" "*" "@types/node" "*" -"@types/http-proxy@*": - version "1.17.3" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.3.tgz#348e1b808ff9585423cb909e9992d89ccdbf4c14" - integrity sha512-wIPqXANye5BbORbuh74exbwNzj+UWCwWyeEFJzUQ7Fq3W2NSAy+7x7nX1fgbEypr2/TdKqpeuxLnXWgzN533/Q== +"@types/html-minifier-terser@^5.0.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" + integrity sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w== + +"@types/html-minifier@^3.5.3": + version "3.5.3" + resolved "https://registry.yarnpkg.com/@types/html-minifier/-/html-minifier-3.5.3.tgz#5276845138db2cebc54c789e0aaf87621a21e84f" + integrity sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg== dependencies: - "@types/node" "*" + "@types/clean-css" "*" + "@types/relateurl" "*" + "@types/uglify-js" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" - integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== "@types/istanbul-lib-report@*": version "3.0.0" @@ -2940,78 +3867,163 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" - integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" + integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest@^25.1.3": - version "25.1.3" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.3.tgz#9b0b5addebccfb631175870be8ba62182f1bc35a" - integrity sha512-jqargqzyJWgWAJCXX96LBGR/Ei7wQcZBvRv0PLEu9ZByMfcs23keUJrKv9FMR6YZf9YCbfqDqgmY+JUBsnqhrg== +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: - jest-diff "^25.1.0" - pretty-format "^25.1.0" + "@types/istanbul-lib-report" "*" -"@types/json-schema@^7.0.3": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" - integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== +"@types/jest@^26.0.24": + version "26.0.24" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" + integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== + dependencies: + jest-diff "^26.0.0" + pretty-format "^26.0.0" -"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" - integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== -"@types/mime@*": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" - integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/minimatch@*": +"@types/less@^3.0.1": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + resolved "https://registry.yarnpkg.com/@types/less/-/less-3.0.3.tgz#f9451dbb9548d25391107d65d6401a0cfb15db92" + integrity sha512-1YXyYH83h6We1djyoUEqTlVyQtCfJAFXELSKW2ZRtjHD4hQ82CC4lvrv5D0l0FLcKBaiPbXyi3MpMsI9ZRgKsw== + +"@types/memory-fs@*": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@types/memory-fs/-/memory-fs-0.3.3.tgz#9b63b353d4b9b190db19caa765778bede3fe5d95" + integrity sha512-rLEYzl1xODshz+Lm+YX8NYws8Xw7/qcYbQInMkotl96VpLZmUvoCfYYGxfajMSiugANV02QO5Fc+R98KKeE4gQ== + dependencies: + "@types/node" "*" + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/minimatch@*": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/minimist@^1.2.0": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" + integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== + +"@types/node-sass@^4.11.1": + version "4.11.2" + resolved "https://registry.yarnpkg.com/@types/node-sass/-/node-sass-4.11.2.tgz#ecdaa44a1ba8847bf7dea2aadbfe33a91a263514" + integrity sha512-pOFlTw/OtZda4e+yMjq6/QYuvY0RDMQ+mxXdWj7rfSyf18V8hS4SfgurO+MasAkQsv6Wt6edOGlwh5QqJml9gw== + dependencies: + "@types/node" "*" + +"@types/node@*", "@types/node@>= 8", "@types/node@>=6": + version "16.7.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.2.tgz#0465a39b5456b61a04d98bd5545f8b34be340cb7" + integrity sha512-TbG4TOx9hng8FKxaVrCisdaxKxqEwJ3zwHoCWXZ0Jw6mnvTInpaB99/2Cy4+XxpXtjNv9/TgfGSvZFyfV/t8Fw== + +"@types/node@10.17.13": + version "10.17.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" + integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== + +"@types/node@12.12.50": + version "12.12.50" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" + integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== -"@types/node@*", "@types/node@>= 8", "@types/node@>=6", "@types/node@^13.7.7": - version "13.7.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.7.tgz#1628e6461ba8cc9b53196dfeaeec7b07fa6eea99" - integrity sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg== +"@types/node@^12.12.14", "@types/node@^12.12.2", "@types/node@^12.12.47": + version "12.20.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.21.tgz#575e91f59c2e79318c2d39a48286c6954e484fd5" + integrity sha512-Qk7rOvV2A4vNgXNS88vEvbJE1NDFPCQ8AU+pNElrU2bA4yrRDef3fg3SUe+xkwyin3Bpg/Xh5JkNWTlsOcS2tA== "@types/normalize-package-data@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" - integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@types/optimize-css-assets-webpack-plugin@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#eb2c8b9d3f9f14b72639ac18a1a72998a4313083" + integrity sha512-dpRBcoyUD1uJLzyrnEl0LCwqPc/vprmOrl6fkk0dZEZe0b28rsB6t4WsbFNitFhD4oS8lRZO1+MiKzslfCTTNQ== + dependencies: + "@types/webpack" "^4" "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prettier@^2.1.5": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" + integrity sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog== + +"@types/pug@^2.0.4": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.5.tgz#69bc700934dd473c7ab97270bd2dbacefe562231" + integrity sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA== + "@types/q@^1.5.1": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" - integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== + version "1.5.5" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" + integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== "@types/range-parser@*": - version "1.2.3" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" - integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/serve-static@*": - version "1.13.3" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" - integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g== +"@types/relateurl@*": + version "0.2.29" + resolved "https://registry.yarnpkg.com/@types/relateurl/-/relateurl-0.2.29.tgz#68ccecec3d4ffdafb9c577fe764f912afc050fe6" + integrity sha512-QSvevZ+IRww2ldtfv1QskYsqVVVwCKQf1XbwtcyyoRvLIQzfyPhj/C+3+PKzSDRdiyejaiLgnq//XTkleorpLg== + +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== dependencies: - "@types/express-serve-static-core" "*" - "@types/mime" "*" + "@types/node" "*" -"@types/sizzle@2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" - integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== +"@types/serve-static@*", "@types/serve-static@^1.13.4": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/sinonjs__fake-timers@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08" + integrity sha512-E1dU4fzC9wN2QK2Cr1MLCfyHM8BoNnRFvuf45LYMPNDA+WqbNzC45S4UzPxvp1fFJ1rvSGU0bPvdd35VLmXG8g== + +"@types/sizzle@^2.3.2": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" + integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== "@types/source-list-map@*": version "0.1.2" @@ -3023,275 +4035,392 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== -"@types/strip-bom@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" - integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I= +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== -"@types/strip-json-comments@0.0.30": - version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" - integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/tapable@^1", "@types/tapable@^1.0.5": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== -"@types/tapable@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" - integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== +"@types/terser-webpack-plugin@^2.2.1": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@types/terser-webpack-plugin/-/terser-webpack-plugin-2.2.3.tgz#53ee74f6c237843ddf98ac409c250226412db85b" + integrity sha512-IqmP1OBzNyx9ZNj/3Svf0QFN+cJUrtECceVLENlXhXX4snmkgxo8n3U3wMJv1c066FXqwP+iorzCZCJhshytsw== + dependencies: + "@types/webpack" "^4" + terser "^4.3.9" "@types/uglify-js@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082" - integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ== + version "3.13.1" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.13.1.tgz#5e889e9e81e94245c75b6450600e1c5ea2878aea" + integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ== dependencies: source-map "^0.6.1" -"@types/webpack-dev-server@^3.10": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.10.0.tgz#5121ed285357b3b7463cac0d35e9b93819cf2167" - integrity sha512-ct/g/4WEEqOpXPqGX31TlZSzF1Sb7LXIViBz0oBSdH08XtNgY/hq/faXkw0yTxqvj7HJwS8dy2ggzqHposf1fQ== +"@types/webpack-bundle-analyzer@^3.8.0": + version "3.9.4" + resolved "https://registry.yarnpkg.com/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.4.tgz#029d31294a3dc36a9d655cf36de30fe9a254e72d" + integrity sha512-I00B3BPLnKArbbLMhocyvY+jEwEAnIWoyPRdWZD+4z6q91dubgLR0czoTdDwfTlmjSfuT5HcMc56b6cCxRKbbQ== dependencies: - "@types/connect-history-api-fallback" "*" - "@types/express" "*" - "@types/http-proxy-middleware" "*" - "@types/serve-static" "*" - "@types/webpack" "*" + "@types/webpack" "^4" + +"@types/webpack-dev-middleware@^3.7.1": + version "3.7.5" + resolved "https://registry.yarnpkg.com/@types/webpack-dev-middleware/-/webpack-dev-middleware-3.7.5.tgz#a5e87668d36b3e8cf2c58a9e27314b7cf0999fce" + integrity sha512-jHcCoRc2Hh6oDozoc27ibKAqrfn8nlewheoFV704EMJqheaOe7HxZZp9Q39bPh2eUDLtEJApLNQy6l20yT/4jA== + dependencies: + "@types/connect" "*" + "@types/memory-fs" "*" + "@types/webpack" "^4" + loglevel "^1.6.2" + +"@types/webpack-hot-middleware@^2.25.3": + version "2.25.5" + resolved "https://registry.yarnpkg.com/@types/webpack-hot-middleware/-/webpack-hot-middleware-2.25.5.tgz#b42c7a00fa3e508b3fb9809cd7261f6dbe01355f" + integrity sha512-/eRWWMgZteNzl17qLCRdRmtKPZuWy984b11Igz9+BAU5a99Hc2AJinnMohMPVahGRSHby4XwsnjlgIt9m0Ce3g== + dependencies: + "@types/connect" "*" + "@types/webpack" "^4" "@types/webpack-sources@*": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.6.tgz#3d21dfc2ec0ad0c77758e79362426a9ba7d7cbcb" - integrity sha512-FtAWR7wR5ocJ9+nP137DV81tveD/ZgB1sadnJ/axUGM3BUVfRPx8oQNMtv3JNfTeHx3VP7cXiyfR/jmtEsVHsQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" + integrity sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg== dependencies: "@types/node" "*" "@types/source-list-map" "*" - source-map "^0.6.1" + source-map "^0.7.3" -"@types/webpack@*", "@types/webpack@^4.41": - version "4.41.7" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.7.tgz#22be27dbd4362b01c3954ca9b021dbc9328d9511" - integrity sha512-OQG9viYwO0V1NaNV7d0n79V+n6mjOV30CwgFPIfTzwmk8DHbt+C4f2aBGdCYbo3yFyYD6sjXfqqOjwkl1j+ulA== +"@types/webpack@^4", "@types/webpack@^4.41.17", "@types/webpack@^4.41.8": + version "4.41.30" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.30.tgz#fd3db6d0d41e145a8eeeafcd3c4a7ccde9068ddc" + integrity sha512-GUHyY+pfuQ6haAfzu4S14F+R5iGRwN6b2FRNJY7U0NilmFAqbsOfK6j1HwuLBAqwRIT+pVdNDJGJ6e8rpp0KHA== dependencies: - "@types/anymatch" "*" "@types/node" "*" - "@types/tapable" "*" + "@types/tapable" "^1" "@types/uglify-js" "*" "@types/webpack-sources" "*" + anymatch "^3.0.0" source-map "^0.6.0" +"@types/websocket@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.2.tgz#d2855c6a312b7da73ed16ba6781815bf30c6187a" + integrity sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" - integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + version "20.2.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" + integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== "@types/yargs@^13.0.0": - version "13.0.8" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.8.tgz#a38c22def2f1c2068f8971acb3ea734eb3c64a99" - integrity sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA== + version "13.0.12" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.12.tgz#d895a88c703b78af0465a9de88aa92c61430b092" + integrity sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ== dependencies: "@types/yargs-parser" "*" "@types/yargs@^15.0.0": - version "15.0.4" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299" - integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg== + version "15.0.14" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" + integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== dependencies: "@types/yargs-parser" "*" -"@types/zen-observable@^0.8.0": +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + +"@types/zen-observable@0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^1.7.1-alpha.17": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz#22fed9b16ddfeb402fd7bcde56307820f6ebc49f" - integrity sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g== +"@types/zen-observable@0.8.3", "@types/zen-observable@^0.8.0": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.3.tgz#781d360c282436494b32fe7d9f7f8e64b3118aa3" + integrity sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw== + +"@typescript-eslint/eslint-plugin@^4.15.2": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.3.tgz#95cb8029a8bd8bd9c7f4ab95074a7cb2115adefa" + integrity sha512-tBgfA3K/3TsZY46ROGvoRxQr1wBkclbVqRQep97MjVHJzcRBURRY3sNFqLk0/Xr//BY5hM9H2p/kp+6qim85SA== dependencies: - "@typescript-eslint/experimental-utils" "1.13.0" - eslint-utils "^1.3.1" + "@typescript-eslint/experimental-utils" "4.29.3" + "@typescript-eslint/scope-manager" "4.29.3" + debug "^4.3.1" functional-red-black-tree "^1.0.1" - regexpp "^2.0.1" - tsutils "^3.7.0" - -"@typescript-eslint/experimental-utils@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" - integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "1.13.0" - eslint-scope "^4.0.0" - -"@typescript-eslint/experimental-utils@2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" - integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.27.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.3.tgz#52e437a689ccdef73e83c5106b34240a706f15e1" + integrity sha512-ffIvbytTVWz+3keg+Sy94FG1QeOvmV9dP2YSdLFHw/ieLXWCa3U1TYu8IRCOpMv2/SPS8XqhM1+ou1YHsdzKrg== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.29.3" + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/typescript-estree" "4.29.3" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@^4.15.2": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.29.3.tgz#2ac25535f34c0e98f50c0e6b28c679c2357d45f2" + integrity sha512-jrHOV5g2u8ROghmspKoW7pN8T/qUzk0+DITun0MELptvngtMrwUJ1tv5zMI04CYVEUsSrN4jV7AKSv+I0y0EfQ== + dependencies: + "@typescript-eslint/scope-manager" "4.29.3" + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/typescript-estree" "4.29.3" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.29.3.tgz#497dec66f3a22e459f6e306cf14021e40ec86e19" + integrity sha512-x+w8BLXO7iWPkG5mEy9bA1iFRnk36p/goVlYobVWHyDw69YmaH9q6eA+Fgl7kYHmFvWlebUTUfhtIg4zbbl8PA== + dependencies: + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/visitor-keys" "4.29.3" + +"@typescript-eslint/types@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.29.3.tgz#d7980c49aef643d0af8954c9f14f656b7fd16017" + integrity sha512-s1eV1lKNgoIYLAl1JUba8NhULmf+jOmmeFO1G5MN/RBCyyzg4TIOfIOICVNC06lor+Xmy4FypIIhFiJXOknhIg== + +"@typescript-eslint/typescript-estree@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.3.tgz#1bafad610015c4ded35c85a70b6222faad598b40" + integrity sha512-45oQJA0bxna4O5TMwz55/TpgjX1YrAPOI/rb6kPgmdnemRZx/dB0rsx+Ku8jpDvqTxcE1C/qEbVHbS3h0hflag== + dependencies: + "@typescript-eslint/types" "4.29.3" + "@typescript-eslint/visitor-keys" "4.29.3" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" -"@typescript-eslint/parser@^1.7.1-alpha.17": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355" - integrity sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ== +"@typescript-eslint/visitor-keys@4.29.3": + version "4.29.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.3.tgz#c691760a00bd86bf8320d2a90a93d86d322f1abf" + integrity sha512-MGGfJvXT4asUTeVs0Q2m+sY63UsfnA+C/FDgBKV3itLBmM9H0u+URcneePtkd0at1YELmZK6HSolCqM4Fzs6yA== dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "1.13.0" - "@typescript-eslint/typescript-estree" "1.13.0" - eslint-visitor-keys "^1.0.0" + "@typescript-eslint/types" "4.29.3" + eslint-visitor-keys "^2.0.0" -"@typescript-eslint/parser@^2.26.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.27.0.tgz#d91664335b2c46584294e42eb4ff35838c427287" - integrity sha512-HFUXZY+EdwrJXZo31DW4IS1ujQW3krzlRjBrFRrJcMDh0zCu107/nRfhk/uBasO8m0NVDbBF5WZKcIUMRO7vPg== +"@vue-storefront/commercetools-api@~1.2.0": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@vue-storefront/commercetools-api/-/commercetools-api-1.2.4.tgz#c85fb21dfc931143f1516f64aeac003a7de38a47" + integrity sha512-rHV3wXfIT85Si5po+hwrnUnP5KWtw7SbNt7akxMDWOSbkg3z+tMAhDDU9lwVOFIvsCiOdBmxty/gQFPbdFrcXw== + dependencies: + "@apollo/client" "^3.2.9" + "@commercetools/sdk-auth" "^3.0.1" + "@vue-storefront/core" "^2.3.4" + apollo-cache-inmemory "^1.6.6" + apollo-client "^2.6.10" + apollo-link "^1.2.14" + apollo-link-context "^1.0.20" + apollo-link-error "^1.1.13" + apollo-link-http "^1.5.17" + apollo-link-retry "^2.2.16" + graphql "^14.5.8" + graphql-tag "^2.10.1" + isomorphic-fetch "^2.2.1" + +"@vue-storefront/theme-utilities@^0.1.3": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@vue-storefront/theme-utilities/-/theme-utilities-0.1.5.tgz#0bab806b79694ec13714ce42f45fc771ea72480c" + integrity sha512-RGFoOIeXWSd7qww1yq+f7DjZ3w/7VNPEWyUNHYYSgicB0+qj4/aJnLVFSh9SjEDqC/fU6sgqPEJHJ785zwe/dg== dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.27.0" - "@typescript-eslint/typescript-estree" "2.27.0" - eslint-visitor-keys "^1.1.0" + chalk "^4.1.0" + chokidar "^3.4.3" + ejs "^3.1.5" + glob "^7.1.6" + rimraf "^3.0.2" -"@typescript-eslint/typescript-estree@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e" - integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw== - dependencies: - lodash.unescape "4.0.1" - semver "5.5.0" +"@vue/babel-helper-vue-jsx-merge-props@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz#31624a7a505fb14da1d58023725a4c5f270e6a81" + integrity sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA== -"@typescript-eslint/typescript-estree@2.27.0": - version "2.27.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" - integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== - dependencies: - debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^6.3.0" - tsutils "^3.17.1" +"@vue/babel-helper-vue-transform-on@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.2.tgz#9b9c691cd06fc855221a2475c3cc831d774bc7dc" + integrity sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA== -"@vue/babel-helper-vue-jsx-merge-props@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040" - integrity sha512-6tyf5Cqm4m6v7buITuwS+jHzPlIPxbFzEhXR5JGZpbrvOcp1hiQKckd305/3C7C36wFekNTQSxAtgeM0j0yoUw== +"@vue/babel-plugin-jsx@^1.0.3": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.6.tgz#184bf3541ab6efdbe5079ab8b20c19e2af100bfb" + integrity sha512-RzYsvBhzKUmY2YG6LoV+W5PnlnkInq0thh1AzCmewwctAgGN6e9UFon6ZrQQV1CO5G5PeME7MqpB+/vvGg0h4g== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + "@vue/babel-helper-vue-transform-on" "^1.0.2" + camelcase "^6.0.0" + html-tags "^3.1.0" + svg-tags "^1.0.0" -"@vue/babel-plugin-transform-vue-jsx@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.1.2.tgz#c0a3e6efc022e75e4247b448a8fc6b86f03e91c0" - integrity sha512-YfdaoSMvD1nj7+DsrwfTvTnhDXI7bsuh+Y5qWwvQXlD24uLgnsoww3qbiZvWf/EoviZMrvqkqN4CBw0W3BWUTQ== +"@vue/babel-plugin-transform-vue-jsx@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.2.1.tgz#646046c652c2f0242727f34519d917b064041ed7" + integrity sha512-HJuqwACYehQwh1fNT8f4kyzqlNMpBuUK4rSiSES5D4QsYncv5fxFsLyrxFPG2ksO7t5WP+Vgix6tt6yKClwPzA== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0" + "@vue/babel-helper-vue-jsx-merge-props" "^1.2.1" html-tags "^2.0.0" lodash.kebabcase "^4.1.1" svg-tags "^1.0.0" "@vue/babel-preset-app@^4.1.2": - version "4.2.3" - resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.2.3.tgz#608b2c9f7ca677e793833662fc727ff9137a9a35" - integrity sha512-Xlc8d9Ebgu9pNZMUxKZWVP2CctVZzfX3LAxjBDWAAIiVpdXX4IkQQCevDhgiANFzlmE3KXtiSgPGs57Sso2g7Q== + version "4.5.13" + resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.13.tgz#cb475321e4c73f7f110dac29a48c2a9cb80afeb6" + integrity sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw== dependencies: - "@babel/core" "^7.8.4" - "@babel/helper-compilation-targets" "^7.8.4" + "@babel/core" "^7.11.0" + "@babel/helper-compilation-targets" "^7.9.6" "@babel/helper-module-imports" "^7.8.3" "@babel/plugin-proposal-class-properties" "^7.8.3" "@babel/plugin-proposal-decorators" "^7.8.3" "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-jsx" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.8.3" - "@babel/preset-env" "^7.8.4" - "@babel/runtime" "^7.8.4" - "@vue/babel-preset-jsx" "^1.1.2" - babel-plugin-dynamic-import-node "^2.3.0" - core-js "^3.6.4" - core-js-compat "^3.6.4" + "@babel/plugin-transform-runtime" "^7.11.0" + "@babel/preset-env" "^7.11.0" + "@babel/runtime" "^7.11.0" + "@vue/babel-plugin-jsx" "^1.0.3" + "@vue/babel-preset-jsx" "^1.2.4" + babel-plugin-dynamic-import-node "^2.3.3" + core-js "^3.6.5" + core-js-compat "^3.6.5" + semver "^6.1.0" -"@vue/babel-preset-jsx@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.1.2.tgz#2e169eb4c204ea37ca66c2ea85a880bfc99d4f20" - integrity sha512-zDpVnFpeC9YXmvGIDSsKNdL7qCG2rA3gjywLYHPCKDT10erjxF4U+6ay9X6TW5fl4GsDlJp9bVfAVQAAVzxxvQ== +"@vue/babel-preset-jsx@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.2.4.tgz#92fea79db6f13b01e80d3a0099e2924bdcbe4e87" + integrity sha512-oRVnmN2a77bYDJzeGSt92AuHXbkIxbf/XXSE3klINnh9AXBmVS1DGa1f0d+dDYpLfsAKElMnqKTQfKn7obcL4w== + dependencies: + "@vue/babel-helper-vue-jsx-merge-props" "^1.2.1" + "@vue/babel-plugin-transform-vue-jsx" "^1.2.1" + "@vue/babel-sugar-composition-api-inject-h" "^1.2.1" + "@vue/babel-sugar-composition-api-render-instance" "^1.2.4" + "@vue/babel-sugar-functional-vue" "^1.2.2" + "@vue/babel-sugar-inject-h" "^1.2.2" + "@vue/babel-sugar-v-model" "^1.2.3" + "@vue/babel-sugar-v-on" "^1.2.3" + +"@vue/babel-sugar-composition-api-inject-h@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.2.1.tgz#05d6e0c432710e37582b2be9a6049b689b6f03eb" + integrity sha512-4B3L5Z2G+7s+9Bwbf+zPIifkFNcKth7fQwekVbnOA3cr3Pq71q71goWr97sk4/yyzH8phfe5ODVzEjX7HU7ItQ== dependencies: - "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.1.2" - "@vue/babel-sugar-functional-vue" "^1.1.2" - "@vue/babel-sugar-inject-h" "^1.1.2" - "@vue/babel-sugar-v-model" "^1.1.2" - "@vue/babel-sugar-v-on" "^1.1.2" + "@babel/plugin-syntax-jsx" "^7.2.0" -"@vue/babel-sugar-functional-vue@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.1.2.tgz#f7e24fba09e6f1ee70104560a8808057555f1a9a" - integrity sha512-YhmdJQSVEFF5ETJXzrMpj0nkCXEa39TvVxJTuVjzvP2rgKhdMmQzlJuMv/HpadhZaRVMCCF3AEjjJcK5q/cYzQ== +"@vue/babel-sugar-composition-api-render-instance@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.2.4.tgz#e4cbc6997c344fac271785ad7a29325c51d68d19" + integrity sha512-joha4PZznQMsxQYXtR3MnTgCASC9u3zt9KfBxIeuI5g2gscpTsSKRDzWQt4aqNIpx6cv8On7/m6zmmovlNsG7Q== dependencies: "@babel/plugin-syntax-jsx" "^7.2.0" -"@vue/babel-sugar-inject-h@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.1.2.tgz#8a5276b6d8e2ed16ffc8078aad94236274e6edf0" - integrity sha512-VRSENdTvD5htpnVp7i7DNuChR5rVMcORdXjvv5HVvpdKHzDZAYiLSD+GhnhxLm3/dMuk8pSzV+k28ECkiN5m8w== +"@vue/babel-sugar-functional-vue@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.2.2.tgz#267a9ac8d787c96edbf03ce3f392c49da9bd2658" + integrity sha512-JvbgGn1bjCLByIAU1VOoepHQ1vFsroSA/QkzdiSs657V79q6OwEWLCQtQnEXD/rLTA8rRit4rMOhFpbjRFm82w== dependencies: "@babel/plugin-syntax-jsx" "^7.2.0" -"@vue/babel-sugar-v-model@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.1.2.tgz#1ff6fd1b800223fc9cb1e84dceb5e52d737a8192" - integrity sha512-vLXPvNq8vDtt0u9LqFdpGM9W9IWDmCmCyJXuozlq4F4UYVleXJ2Fa+3JsnTZNJcG+pLjjfnEGHci2339Kj5sGg== +"@vue/babel-sugar-inject-h@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.2.2.tgz#d738d3c893367ec8491dcbb669b000919293e3aa" + integrity sha512-y8vTo00oRkzQTgufeotjCLPAvlhnpSkcHFEp60+LJUwygGcd5Chrpn5480AQp/thrxVm8m2ifAk0LyFel9oCnw== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@vue/babel-sugar-v-model@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.2.3.tgz#fa1f29ba51ebf0aa1a6c35fa66d539bc459a18f2" + integrity sha512-A2jxx87mySr/ulAsSSyYE8un6SIH0NWHiLaCWpodPCVOlQVODCaSpiR4+IMsmBr73haG+oeCuSvMOM+ttWUqRQ== dependencies: "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-helper-vue-jsx-merge-props" "^1.0.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.1.2" + "@vue/babel-helper-vue-jsx-merge-props" "^1.2.1" + "@vue/babel-plugin-transform-vue-jsx" "^1.2.1" camelcase "^5.0.0" html-tags "^2.0.0" svg-tags "^1.0.0" -"@vue/babel-sugar-v-on@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.1.2.tgz#b2ef99b8f2fab09fbead25aad70ef42e1cf5b13b" - integrity sha512-T8ZCwC8Jp2uRtcZ88YwZtZXe7eQrJcfRq0uTFy6ShbwYJyz5qWskRFoVsdTi9o0WEhmQXxhQUewodOSCUPVmsQ== +"@vue/babel-sugar-v-on@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.2.3.tgz#342367178586a69f392f04bfba32021d02913ada" + integrity sha512-kt12VJdz/37D3N3eglBywV8GStKNUhNrsxChXIV+o0MwVXORYuhDTHJRKPgLJRb/EY3vM2aRFQdxJBp9CLikjw== dependencies: "@babel/plugin-syntax-jsx" "^7.2.0" - "@vue/babel-plugin-transform-vue-jsx" "^1.1.2" + "@vue/babel-plugin-transform-vue-jsx" "^1.2.1" camelcase "^5.0.0" "@vue/component-compiler-utils@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.1.1.tgz#d4ef8f80292674044ad6211e336a302e4d2a6575" - integrity sha512-+lN3nsfJJDGMNz7fCpcoYIORrXo0K3OTsdr8jCM7FuqdI4+70TY6gxY6viJ2Xi1clqyPg7LpeOWwjF31vSMmUw== + version "3.2.2" + resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.2.tgz#2f7ed5feed82ff7f0284acc11d525ee7eff22460" + integrity sha512-rAYMLmgMuqJFWAOb3Awjqqv5X3Q3hVr4jH/kgrFJpiU0j3a90tnNBplqbj+snzrgZhC9W128z+dtgMifOiMfJg== dependencies: consolidate "^0.15.1" hash-sum "^1.0.2" lru-cache "^4.1.2" merge-source-map "^1.1.0" - postcss "^7.0.14" + postcss "^7.0.36" postcss-selector-parser "^6.0.2" - prettier "^1.18.2" source-map "~0.6.1" vue-template-es2015-compiler "^1.9.0" + optionalDependencies: + prettier "^1.18.2" + +"@vue/composition-api@1.0.0-beta.21": + version "1.0.0-beta.21" + resolved "https://registry.yarnpkg.com/@vue/composition-api/-/composition-api-1.0.0-beta.21.tgz#d5a3c68afc8b569dfc3eccd69998388bb7f6a16c" + integrity sha512-tgbvDpLvKQ1GrII424wsoyzPCsG0oTFf38emMq495SfLY7RmUqhVIl81pvnC5489PPrCxDkbauJHJrhlcXfbTQ== + dependencies: + tslib "^2.0.1" -"@vue/test-utils@^1.0.0-beta.19", "@vue/test-utils@^1.0.0-beta.29": - version "1.0.0-beta.31" - resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.31.tgz#580d6e45f07452e497d69807d80986e713949b73" - integrity sha512-IlhSx5hyEVnbvDZ3P98R1jNmy88QAd/y66Upn4EcvxSD5D4hwOutl3dIdfmSTSXs4b9DIMDnEVjX7t00cvOnvg== +"@vue/eslint-config-typescript@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-7.0.0.tgz#220c70c2edf7a253e739298525f4d401b8ef0038" + integrity sha512-UxUlvpSrFOoF8aQ+zX1leYiEBEm7CZmXYn/ZEM1zwSadUzpamx56RB4+Htdjisv1mX2tOjBegNUqH3kz2OL+Aw== + dependencies: + vue-eslint-parser "^7.0.0" + +"@vue/test-utils@^1.0.0-beta.27", "@vue/test-utils@^1.0.0-beta.30", "@vue/test-utils@^1.1.3": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.2.2.tgz#0242ea4e202d4853541bb167fead3f2249140ab7" + integrity sha512-P+yiAsszoy8z1TqXiVUnAZaJj0WGGz5fCxm4bOSI6Cpwy1+PNYwYxDv0ROAA/SUtOPppV+aD8tp/QWwxf8ROJw== dependencies: dom-event-types "^1.0.0" lodash "^4.17.15" pretty "^2.0.0" -"@vuepress/core@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/core/-/core-1.3.1.tgz#36db30d13e917f172f931a78b5e916c923523675" - integrity sha512-BBtM3imJUPwCTz0Fzl++ZLgf1afcsas4jo/wbVvroIdI0R6GEbXdivnisVGD48tZ10WcwvY94tlL1jWO8xV6bg== +"@vuepress/core@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/core/-/core-1.8.2.tgz#4f5bafc894691bfea4146294a582a129483daf2a" + integrity sha512-lh9BLC06k9s0wxTuWtCkiNj49fkbW87enp0XSrFZHEoyDGSGndQjZmMMErcHc5Hx7nrW1nzc33sPH1NNtJl0hw== dependencies: "@babel/core" "^7.8.4" "@vue/babel-preset-app" "^4.1.2" - "@vuepress/markdown" "^1.3.1" - "@vuepress/markdown-loader" "^1.3.1" - "@vuepress/plugin-last-updated" "^1.3.1" - "@vuepress/plugin-register-components" "^1.3.1" - "@vuepress/shared-utils" "^1.3.1" + "@vuepress/markdown" "1.8.2" + "@vuepress/markdown-loader" "1.8.2" + "@vuepress/plugin-last-updated" "1.8.2" + "@vuepress/plugin-register-components" "1.8.2" + "@vuepress/shared-utils" "1.8.2" autoprefixer "^9.5.1" babel-loader "^8.0.4" cache-loader "^3.0.0" @@ -3313,7 +4442,7 @@ url-loader "^1.0.1" vue "^2.6.10" vue-loader "^15.7.1" - vue-router "^3.1.3" + vue-router "^3.4.5" vue-server-renderer "^2.6.10" vue-template-compiler "^2.6.10" vuepress-html-webpack-plugin "^3.2.0" @@ -3324,21 +4453,21 @@ webpack-merge "^4.1.2" webpackbar "3.2.0" -"@vuepress/markdown-loader@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/markdown-loader/-/markdown-loader-1.3.1.tgz#6ee5db3ef179c42db406e3542e90cc4a3f5baf39" - integrity sha512-JxjQgSClW51hE0bCrcAqnG0yrvVURzcZwP2zbWkcCMD7vomHbvkHyPmuf6oa8Jk4S//RQUYINrzC/KrDjVuzIQ== +"@vuepress/markdown-loader@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/markdown-loader/-/markdown-loader-1.8.2.tgz#b2a58291a967f2bbe0af6e58f9542f5911879233" + integrity sha512-mWzFXikCUcAN/chpKkqZpRYKdo0312hMv8cBea2hvrJYV6y4ODB066XKvXN8JwOcxuCjxWYJkhWGr+pXq1oTtw== dependencies: - "@vuepress/markdown" "^1.3.1" + "@vuepress/markdown" "1.8.2" loader-utils "^1.1.0" lru-cache "^5.1.1" -"@vuepress/markdown@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/markdown/-/markdown-1.3.1.tgz#9bd41e9b2fa7ed7e3364bc4883ef21f13bdd936d" - integrity sha512-UJoGHR9GsFnPk+Jot8tieO4M6WJQ5CkdIWlQfbpC1+Z0ETJjlNIel23BKLNzqfo3NhLq+/i33RnzMVzkBKlVvQ== +"@vuepress/markdown@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/markdown/-/markdown-1.8.2.tgz#50ea5a1962591a436b26d1aa2b111df37eb9ea8a" + integrity sha512-zznBHVqW+iBkznF/BO/GY9RFu53khyl0Ey0PnGqvwCJpRLNan6y5EXgYumtjw2GSYn5nDTTALYxtyNBdz64PKg== dependencies: - "@vuepress/shared-utils" "^1.3.1" + "@vuepress/shared-utils" "1.8.2" markdown-it "^8.4.1" markdown-it-anchor "^5.0.2" markdown-it-chain "^1.3.0" @@ -3346,213 +4475,212 @@ markdown-it-table-of-contents "^0.4.0" prismjs "^1.13.0" -"@vuepress/plugin-active-header-links@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.3.1.tgz#b82a69b963edb3cf5361ef5a0a38642285757130" - integrity sha512-mrawXXAv2K1GrD1JNoFHxF8xX3KiphVcwvf+58GXpsyAQ5ag5X1BZG3gCA1JdNFUe3SXRh5jF6HTBuM2dc6Ovg== +"@vuepress/plugin-active-header-links@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.8.2.tgz#0cb9b29c826dd97d35357a9b09c962ef782cb793" + integrity sha512-JmXAQg8D7J8mcKe2Ue3BZ9dOCzJMJXP4Cnkkc/IrqfDg0ET0l96gYWZohCqlvRIWt4f0VPiFAO4FLYrW+hko+g== dependencies: lodash.debounce "^4.0.8" -"@vuepress/plugin-last-updated@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/plugin-last-updated/-/plugin-last-updated-1.3.1.tgz#70f241181a0d485029329a7c9d0f0629d4242b5b" - integrity sha512-n1EhhFcaWxQtbC9ICyLg8kmSULjV18wYMbHCyaKRYAvyhlPau95zbSpQfG2Nl3ZgFR6kRodK6AmZUOgho0zh/g== +"@vuepress/plugin-last-updated@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-last-updated/-/plugin-last-updated-1.8.2.tgz#7ce689f8d5050cf0213949bc2e5aa879c09ff4b1" + integrity sha512-pYIRZi52huO9b6HY3JQNPKNERCLzMHejjBRt9ekdnJ1xhLs4MmRvt37BoXjI/qzvXkYtr7nmGgnKThNBVRTZuA== dependencies: cross-spawn "^6.0.5" -"@vuepress/plugin-nprogress@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/plugin-nprogress/-/plugin-nprogress-1.3.1.tgz#4fb519f3a88cdc07439ac3a08595ab7b5ed182a6" - integrity sha512-vDBnIhTgGZbADwhaatSLsFnuj+MDDpCWQ79m9o+8RtMZO2HemedcCRNIj/ZLRJSBFjXrDdnXF5lpW4EEIeRaew== +"@vuepress/plugin-nprogress@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-nprogress/-/plugin-nprogress-1.8.2.tgz#dc6c082925420c8c59ecb7fc2d4a9401f6d4664a" + integrity sha512-3TOBee2NM3WLr1tdjDTGfrAMggjN+OlEPyKyv8FqThsVkDYhw48O3HwqlThp9KX7UbL3ExxIFBwWRFLC+kYrdw== dependencies: nprogress "^0.2.0" -"@vuepress/plugin-register-components@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/plugin-register-components/-/plugin-register-components-1.3.1.tgz#0f0683b9f69542d3e7a6481e48011d623648b56b" - integrity sha512-ae/94omRTPZkJKuVic8Rvzfnu2NtqsyVPYTL6qcnjDgxieR3L7EAYLNEvYpg1jof+QTHoEDCaVU2c63chZcfEQ== +"@vuepress/plugin-register-components@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-register-components/-/plugin-register-components-1.8.2.tgz#2fb45a68b0a1efb8822670d95c3b231a2d0eb74d" + integrity sha512-6SUq3nHFMEh9qKFnjA8QnrNxj0kLs7+Gspq1OBU8vtu0NQmSvLFZVaMV7pzT/9zN2nO5Pld5qhsUJv1g71MrEA== dependencies: - "@vuepress/shared-utils" "^1.3.1" + "@vuepress/shared-utils" "1.8.2" -"@vuepress/plugin-search@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/plugin-search/-/plugin-search-1.3.1.tgz#eee95c9c79a316a64ab046696a90a2ee1db35c34" - integrity sha512-iOIvMWUTPHrGxjDprFoGTcuI8Y8/6e6JjLO4mO6qe6qVqR1yCQ8cJzVYXIizjEHUFYJ04uZ3jF9gBV8npS+3ZQ== +"@vuepress/plugin-search@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/plugin-search/-/plugin-search-1.8.2.tgz#74b92f663acf6b4560e15dc0442a84c4e874e206" + integrity sha512-JrSJr9o0Kar14lVtZ4wfw39pplxvvMh8vDBD9oW09a+6Zi/4bySPGdcdaqdqGW+OHSiZNvG+6uyfKSBBBqF6PA== -"@vuepress/shared-utils@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/shared-utils/-/shared-utils-1.3.1.tgz#930038dfadf28f39147e6cb781e9930259995a7e" - integrity sha512-MlIAlnptjDC9+l0SJKW6BpkuwtxfKDzq4Rmag75RdyIqkkNv4EsCXZ8Y3HSuzENWFBwoD31jLC+nCZ3hULcvSg== +"@vuepress/shared-utils@1.8.2", "@vuepress/shared-utils@^1.2.0": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/shared-utils/-/shared-utils-1.8.2.tgz#5ec1601f2196aca34ad82eed7c9be2d7948f705b" + integrity sha512-6kGubc7iBDWruEBUU7yR+sQ++SOhMuvKWvWeTZJKRZedthycdzYz7QVpua0FaZSAJm5/dIt8ymU4WQvxTtZgTQ== dependencies: chalk "^2.3.2" - diacritics "^1.3.0" escape-html "^1.0.3" fs-extra "^7.0.1" globby "^9.2.0" gray-matter "^4.0.1" hash-sum "^1.0.2" semver "^6.0.0" + toml "^3.0.0" upath "^1.1.0" -"@vuepress/theme-default@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@vuepress/theme-default/-/theme-default-1.3.1.tgz#ec701a2c9d2de542400c42f627d2009921f1b424" - integrity sha512-CihkB6/+5vfgeTI5HRDs4+QgTkIN4/K54OpQCGLW51OinXuz4rjMVQW2uSlSqSeKEr+MERHa+Jc5deIpA0opoA== +"@vuepress/theme-default@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vuepress/theme-default/-/theme-default-1.8.2.tgz#7f474036c752c1f9801b83f68f5c70c092b182b4" + integrity sha512-rE7M1rs3n2xp4a/GrweO8EGwqFn3EA5gnFWdVmVIHyr7C1nix+EqjpPQF1SVWNnIrDdQuCw38PqS+oND1K2vYw== dependencies: - "@vuepress/plugin-active-header-links" "^1.3.1" - "@vuepress/plugin-nprogress" "^1.3.1" - "@vuepress/plugin-search" "^1.3.1" + "@vuepress/plugin-active-header-links" "1.8.2" + "@vuepress/plugin-nprogress" "1.8.2" + "@vuepress/plugin-search" "1.8.2" docsearch.js "^2.5.2" lodash "^4.17.15" - stylus "^0.54.5" + stylus "^0.54.8" stylus-loader "^3.0.2" vuepress-plugin-container "^2.0.2" vuepress-plugin-smooth-scroll "^0.0.3" -"@webassemblyjs/ast@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" - integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== - dependencies: - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" - -"@webassemblyjs/floating-point-hex-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" - integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== - -"@webassemblyjs/helper-api-error@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" - integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== - -"@webassemblyjs/helper-buffer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" - integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== - -"@webassemblyjs/helper-code-frame@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" - integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== - dependencies: - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/helper-fsm@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" - integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== - -"@webassemblyjs/helper-module-context@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" - integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== - dependencies: - "@webassemblyjs/ast" "1.8.5" - mamacro "^0.0.3" - -"@webassemblyjs/helper-wasm-bytecode@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" - integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== - -"@webassemblyjs/helper-wasm-section@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" - integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - -"@webassemblyjs/ieee754@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" - integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" - integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" - integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== - -"@webassemblyjs/wasm-edit@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" - integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/helper-wasm-section" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-opt" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/wasm-gen@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" - integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wasm-opt@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" - integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - -"@webassemblyjs/wasm-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" - integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wast-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" - integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/floating-point-hex-parser" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-code-frame" "1.8.5" - "@webassemblyjs/helper-fsm" "1.8.5" +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" - integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" "@wry/context@^0.4.0": @@ -3563,13 +4691,34 @@ "@types/node" ">=6" tslib "^1.9.3" +"@wry/context@^0.6.0": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.1.tgz#c3c29c0ad622adb00f6a53303c4f965ee06ebeb2" + integrity sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw== + dependencies: + tslib "^2.3.0" + "@wry/equality@^0.1.2": - version "0.1.9" - resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.9.tgz#b13e18b7a8053c6858aa6c85b54911fb31e3a909" - integrity sha512-mB6ceGjpMGz1ZTza8HYnrPGos2mC6So4NhS1PtZ8s4Qt0K7fBiIGhpSxUbQmhwcSWE3no+bYxmI2OL6KuXYmoQ== + version "0.1.11" + resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790" + integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA== dependencies: tslib "^1.9.3" +"@wry/equality@^0.5.0": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.2.tgz#72c8a7a7d884dff30b612f4f8464eba26c080e73" + integrity sha512-oVMxbUXL48EV/C0/M7gLVsoK6qRHPS85x8zECofEZOVvxGmIPLA9o5Z27cc2PoAyZz1S2VoM2A7FLAnpfGlneA== + dependencies: + tslib "^2.3.0" + +"@wry/trie@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.3.1.tgz#2279b790f15032f8bcea7fc944d27988e5b3b139" + integrity sha512-WwB53ikYudh9pIorgxrkHKrQZcCqNM/Q/bDzZBffEaGUKGuHrRb3zZUT9Sh2qw9yogC7SsdRmQ1ER0pqvd3bfw== + dependencies: + tslib "^2.3.0" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -3589,7 +4738,7 @@ mkdirp-promise "^5.0.1" mz "^2.5.0" -JSONStream@^1.0.4, JSONStream@^1.3.4: +JSONStream@^1.0.3, JSONStream@^1.0.4, JSONStream@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== @@ -3597,17 +4746,17 @@ JSONStream@^1.0.4, JSONStream@^1.3.4: jsonparse "^1.2.0" through ">=2.2.7 <3" -abab@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" - integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== +abab@^2.0.0, abab@^2.0.3, abab@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abort-controller@^3.0.0: +abort-controller@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== @@ -3622,7 +4771,7 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-globals@^4.3.2: +acorn-globals@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== @@ -3630,42 +4779,62 @@ acorn-globals@^4.3.2: acorn "^6.0.1" acorn-walk "^6.0.1" -acorn-jsx@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== dependencies: - acorn "^3.0.4" + acorn "^7.1.1" + acorn-walk "^7.1.1" -acorn-jsx@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" - integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== +acorn-jsx@^5.2.0, 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-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2, acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" -acorn-walk@^6.0.1, acorn-walk@^6.1.1: +acorn-walk@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== -acorn@^3.0.4: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= +acorn-walk@^7.0.0, acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^5.5.0: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== +acorn-walk@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.1.1.tgz#3ddab7f84e4a7e2313f6c414c5b7dac85f4e3ebc" + integrity sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w== -acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.7, acorn@^6.2.1: - version "6.4.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" - integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== +acorn@^5.5.3: + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== -acorn@^7.1.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" - integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== +acorn@^6.0.1, acorn@^6.4.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +acorn@^7.0.0, acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.0.4, acorn@^8.2.4: + version "8.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" + integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== agent-base@4, agent-base@^4.3.0: version "4.3.0" @@ -3675,9 +4844,9 @@ agent-base@4, agent-base@^4.3.0: es6-promisify "^5.0.0" agent-base@6: - version "6.0.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.0.tgz#5d0101f19bbfaed39980b22ae866de153b93f09a" - integrity sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw== + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== dependencies: debug "4" @@ -3700,42 +4869,25 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== -ajv-keywords@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" - integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= - -ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" - integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== - -ajv@^5.2.3, ajv@^5.3.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.12.2: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@~6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3745,6 +4897,16 @@ ajv@^6.12.2: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.1: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + algoliasearch@^3.24.5: version "3.35.1" resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-3.35.1.tgz#297d15f534a3507cab2f5dfb996019cac7568f0c" @@ -3776,25 +4938,6 @@ amdefine@>=0.0.4: resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= -amp-message@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/amp-message/-/amp-message-0.1.2.tgz#a78f1c98995087ad36192a41298e4db49e3dfc45" - integrity sha1-p48cmJlQh602GSpBKY5NtJ49/EU= - dependencies: - amp "0.3.1" - -amp@0.3.1, amp@~0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/amp/-/amp-0.3.1.tgz#6adf8d58a74f361e82c1fa8d389c079e139fc47d" - integrity sha1-at+NWKdPNh6CwfqNOJwHnhOfxH0= - -ansi-align@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" - integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38= - dependencies: - string-width "^2.0.0" - ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" @@ -3807,22 +4950,22 @@ 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-escapes@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" - integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= +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.0.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-escapes@^4.1.0, ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== +ansi-escapes@^4.1.0, ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: - type-fest "^0.11.0" + type-fest "^0.21.3" ansi-html@0.0.7: version "0.0.7" @@ -3839,7 +4982,7 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.1.0: +ansi-regex@^4.0.0, ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== @@ -3849,7 +4992,7 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-styles@^2.1.0, ansi-styles@^2.2.1: +ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= @@ -3862,13 +5005,17 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: color-convert "^1.9.0" ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" - integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: - "@types/color-name" "^1.1.1" color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + any-observable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" @@ -3887,90 +5034,128 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.3, anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== +anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" picomatch "^2.0.4" -apollo-cache-inmemory@^1.6.5: - version "1.6.5" - resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz#2ccaa3827686f6ed7fb634203dbf2b8d7015856a" - integrity sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA== +apollo-cache-inmemory@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz#56d1f2a463a6b9db32e9fa990af16d2a008206fd" + integrity sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A== dependencies: - apollo-cache "^1.3.4" - apollo-utilities "^1.3.3" + apollo-cache "^1.3.5" + apollo-utilities "^1.3.4" optimism "^0.10.0" ts-invariant "^0.4.0" tslib "^1.10.0" -apollo-cache@1.3.4, apollo-cache@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.4.tgz#0c9f63c793e1cd6e34c450f7668e77aff58c9a42" - integrity sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA== +apollo-cache@1.3.5, apollo-cache@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.5.tgz#9dbebfc8dbe8fe7f97ba568a224bca2c5d81f461" + integrity sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA== dependencies: - apollo-utilities "^1.3.3" + apollo-utilities "^1.3.4" tslib "^1.10.0" -apollo-client@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.8.tgz#01cebc18692abf90c6b3806414e081696b0fa537" - integrity sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw== +apollo-client@^2.6.10: + version "2.6.10" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.10.tgz#86637047b51d940c8eaa771a4ce1b02df16bea6a" + integrity sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA== dependencies: "@types/zen-observable" "^0.8.0" - apollo-cache "1.3.4" + apollo-cache "1.3.5" apollo-link "^1.0.0" - apollo-utilities "1.3.3" + apollo-utilities "1.3.4" symbol-observable "^1.0.2" ts-invariant "^0.4.0" tslib "^1.10.0" zen-observable "^0.8.0" -apollo-link-http-common@^0.2.15: - version "0.2.15" - resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.15.tgz#304e67705122bf69a9abaded4351b10bc5efd6d9" - integrity sha512-+Heey4S2IPsPyTf8Ag3PugUupASJMW894iVps6hXbvwtg1aHSNMXUYO5VG7iRHkPzqpuzT4HMBanCTXPjtGzxg== +apollo-link-context@^1.0.20: + version "1.0.20" + resolved "https://registry.yarnpkg.com/apollo-link-context/-/apollo-link-context-1.0.20.tgz#1939ac5dc65d6dff0c855ee53521150053c24676" + integrity sha512-MLLPYvhzNb8AglNsk2NcL9AvhO/Vc9hn2ZZuegbhRHGet3oGr0YH9s30NS9+ieoM0sGT11p7oZ6oAILM/kiRBA== + dependencies: + apollo-link "^1.2.14" + tslib "^1.9.3" + +apollo-link-error@^1.1.13: + version "1.1.13" + resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.13.tgz#c1a1bb876ffe380802c8df0506a32c33aad284cd" + integrity sha512-jAZOOahJU6bwSqb2ZyskEK1XdgUY9nkmeclCrW7Gddh1uasHVqmoYc4CKdb0/H0Y1J9lvaXKle2Wsw/Zx1AyUg== + dependencies: + apollo-link "^1.2.14" + apollo-link-http-common "^0.2.16" + tslib "^1.9.3" + +apollo-link-http-common@^0.2.16: + version "0.2.16" + resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz#756749dafc732792c8ca0923f9a40564b7c59ecc" + integrity sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg== dependencies: - apollo-link "^1.2.13" + apollo-link "^1.2.14" ts-invariant "^0.4.0" tslib "^1.9.3" -apollo-link-http@^1.5.16: - version "1.5.16" - resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.16.tgz#44fe760bcc2803b8a7f57fc9269173afb00f3814" - integrity sha512-IA3xA/OcrOzINRZEECI6IdhRp/Twom5X5L9jMehfzEo2AXdeRwAMlH5LuvTZHgKD8V1MBnXdM6YXawXkTDSmJw== +apollo-link-http@^1.5.17: + version "1.5.17" + resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.17.tgz#499e9f1711bf694497f02c51af12d82de5d8d8ba" + integrity sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg== dependencies: - apollo-link "^1.2.13" - apollo-link-http-common "^0.2.15" + apollo-link "^1.2.14" + apollo-link-http-common "^0.2.16" tslib "^1.9.3" -apollo-link@^1.0.0, apollo-link@^1.2.13: - version "1.2.13" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.13.tgz#dff00fbf19dfcd90fddbc14b6a3f9a771acac6c4" - integrity sha512-+iBMcYeevMm1JpYgwDEIDt/y0BB7VWyvlm/7x+TIPNLHCTCMgcEgDuW5kH86iQZWo0I7mNwQiTOz+/3ShPFmBw== +apollo-link-retry@^2.2.16: + version "2.2.16" + resolved "https://registry.yarnpkg.com/apollo-link-retry/-/apollo-link-retry-2.2.16.tgz#745ff51e60a7a68b34c8d382832856c43a9c306c" + integrity sha512-7F9+meFAz4dw5gtgtLsRFqJW6QzNOhTzt5R5Hsy+yFhkTW9LddgYO7gxN9n7RN/7Ouosh3TcpUkdHs2laC+0sA== + dependencies: + "@types/zen-observable" "0.8.0" + apollo-link "^1.2.14" + tslib "^1.9.3" + +apollo-link-schema@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/apollo-link-schema/-/apollo-link-schema-1.2.5.tgz#a1aad9adf7eefc9252797b0a67a4d41f215237db" + integrity sha512-7XUS8fOsObJt9rzp8CUuZ/a9TNUBoChWwEDmdVmYxTlzgGcyUXxkLXkMS9CHUb0cx04jiiWjWQc41C4iakSmzA== + dependencies: + apollo-link "^1.2.14" + tslib "^1.9.3" + +apollo-link@^1.0.0, apollo-link@^1.2.14: + version "1.2.14" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9" + integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg== dependencies: apollo-utilities "^1.3.0" ts-invariant "^0.4.0" tslib "^1.9.3" - zen-observable-ts "^0.8.20" + zen-observable-ts "^0.8.21" -apollo-utilities@1.3.3, apollo-utilities@^1.3.0, apollo-utilities@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.3.tgz#f1854715a7be80cd810bc3ac95df085815c0787c" - integrity sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw== +apollo-upload-client@14.1.3: + version "14.1.3" + resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-14.1.3.tgz#91f39011897bd08e99c0de0164e77ad2f3402247" + integrity sha512-X2T+7pHk5lcaaWnvP9h2tuAAMCzOW6/9juedQ0ZuGp3Ufl81BpDISlCs0o6u29wBV0RRT/QpMU2gbP+3FCfVpQ== + dependencies: + "@apollo/client" "^3.2.5" + "@babel/runtime" "^7.12.5" + extract-files "^9.0.0" + +apollo-utilities@1.3.4, apollo-utilities@^1.3.0, apollo-utilities@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.4.tgz#6129e438e8be201b6c55b0f13ce49d2c7175c9cf" + integrity sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig== dependencies: "@wry/equality" "^0.1.2" fast-json-stable-stringify "^2.0.0" ts-invariant "^0.4.0" tslib "^1.10.0" -app-root-path@^2.0.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== - aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -3981,10 +5166,10 @@ aproba@^2.0.0: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== -arch@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" - integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== +arch@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== are-we-there-yet@~1.1.2: version "1.1.5" @@ -3999,7 +5184,12 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: +arg@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" + integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA== + +argparse@^1.0.7, argparse@~1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -4051,13 +5241,15 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" - integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== +array-includes@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" is-string "^1.0.5" array-union@^1.0.1, array-union@^1.0.2: @@ -4067,6 +5259,11 @@ array-union@^1.0.1, array-union@^1.0.2: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -4077,37 +5274,34 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.1: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" - integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== +array.prototype.flat@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.1" arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -arrify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - -asap@^2.0.0: +asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" asn1@~0.2.3: version "0.2.4" @@ -4121,7 +5315,7 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert@^1.1.1: +assert@^1.1.1, assert@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== @@ -4139,6 +5333,11 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +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" @@ -4154,37 +5353,27 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -async-listener@^0.6.0: - version "0.6.10" - resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" - integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== - dependencies: - semver "^5.3.0" - shimmer "^1.1.0" - -async@1.5: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= +async-lock@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.3.0.tgz#0fba111bea8b9693020857eba4f9adca173df3e5" + integrity sha512-8A7SkiisnEgME2zEedtDYPxUPzdv3x//E7n5IFktPAtMYSEAV7eNJF0rMwrVyUFj6d/8rgajLantbjcNRQYXIg== -async@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" - integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== - dependencies: - lodash "^4.17.10" +async@0.9.x: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= -async@^2.5, async@^2.6, async@^2.6.2: +async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== dependencies: lodash "^4.17.14" -async@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" - integrity sha1-+PwEyjoTeErenhZBr5hXjPvWR6k= +async@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" + integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== asynckit@^0.4.0: version "0.4.0" @@ -4213,30 +5402,23 @@ autocomplete.js@0.36.0: dependencies: immediate "^3.2.3" -autoprefixer@^8.6.2: - version "8.6.5" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-8.6.5.tgz#343f3d193ed568b3208e00117a1b96eb691d4ee9" - integrity sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig== +autoprefixer@^9.5.1, autoprefixer@^9.6.1: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== dependencies: - browserslist "^3.2.8" - caniuse-lite "^1.0.30000864" + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^6.0.23" - postcss-value-parser "^3.2.3" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" -autoprefixer@^9.5.1: - version "9.7.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.4.tgz#f8bf3e06707d047f0641d87aee8cfb174b2a5378" - integrity sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g== - dependencies: - browserslist "^4.8.3" - caniuse-lite "^1.0.30001020" - chalk "^2.4.2" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.26" - postcss-value-parser "^4.0.2" +awesome-phonenumber@^2.51.2: + version "2.58.0" + resolved "https://registry.yarnpkg.com/awesome-phonenumber/-/awesome-phonenumber-2.58.0.tgz#eaa788910b567abd6c1283025bcf491ac5b55d85" + integrity sha512-rsbIn7Htq/QqUfJ7E53oGiGnLca5SUJEshg8zG5h9WK+fTxoGA12/NDKC5eCvkK2eaP8gR/RVA1yuf0Arib7vg== aws-sign2@~0.7.0: version "0.7.0" @@ -4244,208 +5426,18 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" - integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= +axios@0.21.1: + version "0.21.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" + integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@^6.26.0: - version "6.26.3" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" - integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.1" - debug "^2.6.9" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.8" - slash "^1.0.0" - source-map "^0.5.7" + follow-redirects "^1.10.0" -babel-core@^7.0.0-bridge.0: - version "7.0.0-bridge.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" - integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== - -babel-eslint@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-9.0.0.tgz#7d9445f81ed9f60aff38115f838970df9f2b6220" - integrity sha512-itv1MwE3TMbY0QtNfeL7wzak1mV47Uy+n6HtSOO4Xd7rvmO+tsGQSgyOEEgo6Y2vHZKZphaoelNeSVj4vkLA1g== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.0.0" - "@babel/traverse" "^7.0.0" - "@babel/types" "^7.0.0" - eslint-scope "3.7.1" - eslint-visitor-keys "^1.0.0" - -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-helper-bindify-decorators@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330" - integrity sha1-FMGeXxQte0fxmlJDHlKxzLxAozA= - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" - integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= - dependencies: - babel-helper-explode-assignable-expression "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-explode-assignable-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" - integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo= - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-explode-class@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb" - integrity sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes= - dependencies: - babel-helper-bindify-decorators "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-regex@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" - integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-remap-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-jest@^24.1.0: +babel-jest@^24.1.0, babel-jest@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== @@ -4458,49 +5450,36 @@ babel-jest@^24.1.0: chalk "^2.4.2" slash "^2.0.0" -babel-jest@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.1.0.tgz#206093ac380a4b78c4404a05b3277391278f80fb" - integrity sha512-tz0VxUhhOE2y+g8R2oFrO/2VtVjA1lkJeavlhExuRBg3LdNJY9gwQ+Vcvqt9+cqy71MCTJhewvTB7Qtnnr9SWg== +babel-jest@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.0.6.tgz#e99c6e0577da2655118e3608b68761a5a69bd0d8" + integrity sha512-iTJyYLNc4wRofASmofpOc5NK9QunwMk+TLFgGXsTFS8uEqmd8wdI7sga0FPe2oVH3b5Agt/EAK1QjPEuKL8VfA== dependencies: - "@jest/transform" "^25.1.0" - "@jest/types" "^25.1.0" - "@types/babel__core" "^7.1.0" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^25.1.0" - chalk "^3.0.0" + babel-preset-jest "^27.0.6" + chalk "^4.0.0" + graceful-fs "^4.2.4" slash "^3.0.0" -babel-loader@^8.0.4, babel-loader@^8.0.6: - version "8.0.6" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb" - integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw== - dependencies: - find-cache-dir "^2.0.0" - loader-utils "^1.0.2" - mkdirp "^0.5.1" - pify "^4.0.1" - -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-check-es2015-constants@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" - integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= +babel-loader@^8.0.4, babel-loader@^8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" + integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== dependencies: - babel-runtime "^6.22.0" + find-cache-dir "^3.3.1" + loader-utils "^1.4.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" -babel-plugin-dynamic-import-node@^2.2.0, babel-plugin-dynamic-import-node@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" - integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ== - dependencies: - object.assign "^4.1.0" +babel-plugin-add-module-exports@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-1.0.2.tgz#96cd610d089af664f016467fc4567c099cce2d9c" + integrity sha512-4paN7RivvU3Rzju1vGSHWPjO8Y0rI6droWvSFKI6dvEQ4mvoV0zGojnlzVRfI6N8zISo6VERXt3coIuVmzuvNg== + optionalDependencies: + chokidar "^2.0.4" babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" @@ -4537,349 +5516,95 @@ babel-plugin-jest-hoist@^24.9.0: dependencies: "@types/babel__traverse" "^7.0.6" -babel-plugin-jest-hoist@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.1.0.tgz#fb62d7b3b53eb36c97d1bc7fec2072f9bd115981" - integrity sha512-oIsopO41vW4YFZ9yNYoLQATnnN46lp+MZ6H4VvPKFkcc2/fkl3CfE/NZZSmnEIEsJRmJAgkVEK0R7Zbl50CpTw== +babel-plugin-jest-hoist@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.6.tgz#f7c6b3d764af21cb4a2a1ab6870117dbde15b456" + integrity sha512-CewFeM9Vv2gM7Yr9n5eyyLVPRSiBnk6lKZRjgwYnGKSl9M14TMn2vkN02wTF04OGuSDLEzlWiMzvjXuW9mB6Gw== dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= - -babel-plugin-syntax-async-generators@^6.5.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" - integrity sha1-a8lj67FuzLrmuStZbrfzXDQqi5o= - -babel-plugin-syntax-class-properties@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" - integrity sha1-1+sjt5oxf4VDlixQW4J8fWysJ94= - -babel-plugin-syntax-decorators@^6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" - integrity sha1-MSVjtNvePMgGzuPkFszurd0RrAs= - -babel-plugin-syntax-dynamic-import@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" - integrity sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo= - -babel-plugin-syntax-exponentiation-operator@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" - integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= - -babel-plugin-syntax-object-rest-spread@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" - integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= - -babel-plugin-syntax-trailing-function-commas@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= - -babel-plugin-transform-async-generator-functions@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db" - integrity sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds= - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-generators "^6.5.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" - integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-class-properties@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" - integrity sha1-anl2PqYdM9NvN7YRqp3vgagbRqw= - dependencies: - babel-helper-function-name "^6.24.1" - babel-plugin-syntax-class-properties "^6.8.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-decorators@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d" - integrity sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0= - dependencies: - babel-helper-explode-class "^6.24.1" - babel-plugin-syntax-decorators "^6.13.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-arrow-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.23.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-duplicate-keys@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" - integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-for-of@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" - integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= - dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1, babel-plugin-transform-es2015-modules-commonjs@^6.26.0: - version "6.26.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" - integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-umd@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" - integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= - dependencies: - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-object-super@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-sticky-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" - integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-template-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-typeof-symbol@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" - integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-unicode-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" - integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - regexpu-core "^2.0.0" - -babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" - integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= - dependencies: - babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" - babel-plugin-syntax-exponentiation-operator "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-object-rest-spread@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" - integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= - dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" - babel-runtime "^6.26.0" - -babel-plugin-transform-regenerator@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" - integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= - dependencies: - regenerator-transform "^0.10.0" +babel-plugin-polyfill-corejs2@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz#e9124785e6fd94f94b618a7954e5693053bf5327" + integrity sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ== + dependencies: + "@babel/compat-data" "^7.13.11" + "@babel/helper-define-polyfill-provider" "^0.2.2" + semver "^6.1.1" -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= +babel-plugin-polyfill-corejs3@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.4.tgz#68cb81316b0e8d9d721a92e0009ec6ecd4cd2ca9" + integrity sha512-z3HnJE5TY/j4EFEa/qpQMSbcUJZ5JQi+3UFjXzn6pQCmIKc5Ug5j98SuYyH+m4xQnvKlMDIW4plLfgyVnd0IcQ== dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" + "@babel/helper-define-polyfill-provider" "^0.2.2" + core-js-compat "^3.14.0" -babel-preset-env@^1.6.x: - version "1.7.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" - integrity sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg== - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^3.2.6" - invariant "^2.2.2" - semver "^5.3.0" +babel-plugin-polyfill-regenerator@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz#b310c8d642acada348c1fa3b3e6ce0e851bee077" + integrity sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.2.2" + +babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: + version "7.0.0-beta.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" + integrity sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ== + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-fbjs@^3.3.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz#38a14e5a7a3b285a3f3a86552d650dca5cf6111c" + integrity sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-syntax-class-properties" "^7.0.0" + "@babel/plugin-syntax-flow" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-block-scoped-functions" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-member-expression-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-property-literals" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" babel-preset-jest@^24.9.0: version "24.9.0" @@ -4889,107 +5614,33 @@ babel-preset-jest@^24.9.0: "@babel/plugin-syntax-object-rest-spread" "^7.0.0" babel-plugin-jest-hoist "^24.9.0" -babel-preset-jest@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.1.0.tgz#d0aebfebb2177a21cde710996fce8486d34f1d33" - integrity sha512-eCGn64olaqwUMaugXsTtGAM2I0QTahjEtnRu0ql8Ie+gDWAc1N6wqN0k2NilnyTunM69Pad7gJY7LOtwLimoFQ== +babel-preset-jest@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.0.6.tgz#909ef08e9f24a4679768be2f60a3df0856843f9d" + integrity sha512-WObA0/Biw2LrVVwZkF/2GqbOdzhKD6Fkdwhoy9ASIrOWr/zodcSpQh72JOkEn6NWyjmnPDjNSqaGN4KnpKzhXw== dependencies: - "@babel/plugin-syntax-bigint" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^25.1.0" - -babel-preset-stage-2@^6.13.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1" - integrity sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE= - dependencies: - babel-plugin-syntax-dynamic-import "^6.18.0" - babel-plugin-transform-class-properties "^6.24.1" - babel-plugin-transform-decorators "^6.24.1" - babel-preset-stage-3 "^6.24.1" - -babel-preset-stage-3@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395" - integrity sha1-g2raCp56f6N8sTj7kyb4eTSkg5U= - dependencies: - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-generator-functions "^6.24.1" - babel-plugin-transform-async-to-generator "^6.24.1" - babel-plugin-transform-exponentiation-operator "^6.24.1" - babel-plugin-transform-object-rest-spread "^6.22.0" - -babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.24.1, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" + babel-plugin-jest-hoist "^27.0.6" + babel-preset-current-node-syntax "^1.0.0" -babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" +babelify@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5" + integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg== -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== +backo2@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2, base64-js@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== +base64-js@^1.0.2, base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== base@^0.11.1: version "0.11.2" @@ -5017,9 +5668,14 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" before-after-hook@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== + +bezier-easing@2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" - integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== + resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86" + integrity sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY= bfj@^6.1.1: version "6.1.2" @@ -5041,11 +5697,6 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bignumber.js@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" - integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== - binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -5063,10 +5714,10 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -blessed@^0.1.81: - version "0.1.81" - resolved "https://registry.yarnpkg.com/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" - integrity sha1-+WLWh+wsNpVwrnGvhDJW5tDKESk= +blob-util@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== block-stream@*: version "0.0.9" @@ -5075,20 +5726,25 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" - integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw= +bluebird@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== -bluebird@^3.1.1, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5: +bluebird@^3.1.1, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== body-parser@1.19.0: version "1.19.0" @@ -5106,17 +5762,10 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" -body-scroll-lock@^2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-2.6.4.tgz#567abc60ef4d656a79156781771398ef40462e94" - integrity sha512-NP08WsovlmxEoZP9pdlqrE+AhNaivlTrz9a0FF37BQsnOrpN48eNqivKkE7SYpM9N+YIPjsdVzfLAUQDBm6OQw== - -bodybuilder@2.2.21: - version "2.2.21" - resolved "https://registry.yarnpkg.com/bodybuilder/-/bodybuilder-2.2.21.tgz#1a7a5d31189cf10a6fbd87668ac8aa386ac8cc38" - integrity sha512-Ys0Cas7ri7dcv3f62HwB2Lclk2oBCv86pakuEs4liK6hhL9ikTEairZ700k4VHD7vgvr2CW2Kogh4E5Ivmkg2A== - dependencies: - lodash "^4.17.11" +body-scroll-lock@^3.0.1: + version "3.1.5" + resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz#c1392d9217ed2c3e237fee1e910f6cdd80b7aaec" + integrity sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg== bonjour@^3.5.0: version "3.5.0" @@ -5135,24 +5784,6 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -bowser@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9" - integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA== - -boxen@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" - integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== - dependencies: - ansi-align "^2.0.0" - camelcase "^4.0.0" - chalk "^2.0.1" - cli-boxes "^1.0.0" - string-width "^2.0.0" - term-size "^1.2.0" - widest-line "^2.0.0" - boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -5167,6 +5798,20 @@ boxen@^4.2.0: type-fest "^0.8.1" widest-line "^3.1.0" +boxen@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.0.1.tgz#657528bdd3f59a772b8279b831f27ec2c744664b" + integrity sha512-49VBlw+PrWEF51aCmy7QIteYPIFZxSpvqBdP/2itCPPlJ49kj9zg/XPRFrdkne2W+CfwXUls8exMvu1RysZpKA== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.0" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -5198,23 +5843,42 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -browser-process-hrtime@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" - integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw== +browser-pack@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" + integrity sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA== + dependencies: + JSONStream "^1.0.3" + combine-source-map "~0.8.0" + defined "^1.0.0" + safe-buffer "^5.1.1" + through2 "^2.0.0" + umd "^3.0.0" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browser-resolve@^1.11.3: +browser-resolve@^1.11.0, browser-resolve@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== dependencies: resolve "1.1.7" +browser-resolve@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b" + integrity sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ== + dependencies: + resolve "^1.17.0" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -5246,60 +5910,154 @@ browserify-des@^1.0.0: inherits "^2.0.1" safe-buffer "^5.1.2" -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== dependencies: - bn.js "^4.1.0" + bn.js "^5.0.0" randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" -browserify-zlib@^0.2.0: +browserify-zlib@^0.2.0, browserify-zlib@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" -browserslist@^3.2.6, browserslist@^3.2.8: - version "3.2.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" - integrity sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ== - dependencies: - caniuse-lite "^1.0.30000844" - electron-to-chromium "^1.3.47" +browserify@16.2.3: + version "16.2.3" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.3.tgz#7ee6e654ba4f92bce6ab3599c3485b1cc7a0ad0b" + integrity sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ== + dependencies: + JSONStream "^1.0.3" + assert "^1.4.0" + browser-pack "^6.0.1" + browser-resolve "^1.11.0" + browserify-zlib "~0.2.0" + buffer "^5.0.2" + cached-path-relative "^1.0.0" + concat-stream "^1.6.0" + console-browserify "^1.1.0" + constants-browserify "~1.0.0" + crypto-browserify "^3.0.0" + defined "^1.0.0" + deps-sort "^2.0.0" + domain-browser "^1.2.0" + duplexer2 "~0.1.2" + events "^2.0.0" + glob "^7.1.0" + has "^1.0.0" + htmlescape "^1.1.0" + https-browserify "^1.0.0" + inherits "~2.0.1" + insert-module-globals "^7.0.0" + labeled-stream-splicer "^2.0.0" + mkdirp "^0.5.0" + module-deps "^6.0.0" + os-browserify "~0.3.0" + parents "^1.0.1" + path-browserify "~0.0.0" + process "~0.11.0" + punycode "^1.3.2" + querystring-es3 "~0.2.0" + read-only-stream "^2.0.0" + readable-stream "^2.0.2" + resolve "^1.1.4" + shasum "^1.0.0" + shell-quote "^1.6.1" + stream-browserify "^2.0.0" + stream-http "^2.0.0" + string_decoder "^1.1.1" + subarg "^1.0.0" + syntax-error "^1.1.1" + through2 "^2.0.0" + timers-browserify "^1.0.1" + tty-browserify "0.0.1" + url "~0.11.0" + util "~0.10.1" + vm-browserify "^1.0.0" + xtend "^4.0.0" -browserslist@^4.0.0, browserslist@^4.8.3, browserslist@^4.8.5: - version "4.9.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.9.1.tgz#01ffb9ca31a1aef7678128fc6a2253316aa7287c" - integrity sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw== - dependencies: - caniuse-lite "^1.0.30001030" - electron-to-chromium "^1.3.363" - node-releases "^1.1.50" +browserify@^16.1.0: + version "16.5.2" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.2.tgz#d926835e9280fa5fd57f5bc301f2ef24a972ddfe" + integrity sha512-TkOR1cQGdmXU9zW4YukWzWVSJwrxmNdADFbqbE3HFgQWe5wqZmOawqZ7J/8MPCwk/W8yY7Y0h+7mOtcZxLP23g== + dependencies: + JSONStream "^1.0.3" + assert "^1.4.0" + browser-pack "^6.0.1" + browser-resolve "^2.0.0" + browserify-zlib "~0.2.0" + buffer "~5.2.1" + cached-path-relative "^1.0.0" + concat-stream "^1.6.0" + console-browserify "^1.1.0" + constants-browserify "~1.0.0" + crypto-browserify "^3.0.0" + defined "^1.0.0" + deps-sort "^2.0.0" + domain-browser "^1.2.0" + duplexer2 "~0.1.2" + events "^2.0.0" + glob "^7.1.0" + has "^1.0.0" + htmlescape "^1.1.0" + https-browserify "^1.0.0" + inherits "~2.0.1" + insert-module-globals "^7.0.0" + labeled-stream-splicer "^2.0.0" + mkdirp-classic "^0.5.2" + module-deps "^6.2.3" + os-browserify "~0.3.0" + parents "^1.0.1" + path-browserify "~0.0.0" + process "~0.11.0" + punycode "^1.3.2" + querystring-es3 "~0.2.0" + read-only-stream "^2.0.0" + readable-stream "^2.0.2" + resolve "^1.1.4" + shasum "^1.0.0" + shell-quote "^1.6.1" + stream-browserify "^2.0.0" + stream-http "^3.0.0" + string_decoder "^1.1.1" + subarg "^1.0.0" + syntax-error "^1.1.1" + through2 "^2.0.0" + timers-browserify "^1.0.1" + tty-browserify "0.0.1" + url "~0.11.0" + util "~0.10.1" + vm-browserify "^1.0.0" + xtend "^4.0.0" -browserslist@^4.12.0: - version "4.12.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" - integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== +browserslist@*, browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.6, browserslist@^4.16.8, browserslist@^4.6.0, browserslist@^4.6.4: + version "4.16.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.8.tgz#cb868b0b554f137ba6e33de0ecff2eda403c4fb0" + integrity sha512-sc2m9ohR/49sWEbPj14ZSSZqp+kbi16aLao42Hmn3Z8FpjuMaq2xCA2l4zl9ITfyzvnvyE0hcg62YkIGKxgaNQ== dependencies: - caniuse-lite "^1.0.30001043" - electron-to-chromium "^1.3.413" - node-releases "^1.1.53" - pkg-up "^2.0.0" + caniuse-lite "^1.0.30001251" + colorette "^1.3.0" + electron-to-chromium "^1.3.811" + escalade "^3.1.1" + node-releases "^1.1.75" bs-logger@0.x: version "0.2.6" @@ -5325,15 +6083,10 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - -buffer-from@1.x, buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer-indexof@^1.0.0: version "1.1.1" @@ -5359,10 +6112,26 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" - integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== +buffer@^5.0.2, buffer@^5.1.0, buffer@^5.7.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" + integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + +builtin-modules@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" + integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA== builtin-status-codes@^3.0.0: version "3.0.0" @@ -5395,14 +6164,14 @@ bytes@3.1.0: integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== cac@^6.5.6: - version "6.5.6" - resolved "https://registry.yarnpkg.com/cac/-/cac-6.5.6.tgz#0120e39c9e56a7ab6418b078e6ad0595f2982375" - integrity sha512-8jsGLeBiYEVYTDExaj/rDPG4tyra4yjjacIL10TQ+MobPcg9/IST+dkKLu6sOzq0GcIC6fQqX1nkH9HoskQLAw== + version "6.7.3" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.3.tgz#10410b8611677990cc2e3c8b576d471c1d71b768" + integrity sha512-ECVqVZh74qgSuZG9YOt2OJPI3wGcf+EwwuF/XIOYqZBD0KZYLtgPWqFPxmDPQ6joxI1nOlvVgRV6VT53Ooyocg== cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== dependencies: bluebird "^3.5.5" chownr "^1.1.1" @@ -5420,6 +6189,30 @@ cacache@^12.0.0, cacache@^12.0.2, cacache@^12.0.3: unique-filename "^1.1.1" y18n "^4.0.0" +cacache@^15.0.5: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -5447,6 +6240,18 @@ cache-loader@^3.0.0: neo-async "^2.6.1" schema-utils "^1.0.0" +cache-loader@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cache-loader/-/cache-loader-4.1.0.tgz#9948cae353aec0a1fcb1eafda2300816ec85387e" + integrity sha512-ftOayxve0PwKzBF/GLsZNC9fJBXl8lkZE3TOsjkboHfVHVkL39iUEs1FO07A33mizmci5Dudt38UZrrYXDtbhw== + dependencies: + buffer-json "^2.0.0" + find-cache-dir "^3.0.0" + loader-utils "^1.2.3" + mkdirp "^0.5.1" + neo-async "^2.6.1" + schema-utils "^2.0.0" + cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -5460,12 +6265,28 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -cachedir@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.3.0.tgz#5e01928bf2d95b5edd94b0942188246740e0dbc4" - integrity sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg== +cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" + integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== + +cachedir@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.2.0.tgz#19afa4305e05d79e417566882e0c8f960f62ff0e" + integrity sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ== + +cachedir@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" + integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: - os-homedir "^1.0.1" + function-bind "^1.1.1" + get-intrinsic "^1.0.2" call-me-maybe@^1.0.1: version "1.0.1" @@ -5479,13 +6300,6 @@ caller-callsite@^2.0.0: dependencies: callsites "^2.0.0" -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= - dependencies: - callsites "^0.2.0" - caller-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" @@ -5493,11 +6307,6 @@ caller-path@^2.0.0: dependencies: caller-callsite "^2.0.0" -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= - callsites@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" @@ -5516,6 +6325,14 @@ camel-case@3.0.x, camel-case@^3.0.0: no-case "^2.2.0" upper-case "^1.1.1" +camel-case@4.1.2, camel-case@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -5533,17 +6350,21 @@ camelcase-keys@^4.0.0: map-obj "^2.0.0" quick-lru "^1.0.0" +camelcase-keys@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" + integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== + dependencies: + camelcase "^5.3.1" + map-obj "^4.0.0" + quick-lru "^4.0.1" + camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - -camelcase@^4.0.0, camelcase@^4.1.0: +camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= @@ -5553,10 +6374,15 @@ camelcase@^5.0.0, camelcase@^5.2.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelize@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" - integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= +camelcase@^6.0.0, camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== + +can-use-dom@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a" + integrity sha1-IsxKNKCrxDlQ9CxkEQJKP2NmtFo= caniuse-api@^3.0.0: version "3.0.0" @@ -5568,10 +6394,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000864, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001030, caniuse-lite@^1.0.30001043: - version "1.0.30001170" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001170.tgz" - integrity sha512-Dd4d/+0tsK0UNLrZs3CvNukqalnVTRrxb5mcQm8rHL49t7V5ZaTygwXkrq+FB+dVDf++4ri8eJnFEJAB8332PA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001228, caniuse-lite@^1.0.30001251: + version "1.0.30001252" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001252.tgz#cb16e4e3dafe948fc4a9bb3307aea054b912019a" + integrity sha512-I56jhWDGMtdILQORdusxBOH+Nl/KgQSdDmpJezYddnAkVOmnoU8zwjTV9xAjMIYxr0iPreEAVylCGcmHCjfaOw== capture-exit@^2.0.0: version "2.0.0" @@ -5580,33 +6406,23 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" -capture-stack-trace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" - integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== - -case-sensitive-paths-webpack-plugin@^2.1.2: - version "2.3.0" - resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7" - integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.1.tgz#509afb67066e7499f7eb3535c77445772ae2d019" - integrity sha1-UJr7ZwZudJn36zU1x3RFdyri0Bk= +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= dependencies: - ansi-styles "^2.1.0" + ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" has-ansi "^2.0.0" strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -5615,17 +6431,6 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^1.0.0, chalk@^1.1, chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -5634,30 +6439,25 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -charm@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/charm/-/charm-0.1.2.tgz#06c21eed1a1b06aeb67553cdc53e23274bac2296" - integrity sha1-BsIe7RobBq62dVPNxT4jJ0usIpY= - -check-more-types@2.24.0: +check-more-types@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= @@ -5667,7 +6467,7 @@ check-types@^8.0.3: resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ== -chokidar@^2, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.8: +chokidar@^2.0.3, chokidar@^2.0.4, chokidar@^2.1.1, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -5686,34 +6486,37 @@ chokidar@^2, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.4.2: - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== +chokidar@^3.3.1, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.4.3, chokidar@^3.5.1, chokidar@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" -chownr@^1.1.1, chownr@^1.1.2: +chownr@^1.1.1, chownr@^1.1.2, chownr@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== -ci-info@^1.5.0, ci-info@^1.6.0: +ci-info@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== @@ -5723,6 +6526,11 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.0.0, ci-info@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -5731,10 +6539,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== class-utils@^0.3.5: version "0.3.6" @@ -5746,22 +6554,27 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -clean-css@4.2.x, clean-css@^4.2.1: +clean-css@4.2.x, clean-css@^4.2.1, clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== dependencies: source-map "~0.6.0" -cli-boxes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" - integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= +clean-git-ref@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/clean-git-ref/-/clean-git-ref-2.0.1.tgz#dcc0ca093b90e527e67adb5a5e55b1af6816dcd9" + integrity sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw== -cli-boxes@^2.2.0: +clean-stack@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.0.tgz#538ecae8f9c6ca508e3c3c95b453fe93cb4c168d" - integrity sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w== + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-boxes@^2.2.0, cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== cli-cursor@^1.0.2: version "1.0.2" @@ -5777,17 +6590,22 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-spinners@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" - integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw= +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-table-redemption@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cli-table-redemption/-/cli-table-redemption-1.0.1.tgz#0359d8c34df74980029d76dff071a05a127c4fdd" - integrity sha512-SjVCciRyx01I4azo2K2rcc0NP/wOceXGzG1ZpYkEulbbIxDA/5YWv0oxG2HtQ4v8zPC6bgbRI7SbNaTZCxMNkg== +cli-table3@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" + integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== dependencies: - chalk "^1.1.3" + object-assign "^4.1.0" + string-width "^4.2.0" + optionalDependencies: + colors "^1.1.2" cli-truncate@^0.2.1: version "0.2.1" @@ -5797,37 +6615,23 @@ cli-truncate@^0.2.1: slice-ansi "0.0.4" string-width "^1.0.1" -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -clipboard@^2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" - integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg== +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: - good-listener "^1.2.2" - select "^1.1.2" - tiny-emitter "^2.0.0" + slice-ansi "^3.0.0" + string-width "^4.2.0" -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== cliui@^5.0.0: version "5.0.0" @@ -5847,6 +6651,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -5863,20 +6676,17 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -clone@2.x: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= - clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -cluster-key-slot@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" - integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== +cloudinary-build-url@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/cloudinary-build-url/-/cloudinary-build-url-0.1.1.tgz#1ed7a88d23b84600b001df6608ee09d9c9d48ddb" + integrity sha512-SlCdVyHC9THzMsMc4FnXDW6UA/N0CbgPeDoIHjTPUFxXosgPUC7Jio2uD+rc8SF/YNevPwaRzjIRhGMTznO0jA== + dependencies: + "@cld-apis/utils" "^0.1.0" co@^4.6.0: version "4.6.0" @@ -5892,20 +6702,28 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -coalescy@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/coalescy/-/coalescy-1.0.0.tgz#4b065846b836361ada6c4b4a4abf4bc1cac31bf1" - integrity sha1-SwZYRrg2NhrabEtKSr9LwcrDG/E= - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +coffeeify@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/coffeeify/-/coffeeify-3.0.1.tgz#5e2753000c50bd24c693115f33864248dd11136c" + integrity sha512-Qjnr7UX6ldK1PHV7wCnv7AuCd4q19KTUtwJnu/6JRJB4rfm12zvcXtKdacUoePOKr1I4ka/ydKiwWpNAdsQb0g== + dependencies: + convert-source-map "^1.3.0" + through2 "^2.0.0" + +coffeescript@1.12.7: + version "1.12.7" + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" + integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== + collect-v8-coverage@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.0.tgz#150ee634ac3650b71d9c985eb7f608942334feb1" - integrity sha512-VKIhJgvk8E1W28m5avZ2Gv2Ruv5YiF56ug2oclvaG9md69BuZImMG2sk9g7QNKLUbtYAKQjXjYxbYZVUlMMKmQ== + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== collection-visit@^1.0.0: version "1.0.0" @@ -5915,7 +6733,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -5939,26 +6757,36 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== +color-string@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" + integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" color@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" - integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" + color-convert "^1.9.3" + color-string "^1.6.0" -colors@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= +colorette@^1.2.1, colorette@^1.2.2, colorette@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" + integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +colors@~1.2.1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc" + integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg== columnify@^1.5.4: version "1.5.4" @@ -5968,44 +6796,74 @@ columnify@^1.5.4: strip-ansi "^3.0.0" wcwidth "^1.0.0" -combined-stream@^1.0.6, combined-stream@~1.0.6: +combine-source-map@^0.8.0, combine-source-map@~0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" + integrity sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos= + dependencies: + convert-source-map "~1.1.0" + inline-source-map "~0.6.0" + lodash.memoize "~3.0.3" + source-map "~0.5.3" + +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -command-exists@^1.2.2: - version "1.2.8" - resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" - integrity sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw== +commander@2.17.x: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@2, commander@^2.14.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.9.0, commander@~2.20.3: +commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@2.13.0, commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" - integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== +commander@^5.0.0, commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@2.17.x: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== commander@~2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== -common-tags@1.8.0: +commitizen@^4.0.3, commitizen@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.2.4.tgz#a3e5b36bd7575f6bf6e7aa19dbbf06b0d8f37165" + integrity sha512-LlZChbDzg3Ir3O2S7jSo/cgWp5/QwylQVr59K4xayVq8S4/RdKzSyJkghAiZZHfhh5t4pxunUoyeg0ml1q/7aw== + dependencies: + cachedir "2.2.0" + cz-conventional-changelog "3.2.0" + dedent "0.7.0" + detect-indent "6.0.0" + find-node-modules "^2.1.2" + find-root "1.1.0" + fs-extra "8.1.0" + glob "7.1.4" + inquirer "6.5.2" + is-utf8 "^0.2.1" + lodash "^4.17.20" + minimist "1.2.5" + strip-bom "4.0.0" + strip-json-comments "3.0.1" + +common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== @@ -6015,13 +6873,18 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -compare-func@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648" - integrity sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg= +compare-func@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" + integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== dependencies: array-ify "^1.0.0" - dot-prop "^3.0.0" + dot-prop "^5.1.0" + +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== component-emitter@^1.2.1: version "1.3.0" @@ -6053,7 +6916,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.6.0: +concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@^1.6.2, concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -6083,32 +6946,13 @@ condense-newlines@^0.2.1: kind-of "^3.0.2" config-chain@^1.1.11, config-chain@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" - integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== dependencies: ini "^1.3.4" proto-list "~1.2.1" -config@^1.30.0: - version "1.31.0" - resolved "https://registry.yarnpkg.com/config/-/config-1.31.0.tgz#ab08aeba6536015d220cd0afe14b3e0501082542" - integrity sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA== - dependencies: - json5 "^1.0.1" - -configstore@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" - integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== - dependencies: - dot-prop "^4.1.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -6126,10 +6970,30 @@ connect-history-api-fallback@^1.5.0, connect-history-api-fallback@^1.6.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== -consola@^2.6.0: - version "2.11.3" - resolved "https://registry.yarnpkg.com/consola/-/consola-2.11.3.tgz#f7315836224c143ac5094b47fd4c816c2cd1560e" - integrity sha512-aoW0YIIAmeftGR8GSpw6CGQluNdkWMWh3yEFjH/hmynTYnMtibXszii3lxCXmk8YxJtI3FAK5aTiquA5VH68Gw== +connect@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +consola@2.15.3, consola@^2.10.0, consola@^2.10.1, consola@^2.15.0, consola@^2.15.3, consola@^2.6.0, consola@^2.9.0: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + +consola@^1.4.5: + version "1.4.5" + resolved "https://registry.yarnpkg.com/consola/-/consola-1.4.5.tgz#09732d07cb50af07332e54e0f42fafb92b962c4a" + integrity sha512-movqq3MbyXbSf7cG/x+EbO3VjKQVZPB/zeB5+lN1TuBYh9BWDemLQca9P+a4xpO4lXva9rz+Bd8XyqlH136Lww== + dependencies: + chalk "^2.3.2" + figures "^2.0.0" + lodash "^4.17.5" + std-env "^1.1.0" console-browserify@^1.1.0: version "1.2.0" @@ -6141,11 +7005,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -console-log-level@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/console-log-level/-/console-log-level-1.4.1.tgz#9c5a6bb9ef1ef65b05aba83028b0ff894cdf630a" - integrity sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ== - consolidate@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7" @@ -6153,16 +7012,11 @@ consolidate@^0.15.1: dependencies: bluebird "^3.1.1" -constants-browserify@^1.0.0: +constants-browserify@^1.0.0, constants-browserify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -contains-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" - integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= - content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -6170,30 +7024,26 @@ content-disposition@0.5.3: dependencies: safe-buffer "5.1.2" -content-security-policy-builder@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz#0a2364d769a3d7014eec79ff7699804deb8cfcbb" - integrity sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ== - content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -continuation-local-storage@^3.1.4, continuation-local-storage@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" - integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== +conventional-changelog-angular@^5.0.11, conventional-changelog-angular@^5.0.3: + version "5.0.12" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz#c979b8b921cbfe26402eb3da5bbfda02d865a2b9" + integrity sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw== dependencies: - async-listener "^0.6.0" - emitter-listener "^1.1.1" + compare-func "^2.0.0" + q "^1.5.1" -conventional-changelog-angular@^5.0.3: - version "5.0.6" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.6.tgz#269540c624553aded809c29a3508fdc2b544c059" - integrity sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA== +conventional-changelog-conventionalcommits@^4.3.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.0.tgz#7fc17211dbca160acf24687bd2fdd5fd767750eb" + integrity sha512-sj9tj3z5cnHaSJCYObA9nISf7eq/YjscLPoq6nmew4SiOjxqL2KRpK20fjnjVbpNDjJ2HR3MoVcWKXwbVvzS0A== dependencies: - compare-func "^1.3.1" + compare-func "^2.0.0" + lodash "^4.17.15" q "^1.5.1" conventional-changelog-core@^3.1.6: @@ -6216,45 +7066,50 @@ conventional-changelog-core@^3.1.6: through2 "^3.0.0" conventional-changelog-preset-loader@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.0.tgz#580fa8ab02cef22c24294d25e52d7ccd247a9a6a" - integrity sha512-/rHb32J2EJnEXeK4NpDgMaAVTFZS3o1ExmjKMtYVgIC4MQn0vkNSbYpdGRotkfGGRWiqk3Ri3FBkiZGbAfIfOQ== + version "2.3.4" + resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz#14a855abbffd59027fd602581f1f34d9862ea44c" + integrity sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g== conventional-changelog-writer@^4.0.6: - version "4.0.11" - resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.0.11.tgz#9f56d2122d20c96eb48baae0bf1deffaed1edba4" - integrity sha512-g81GQOR392I+57Cw3IyP1f+f42ME6aEkbR+L7v1FBBWolB0xkjKTeCWVguzRrp6UiT1O6gBpJbEy2eq7AnV1rw== + version "4.1.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz#1ca7880b75aa28695ad33312a1f2366f4b12659f" + integrity sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw== dependencies: - compare-func "^1.3.1" - conventional-commits-filter "^2.0.2" + compare-func "^2.0.0" + conventional-commits-filter "^2.0.7" dateformat "^3.0.0" - handlebars "^4.4.0" + handlebars "^4.7.6" json-stringify-safe "^5.0.1" lodash "^4.17.15" - meow "^5.0.0" + meow "^8.0.0" semver "^6.0.0" split "^1.0.0" - through2 "^3.0.0" + through2 "^4.0.0" -conventional-commits-filter@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.2.tgz#f122f89fbcd5bb81e2af2fcac0254d062d1039c1" - integrity sha512-WpGKsMeXfs21m1zIw4s9H5sys2+9JccTzpN6toXtxhpw2VNF2JUXwIakthKBy+LN4DvJm+TzWhxOMWOs1OFCFQ== +conventional-commit-types@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz#7c9214e58eae93e85dd66dbfbafe7e4fffa2365b" + integrity sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg== + +conventional-commits-filter@^2.0.2, conventional-commits-filter@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz#f8d9b4f182fce00c9af7139da49365b136c8a0b3" + integrity sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA== dependencies: lodash.ismatch "^4.4.0" modify-values "^1.0.0" -conventional-commits-parser@^3.0.3: - version "3.0.8" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.0.8.tgz#23310a9bda6c93c874224375e72b09fb275fe710" - integrity sha512-YcBSGkZbYp7d+Cr3NWUeXbPDFUN6g3SaSIzOybi8bjHL5IJ5225OSCxJJ4LgziyEJ7AaJtE9L2/EU6H7Nt/DDQ== +conventional-commits-parser@^3.0.0, conventional-commits-parser@^3.0.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.1.tgz#ba44f0b3b6588da2ee9fd8da508ebff50d116ce2" + integrity sha512-OG9kQtmMZBJD/32NEw5IhN5+HnBqVjy03eC+I71I0oQRFA5rOgA4OtPOYG7mz1GkCfCNxn3gKIX8EiHJYuf1cA== dependencies: JSONStream "^1.0.4" is-text-path "^1.0.1" lodash "^4.17.15" - meow "^5.0.0" - split2 "^2.0.0" - through2 "^3.0.0" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" trim-off-newlines "^1.0.0" conventional-recommended-bump@^5.0.0: @@ -6271,23 +7126,62 @@ conventional-recommended-bump@^5.0.0: meow "^4.0.0" q "^1.5.1" -convert-source-map@^1.4.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== +convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" +convert-source-map@~1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" + integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA= + +cookie-parser@^1.4.5: + version "1.4.5" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.5.tgz#3e572d4b7c0c80f9c61daf604e4336831b5d1d49" + integrity sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= +cookie-universal-nuxt@^2.1.3: + version "2.1.5" + resolved "https://registry.yarnpkg.com/cookie-universal-nuxt/-/cookie-universal-nuxt-2.1.5.tgz#669f2ff95b1bc1962c86edd69c954f60729e71e5" + integrity sha512-P0WCTKIyemWNtHi9lxrS5cxZmieOIEjt28B7Alu6cdSB9RqtUtpkqYyV9PRK6oJrT5eIPDYjHsJQlh6SUrFJOg== + dependencies: + "@types/cookie" "^0.3.3" + cookie-universal "^2.1.5" + +cookie-universal@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/cookie-universal/-/cookie-universal-2.1.5.tgz#9a6cefbfb61c750a1b8ee2610bf71566bd719544" + integrity sha512-nqOOmEkovCQxNYGIyzhcwsmh4c7xnxe7RWdiYFOoml9MP4L32IlU3LdPC7r7nQEnnM+9Uxlk/UhtvBl5is6M/w== + dependencies: + "@types/cookie" "^0.3.3" + cookie "^0.4.0" + cookie@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= + +cookie@^0.4.0, cookie@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -6306,9 +7200,9 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= copy-webpack-plugin@^5.0.2: - version "5.1.1" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz#5481a03dea1123d88a988c6ff8b78247214f0b88" - integrity sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg== + version "5.1.2" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz#8a889e1dcafa6c91c6cd4be1ad158f1d3823bae2" + integrity sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ== dependencies: cacache "^12.0.3" find-cache-dir "^2.1.0" @@ -6320,33 +7214,41 @@ copy-webpack-plugin@^5.0.2: normalize-path "^3.0.0" p-limit "^2.2.1" schema-utils "^1.0.0" - serialize-javascript "^2.1.2" + serialize-javascript "^4.0.0" webpack-log "^2.0.0" -core-js-compat@^3.6.2, core-js-compat@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" - integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== +core-js-compat@^3.1.1, core-js-compat@^3.12.1, core-js-compat@^3.14.0, core-js-compat@^3.16.0, core-js-compat@^3.6.5: + version "3.16.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.16.3.tgz#ae12a6e20505a1d79fbd16b6689dfc77fc989114" + integrity sha512-A/OtSfSJQKLAFRVd4V0m6Sep9lPdjD8bpN8v3tCCGwE0Tmh0hOiVDm9tw6mXmWOKOSZIyr3EkywPo84cJjGvIQ== dependencies: - browserslist "^4.8.3" + browserslist "^4.16.8" semver "7.0.0" -core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== +core-js@^2.5.3, core-js@^2.6.5: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== +core-js@^3.0.1, core-js@^3.6.4, core-js@^3.6.5: + version "3.16.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.16.3.tgz#1f2d43c51a9ed014cc6c83440af14697ae4b75f2" + integrity sha512-lM3GftxzHNtPNUJg0v4pC2RC6puwMd6VZA7vXUczi+SKmCWSf4JwO89VJGMqbzmB7jlK7B5hr3S64PqwFL49cA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.0: +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cosmiconfig@^5.0.0, cosmiconfig@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== @@ -6367,22 +7269,41 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" + "@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" -create-error-class@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= +crc-32@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" + integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== + dependencies: + exit-on-epipe "~1.0.1" + printj "~1.1.0" + +crc@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== dependencies: - capture-stack-trace "^1.0.0" + buffer "^5.1.0" -create-hash@^1.1.0, create-hash@^1.1.2: +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -6393,7 +7314,7 @@ create-hash@^1.1.0, create-hash@^1.1.2: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -6405,39 +7326,38 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cron@^1.3: - version "1.8.2" - resolved "https://registry.yarnpkg.com/cron/-/cron-1.8.2.tgz#4ac5e3c55ba8c163d84f3407bde94632da8370ce" - integrity sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg== +create-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-env@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.3.tgz#4256b71e49b3a40637a0ce70768a6ef5c72ae941" + integrity sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag== dependencies: - moment-timezone "^0.5.x" + cross-spawn "^7.0.0" -cross-env@^3.1.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-3.2.4.tgz#9e0585f277864ed421ce756f81a980ff0d698aba" - integrity sha1-ngWF8neGTtQhznVvgamA/w1piro= +cross-fetch@3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" + integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== dependencies: - cross-spawn "^5.1.0" - is-windows "^1.0.0" + node-fetch "2.6.1" -cross-fetch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" - integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw== +cross-fetch@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.2.tgz#ee0c2f18844c4fde36150c2a4ddc068d20c1bc41" + integrity sha512-+JhD65rDNqLbGmB3Gzs3HrEKC0aQnD+XA3SY6RjgkF88jV2q5cTc5+CwxlS3sdmLk98gpPt5CF9XRnPdlxZe6w== dependencies: - node-fetch "2.6.0" - whatwg-fetch "3.0.0" + node-fetch "2.6.1" -cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== +cross-fetch@3.1.4, cross-fetch@^3.0.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" + node-fetch "2.6.1" cross-spawn@^3.0.0: version "3.0.1" @@ -6447,25 +7367,27 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.0.1, cross-spawn@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: - lru-cache "^4.0.1" + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" - integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg== +cross-spawn@^7.0.0, 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== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.11.0: +crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -6482,21 +7404,18 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-js@~3.1.2-2: - version "3.1.8" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5" - integrity sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU= - -crypto-random-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" - integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -6510,23 +7429,13 @@ css-declaration-sorter@^4.0.1: postcss "^7.0.1" timsort "^0.3.0" -css-loader@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.1.tgz#6885bb5233b35ec47b006057da01cc640b6b79fe" - integrity sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw== +css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== dependencies: - babel-code-frame "^6.26.0" - css-selector-tokenizer "^0.7.0" - icss-utils "^2.1.0" - loader-utils "^1.0.2" - lodash "^4.17.11" - postcss "^6.0.23" - postcss-modules-extract-imports "^1.2.0" - postcss-modules-local-by-default "^1.2.0" - postcss-modules-scope "^1.1.0" - postcss-modules-values "^1.3.0" - postcss-value-parser "^3.3.0" - source-list-map "^2.0.0" + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" css-loader@^2.1.1: version "2.1.1" @@ -6545,6 +7454,24 @@ css-loader@^2.1.1: postcss-value-parser "^3.3.0" schema-utils "^1.0.0" +css-loader@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e" + integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== + dependencies: + camelcase "^6.0.0" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^2.0.0" + postcss "^7.0.32" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.3" + postcss-modules-scope "^2.2.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.1" + semver "^7.3.2" + css-parse@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" @@ -6552,20 +7479,17 @@ css-parse@~2.0.0: dependencies: css "^2.0.0" +css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" + css-select-base-adapter@^0.1.1: version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== css-select@^2.0.0: version "2.1.0" @@ -6577,14 +7501,16 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" -css-selector-tokenizer@^0.7.0: - version "0.7.2" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.2.tgz#11e5e27c9a48d90284f22d45061c303d7a25ad87" - integrity sha512-yj856NGuAymN6r8bn8/Jl46pR+OC3eEvAhfGYDUe7YPtTPAYrSSw4oAniZ9Y8T5B92hjhwTBLUen0/vKPxf6pw== +css-select@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" + integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== dependencies: - cssesc "^3.0.0" - fastparse "^1.1.2" - regexpu-core "^4.6.0" + boolbase "^1.0.0" + css-what "^5.0.0" + domhandler "^4.2.0" + domutils "^2.6.0" + nth-check "^2.0.0" css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" @@ -6594,15 +7520,23 @@ css-tree@1.0.0-alpha.37: mdn-data "2.0.4" source-map "^0.6.1" -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +css-tree@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" css-what@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" - integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" + integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== + +css-what@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" + integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== css@^2.0.0, css@^2.1.0: version "2.2.4" @@ -6614,15 +7548,25 @@ css@^2.0.0, css@^2.1.0: source-map-resolve "^0.5.2" urix "^0.1.0" +cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" - integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== +cssnano-preset-default@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff" + integrity sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ== dependencies: css-declaration-sorter "^4.0.1" cssnano-util-raw-cache "^4.0.1" @@ -6652,7 +7596,7 @@ cssnano-preset-default@^4.0.7: postcss-ordered-values "^4.1.2" postcss-reduce-initial "^4.0.3" postcss-reduce-transforms "^4.0.2" - postcss-svgo "^4.0.2" + postcss-svgo "^4.0.3" postcss-unique-selectors "^4.0.1" cssnano-util-get-arguments@^4.0.0: @@ -6677,40 +7621,52 @@ cssnano-util-same-parent@^4.0.0: resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== -cssnano@^4.1.10: - version "4.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" - integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== +cssnano@^4.1.10, cssnano@^4.1.11: + version "4.1.11" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.11.tgz#c7b5f5b81da269cb1fd982cb960c1200910c9a99" + integrity sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g== dependencies: cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" + cssnano-preset-default "^4.0.8" is-resolvable "^1.0.0" postcss "^7.0.0" csso@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.2.tgz#e5f81ab3a56b8eefb7f0092ce7279329f454de3d" - integrity sha512-kS7/oeNVXkHWxby5tHVxlhjizRCSv8QdU7hB2FpdAibDU8FjTAolhNjKNTiLzXtUrKT6HwClE81yXwEk1309wg== + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== dependencies: - css-tree "1.0.0-alpha.37" + css-tree "^1.1.2" + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== -cssom@^0.4.1: +cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== +cssstyle@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" -cssstyle@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992" - integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA== +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: cssom "~0.3.6" +cuint@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" + integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -6718,63 +7674,98 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" -cycle@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" - integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= - cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@^3.1.5: - version "3.8.3" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.3.tgz#e921f5482f1cbe5814891c878f26e704bbffd8f4" - integrity sha512-I9L/d+ilTPPA4vq3NC1OPKmw7jJIpMKNdyfR8t1EXYzYCjyqbc59migOm1YSse/VRbISLJ+QGb5k4Y3bz2lkYw== - dependencies: - "@cypress/listr-verbose-renderer" "0.4.1" - "@cypress/xvfb" "1.2.4" - "@types/sizzle" "2.3.2" - arch "2.1.1" - bluebird "3.5.0" - cachedir "1.3.0" - chalk "2.4.2" - check-more-types "2.24.0" - commander "2.15.1" - common-tags "1.8.0" - debug "3.2.6" - eventemitter2 "4.1.2" - execa "0.10.0" - executable "4.1.1" - extract-zip "1.6.7" - fs-extra "5.0.0" - getos "3.1.1" - is-ci "1.2.1" - is-installed-globally "0.1.0" - lazy-ass "1.6.0" - listr "0.12.0" - lodash "4.17.15" - log-symbols "2.2.0" - minimist "1.2.0" - moment "2.24.0" - ramda "0.24.1" - request "2.88.0" - request-progress "3.0.0" - supports-color "5.5.0" - tmp "0.1.0" - untildify "3.0.3" - url "0.11.0" - yauzl "2.10.0" - -d3-dsv@^1.0.8: - version "1.2.0" - resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c" - integrity sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g== +cypress-pipe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cypress-pipe/-/cypress-pipe-2.0.0.tgz#577df7a70a8603d89a96dfe4092a605962181af8" + integrity sha512-KW9s+bz4tFLucH3rBGfjW+Q12n7S4QpUSSyxiGrgPOfoHlbYWzAGB3H26MO0VTojqf9NVvfd5Kt0MH5XMgbfyg== + +cypress-tags@^0.0.20: + version "0.0.20" + resolved "https://registry.yarnpkg.com/cypress-tags/-/cypress-tags-0.0.20.tgz#1fac58881dfff5714c3b32b08ad9011b97877534" + integrity sha512-svGX0EmUopPQyGJJFjnVmB9O9r61Rz3ugPhPeY1xMYWwLGWEkhNCQEjdlgScOjh1mJZQJhBKUa8Scs7C184HVA== + dependencies: + "@cypress/browserify-preprocessor" "^3.0.1" + cypress "^6.5.0" + through "^2.3.8" + +cypress@^6.5.0, cypress@^6.6.0: + version "6.9.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.9.1.tgz#ce1106bfdc47f8d76381dba63f943447883f864c" + integrity sha512-/RVx6sOhsyTR9sd9v0BHI4tnDZAhsH9rNat7CIKCUEr5VPWxyfGH0EzK4IHhAqAH8vjFcD4U14tPiJXshoUrmQ== + dependencies: + "@cypress/listr-verbose-renderer" "^0.4.1" + "@cypress/request" "^2.88.5" + "@cypress/xvfb" "^1.2.4" + "@types/node" "12.12.50" + "@types/sinonjs__fake-timers" "^6.0.1" + "@types/sizzle" "^2.3.2" + arch "^2.1.2" + blob-util "2.0.2" + bluebird "^3.7.2" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-table3 "~0.6.0" + commander "^5.1.0" + common-tags "^1.8.0" + dayjs "^1.9.3" + debug "4.3.2" + eventemitter2 "^6.4.2" + execa "^4.0.2" + executable "^4.1.1" + extract-zip "^1.7.0" + fs-extra "^9.0.1" + getos "^3.2.1" + is-ci "^2.0.0" + is-installed-globally "^0.3.2" + lazy-ass "^1.6.0" + listr "^0.14.3" + lodash "^4.17.19" + log-symbols "^4.0.0" + minimist "^1.2.5" + moment "^2.29.1" + ospath "^1.2.2" + pretty-bytes "^5.4.1" + ramda "~0.27.1" + request-progress "^3.0.0" + supports-color "^7.2.0" + tmp "~0.2.1" + untildify "^4.0.0" + url "^0.11.0" + yauzl "^2.10.0" + +cz-conventional-changelog@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-3.2.0.tgz#6aef1f892d64113343d7e455529089ac9f20e477" + integrity sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg== + dependencies: + chalk "^2.4.1" + commitizen "^4.0.3" + conventional-commit-types "^3.0.0" + lodash.map "^4.5.1" + longest "^2.0.1" + word-wrap "^1.0.3" + optionalDependencies: + "@commitlint/load" ">6.1.1" + +cz-conventional-changelog@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz#9246947c90404149b3fe2cf7ee91acad3b7d22d2" + integrity sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw== dependencies: - commander "2" - iconv-lite "0.4" - rw "1" + chalk "^2.4.1" + commitizen "^4.0.3" + conventional-commit-types "^3.0.0" + lodash.map "^4.5.1" + longest "^2.0.1" + word-wrap "^1.0.3" + optionalDependencies: + "@commitlint/load" ">6.1.1" dargs@^4.0.1: version "4.1.0" @@ -6783,6 +7774,16 @@ dargs@^4.0.1: dependencies: number-is-nan "^1.0.0" +dargs@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" + integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== + +dash-ast@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" + integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -6790,12 +7791,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dasherize@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" - integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= - -data-urls@^1.1.0: +data-urls@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== @@ -6804,27 +7800,41 @@ data-urls@^1.1.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +dataloader@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.0.0.tgz#41eaf123db115987e21ca93c005cd7753c55fe6f" + integrity sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ== + date-fns@^1.27.2: version "1.30.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== -dateformat@^3.0.0: +dateformat@^3.0.0, dateformat@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.8.21: - version "1.8.21" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.21.tgz#98299185b72b9b679f31c7ed987b63923c961552" - integrity sha512-1kbWK0hziklUHkGgiKr7xm59KwAg/K3Tp7H/8X+f58DnNCwY3pKYjOCJpIlVs125FRBukGVZdKZojC073D0IeQ== +dayjs@^1.9.3: + version "1.10.6" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63" + integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw== de-indent@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -6838,26 +7848,33 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@3.2.6, debug@^3, debug@^3.0, debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== +debug@4, debug@4.3.2, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: - ms "^2.1.1" + ms "2.1.2" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" +debug@^3.1.0, debug@^3.1.1, debug@^3.2.6, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= -decamelize-keys@^1.0.0: +decamelize-keys@^1.0.0, decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= @@ -6865,11 +7882,16 @@ decamelize-keys@^1.0.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decimal.js@^10.2.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -6882,7 +7904,14 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -dedent@^0.7.0: +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +dedent@0.7.0, dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= @@ -6904,18 +7933,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: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deep-metrics@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deep-metrics/-/deep-metrics-0.0.1.tgz#8ac3333195cc5eca059b224eb1ca61fc4cda50fd" - integrity sha512-732WmZgCWxOkf4QBvrCjPPuT6wTEzaGye/4JqYsU/sO0J53UNX4PBwK0JV262BZ5cxgLmKhU+NlrtKdPDgybkg== - dependencies: - semver "^5.3.0" - deepmerge@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753" @@ -6975,17 +7997,30 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -del@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" - integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU= - dependencies: - globby "^6.1.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - p-map "^1.1.1" - pify "^3.0.0" - rimraf "^2.2.8" +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +defu@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/defu/-/defu-2.0.4.tgz#09659a6e87a8fd7178be13bd43e9357ebf6d1c46" + integrity sha512-G9pEH1UUMxShy6syWk01VQSRVs3CDWtlxtZu7A+NyqjxaCA4gSlWAKDBx6QiUEKezqS8+DUlXLI14Fp05Hmpwg== + +defu@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/defu/-/defu-3.2.2.tgz#be20f4cc49b9805d54ee6b610658d53894942e97" + integrity sha512-8UWj5lNv7HD+kB0e9w77Z7TdQlbUYDVWqITLHNqFIn6khrNHv5WQo38Dcm1f6HeNyZf0U7UbPf6WeZDSdCzGDQ== + +defu@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/defu/-/defu-4.0.1.tgz#9d7d7a48f9295f08285d153dcff174c89b9bcb22" + integrity sha512-lC+G0KvvWRbisQa50+iFelm3/eMmwo4IlBmfASOVlw9MZpHHyQeVsZxc5j23+TQy5ydgEoTVSrWl7ptou1kzJQ== + +defu@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/defu/-/defu-5.0.0.tgz#5768f0d402a555bfc4c267246b20f82ce8b5a10b" + integrity sha512-VHg73EDeRXlu7oYWRmmrNp/nl7QkdXUxkQQKig0Zk8daNmm84AbGoC8Be6/VVLJEKxn12hR0UBmz8O+xQiAPKQ== del@^4.1.1: version "4.1.1" @@ -7005,26 +8040,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegate@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" - integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== - delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -denque@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" - integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -7035,6 +8055,16 @@ deprecation@^2.0.0, deprecation@^2.3.1: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== +deps-sort@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.1.tgz#9dfdc876d2bcec3386b6829ac52162cda9fa208d" + integrity sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw== + dependencies: + JSONStream "^1.0.3" + shasum-object "^1.0.0" + subarg "^1.0.0" + through2 "^2.0.0" + des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -7043,7 +8073,12 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destroy@~1.0.4: +destr@^1.0.0, destr@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/destr/-/destr-1.1.0.tgz#2da6add6ba71e04fd0abfb1e642d4f6763235095" + integrity sha512-Ev/sqS5AzzDwlpor/5wFCDu0dYMQu/0x2D6XfAsQ0E7uQmamIgYJ6Dppo2T2EOFVkeVYWjc+PCLKaqZZ57qmLg== + +destroy@^1.0.4, destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= @@ -7053,24 +8088,20 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= - dependencies: - repeating "^2.0.0" +detect-indent@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" + integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== detect-indent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= -detect-installed@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-installed/-/detect-installed-2.0.4.tgz#a0850465e7c3ebcff979d6b6535ad344b80dd7c5" - integrity sha1-oIUEZefD68/5eda2U1rTRLgN18U= - dependencies: - get-installed-path "^2.0.3" +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= detect-newline@^3.0.0: version "3.1.0" @@ -7078,9 +8109,23 @@ detect-newline@^3.0.0: integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +devalue@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/devalue/-/devalue-2.0.1.tgz#5d368f9adc0928e47b77eea53ca60d2f346f9762" + integrity sha512-I2TiqT5iWBEyB8GRfTDP0hiLZ0YeDJZ+upDxjBfOC2lebO5LezQMv7QvIUTzdb64jQyAKLf1AHADtGN+jw6v8Q== dezalgo@^1.0.0: version "1.0.3" @@ -7090,31 +8135,36 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" -diacritics@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1" - integrity sha1-PvqHMj67hj5mls67AILUj/PW96E= +diff-sequences@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" + integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== -diff-sequences@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32" - integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw== +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== -diff2html@^2.12.1: - version "2.12.2" - resolved "https://registry.yarnpkg.com/diff2html/-/diff2html-2.12.2.tgz#356d35f9c87c42ebd11558bedf1c99c5b00886e8" - integrity sha512-G/Zn1KyG/OeC+67N/P26WHsQpjrjUiRyWGvg29ypy3MxSsBmF0bzsU/Irq70i2UAg+f/MzmLx4v/Nkt01TOU3g== - dependencies: - diff "^4.0.1" - hogan.js "^3.0.2" - merge "^1.2.1" - whatwg-fetch "^3.0.0" +diff-sequences@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" + integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== + +diff3@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/diff3/-/diff3-0.0.3.tgz#d4e5c3a4cdf4e5fe1211ab42e693fcb4321580fc" + integrity sha1-1OXDpM305f4SEatC5pP8tDIVgPw= diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -7131,15 +8181,22 @@ dir-glob@^2.0.0, dir-glob@^2.2.2: dependencies: path-type "^3.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== dependencies: ip "^1.1.0" safe-buffer "^5.0.1" @@ -7164,14 +8221,6 @@ docsearch.js@^2.5.2: to-factory "^1.0.0" zepto "^1.2.0" -doctrine@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -7186,7 +8235,7 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-converter@^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" integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== @@ -7206,32 +8255,34 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom-urls@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/dom-urls/-/dom-urls-1.1.0.tgz#001ddf81628cd1e706125c7176f53ccec55d918e" - integrity sha1-AB3fgWKM0ecGElxxdvU8zsVdkY4= +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== dependencies: - urijs "^1.16.1" + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== -domain-browser@^1.1.1: +domain-browser@^1.1.1, domain-browser@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.1: +domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== domexception@^1.0.1: version "1.0.1" @@ -7240,22 +8291,21 @@ domexception@^1.0.1: dependencies: webidl-conversions "^4.0.2" -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== dependencies: - domelementtype "1" + webidl-conversions "^5.0.0" -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= +domhandler@^4.0.0, domhandler@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059" + integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA== dependencies: - dom-serializer "0" - domelementtype "1" + domelementtype "^2.2.0" -domutils@^1.5.1, domutils@^1.7.0: +domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== @@ -7263,41 +8313,58 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dont-sniff-mimetype@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz#c7d0427f8bcb095762751252af59d148b0a623b2" - integrity sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug== +domutils@^2.5.2, domutils@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.7.0.tgz#8ebaf0c41ebafcf55b0b72ec31c56323712c5442" + integrity sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" - integrity sha1-G3CK8JSknJoOfbyteQq6U52sEXc= +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== dependencies: - is-obj "^1.0.0" + no-case "^3.0.4" + tslib "^2.0.3" -dot-prop@^4.1.0, dot-prop@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" - integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== +dot-prop@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" + integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ== dependencies: is-obj "^1.0.0" -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== +dot-prop@^5.1.0, dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== dependencies: is-obj "^2.0.0" +dotenv@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" + integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== + +duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= +duplexer@^0.1.1, duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" @@ -7317,13 +8384,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - editorconfig@^0.15.3: version "0.15.3" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" @@ -7344,40 +8404,40 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== -electron-to-chromium@^1.3.363, electron-to-chromium@^1.3.47: - version "1.3.367" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.367.tgz#48abffcaa6591051b612ae70ddc657763ede2662" - integrity sha512-GCHQreWs4zhKA48FNXCjvpV4kTnKoLu2PSAfKX394g34NPvTs2pPh1+jzWitNwhmOYI8zIqt36ulRVRZUgqlfA== +ejs@^3.0.2, ejs@^3.1.5: + version "3.1.6" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" + integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + dependencies: + jake "^10.6.1" -electron-to-chromium@^1.3.413: - version "1.3.455" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.455.tgz#fd65a3f5db6ffa83eb7c84f16ea9b1b7396f537d" - integrity sha512-4lwnxp+ArqOX9hiLwLpwhfqvwzUHFuDgLz4NTiU3lhygUzWtocIJ/5Vix+mWVNE2HQ9aI1k2ncGe5H/0OktMvA== +electron-to-chromium@^1.3.811: + version "1.3.820" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.820.tgz#3b2672b59ed17847ed19f1281547f37bbfda87bb" + integrity sha512-5cFwDmo2yzEA9hn55KZ9+cX/b6DSFvpKz8Hb2fiDmriXWB+DBoXKXmncQwNRFBBTlUdsvPHCoy594OoMLAO0Tg== elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= -elliptic@^6.0.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" -emitter-listener@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" - integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== - dependencies: - shimmer "^1.2.0" +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== emoji-regex@^7.0.1: version "7.0.3" @@ -7399,22 +8459,17 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -empty-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/empty-dir/-/empty-dir-1.0.0.tgz#be3ea41ca6798dc27bb9407f035888150e4c2995" - integrity sha512-97qcDM6mUA1jAeX6cktw7akc5awIGA+VIkA5MygKOKA+c2Vseo/xwKN0JNJTUhZUtPwZboKVD2p1xu+sV/F4xA== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: - iconv-lite "~0.4.13" + iconv-lite "^0.6.2" end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" @@ -7423,43 +8478,36 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== dependencies: graceful-fs "^4.1.2" - memory-fs "^0.4.0" + memory-fs "^0.5.0" tapable "^1.0.0" -enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" - integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== +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: - graceful-fs "^4.1.2" - memory-fs "^0.5.0" - tapable "^1.0.0" + ansi-colors "^4.1.1" -ent@^2.2.0: +entities@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^1.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" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== - env-paths@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" - integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== envify@^4.0.0: version "4.1.0" @@ -7470,9 +8518,9 @@ envify@^4.0.0: through "~2.3.4" envinfo@^7.2.0, envinfo@^7.3.1: - version "7.5.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.0.tgz#91410bb6db262fb4f1409bd506e9ff57e91023f4" - integrity sha512-jDgnJaF/Btomk+m3PZDTTCb5XIIIX3zYItnCRfF73zVgvinLoRomuhi75Y4su0PtQxWz4v66XnLLckyvyJTOIQ== + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== err-code@^1.0.0: version "1.1.2" @@ -7480,9 +8528,9 @@ err-code@^1.0.0: integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA= errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== dependencies: prr "~1.0.1" @@ -7493,22 +8541,35 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: - version "1.17.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" - integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== +error-stack-parser@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + dependencies: + stackframe "^1.1.1" + +es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.11.0" object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" es-to-primitive@^1.2.1: version "1.2.1" @@ -7519,7 +8580,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es6-promise@^4.0.3, es6-promise@^4.0.5, es6-promise@^4.1.0, es6-promise@^4.2.8: +es6-promise@^4.0.3, es6-promise@^4.1.0: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -7531,6 +8592,11 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -7541,20 +8607,25 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-regexp@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/escape-regexp/-/escape-regexp-0.0.1.tgz#f44bda12d45bbdf9cb7f862ee7e4827b3dd32254" - integrity sha1-9EvaEtRbvfnLf4Yu5+SCez3TIlQ= - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.11.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" - integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escodegen@^1.9.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== dependencies: esprima "^4.0.1" estraverse "^4.2.0" @@ -7563,131 +8634,101 @@ escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -eslint-config-standard@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz#87ee0d3c9d95382dc761958cbb23da9eea31e0ba" - integrity sha512-oDdENzpViEe5fwuRCWla7AXQd++/oyIp8zP+iP9jiUPG6NBj3SHgdgtl/kTn00AjeN+1HNvavTKmYbMo+xMOlw== - -eslint-config-standard@^12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz#638b4c65db0bd5a41319f96bba1f15ddad2107d9" - integrity sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ== - -eslint-friendly-formatter@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-friendly-formatter/-/eslint-friendly-formatter-4.0.1.tgz#27d504dc837f7caddbf201b2e84a4ee730ba3efa" - integrity sha1-J9UE3IN/fK3b8gGy6EpO5zC6Pvo= +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== dependencies: - chalk "^2.0.1" - coalescy "1.0.0" - extend "^3.0.0" - minimist "^1.2.0" - strip-ansi "^4.0.0" - text-table "^0.2.0" + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" -eslint-import-resolver-node@^0.3.2: - version "0.3.3" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" - integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg== - dependencies: - debug "^2.6.9" - resolve "^1.13.1" +eslint-config-standard@^16.0.2: + version "16.0.3" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz#6c8761e544e96c531ff92642eeb87842b8488516" + integrity sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg== -eslint-loader@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.2.1.tgz#28b9c12da54057af0845e2a6112701a2f6bf8337" - integrity sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg== +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== dependencies: - loader-fs-cache "^1.0.0" - loader-utils "^1.0.2" - object-assign "^4.0.1" - object-hash "^1.1.4" - rimraf "^2.6.1" + debug "^3.2.7" + resolve "^1.20.0" -eslint-module-utils@^2.4.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz#7878f7504824e1b857dd2505b59a8e5eda26a708" - integrity sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q== +eslint-module-utils@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz#94e5540dd15fe1522e8ffa3ec8db3b7fa7e7a534" + integrity sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q== dependencies: - debug "^2.6.9" + debug "^3.2.7" pkg-dir "^2.0.0" -eslint-plugin-import@^2.12.0, eslint-plugin-import@^2.13.0: - version "2.20.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" - integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw== - dependencies: - array-includes "^3.0.3" - array.prototype.flat "^1.2.1" - contains-path "^0.1.0" - debug "^2.6.9" - doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.1" - has "^1.0.3" - minimatch "^3.0.4" - object.values "^1.1.0" - read-pkg-up "^2.0.0" - resolve "^1.12.0" - -eslint-plugin-node@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz#bf19642298064379315d7a4b2a75937376fa05e4" - integrity sha512-Q/Cc2sW1OAISDS+Ji6lZS2KV4b7ueA/WydVWd1BECTQwVvfQy5JAi3glhINoKzoMnfnuRgNP+ZWKrGAbp3QDxw== - dependencies: - ignore "^3.3.6" - minimatch "^3.0.4" - resolve "^1.3.3" - semver "^5.4.1" - -eslint-plugin-promise@^3.7.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.8.0.tgz#65ebf27a845e3c1e9d6f6a5622ddd3801694b621" - integrity sha512-JiFL9UFR15NKpHyGii1ZcvmtIqa3UTwiDAGb8atSffe43qJ3+1czVGN6UtkklpcJ2DVnqvTMzEKRaJdBkAL2aQ== - -eslint-plugin-standard@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz#2a9e21259ba4c47c02d53b2d0c9135d4b1022d47" - integrity sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w== - -eslint-plugin-vue-storefront@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue-storefront/-/eslint-plugin-vue-storefront-0.0.1.tgz#23e99e73977dfb26526c7ec00b76d43e6e93af3d" - integrity sha512-dcmI2z+OJ8sE4RY/Jptp8YiIp95oCjEBIIJhm+DoE2nIDgzs8JkIOgTm/XIMZsDQNN1/OctU4CF9WfZFgZXFCw== - dependencies: - requireindex "~1.2.0" - -eslint-plugin-vue@^4.5.0: - version "4.7.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-4.7.1.tgz#c829b9fc62582c1897b5a0b94afd44ecca511e63" - integrity sha512-esETKhVMI7Vdli70Wt4bvAwnZBJeM0pxVX9Yb0wWKxdCJc2EADalVYK/q2FzMw8oKN0wPMdqVCKS8kmR89recA== +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== dependencies: - vue-eslint-parser "^2.0.3" + eslint-utils "^2.0.0" + regexpp "^3.0.0" -eslint-plugin-vue@^5.2.2: - version "5.2.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-5.2.3.tgz#3ee7597d823b5478804b2feba9863b1b74273961" - integrity sha512-mGwMqbbJf0+VvpGR5Lllq0PMxvTdrZ/ZPjmhkacrCHbubJeJOt+T6E3HUzAifa2Mxi7RSdJfC9HFpOeSYVMMIw== +eslint-plugin-import@^2.22.1: + version "2.24.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz#2c8cd2e341f3885918ee27d18479910ade7bb4da" + integrity sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q== dependencies: - vue-eslint-parser "^5.0.0" + array-includes "^3.1.3" + array.prototype.flat "^1.2.4" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.6.2" + find-up "^2.0.0" + has "^1.0.3" + is-core-module "^2.6.0" + minimatch "^3.0.4" + object.values "^1.1.4" + pkg-up "^2.0.0" + read-pkg-up "^3.0.0" + resolve "^1.20.0" + tsconfig-paths "^3.11.0" -eslint-scope@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" - integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" -eslint-scope@^3.7.1: - version "3.7.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" - integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA== +eslint-plugin-promise@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz#61485df2a359e03149fdafc0a68b0e030ad2ac45" + integrity sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ== + +eslint-plugin-standard@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-5.0.0.tgz#c43f6925d669f177db46f095ea30be95476b1ee4" + integrity sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg== + +eslint-plugin-vue@^7.6.0: + version "7.17.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-7.17.0.tgz#41e0bdb5effca5ee26f087f924be987eb41ef573" + integrity sha512-Rq5R2QetDCgC+kBFQw1+aJ5B93tQ4xqZvoCUxuIzwTonngNArsdP8ChM8PowIzsJvRtWl4ltGh/bZcN3xhFWSw== dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" + eslint-utils "^2.1.0" + natural-compare "^1.4.0" + semver "^6.3.0" + vue-eslint-parser "^7.10.0" -eslint-scope@^4.0.0, eslint-scope@^4.0.3: +eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== @@ -7695,175 +8736,152 @@ eslint-scope@^4.0.0, eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" - integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== +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== dependencies: - esrecurse "^4.1.0" + esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^1.3.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" -eslint-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" - integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== +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 "^1.1.0" - -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" - integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== + eslint-visitor-keys "^2.0.0" -eslint@^4.16.0: - version "4.19.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" - integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ== - dependencies: - ajv "^5.3.0" - babel-code-frame "^6.22.0" - chalk "^2.1.0" - concat-stream "^1.6.0" - cross-spawn "^5.1.0" - debug "^3.1.0" - doctrine "^2.1.0" - eslint-scope "^3.7.1" - eslint-visitor-keys "^1.0.0" - espree "^3.5.4" - esquery "^1.0.0" - esutils "^2.0.2" - file-entry-cache "^2.0.0" - functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.0.1" - ignore "^3.3.3" - imurmurhash "^0.1.4" - inquirer "^3.0.6" - is-resolvable "^1.0.0" - js-yaml "^3.9.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.4" - minimatch "^3.0.2" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^7.0.0" - progress "^2.0.0" - regexpp "^1.0.1" - require-uncached "^1.0.3" - semver "^5.3.0" - strip-ansi "^4.0.0" - strip-json-comments "~2.0.1" - table "4.0.2" - text-table "~0.2.0" +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@^5.0.0: - version "5.16.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" - integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== - dependencies: - "@babel/code-frame" "^7.0.0" - ajv "^6.9.1" - chalk "^2.1.0" - cross-spawn "^6.0.5" +eslint-visitor-keys@^2.0.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@^7.20.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^4.0.3" - eslint-utils "^1.3.1" - eslint-visitor-keys "^1.0.0" - espree "^5.0.1" - esquery "^1.0.1" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.7.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.2.2" - js-yaml "^3.13.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.11" + levn "^0.4.1" + lodash.merge "^4.6.2" minimatch "^3.0.4" - mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" + optionator "^0.9.1" progress "^2.0.0" - regexpp "^2.0.1" - semver "^5.5.1" - strip-ansi "^4.0.0" - strip-json-comments "^2.0.1" - table "^5.2.3" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" text-table "^0.2.0" + v8-compile-cache "^2.0.3" -espree@^3.5.2, espree@^3.5.4: - version "3.5.4" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" - integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== - dependencies: - acorn "^5.5.0" - acorn-jsx "^3.0.0" - -espree@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-4.1.0.tgz#728d5451e0fd156c04384a7ad89ed51ff54eb25f" - integrity sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w== +espree@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== dependencies: - acorn "^6.0.2" - acorn-jsx "^5.0.0" - eslint-visitor-keys "^1.0.0" + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" -espree@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" - integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: - acorn "^6.0.7" - acorn-jsx "^5.0.0" - eslint-visitor-keys "^1.0.0" + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" esprima@^4.0.0, esprima@^4.0.1: 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.0.0, esquery@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.1.0.tgz#c5c0b66f383e7656404f86b31334d72524eddb48" - integrity sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q== +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 "^4.0.0" + estraverse "^5.1.0" -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== +esrecurse@^4.1.0, esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: - estraverse "^4.1.0" + estraverse "^5.2.0" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== +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== + +estree-walker@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" + integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== + +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + +estree-walker@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: +etag@^1.8.1, etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= @@ -7873,20 +8891,10 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter2@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-1.0.5.tgz#f983610517b1737c0b9dc643beca93893c04df18" - integrity sha1-+YNhBRexc3wLncZDvsqTiTwE3xg= - -eventemitter2@4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" - integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU= - -eventemitter2@~0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" - integrity sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas= +eventemitter2@^6.4.2: + version "6.4.4" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.4.tgz#aa96e8275c4dbeb017a5d0e03780c65612a1202b" + integrity sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw== eventemitter3@^3.1.0: version "3.1.2" @@ -7894,24 +8902,34 @@ eventemitter3@^3.1.0: integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== eventemitter3@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.0.tgz#d65176163887ee59f386d64c82610b696a4a74eb" - integrity sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg== + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= +events@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" + integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg== + events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +eventsource-polyfill@^0.9.6: + version "0.9.6" + resolved "https://registry.yarnpkg.com/eventsource-polyfill/-/eventsource-polyfill-0.9.6.tgz#10e0d187f111b167f28fdab918843ce7d818f13c" + integrity sha1-EODRh/ERsWfyj9q5GIQ859gY8Tw= eventsource@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" - integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + version "1.1.0" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.1.0.tgz#00e8ca7c92109e94b0ddf32dac677d841028cfaf" + integrity sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg== dependencies: original "^1.0.0" @@ -7924,35 +8942,9 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: safe-buffer "^5.1.1" exec-sh@^0.3.2: - version "0.3.4" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" - integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== - -execa@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" - integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== - dependencies: - cross-spawn "^6.0.0" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" - integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= - dependencies: - cross-spawn "^5.0.1" - get-stream "^3.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" + version "0.3.6" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" + integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== execa@^1.0.0: version "1.0.0" @@ -7967,10 +8959,10 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" - integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== +execa@^4.0.2, execa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== dependencies: cross-spawn "^7.0.0" get-stream "^5.0.0" @@ -7979,26 +8971,25 @@ execa@^3.2.0: merge-stream "^2.0.0" npm-run-path "^4.0.0" onetime "^5.1.0" - p-finally "^2.0.0" signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.2.tgz#ad87fb7b2d9d564f70d2b62d511bee41d5cbb240" - integrity sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" is-stream "^2.0.0" merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" strip-final-newline "^2.0.0" -executable@4.1.1: +executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== @@ -8010,6 +9001,11 @@ exit-hook@^1.0.0: resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= +exit-on-epipe@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" + integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -8035,19 +9031,31 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-25.1.0.tgz#7e8d7b06a53f7d66ec927278db3304254ee683ee" - integrity sha512-wqHzuoapQkhc3OKPlrpetsfueuEiMf3iWh0R8+duCu9PIjXoP7HgD5aeypwTnXUAjC8aMsiVDaWwlbJ1RlQ38g== +expect@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" + integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== dependencies: - "@jest/types" "^25.1.0" - ansi-styles "^4.0.0" - jest-get-type "^25.1.0" - jest-matcher-utils "^25.1.0" - jest-message-util "^25.1.0" - jest-regex-util "^25.1.0" + "@jest/types" "^24.9.0" + ansi-styles "^3.2.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.9.0" + +expect@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.0.6.tgz#a4d74fbe27222c718fff68ef49d78e26a8fd4c05" + integrity sha512-psNLt8j2kwg42jGBDSfAlU49CEZxejN1f1PlANWDZqIhBOVU/c2Pm888FcjWJzFewhIsNWfZJeLjUjtKGiPuSw== + dependencies: + "@jest/types" "^27.0.6" + ansi-styles "^5.0.0" + jest-get-type "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-regex-util "^27.0.6" -express@^4.14.0, express@^4.16.3, express@^4.17.1: +express@^4.16.3, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== @@ -8098,20 +9106,11 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: +extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^2.0.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -8135,6 +9134,21 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-css-chunks-webpack-plugin@^4.9.0: + version "4.9.0" + resolved "https://registry.yarnpkg.com/extract-css-chunks-webpack-plugin/-/extract-css-chunks-webpack-plugin-4.9.0.tgz#da5e6b1d8b39a398c817ffc98550f4ccb6d795e1" + integrity sha512-HNuNPCXRMqJDQ1OHAUehoY+0JVCnw9Y/H22FQzYVwo8Ulgew98AGDu0grnY5c7xwiXHjQa6yJ/1dxLCI/xqTyQ== + dependencies: + loader-utils "^2.0.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" + +extract-files@9.0.0, extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== + extract-from-css@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/extract-from-css/-/extract-from-css-0.4.4.tgz#1ea7df2e7c7c6eb9922fa08e8adaea486f6f8f92" @@ -8142,15 +9156,15 @@ extract-from-css@^0.4.4: dependencies: css "^2.1.0" -extract-zip@1.6.7, extract-zip@^1.6.5: - version "1.6.7" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" - integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= +extract-zip@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" + integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== dependencies: - concat-stream "1.6.2" - debug "2.6.9" - mkdirp "0.5.1" - yauzl "2.4.1" + concat-stream "^1.6.2" + debug "^2.6.9" + mkdirp "^0.5.4" + yauzl "^2.10.0" extsprintf@1.3.0: version "1.3.0" @@ -8162,20 +9176,10 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -eyes@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= - -fast-deep-equal@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" - integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "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-glob@^2.2.6: version "2.2.7" @@ -8189,37 +9193,43 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.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= -fast-text-encoding@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz#3e5ce8293409cfaa7177a71b9ca84e1b1e6f25ef" - integrity sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ== - -fastparse@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" - integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== +fast-safe-stringify@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" + integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= +fastq@^1.6.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" + integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== dependencies: - websocket-driver ">=0.5.1" + reusify "^1.0.4" -faye-websocket@~0.11.1: - version "0.11.3" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" - integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== dependencies: websocket-driver ">=0.5.1" @@ -8230,17 +9240,23 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fclone@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640" - integrity sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA= +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== -fd-slicer@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" - integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= +fbjs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165" + integrity sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg== dependencies: - pend "~1.2.0" + cross-fetch "^3.0.4" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" fd-slicer@~1.1.0: version "1.1.0" @@ -8249,15 +9265,10 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -feature-policy@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069" - integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ== - figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^1.7.0: version "1.7.0" @@ -8281,28 +9292,12 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== - dependencies: - flat-cache "^2.0.1" - -file-loader@^1.1.11: - version "1.1.11" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8" - integrity sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg== +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: - loader-utils "^1.0.2" - schema-utils "^0.4.5" + flat-cache "^3.0.4" file-loader@^3.0.1: version "3.0.1" @@ -8312,11 +9307,26 @@ file-loader@^3.0.1: loader-utils "^1.0.2" schema-utils "^1.0.0" +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filelist@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" + integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + dependencies: + minimatch "^3.0.4" + filesize@^3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317" @@ -8339,7 +9349,12 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@~1.1.2: +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= + +finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== @@ -8352,23 +9367,6 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-babel-config@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2" - integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA== - dependencies: - json5 "^0.5.1" - path-exists "^3.0.0" - -find-cache-dir@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" - integrity sha1-yN765XyKUqinhPnjHFfHQumToLk= - dependencies: - commondir "^1.0.1" - mkdirp "^0.5.1" - pkg-dir "^1.0.0" - find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -8378,6 +9376,28 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-cache-dir@^3.0.0, find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-node-modules@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c" + integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug== + dependencies: + findup-sync "^4.0.0" + merge "^2.1.0" + +find-root@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -8408,39 +9428,53 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +find-versions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" + integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== + dependencies: + semver-regex "^3.1.2" + +findup-sync@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" + integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== dependencies: detect-file "^1.0.0" is-glob "^4.0.0" - micromatch "^3.0.4" + micromatch "^4.0.2" resolve-dir "^1.0.1" -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== +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: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" + flatted "^3.1.0" + rimraf "^3.0.2" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" +flat@^5.0.0: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" - integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== +flatted@^3.1.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" + integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== + +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== flush-write-stream@^1.0.0: version "1.1.1" @@ -8450,17 +9484,17 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -fn-name@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" - integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc= +follow-redirects@^1.0.0, follow-redirects@^1.10.0: + version "1.14.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.2.tgz#cecb825047c00f5e66b142f90fed4f515dec789b" + integrity sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA== -follow-redirects@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb" - integrity sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== dependencies: - debug "^3.0.0" + is-callable "^1.1.3" for-in@^1.0.2: version "1.0.2" @@ -8477,10 +9511,10 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -fork-ts-checker-webpack-plugin@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.2.0.tgz#d13af02e24d1b17f769af6bdf41c1e849e1615cc" - integrity sha512-DTNbOhq6lRdjYprukX54JMeYJgQ0zMow+R5BMLwWxEX2NAXthIkwnV8DBmsWjwNLSUItKZM4TCCJbtgrtKBu2Q== +fork-ts-checker-webpack-plugin@^6.1.1: + version "6.3.2" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.3.2.tgz#96555f9f05c1cf44af3aef7db632489a3b6ff085" + integrity sha512-L3n1lrV20pRa7ocAuM2YW4Ux1yHM8+dV4shqPdHf1xoeG5KQhp3o0YySvNsBKBISQOCN4N2Db9DV4xYN6xXwyQ== dependencies: "@babel/code-frame" "^7.8.3" "@types/json-schema" "^7.0.5" @@ -8489,12 +9523,31 @@ fork-ts-checker-webpack-plugin@^6.2.0: cosmiconfig "^6.0.0" deepmerge "^4.2.2" fs-extra "^9.0.0" + glob "^7.1.6" memfs "^3.1.2" minimatch "^3.0.4" schema-utils "2.7.0" semver "^7.3.2" tapable "^1.0.0" +form-data@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -8504,10 +9557,10 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== fragment-cache@^0.2.1: version "0.2.1" @@ -8516,7 +9569,7 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fresh@0.5.2: +fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= @@ -8529,30 +9582,35 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= - -fs-extra@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" - integrity sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ== +fs-extra@8.1.0, fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== dependencies: - graceful-fs "^4.1.2" + graceful-fs "^4.2.0" jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" - integrity sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA= +fs-extra@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3" + integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g== dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" -fs-extra@^7.0.1: +fs-extra@^7.0.0, fs-extra@^7.0.1, fs-extra@~7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== @@ -8561,16 +9619,7 @@ fs-extra@^7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^9.0.0: +fs-extra@^9.0.0, fs-extra@^9.0.1, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -8580,17 +9629,29 @@ fs-extra@^9.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-minipass@^1.2.5: +fs-memo@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fs-memo/-/fs-memo-1.2.0.tgz#a2ec3be606b902077adbb37ec529c5ec5fb2e037" + integrity sha512-YEexkCpL4j03jn5SxaMHqcO6IuWuqm8JFUYhyCep7Ao89JIYmB8xoKhK7zXXJ9cCaNXpyNH5L3QtAmoxjoHW2w== + +fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== dependencies: minipass "^2.6.0" -fs-monkey@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.1.tgz#4a82f36944365e619f4454d9fff106553067b781" - integrity sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA== +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-monkey@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== fs-write-stream-atomic@^1.0.8: version "1.0.10" @@ -8608,19 +9669,14 @@ fs.realpath@^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.2.7: - version "1.2.11" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" - integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== dependencies: bindings "^1.5.0" nan "^2.12.1" -fsevents@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== - -fsevents@~2.3.1: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -8635,6 +9691,11 @@ fstream@^1.0.0, fstream@^1.0.12: mkdirp ">=0.5 0" rimraf "2" +fsu@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fsu/-/fsu-1.1.1.tgz#bd36d3579907c59d85b257a75b836aa9e0c31834" + integrity sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -8645,15 +9706,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -g-status@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/g-status/-/g-status-2.0.2.tgz#270fd32119e8fc9496f066fe5fe88e0a6bc78b97" - integrity sha512-kQoE9qH+T1AHKgSSD0Hkv98bobE90ILQcXAF4wvGgsr7uFqNvwmh8j+Lq3l0RVt3E3HjSbv2B9biEGcEtpHLCA== - dependencies: - arrify "^1.0.1" - matcher "^1.0.0" - simple-git "^1.85.0" - gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -8668,17 +9720,6 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaxios@^2.1.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-2.3.2.tgz#ed666826c2039b89d384907cc075595269826553" - integrity sha512-K/+py7UvKRDaEwEKlLiRKrFr+wjGjsMz5qH7Vs549QJS7cpSCOT/BbWL7pzqECflc46FcNPipjSfB+V1m8PAhw== - dependencies: - abort-controller "^3.0.0" - extend "^3.0.2" - https-proxy-agent "^5.0.0" - is-stream "^2.0.0" - node-fetch "^2.3.0" - gaze@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" @@ -8686,46 +9727,45 @@ gaze@^1.0.0: dependencies: globule "^1.0.0" -gcp-metadata@^3.0.0, gcp-metadata@^3.4.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-3.5.0.tgz#6d28343f65a6bbf8449886a0c0e4a71c77577055" - integrity sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA== - dependencies: - gaxios "^2.1.0" - json-bigint "^0.3.0" - genfun@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537" integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA== -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +get-assigned-identifiers@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" + integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== -get-caller-file@^2.0.1: +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-installed-path@^2.0.3: - version "2.1.1" - resolved "https://registry.yarnpkg.com/get-installed-path/-/get-installed-path-2.1.1.tgz#a1f33dc6b8af542c9331084e8edbe37fe2634152" - integrity sha512-Qkn9eq6tW5/q9BDVdMpB8tOHljX9OSP0jRC5TRNVA4qRc839t4g8KQaR8t0Uv0EFVL0MlyG7m/ofjEgAROtYsA== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== dependencies: - global-modules "1.0.0" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-pkg-repo@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d" @@ -8737,6 +9777,13 @@ get-pkg-repo@^1.0.0: parse-github-repo-url "^1.3.0" through2 "^2.0.0" +get-port-please@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-2.2.0.tgz#3fabbbe2f9d8e6b7c47e1cddd71fc4c593f1deac" + integrity sha512-1c7Np/cpA7XCB6IrPAdaBaJjlGHTqg4P82h/ZqyBL6dCdwRzZBOFGZO7FL2KaZ2uNvD6v8QilA7LZwMpmIggDQ== + dependencies: + fs-memo "^1.2.0" + get-port@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" @@ -8747,16 +9794,6 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= -get-stdin@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" - integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== - -get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -8765,23 +9802,28 @@ get-stream@^4.0.0, get-stream@^4.1.0: pump "^3.0.0" get-stream@^5.0.0, get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= -getos@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.1.tgz#967a813cceafee0156b0483f7cffa5b3eff029c5" - integrity sha512-oUP1rnEhAr97rkitiszGP9EgDVYnmchgFzfqRzSkgtfv7ai6tEi7Ko8GgjNXts7VLWEqrTWyhsOKLe5C5b/Zkg== +getos@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/getos/-/getos-3.2.1.tgz#0134d1f4e00eb46144c5a9c0ac4dc087cbb27dc5" + integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== dependencies: - async "2.6.1" + async "^3.2.0" getpass@^0.1.1: version "0.1.7" @@ -8790,6 +9832,11 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +git-config-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/git-config-path/-/git-config-path-2.0.0.tgz#62633d61af63af4405a5024efd325762f58a181b" + integrity sha512-qc8h1KIQbJpp+241id3GuAtkdyJ+IK+LIVtkiFTRKRrmddDzs3SI9CvP1QYmWBFvm1I/PWRwj//of8bgAc0ltA== + git-raw-commits@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.0.tgz#d92addf74440c14bcc5c83ecce3fb7f8a79118b5" @@ -8801,6 +9848,17 @@ git-raw-commits@2.0.0: split2 "^2.0.0" through2 "^2.0.0" +git-raw-commits@^2.0.0: + version "2.0.10" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.10.tgz#e2255ed9563b1c9c3ea6bd05806410290297bbc1" + integrity sha512-sHhX5lsbG9SOO6yXdlwgEMQ/ljIn7qMpAbJZCGfXX2fq5T8M5SrDnpYk9/4HswTildcIqatsWa91vty6VhWSaQ== + dependencies: + dargs "^7.0.0" + lodash "^4.17.15" + meow "^8.0.0" + split2 "^3.0.0" + through2 "^4.0.0" + git-remote-origin-url@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" @@ -8818,17 +9876,17 @@ git-semver-tags@^2.0.3: semver "^6.0.0" git-up@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.1.tgz#cb2ef086653640e721d2042fe3104857d89007c0" - integrity sha512-LFTZZrBlrCrGCG07/dm1aCjjpL1z9L3+5aEeI9SBhAqSc+kiA9Or1bgZhQFNppJX6h/f5McrvJt1mQXTFm6Qrw== + version "4.0.5" + resolved "https://registry.yarnpkg.com/git-up/-/git-up-4.0.5.tgz#e7bb70981a37ea2fb8fe049669800a1f9a01d759" + integrity sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA== dependencies: is-ssh "^1.3.0" - parse-url "^5.0.0" + parse-url "^6.0.0" -git-url-parse@^11.1.2: - version "11.1.2" - resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.1.2.tgz#aff1a897c36cc93699270587bea3dbcbbb95de67" - integrity sha512-gZeLVGY8QVKMIkckncX+iCq2/L8PlwncvDFKiWkBn9EtCfYDbliRTTp6qzyQ1VMdITUfq7293zDzfpjdiGASSQ== +git-url-parse@^11.1.2, git-url-parse@^11.4.4: + version "11.5.0" + resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.5.0.tgz#acaaf65239cb1536185b19165a24bbc754b3f764" + integrity sha512-TZYSMDeM37r71Lqg1mbnMlOqlHd7BSij9qN7XwTkRqSAYFMihGLGhfHwgqQob3GUhEneKnV4nskN9rbQw2KGxA== dependencies: git-up "^4.0.0" @@ -8839,9 +9897,13 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -"gkt@https://tgz.pm2.io/gkt-1.0.0.tgz": - version "1.0.0" - resolved "https://tgz.pm2.io/gkt-1.0.0.tgz#405502b007f319c3f47175c4474527300f2ab5ad" +glob-all@^3.1.0, glob-all@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/glob-all/-/glob-all-3.2.1.tgz#082ca81afd2247cbd3ed2149bb2630f4dc877d95" + integrity sha512-x877rVkzB3ipid577QOp+eQCR6M5ZyiwrtaYgrX/z3EThaSPFtLDwBXFHc3sH1cG0R0vFYI5SRYeWMMSEyXkUw== + dependencies: + glob "^7.1.2" + yargs "^15.3.1" glob-parent@^3.1.0: version "3.1.0" @@ -8851,14 +9913,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - dependencies: - is-glob "^4.0.1" - -glob-parent@~5.1.0: +glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -8870,10 +9925,10 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -8882,7 +9937,19 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, gl once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@~7.1.1: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-dirs@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= @@ -8890,13 +9957,13 @@ global-dirs@^0.1.0: ini "^1.3.4" global-dirs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" - integrity sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A== + version "2.1.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" + integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== dependencies: - ini "^1.3.5" + ini "1.3.7" -global-modules@1.0.0, global-modules@^1.0.0: +global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== @@ -8905,13 +9972,6 @@ global-modules@1.0.0, global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" -global-modules@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" - integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== - dependencies: - global-prefix "^3.0.0" - global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -8923,15 +9983,6 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -global-prefix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" - integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== - dependencies: - ini "^1.3.5" - kind-of "^6.0.2" - which "^1.3.1" - global@^4.3.2: version "4.4.0" resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" @@ -8940,15 +9991,41 @@ global@^4.3.2: min-document "^2.19.0" process "^0.11.10" -globals@^11.0.1, globals@^11.1.0, globals@^11.7.0: +globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== +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" + +globby@11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" + integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + +globby@^11.0.1, globby@^11.0.3, globby@^11.0.4: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" globby@^6.1.0: version "6.1.0" @@ -8988,60 +10065,14 @@ globby@^9.2.0: slash "^2.0.0" globule@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.1.tgz#90a25338f22b7fbeb527cee63c629aea754d33b9" - integrity sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g== + version "1.3.3" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.3.tgz#811919eeac1ab7344e905f2e3be80a13447973c2" + integrity sha512-mb1aYtDbIjTu4ShMB85m3UzjX9BVKe9WCzsnfMSZk+K5GpIbBOexgg4PPCt5eHDEG5/ZQAUX2Kct02zfiPLsKg== dependencies: glob "~7.1.1" - lodash "~4.17.12" + lodash "~4.17.10" minimatch "~3.0.2" -good-listener@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" - integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= - dependencies: - delegate "^3.1.2" - -google-auth-library@^5.5.0: - version "5.10.1" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-5.10.1.tgz#504ec75487ad140e68dd577c21affa363c87ddff" - integrity sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg== - dependencies: - arrify "^2.0.0" - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - fast-text-encoding "^1.0.0" - gaxios "^2.1.0" - gcp-metadata "^3.4.0" - gtoken "^4.1.0" - jws "^4.0.0" - lru-cache "^5.0.0" - -google-p12-pem@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-2.0.4.tgz#036462394e266472632a78b685f0cc3df4ef337b" - integrity sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg== - dependencies: - node-forge "^0.9.0" - -got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= - dependencies: - create-error-class "^3.0.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -9059,29 +10090,74 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + +graphql-tag@^2.10.1, graphql-tag@^2.12.0, graphql-tag@^2.12.3, graphql-tag@^2.12.5, graphql-tag@^2.2.2: + version "2.12.5" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f" + integrity sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ== + dependencies: + tslib "^2.1.0" + +graphql-tools@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-7.0.5.tgz#63e322d4fa64ef9a7331be837a4f39b374d52d66" + integrity sha512-pJ/ZVfuPEZfGO2Jlk/4mZnqa6blSiUDejTAX+q/ndAbZNUH9Xxdq+6XZy4GDjC1MivC6OnAtLbICWZ6GAvuUhQ== + dependencies: + "@graphql-tools/batch-delegate" "^7.0.0" + "@graphql-tools/batch-execute" "^7.0.0" + "@graphql-tools/code-file-loader" "^6.2.5" + "@graphql-tools/delegate" "^7.0.10" + "@graphql-tools/git-loader" "^6.2.5" + "@graphql-tools/github-loader" "^6.2.5" + "@graphql-tools/graphql-file-loader" "^6.2.5" + "@graphql-tools/graphql-tag-pluck" "^6.2.6" + "@graphql-tools/import" "^6.2.4" + "@graphql-tools/json-file-loader" "^6.2.5" + "@graphql-tools/links" "^7.0.4" + "@graphql-tools/load" "^6.2.5" + "@graphql-tools/load-files" "^6.2.4" + "@graphql-tools/merge" "^6.2.14" + "@graphql-tools/mock" "^7.0.0" + "@graphql-tools/module-loader" "^6.2.5" + "@graphql-tools/optimize" "1.0.1" + "@graphql-tools/relay-operation-optimizer" "^6.2.5" + "@graphql-tools/resolvers-composition" "^6.2.5" + "@graphql-tools/schema" "^7.0.0" + "@graphql-tools/stitch" "^7.3.0" + "@graphql-tools/url-loader" "^6.3.2" + "@graphql-tools/utils" "^7.0.1" + "@graphql-tools/wrap" "^7.0.0" + tslib "~2.2.0" + optionalDependencies: + "@apollo/client" "~3.2.5 || ~3.3.0" -graphql-tag@^2.10.3: - version "2.10.3" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03" - integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA== +graphql-ws@^4.4.1: + version "4.9.0" + resolved "https://registry.yarnpkg.com/graphql-ws/-/graphql-ws-4.9.0.tgz#5cfd8bb490b35e86583d8322f5d5d099c26e365c" + integrity sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag== -graphql@^0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" - integrity sha512-QZ5BL8ZO/B20VA8APauGBg3GyEgZ19eduvpLWoq5x7gMmWnHoy8rlQWPLmWgFvo1yNgjSEFMesmS4R6pPr7xog== +graphql@^14.5.8: + version "14.7.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72" + integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA== dependencies: - iterall "^1.2.1" + iterall "^1.2.2" + +graphql@^15.5.1: + version "15.5.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad" + integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw== gray-matter@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.2.tgz#9aa379e3acaf421193fce7d2a28cebd4518ac454" - integrity sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw== + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== dependencies: - js-yaml "^3.11.0" + js-yaml "^3.13.1" kind-of "^6.0.2" section-matter "^1.0.0" strip-bom-string "^1.0.0" @@ -9091,16 +10167,6 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -gtoken@^4.1.0: - version "4.1.4" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-4.1.4.tgz#925ff1e7df3aaada06611d30ea2d2abf60fcd6a7" - integrity sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA== - dependencies: - gaxios "^2.1.0" - google-p12-pem "^2.0.0" - jws "^4.0.0" - mime "^2.2.0" - gzip-size@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -9109,19 +10175,37 @@ gzip-size@^5.0.0: duplexer "^0.1.1" pify "^4.0.1" +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +hable@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hable/-/hable-3.0.0.tgz#6de089b2df946635cf8134b9e4859f1b62de255f" + integrity sha512-7+G0/2/COR8pwteYFqHIVYfQpuEiO2HXwJrhCBJVgrNrl9O5eaUoJVDGXUJX+0RpGncNVTuestexjk1afj01wQ== + +hammerjs@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" + integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE= + handle-thing@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" - integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -handlebars@^4.4.0: - version "4.7.3" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.3.tgz#8ece2797826886cf8082d1726ff21d2a022550ee" - integrity sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg== +handlebars@^4.7.6: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== dependencies: + minimist "^1.2.5" neo-async "^2.6.0" - optimist "^0.6.1" source-map "^0.6.1" + wordwrap "^1.0.0" optionalDependencies: uglify-js "^3.1.4" @@ -9130,14 +10214,38 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0, har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== dependencies: - ajv "^6.5.5" + ajv "^6.12.3" har-schema "^2.0.0" +hard-rejection@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" + integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== + +hard-source-webpack-plugin@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.13.1.tgz#a99071e25b232f1438a5bc3c99f10a3869e4428e" + integrity sha512-r9zf5Wq7IqJHdVAQsZ4OP+dcUSvoHqDMxJlIzaE2J0TZWn3UjMMrHqwDHR8Jr/pzPfG7XxSe36E7Y8QGNdtuAw== + dependencies: + chalk "^2.4.1" + find-cache-dir "^2.0.0" + graceful-fs "^4.1.11" + lodash "^4.15.0" + mkdirp "^0.5.1" + node-object-hash "^1.2.0" + parse-json "^4.0.0" + pkg-dir "^3.0.0" + rimraf "^2.6.2" + semver "^5.6.0" + tapable "^1.0.0-beta.5" + webpack-sources "^1.0.1" + write-json-file "^2.3.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -9145,6 +10253,11 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -9155,10 +10268,17 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" has-unicode@^2.0.0, has-unicode@^2.0.1: version "2.0.1" @@ -9209,18 +10329,24 @@ has@^1.0.0, has@^1.0.3: function-bind "^1.1.1" hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" hash-sum@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ= +hash-sum@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" + integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== + hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" @@ -9229,67 +10355,25 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hasha@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1" - integrity sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE= +hasha@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== dependencies: - is-stream "^1.0.1" - pinkie-promise "^2.0.0" + is-stream "^2.0.0" + type-fest "^0.8.0" -he@1.2.x, he@^1.1.0, he@^1.2.0: +he@1.2.0, he@1.2.x, he@^1.1.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -helmet-crossdomain@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e" - integrity sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA== - -helmet-csp@2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.10.0.tgz#685dde1747bc16c5e28ad9d91e229a69f0a85e84" - integrity sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w== - dependencies: - bowser "2.9.0" - camelize "1.0.0" - content-security-policy-builder "2.1.0" - dasherize "2.0.0" - -helmet@^3.23.3: - version "3.23.3" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.23.3.tgz#5ba30209c5f73ded4ab65746a3a11bedd4579ab7" - integrity sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA== - dependencies: - depd "2.0.0" - dont-sniff-mimetype "1.1.0" - feature-policy "0.3.0" - helmet-crossdomain "0.4.0" - helmet-csp "2.10.0" - hide-powered-by "1.1.0" - hpkp "2.0.0" - hsts "2.2.0" - nocache "2.1.0" - referrer-policy "1.2.0" - x-xss-protection "1.3.0" - hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -hex2dec@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/hex2dec/-/hex2dec-1.1.2.tgz#8e1ce4bef36a74f7d5723c3fb3090c2860077338" - integrity sha512-Yu+q/XWr2fFQ11tHxPq4p4EiNkb2y+lAacJNhAdRXVfRIcDH6gi7htWFnnlIzvqHMHoWeIsfXlNAjZInpAOJDA== - -hide-powered-by@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.1.0.tgz#be3ea9cab4bdb16f8744be873755ca663383fa7a" - integrity sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg== - -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -9306,13 +10390,12 @@ hogan.js@^3.0.2: mkdirp "0.3.0" nopt "1.0.10" -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" + react-is "^16.7.0" homedir-polyfill@^1.0.1: version "1.0.3" @@ -9327,9 +10410,16 @@ hoopy@^0.1.4: integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hosted-git-info@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" + integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== + dependencies: + lru-cache "^6.0.0" hpack.js@^2.1.6: version "2.1.6" @@ -9341,11 +10431,6 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -hpkp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hpkp/-/hpkp-2.0.0.tgz#10e142264e76215a5d30c44ec43de64dee6d1672" - integrity sha1-EOFCJk52IVpdMMROxD3mTe5tFnI= - hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -9356,18 +10441,6 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -hsts@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/hsts/-/hsts-2.2.0.tgz#09119d42f7a8587035d027dda4522366fe75d964" - integrity sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ== - dependencies: - depd "2.0.0" - -html-comment-regex@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" @@ -9375,15 +10448,35 @@ html-encoding-sniffer@^1.0.2: dependencies: whatwg-encoding "^1.0.1" -html-entities@^1.2.0, html-entities@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" - integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-entities@^1.2.0, html-entities@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== html-escaper@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" - integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig== + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== + dependencies: + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" html-minifier@^3.2.3: version "3.5.21" @@ -9416,30 +10509,40 @@ html-tags@^2.0.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= -html-webpack-plugin@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" - integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s= - dependencies: - html-minifier "^3.2.3" - loader-utils "^0.2.16" - lodash "^4.17.3" - pretty-error "^2.0.2" - tapable "^1.0.0" - toposort "^1.0.0" +html-tags@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" + integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== + +html-webpack-plugin@^4.5.1: + version "4.5.2" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz#76fc83fa1a0f12dd5f7da0404a54e2699666bc12" + integrity sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A== + dependencies: + "@types/html-minifier-terser" "^5.0.0" + "@types/tapable" "^1.0.5" + "@types/webpack" "^4.41.8" + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.20" + pretty-error "^2.1.1" + tapable "^1.1.3" util.promisify "1.0.0" -htmlparser2@^3.3.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== +htmlescape@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" + integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E= + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" http-cache-semantics@^3.8.1: version "3.8.1" @@ -9488,10 +10591,10 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -"http-parser-js@>=0.4.0 <0.4.11": - version "0.4.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" - integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= +http-parser-js@>=0.5.1: + version "0.5.3" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== http-proxy-agent@^2.1.0: version "2.1.0" @@ -9501,7 +10604,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-agent@^4.0.0: +http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== @@ -9521,9 +10624,9 @@ http-proxy-middleware@0.19.1: micromatch "^3.1.10" http-proxy@^1.17.0: - version "1.18.0" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a" - integrity sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ== + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== dependencies: eventemitter3 "^4.0.0" follow-redirects "^1.0.0" @@ -9564,6 +10667,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -9571,57 +10679,52 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -humps@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/humps/-/humps-1.1.0.tgz#99a05cc80b13ae754a3d1e1a92182f271ef1d98f" - integrity sha1-maBcyAsTrnVKPR4akhgvJx7x2Y8= - -husky@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/husky/-/husky-2.7.0.tgz#c0a9a6a3b51146224e11bba0b46bba546e461d05" - integrity sha512-LIi8zzT6PyFpcYKdvWRCn/8X+6SuG2TgYYMrM6ckEYhlp44UcEduVymZGIZNLiwOUjrEud+78w/AsAiqJA/kRg== +husky@^4.2.3: + version "4.3.8" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" + integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== dependencies: - cosmiconfig "^5.2.0" - execa "^1.0.0" - find-up "^3.0.0" - get-stdin "^7.0.0" - is-ci "^2.0.0" - pkg-dir "^4.1.0" - please-upgrade-node "^3.1.1" - read-pkg "^5.1.1" - run-node "^1.0.0" + chalk "^4.0.0" + ci-info "^2.0.0" + compare-versions "^3.6.0" + cosmiconfig "^7.0.0" + find-versions "^4.0.0" + opencollective-postinstall "^2.0.2" + pkg-dir "^5.0.0" + please-upgrade-node "^3.2.0" slash "^3.0.0" + which-pm-runs "^1.0.0" -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-replace-symbols@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" integrity sha1-Bupvg2ead0njhs/h/oEq5dsiPe0= -icss-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" - integrity sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI= - dependencies: - postcss "^6.0.1" - -icss-utils@^4.1.0: +icss-utils@^4.0.0, icss-utils@^4.1.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== dependencies: postcss "^7.0.14" -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +ieee754@^1.1.13, ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== iferr@^0.1.5: version "0.1.5" @@ -9629,13 +10732,13 @@ iferr@^0.1.5: integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= ignore-walk@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" - integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + version "3.0.4" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335" + integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ== dependencies: minimatch "^3.0.4" -ignore@^3.3.3, ignore@^3.3.5, ignore@^3.3.6: +ignore@^3.3.5: version "3.3.10" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== @@ -9645,15 +10748,20 @@ ignore@^4.0.3, ignore@^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.1, 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== + immediate@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" - integrity sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw= + version "3.3.0" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" + integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= +immutable@~3.7.6: + version "3.7.6" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" + integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks= import-cwd@^2.0.0: version "2.1.0" @@ -9670,21 +10778,25 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" - integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" -import-fresh@^3.1.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== +import-from@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" + integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" + resolve-from "^5.0.0" + +import-from@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-4.0.0.tgz#2710b8d66817d232e16f4166e319248d3d5492e2" + integrity sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ== import-from@^2.1.0: version "2.1.0" @@ -9698,7 +10810,12 @@ import-lazy@^2.1.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= -import-local@2.0.0, import-local@^2.0.0: +import-lazy@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" + integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== + +import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== @@ -9720,9 +10837,9 @@ imurmurhash@^0.1.4: integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= in-publish@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" - integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= + version "2.0.1" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" + integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== indent-string@^2.1.0: version "2.1.0" @@ -9736,6 +10853,11 @@ indent-string@^3.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -9754,7 +10876,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -9769,10 +10891,15 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +ini@1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== + ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== init-package-json@^1.10.3: version "1.10.3" @@ -9788,27 +10915,14 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" -inquirer@^3.0.6, inquirer@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== +inline-source-map@~0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" + integrity sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU= dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" + source-map "~0.5.3" -inquirer@^6.2.0, inquirer@^6.2.2, inquirer@^6.3.1: +inquirer@6.5.2, inquirer@^6.2.0: version "6.5.2" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== @@ -9827,6 +10941,41 @@ inquirer@^6.2.0, inquirer@^6.2.2, inquirer@^6.3.1: strip-ansi "^5.1.0" through "^2.3.6" +inquirer@^7.3.3: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +insert-module-globals@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.2.1.tgz#d5e33185181a4e1f33b15f7bf100ee91890d5cb3" + integrity sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg== + dependencies: + JSONStream "^1.0.3" + acorn-node "^1.5.2" + combine-source-map "^0.8.0" + concat-stream "^1.6.1" + is-buffer "^1.1.0" + path-is-absolute "^1.0.1" + process "~0.11.0" + through2 "^2.0.0" + undeclared-identifiers "^1.1.2" + xtend "^4.0.0" + internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -9835,20 +10984,14 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -interpret@1.2.0, interpret@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== - -intl-locales-supported@^1.8.2: - version "1.8.4" - resolved "https://registry.yarnpkg.com/intl-locales-supported/-/intl-locales-supported-1.8.4.tgz#e1d19812afa50dc2e2a2b4741ceb4030522d45b1" - integrity sha512-wO0JhDqhshhkq8Pa9CLcstqd1aCXjfMgfMzjD6mDreS3mTSDbjGiMU+07O8BdJGxed7Q0Wf3TFVjGq0W3Y0n1w== - -intl@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde" - integrity sha1-giRKIZDE5Bn4Nx9ao02qNCDiq94= +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" @@ -9857,31 +11000,6 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - -ioredis@^4.0.0: - version "4.16.0" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.16.0.tgz#44d51288e20da14e5b6f687b2db0312e69106556" - integrity sha512-tlalhtuvnxXJNtrPjec1nGicuOCpi9ErYV/fRfwaWSzktX9ESrzHlcFwj1pVAL326E8dmt7h9pPQZyyVPPksRA== - dependencies: - cluster-key-slot "^1.1.0" - debug "^4.1.1" - denque "^1.1.0" - lodash.defaults "^4.2.0" - lodash.flatten "^4.4.0" - redis-commands "1.5.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^2.0.1" - ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -9922,9 +11040,12 @@ is-accessor-descriptor@^1.0.0: kind-of "^6.0.0" is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-arrayish@^0.2.1: version "0.2.1" @@ -9936,6 +11057,13 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -9950,17 +11078,25 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.1.5: +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.0, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-ci@1.2.1, is-ci@^1.0.10: +is-ci@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== @@ -9974,6 +11110,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-ci@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" + integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== + dependencies: + ci-info "^3.1.1" + is-color-stop@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" @@ -9986,6 +11129,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.1.0, is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" + integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -10001,9 +11151,11 @@ is-data-descriptor@^1.0.0: kind-of "^6.0.0" is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" is-descriptor@^0.1.0: version "0.1.6" @@ -10028,6 +11180,11 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= +is-docker@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -10072,6 +11229,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-glob@4.0.1, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -10079,39 +11243,46 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" +is-https@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-https/-/is-https-3.0.2.tgz#4d24e002e47edd3f1b07f14bc722433354ccba49" + integrity sha512-jFgAKhbNF7J+lTMJxbq5z9bf1V9f8rXn9mP5RSY2GUEW5M0nOiVhVC9dNra96hQDjGpNzskIzusUnXwngqmhAA== -is-installed-globally@0.1.0, is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" +is-https@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-https/-/is-https-4.0.0.tgz#9ee725a334fb517b988278d2674efc96e4f348ed" + integrity sha512-FeMLiqf8E5g6SdiVJsPcNZX8k4h2fBs1wp5Bb6uaNxn58ufK1axBqQZdmAQsqh0t9BuwFObybrdVJh6MKyPlyg== -is-installed-globally@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.1.tgz#679afef819347a72584617fd19497f010b8ed35f" - integrity sha512-oiEcGoQbGc+3/iijAijrK2qFpkNoNjsHOm/5V5iaeydyrS/hnwaRCEgH5cpW0P3T1lSjV5piB7S5b5lEugNLhg== +is-installed-globally@^0.3.1, is-installed-globally@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" + integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== dependencies: global-dirs "^2.0.1" is-path-inside "^3.0.1" -is-npm@^1.0.0: +is-module@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-npm@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -10141,23 +11312,11 @@ is-observable@^1.1.0: dependencies: symbol-observable "^1.1.0" -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= - is-path-cwd@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-in-cwd@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" - integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ== - dependencies: - is-path-inside "^1.0.0" - is-path-in-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" @@ -10165,13 +11324,6 @@ is-path-in-cwd@^2.0.0: dependencies: is-path-inside "^2.1.0" -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= - dependencies: - path-is-inside "^1.0.1" - is-path-inside@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" @@ -10180,9 +11332,9 @@ is-path-inside@^2.1.0: path-is-inside "^1.0.2" is-path-inside@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017" - integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg== + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" @@ -10196,29 +11348,33 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-plain-object@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.0.tgz#47bfc5da1b5d50d64110806c199359482e75a928" - integrity sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg== - dependencies: - isobject "^4.0.0" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= +is-promise@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== -is-regex@^1.0.4, is-regex@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" - integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== +is-promise@^2.1.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + +is-regex@^1.0.4, is-regex@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: - has "^1.0.3" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-regexp@^1.0.0: version "1.0.0" @@ -10230,46 +11386,36 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-retry-allowed@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - is-ssh@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3" - integrity sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg== + version "1.3.3" + resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.3.tgz#7f133285ccd7f2c2c7fc897b771b53d95a2b2c7e" + integrity sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ== dependencies: protocols "^1.1.0" -is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -is-svg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" - integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: - html-comment-regex "^1.1.0" + has-tostringtag "^1.0.0" -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - has-symbols "^1.0.1" + has-symbols "^1.0.2" is-text-path@^1.0.1: version "1.0.1" @@ -10283,7 +11429,12 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-utf8@^0.2.0: +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= @@ -10303,26 +11454,11 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -is-wsl@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" - integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== - is-yarn-global@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== -is@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" - integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -10350,11 +11486,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isobject@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" - integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== - isomorphic-fetch@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" @@ -10363,12 +11494,42 @@ isomorphic-fetch@^2.2.1: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -isstream@0.1.x, isstream@~0.1.2: +isomorphic-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" + integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== + dependencies: + node-fetch "^2.6.1" + whatwg-fetch "^3.4.1" + +isomorphic-git@^1.9.2: + version "1.10.0" + resolved "https://registry.yarnpkg.com/isomorphic-git/-/isomorphic-git-1.10.0.tgz#59a4604d1190d1e7fc52172085da25e6a428bc07" + integrity sha512-CijspEYaOQAnsHWXyq8ICZXzLJ/1wYQAa0jdfLcugA/68oNzrxykjGZz8Up7B8huA1VfkFHm4VviExtj/zpViw== + dependencies: + async-lock "^1.1.0" + clean-git-ref "^2.0.1" + crc-32 "^1.2.0" + diff3 "0.0.3" + ignore "^5.1.4" + minimisted "^2.0.0" + pako "^1.0.10" + pify "^4.0.1" + readable-stream "^3.4.0" + sha.js "^2.4.9" + simple-get "^3.0.2" + +isomorphic-ws@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + +isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-lib-coverage@^2.0.5: +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== @@ -10378,7 +11539,7 @@ istanbul-lib-coverage@^3.0.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== -istanbul-lib-instrument@^3.3.0: +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== @@ -10391,19 +11552,25 @@ istanbul-lib-instrument@^3.3.0: istanbul-lib-coverage "^2.0.5" semver "^6.0.0" -istanbul-lib-instrument@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6" - integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg== +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== dependencies: "@babel/core" "^7.7.5" - "@babel/parser" "^7.7.5" - "@babel/template" "^7.7.4" - "@babel/traverse" "^7.7.4" "@istanbuljs/schema" "^0.1.2" istanbul-lib-coverage "^3.0.0" semver "^6.3.0" +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== + dependencies: + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" + istanbul-lib-report@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" @@ -10413,6 +11580,17 @@ istanbul-lib-report@^3.0.0: make-dir "^3.0.0" supports-color "^7.1.0" +istanbul-lib-source-maps@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + istanbul-lib-source-maps@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" @@ -10422,143 +11600,304 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.0.tgz#d4d16d035db99581b6194e119bbf36c963c5eb70" - integrity sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A== +istanbul-reports@^2.2.6: + version "2.2.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" + integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== + dependencies: + html-escaper "^2.0.0" + +istanbul-reports@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterall@^1.2.1: +iterall@^1.2.1, iterall@^1.2.2: version "1.3.0" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== +jake@^10.6.1: + version "10.8.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" + integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== + dependencies: + async "0.9.x" + chalk "^2.4.2" + filelist "^1.0.1" + minimatch "^3.0.4" + javascript-stringify@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-1.6.0.tgz#142d111f3a6e3dae8f4a9afd77d45855b5a9cce3" integrity sha1-FC0RHzpuPa6PSpr9d9RYVbWpzOM= javascript-stringify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.0.1.tgz#6ef358035310e35d667c675ed63d3eb7c1aa19e5" - integrity sha512-yV+gqbd5vaOYjqlbk16EG89xB5udgjqQF3C5FAORDg4f/IS1Yc5ERCv5e/57yBcfJYw05V5JyIXabhwb75Xxow== - -jest-changed-files@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.1.0.tgz#73dae9a7d9949fdfa5c278438ce8f2ff3ec78131" - integrity sha512-bdL1aHjIVy3HaBO3eEQeemGttsq1BDlHgWcOjEOIAcga7OOEGWHD2WSu8HhL7I1F0mFFyci8VKU4tRNk+qtwDA== - dependencies: - "@jest/types" "^25.1.0" - execa "^3.2.0" - throat "^5.0.0" - -jest-cli@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.1.0.tgz#75f0b09cf6c4f39360906bf78d580be1048e4372" - integrity sha512-p+aOfczzzKdo3AsLJlhs8J5EW6ffVidfSZZxXedJ0mHPBOln1DccqFmGCoO8JWd4xRycfmwy1eoQkMsF8oekPg== - dependencies: - "@jest/core" "^25.1.0" - "@jest/test-result" "^25.1.0" - "@jest/types" "^25.1.0" - chalk "^3.0.0" + version "2.1.0" + resolved "https://registry.yarnpkg.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79" + integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg== + +jest-changed-files@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" + integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== + dependencies: + "@jest/types" "^24.9.0" + execa "^1.0.0" + throat "^4.0.0" + +jest-changed-files@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.0.6.tgz#bed6183fcdea8a285482e3b50a9a7712d49a7a8b" + integrity sha512-BuL/ZDauaq5dumYh5y20sn4IISnf1P9A0TDswTxUi84ORGtVa86ApuBHqICL0vepqAnZiY6a7xeSPWv2/yy4eA== + dependencies: + "@jest/types" "^27.0.6" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.0.6.tgz#dd4df17c4697db6a2c232aaad4e9cec666926668" + integrity sha512-OJlsz6BBeX9qR+7O9lXefWoc2m9ZqcZ5Ohlzz0pTEAG4xMiZUJoacY8f4YDHxgk0oKYxj277AfOk9w6hZYvi1Q== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.0.6" + is-generator-fn "^2.0.0" + jest-each "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-runtime "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + pretty-format "^27.0.6" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af" + integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== + dependencies: + "@jest/core" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" exit "^0.1.2" - import-local "^3.0.2" + import-local "^2.0.0" is-ci "^2.0.0" - jest-config "^25.1.0" - jest-util "^25.1.0" - jest-validate "^25.1.0" + jest-config "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" prompts "^2.0.1" realpath-native "^1.1.0" - yargs "^15.0.0" + yargs "^13.3.0" + +jest-cli@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.0.6.tgz#d021e5f4d86d6a212450d4c7b86cb219f1e6864f" + integrity sha512-qUUVlGb9fdKir3RDE+B10ULI+LQrz+MCflEH2UJyoUjoHHCbxDrMxSzjQAPUMsic4SncI62ofYCcAvW6+6rhhg== + dependencies: + "@jest/core" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + jest-config "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" + prompts "^2.0.1" + yargs "^16.0.3" -jest-config@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.1.0.tgz#d114e4778c045d3ef239452213b7ad3ec1cbea90" - integrity sha512-tLmsg4SZ5H7tuhBC5bOja0HEblM0coS3Wy5LTCb2C8ZV6eWLewHyK+3qSq9Bi29zmWQ7ojdCd3pxpx4l4d2uGw== +jest-config@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5" + integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^25.1.0" - "@jest/types" "^25.1.0" - babel-jest "^25.1.0" - chalk "^3.0.0" + "@jest/test-sequencer" "^24.9.0" + "@jest/types" "^24.9.0" + babel-jest "^24.9.0" + chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^25.1.0" - jest-environment-node "^25.1.0" - jest-get-type "^25.1.0" - jest-jasmine2 "^25.1.0" - jest-regex-util "^25.1.0" - jest-resolve "^25.1.0" - jest-util "^25.1.0" - jest-validate "^25.1.0" - micromatch "^4.0.2" - pretty-format "^25.1.0" + jest-environment-jsdom "^24.9.0" + jest-environment-node "^24.9.0" + jest-get-type "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + micromatch "^3.1.10" + pretty-format "^24.9.0" realpath-native "^1.1.0" -jest-diff@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad" - integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw== +jest-config@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.0.6.tgz#119fb10f149ba63d9c50621baa4f1f179500277f" + integrity sha512-JZRR3I1Plr2YxPBhgqRspDE2S5zprbga3swYNrvY3HfQGu7p/GjyLOqwrYad97tX3U3mzT53TPHVmozacfP/3w== dependencies: - chalk "^3.0.0" - diff-sequences "^25.1.0" - jest-get-type "^25.1.0" - pretty-format "^25.1.0" + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^27.0.6" + "@jest/types" "^27.0.6" + babel-jest "^27.0.6" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + is-ci "^3.0.0" + jest-circus "^27.0.6" + jest-environment-jsdom "^27.0.6" + jest-environment-node "^27.0.6" + jest-get-type "^27.0.6" + jest-jasmine2 "^27.0.6" + jest-regex-util "^27.0.6" + jest-resolve "^27.0.6" + jest-runner "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" + micromatch "^4.0.4" + pretty-format "^27.0.6" + +jest-diff@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" + integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-diff@^26.0.0: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + +jest-diff@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.0.6.tgz#4a7a19ee6f04ad70e0e3388f35829394a44c7b5e" + integrity sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.0.6" + jest-get-type "^27.0.6" + pretty-format "^27.0.6" + +jest-docblock@^24.3.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" + integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== + dependencies: + detect-newline "^2.1.0" -jest-docblock@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.1.0.tgz#0f44bea3d6ca6dfc38373d465b347c8818eccb64" - integrity sha512-370P/mh1wzoef6hUKiaMcsPtIapY25suP6JqM70V9RJvdKLrV4GaGbfUseUVk4FZJw4oTZ1qSCJNdrClKt5JQA== +jest-docblock@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" + integrity sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA== dependencies: detect-newline "^3.0.0" -jest-each@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.1.0.tgz#a6b260992bdf451c2d64a0ccbb3ac25e9b44c26a" - integrity sha512-R9EL8xWzoPySJ5wa0DXFTj7NrzKpRD40Jy+zQDp3Qr/2QmevJgkN9GqioCGtAJ2bW9P/MQRznQHQQhoeAyra7A== +jest-each@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" + integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== dependencies: - "@jest/types" "^25.1.0" - chalk "^3.0.0" - jest-get-type "^25.1.0" - jest-util "^25.1.0" - pretty-format "^25.1.0" - -jest-environment-jsdom@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.1.0.tgz#6777ab8b3e90fd076801efd3bff8e98694ab43c3" - integrity sha512-ILb4wdrwPAOHX6W82GGDUiaXSSOE274ciuov0lztOIymTChKFtC02ddyicRRCdZlB5YSrv3vzr1Z5xjpEe1OHQ== - dependencies: - "@jest/environment" "^25.1.0" - "@jest/fake-timers" "^25.1.0" - "@jest/types" "^25.1.0" - jest-mock "^25.1.0" - jest-util "^25.1.0" - jsdom "^15.1.1" - -jest-environment-node@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.1.0.tgz#797bd89b378cf0bd794dc8e3dca6ef21126776db" - integrity sha512-U9kFWTtAPvhgYY5upnH9rq8qZkj6mYLup5l1caAjjx9uNnkLHN2xgZy5mo4SyLdmrh/EtB9UPpKFShvfQHD0Iw== - dependencies: - "@jest/environment" "^25.1.0" - "@jest/fake-timers" "^25.1.0" - "@jest/types" "^25.1.0" - jest-mock "^25.1.0" - jest-util "^25.1.0" - -jest-fetch-mock@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.1.tgz#4dde94c9e91fa76bceb6001f54e33039f1f02da6" - integrity sha512-R5GVbQLVjk+PCfzf4vFoYDXt+oCEWuYigyaAnucdeVCXkElAgAYXDFFikHxLyCVjSNDc7TCYQZ1ItLNOE6OCrQ== + "@jest/types" "^24.9.0" + chalk "^2.0.1" + jest-get-type "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + +jest-each@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.0.6.tgz#cee117071b04060158dc8d9a66dc50ad40ef453b" + integrity sha512-m6yKcV3bkSWrUIjxkE9OC0mhBZZdhovIW5ergBYirqnkLXkyEn3oUUF/QZgyecA1cF1QFyTE8bRRl8Tfg1pfLA== dependencies: - cross-fetch "^3.0.4" - promise-polyfill "^8.1.3" + "@jest/types" "^27.0.6" + chalk "^4.0.0" + jest-get-type "^27.0.6" + jest-util "^27.0.6" + pretty-format "^27.0.6" + +jest-environment-jsdom@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" + integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + jsdom "^11.5.1" + +jest-environment-jsdom@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.0.6.tgz#f66426c4c9950807d0a9f209c590ce544f73291f" + integrity sha512-FvetXg7lnXL9+78H+xUAsra3IeZRTiegA3An01cWeXBspKXUhAwMM9ycIJ4yBaR0L7HkoMPaZsozCLHh4T8fuw== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/fake-timers" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + jest-mock "^27.0.6" + jest-util "^27.0.6" + jsdom "^16.6.0" + +jest-environment-node@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" + integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + +jest-environment-node@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.0.6.tgz#a6699b7ceb52e8d68138b9808b0c404e505f3e07" + integrity sha512-+Vi6yLrPg/qC81jfXx3IBlVnDTI6kmRr08iVa2hFCWmJt4zha0XW7ucQltCAPhSR0FEKEoJ3i+W4E6T0s9is0w== + dependencies: + "@jest/environment" "^27.0.6" + "@jest/fake-timers" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + jest-mock "^27.0.6" + jest-util "^27.0.6" + +jest-get-type@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" + integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== -jest-get-type@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876" - integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw== +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-get-type@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.0.6.tgz#0eb5c7f755854279ce9b68a9f1a4122f69047cfe" + integrity sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg== jest-haste-map@^24.9.0: version "24.9.0" @@ -10579,64 +11918,107 @@ jest-haste-map@^24.9.0: optionalDependencies: fsevents "^1.2.7" -jest-haste-map@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.1.0.tgz#ae12163d284f19906260aa51fd405b5b2e5a4ad3" - integrity sha512-/2oYINIdnQZAqyWSn1GTku571aAfs8NxzSErGek65Iu5o8JYb+113bZysRMcC/pjE5v9w0Yz+ldbj9NxrFyPyw== +jest-haste-map@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.0.6.tgz#4683a4e68f6ecaa74231679dca237279562c8dc7" + integrity sha512-4ldjPXX9h8doB2JlRzg9oAZ2p6/GpQUNAeiYXqcpmrKbP0Qev0wdZlxSMOmz8mPOEnt4h6qIzXFLDi8RScX/1w== dependencies: - "@jest/types" "^25.1.0" + "@jest/types" "^27.0.6" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" - graceful-fs "^4.2.3" - jest-serializer "^25.1.0" - jest-util "^25.1.0" - jest-worker "^25.1.0" - micromatch "^4.0.2" - sane "^4.0.3" + graceful-fs "^4.2.4" + jest-regex-util "^27.0.6" + jest-serializer "^27.0.6" + jest-util "^27.0.6" + jest-worker "^27.0.6" + micromatch "^4.0.4" walker "^1.0.7" optionalDependencies: - fsevents "^2.1.2" + fsevents "^2.3.2" -jest-jasmine2@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.1.0.tgz#681b59158a430f08d5d0c1cce4f01353e4b48137" - integrity sha512-GdncRq7jJ7sNIQ+dnXvpKO2MyP6j3naNK41DTTjEAhLEdpImaDA9zSAZwDhijjSF/D7cf4O5fdyUApGBZleaEg== +jest-jasmine2@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" + integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^25.1.0" - "@jest/source-map" "^25.1.0" - "@jest/test-result" "^25.1.0" - "@jest/types" "^25.1.0" - chalk "^3.0.0" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" co "^4.6.0" - expect "^25.1.0" + expect "^24.9.0" is-generator-fn "^2.0.0" - jest-each "^25.1.0" - jest-matcher-utils "^25.1.0" - jest-message-util "^25.1.0" - jest-runtime "^25.1.0" - jest-snapshot "^25.1.0" - jest-util "^25.1.0" - pretty-format "^25.1.0" - throat "^5.0.0" - -jest-leak-detector@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.1.0.tgz#ed6872d15aa1c72c0732d01bd073dacc7c38b5c6" - integrity sha512-3xRI264dnhGaMHRvkFyEKpDeaRzcEBhyNrOG5oT8xPxOyUAblIAQnpiR3QXu4wDor47MDTiHbiFcbypdLcLW5w== - dependencies: - jest-get-type "^25.1.0" - pretty-format "^25.1.0" - -jest-matcher-utils@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.1.0.tgz#fa5996c45c7193a3c24e73066fc14acdee020220" - integrity sha512-KGOAFcSFbclXIFE7bS4C53iYobKI20ZWleAdAFun4W1Wz1Kkej8Ng6RRbhL8leaEvIOjGXhGf/a1JjO8bkxIWQ== + jest-each "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + throat "^4.0.0" + +jest-jasmine2@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.0.6.tgz#fd509a9ed3d92bd6edb68a779f4738b100655b37" + integrity sha512-cjpH2sBy+t6dvCeKBsHpW41mjHzXgsavaFMp+VWRf0eR4EW8xASk1acqmljFtK2DgyIECMv2yCdY41r2l1+4iA== dependencies: - chalk "^3.0.0" - jest-diff "^25.1.0" - jest-get-type "^25.1.0" - pretty-format "^25.1.0" + "@babel/traverse" "^7.1.0" + "@jest/environment" "^27.0.6" + "@jest/source-map" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.0.6" + is-generator-fn "^2.0.0" + jest-each "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-runtime "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + pretty-format "^27.0.6" + throat "^6.0.1" + +jest-leak-detector@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" + integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== + dependencies: + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-leak-detector@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.0.6.tgz#545854275f85450d4ef4b8fe305ca2a26450450f" + integrity sha512-2/d6n2wlH5zEcdctX4zdbgX8oM61tb67PQt4Xh8JFAIy6LRKUnX528HulkaG6nD5qDl5vRV1NXejCe1XRCH5gQ== + dependencies: + jest-get-type "^27.0.6" + pretty-format "^27.0.6" + +jest-matcher-utils@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" + integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== + dependencies: + chalk "^2.0.1" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-matcher-utils@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.0.6.tgz#2a8da1e86c620b39459f4352eaa255f0d43e39a9" + integrity sha512-OFgF2VCQx9vdPSYTHWJ9MzFCehs20TsyFi6bIHbk5V1u52zJOnvF0Y/65z3GLZHKRuTgVPY4Z6LVePNahaQ+tA== + dependencies: + chalk "^4.0.0" + jest-diff "^27.0.6" + jest-get-type "^27.0.6" + pretty-format "^27.0.6" jest-message-util@^24.9.0: version "24.9.0" @@ -10652,19 +12034,20 @@ jest-message-util@^24.9.0: slash "^2.0.0" stack-utils "^1.0.1" -jest-message-util@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.1.0.tgz#702a9a5cb05c144b9aa73f06e17faa219389845e" - integrity sha512-Nr/Iwar2COfN22aCqX0kCVbXgn8IBm9nWf4xwGr5Olv/KZh0CZ32RKgZWMVDXGdOahicM10/fgjdimGNX/ttCQ== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^25.1.0" - "@jest/types" "^25.1.0" - "@types/stack-utils" "^1.0.1" - chalk "^3.0.0" - micromatch "^4.0.2" +jest-message-util@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.0.6.tgz#158bcdf4785706492d164a39abca6a14da5ab8b5" + integrity sha512-rBxIs2XK7rGy+zGxgi+UJKP6WqQ+KrBbD1YMj517HYN3v2BG66t3Xan3FWqYHKZwjdB700KiAJ+iES9a0M+ixw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.0.6" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.4" + pretty-format "^27.0.6" slash "^3.0.0" - stack-utils "^1.0.1" + stack-utils "^2.0.3" jest-mock@^24.9.0: version "24.9.0" @@ -10673,139 +12056,253 @@ jest-mock@^24.9.0: dependencies: "@jest/types" "^24.9.0" -jest-mock@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.1.0.tgz#411d549e1b326b7350b2e97303a64715c28615fd" - integrity sha512-28/u0sqS+42vIfcd1mlcg4ZVDmSUYuNvImP4X2lX5hRMLW+CN0BeiKVD4p+ujKKbSPKd3rg/zuhCF+QBLJ4vag== +jest-mock@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.0.6.tgz#0efdd40851398307ba16778728f6d34d583e3467" + integrity sha512-lzBETUoK8cSxts2NYXSBWT+EJNzmUVtVVwS1sU9GwE1DLCfGsngg+ZVSIe0yd0ZSm+y791esiuo+WSwpXJQ5Bw== dependencies: - "@jest/types" "^25.1.0" + "@jest/types" "^27.0.6" + "@types/node" "*" -jest-pnp-resolver@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" - integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== +jest-pnp-resolver@^1.2.1, jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^24.9.0: +jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== -jest-regex-util@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.1.0.tgz#efaf75914267741838e01de24da07b2192d16d87" - integrity sha512-9lShaDmDpqwg+xAd73zHydKrBbbrIi08Kk9YryBEBybQFg/lBWR/2BDjjiSE7KIppM9C5+c03XiDaZ+m4Pgs1w== +jest-regex-util@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.0.6.tgz#02e112082935ae949ce5d13b2675db3d8c87d9c5" + integrity sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ== + +jest-resolve-dependencies@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab" + integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== + dependencies: + "@jest/types" "^24.9.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.9.0" -jest-resolve-dependencies@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.1.0.tgz#8a1789ec64eb6aaa77fd579a1066a783437e70d2" - integrity sha512-Cu/Je38GSsccNy4I2vL12ZnBlD170x2Oh1devzuM9TLH5rrnLW1x51lN8kpZLYTvzx9j+77Y5pqBaTqfdzVzrw== +jest-resolve-dependencies@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.6.tgz#3e619e0ef391c3ecfcf6ef4056207a3d2be3269f" + integrity sha512-mg9x9DS3BPAREWKCAoyg3QucCr0n6S8HEEsqRCKSPjPcu9HzRILzhdzY3imsLoZWeosEbJZz6TKasveczzpJZA== dependencies: - "@jest/types" "^25.1.0" - jest-regex-util "^25.1.0" - jest-snapshot "^25.1.0" + "@jest/types" "^27.0.6" + jest-regex-util "^27.0.6" + jest-snapshot "^27.0.6" -jest-resolve@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.1.0.tgz#23d8b6a4892362baf2662877c66aa241fa2eaea3" - integrity sha512-XkBQaU1SRCHj2Evz2Lu4Czs+uIgJXWypfO57L7JYccmAXv4slXA6hzNblmcRmf7P3cQ1mE7fL3ABV6jAwk4foQ== +jest-resolve@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321" + integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== dependencies: - "@jest/types" "^25.1.0" + "@jest/types" "^24.9.0" browser-resolve "^1.11.3" - chalk "^3.0.0" + chalk "^2.0.1" jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-runner@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.1.0.tgz#fef433a4d42c89ab0a6b6b268e4a4fbe6b26e812" - integrity sha512-su3O5fy0ehwgt+e8Wy7A8CaxxAOCMzL4gUBftSs0Ip32S0epxyZPDov9Znvkl1nhVOJNf4UwAsnqfc3plfQH9w== +jest-resolve@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.0.6.tgz#e90f436dd4f8fbf53f58a91c42344864f8e55bff" + integrity sha512-yKmIgw2LgTh7uAJtzv8UFHGF7Dm7XfvOe/LQ3Txv101fLM8cx2h1QVwtSJ51Q/SCxpIiKfVn6G2jYYMDNHZteA== + dependencies: + "@jest/types" "^27.0.6" + chalk "^4.0.0" + escalade "^3.1.1" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^27.0.6" + jest-validate "^27.0.6" + resolve "^1.20.0" + slash "^3.0.0" + +jest-runner@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" + integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== dependencies: - "@jest/console" "^25.1.0" - "@jest/environment" "^25.1.0" - "@jest/test-result" "^25.1.0" - "@jest/types" "^25.1.0" - chalk "^3.0.0" + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.4.2" exit "^0.1.2" - graceful-fs "^4.2.3" - jest-config "^25.1.0" - jest-docblock "^25.1.0" - jest-haste-map "^25.1.0" - jest-jasmine2 "^25.1.0" - jest-leak-detector "^25.1.0" - jest-message-util "^25.1.0" - jest-resolve "^25.1.0" - jest-runtime "^25.1.0" - jest-util "^25.1.0" - jest-worker "^25.1.0" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-docblock "^24.3.0" + jest-haste-map "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-leak-detector "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" source-map-support "^0.5.6" - throat "^5.0.0" - -jest-runtime@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.1.0.tgz#02683218f2f95aad0f2ec1c9cdb28c1dc0ec0314" - integrity sha512-mpPYYEdbExKBIBB16ryF6FLZTc1Rbk9Nx0ryIpIMiDDkOeGa0jQOKVI/QeGvVGlunKKm62ywcioeFVzIbK03bA== - dependencies: - "@jest/console" "^25.1.0" - "@jest/environment" "^25.1.0" - "@jest/source-map" "^25.1.0" - "@jest/test-result" "^25.1.0" - "@jest/transform" "^25.1.0" - "@jest/types" "^25.1.0" - "@types/yargs" "^15.0.0" - chalk "^3.0.0" - collect-v8-coverage "^1.0.0" + throat "^4.0.0" + +jest-runner@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.0.6.tgz#1325f45055539222bbc7256a6976e993ad2f9520" + integrity sha512-W3Bz5qAgaSChuivLn+nKOgjqNxM7O/9JOJoKDCqThPIg2sH/d4A/lzyiaFgnb9V1/w29Le11NpzTJSzga1vyYQ== + dependencies: + "@jest/console" "^27.0.6" + "@jest/environment" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-docblock "^27.0.6" + jest-environment-jsdom "^27.0.6" + jest-environment-node "^27.0.6" + jest-haste-map "^27.0.6" + jest-leak-detector "^27.0.6" + jest-message-util "^27.0.6" + jest-resolve "^27.0.6" + jest-runtime "^27.0.6" + jest-util "^27.0.6" + jest-worker "^27.0.6" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" + integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + chalk "^2.0.1" exit "^0.1.2" glob "^7.1.3" - graceful-fs "^4.2.3" - jest-config "^25.1.0" - jest-haste-map "^25.1.0" - jest-message-util "^25.1.0" - jest-mock "^25.1.0" - jest-regex-util "^25.1.0" - jest-resolve "^25.1.0" - jest-snapshot "^25.1.0" - jest-util "^25.1.0" - jest-validate "^25.1.0" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^13.3.0" + +jest-runtime@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.0.6.tgz#45877cfcd386afdd4f317def551fc369794c27c9" + integrity sha512-BhvHLRVfKibYyqqEFkybsznKwhrsu7AWx2F3y9G9L95VSIN3/ZZ9vBpm/XCS2bS+BWz3sSeNGLzI3TVQ0uL85Q== + dependencies: + "@jest/console" "^27.0.6" + "@jest/environment" "^27.0.6" + "@jest/fake-timers" "^27.0.6" + "@jest/globals" "^27.0.6" + "@jest/source-map" "^27.0.6" + "@jest/test-result" "^27.0.6" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-haste-map "^27.0.6" + jest-message-util "^27.0.6" + jest-mock "^27.0.6" + jest-regex-util "^27.0.6" + jest-resolve "^27.0.6" + jest-snapshot "^27.0.6" + jest-util "^27.0.6" + jest-validate "^27.0.6" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.0.0" - -jest-serializer-vue@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jest-serializer-vue/-/jest-serializer-vue-2.0.2.tgz#b238ef286357ec6b480421bd47145050987d59b3" - integrity sha1-sjjvKGNX7GtIBCG9RxRQUJh9WbM= - dependencies: - pretty "2.0.0" + yargs "^16.0.3" jest-serializer@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== -jest-serializer@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.1.0.tgz#73096ba90e07d19dec4a0c1dd89c355e2f129e5d" - integrity sha512-20Wkq5j7o84kssBwvyuJ7Xhn7hdPeTXndnwIblKDR2/sy1SUm6rWWiG9kSCgJPIfkDScJCIsTtOKdlzfIHOfKA== +jest-serializer@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.0.6.tgz#93a6c74e0132b81a2d54623251c46c498bb5bec1" + integrity sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" -jest-snapshot@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.1.0.tgz#d5880bd4b31faea100454608e15f8d77b9d221d9" - integrity sha512-xZ73dFYN8b/+X2hKLXz4VpBZGIAn7muD/DAg+pXtDzDGw3iIV10jM7WiHqhCcpDZfGiKEj7/2HXAEPtHTj0P2A== +jest-snapshot@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" + integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^25.1.0" - chalk "^3.0.0" - expect "^25.1.0" - jest-diff "^25.1.0" - jest-get-type "^25.1.0" - jest-matcher-utils "^25.1.0" - jest-message-util "^25.1.0" - jest-resolve "^25.1.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + expect "^24.9.0" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^25.1.0" - semver "^7.1.1" + pretty-format "^24.9.0" + semver "^6.2.0" + +jest-snapshot@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.0.6.tgz#f4e6b208bd2e92e888344d78f0f650bcff05a4bf" + integrity sha512-NTHaz8He+ATUagUgE7C/UtFcRoHqR2Gc+KDfhQIyx+VFgwbeEMjeP+ILpUTLosZn/ZtbNdCF5LkVnN/l+V751A== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/parser" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.0.6" + graceful-fs "^4.2.4" + jest-diff "^27.0.6" + jest-get-type "^27.0.6" + jest-haste-map "^27.0.6" + jest-matcher-utils "^27.0.6" + jest-message-util "^27.0.6" + jest-resolve "^27.0.6" + jest-util "^27.0.6" + natural-compare "^1.4.0" + pretty-format "^27.0.6" + semver "^7.3.2" + +jest-transform-graphql@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/jest-transform-graphql/-/jest-transform-graphql-2.1.0.tgz#903cb66bb27bc2772fd3e5dd4f7e9b57230f5829" + integrity sha1-kDy2a7J7wncv0+XdT36bVyMPWCk= jest-util@^24.9.0: version "24.9.0" @@ -10825,41 +12322,69 @@ jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" -jest-util@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.1.0.tgz#7bc56f7b2abd534910e9fa252692f50624c897d9" - integrity sha512-7did6pLQ++87Qsj26Fs/TIwZMUFBXQ+4XXSodRNy3luch2DnRXsSnmpVtxxQ0Yd6WTipGpbhh2IFP1mq6/fQGw== +jest-util@^27.0.0, jest-util@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.0.6.tgz#e8e04eec159de2f4d5f57f795df9cdc091e50297" + integrity sha512-1JjlaIh+C65H/F7D11GNkGDDZtDfMEM8EBXsvd+l/cxtgQ6QhxuloOaiayt89DxUvDarbVhqI98HhgrM1yliFQ== dependencies: - "@jest/types" "^25.1.0" - chalk "^3.0.0" - is-ci "^2.0.0" - mkdirp "^0.5.1" + "@jest/types" "^27.0.6" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^3.0.0" + picomatch "^2.2.3" -jest-validate@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.1.0.tgz#1469fa19f627bb0a9a98e289f3e9ab6a668c732a" - integrity sha512-kGbZq1f02/zVO2+t1KQGSVoCTERc5XeObLwITqC6BTRH3Adv7NZdYqCpKIZLUgpLXf2yISzQ465qOZpul8abXA== +jest-validate@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" + integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== dependencies: - "@jest/types" "^25.1.0" + "@jest/types" "^24.9.0" camelcase "^5.3.1" - chalk "^3.0.0" - jest-get-type "^25.1.0" + chalk "^2.0.1" + jest-get-type "^24.9.0" leven "^3.1.0" - pretty-format "^25.1.0" + pretty-format "^24.9.0" + +jest-validate@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.0.6.tgz#930a527c7a951927df269f43b2dc23262457e2a6" + integrity sha512-yhZZOaMH3Zg6DC83n60pLmdU1DQE46DW+KLozPiPbSbPhlXXaiUTDlhHQhHFpaqIFRrInko1FHXjTRpjWRuWfA== + dependencies: + "@jest/types" "^27.0.6" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.0.6" + leven "^3.1.0" + pretty-format "^27.0.6" + +jest-watcher@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b" + integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== + dependencies: + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.9.0" + string-length "^2.0.0" -jest-watcher@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.1.0.tgz#97cb4a937f676f64c9fad2d07b824c56808e9806" - integrity sha512-Q9eZ7pyaIr6xfU24OeTg4z1fUqBF/4MP6J801lyQfg7CsnZ/TCzAPvCfckKdL5dlBBEKBeHV0AdyjFZ5eWj4ig== +jest-watcher@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.0.6.tgz#89526f7f9edf1eac4e4be989bcb6dec6b8878d9c" + integrity sha512-/jIoKBhAP00/iMGnTwUBLgvxkn7vsOweDrOTSPzc7X9uOyUtJIDthQBTI1EXz90bdkrxorUZVhJwiB69gcHtYQ== dependencies: - "@jest/test-result" "^25.1.0" - "@jest/types" "^25.1.0" + "@jest/test-result" "^27.0.6" + "@jest/types" "^27.0.6" + "@types/node" "*" ansi-escapes "^4.2.1" - chalk "^3.0.0" - jest-util "^25.1.0" - string-length "^3.1.0" + chalk "^4.0.0" + jest-util "^27.0.6" + string-length "^4.0.1" -jest-worker@^24.9.0: +jest-worker@^24.6.0, jest-worker@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== @@ -10867,55 +12392,100 @@ jest-worker@^24.9.0: merge-stream "^2.0.0" supports-color "^6.1.0" -jest-worker@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.1.0.tgz#75d038bad6fdf58eba0d2ec1835856c497e3907a" - integrity sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg== +jest-worker@^26.5.0: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: + "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" -jest@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-25.1.0.tgz#b85ef1ddba2fdb00d295deebbd13567106d35be9" - integrity sha512-FV6jEruneBhokkt9MQk0WUFoNTwnF76CLXtwNMfsc0um0TlB/LG2yxUd0KqaFjEJ9laQmVWQWS0sG/t2GsuI0w== +jest-worker@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed" + integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^24.1.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" + integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== + dependencies: + import-local "^2.0.0" + jest-cli "^24.9.0" + +jest@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.0.6.tgz#10517b2a628f0409087fbf473db44777d7a04505" + integrity sha512-EjV8aETrsD0wHl7CKMibKwQNQc3gIRBXlTikBmmHUeVMKaPFxdcUIBfoDqTSXDoGJIivAYGqCWVlzCSaVjPQsA== dependencies: - "@jest/core" "^25.1.0" + "@jest/core" "^27.0.6" import-local "^3.0.2" - jest-cli "^25.1.0" + jest-cli "^27.0.6" + +jimp-compact@^0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/jimp-compact/-/jimp-compact-0.16.1.tgz#9582aea06548a2c1e04dd148d7c3ab92075aefa3" + integrity sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww== + +jiti@^1.9.2: + version "1.11.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.11.0.tgz#64120a30d97b9bf37b8b032cf4564dfadc28984c" + integrity sha512-/2c7e61hxxTIN34UeHBB0LCJ5Tq64kgJDV7GR+++e8XRxCKRIKmB8tH6ww1W+Z6Kgd6By+C3RSCu1lXjbPT68A== + +jju@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" + integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= js-base64@^2.1.8: - version "2.5.2" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.2.tgz#313b6274dda718f714d00b3330bbae6e38e90209" - integrity sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ== + version "2.6.4" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" + integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== -js-beautify@^1.6.12, js-beautify@^1.6.14: - version "1.10.3" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.3.tgz#c73fa10cf69d3dfa52d8ed624f23c64c0a6a94c1" - integrity sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ== +js-beautify@^1.6.12: + version "1.14.0" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.0.tgz#2ce790c555d53ce1e3d7363227acf5dc69024c2d" + integrity sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ== dependencies: config-chain "^1.1.12" editorconfig "^0.15.3" glob "^7.1.3" - mkdirp "~0.5.1" - nopt "~4.0.1" + nopt "^5.0.0" -js-sha3@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" - integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== +js-cookie@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" + integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== + +js-cookie@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.0.tgz#db1661d5459920ec95aaf186ccf74ceb4a495164" + integrity sha512-oUbbplKuH07/XX2YD2+Q+GMiPpnVXaRz8npE7suhBH9QEkJe2W7mQ6rwuMXHue3fpfcftQwzgyvGzIHyfCSngQ== + +js-levenshtein@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" -js-yaml@^3.11.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.9.1: +js-yaml@~3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -10928,43 +12498,76 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@^15.1.1: - version "15.2.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" - integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== +jsdom-global@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsdom-global/-/jsdom-global-3.0.2.tgz#6bd299c13b0c4626b2da2c0393cd4385d606acb9" + integrity sha1-a9KZwTsMRiay2iwDk81DhdYGrLk= + +jsdom@^11.5.1: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== dependencies: abab "^2.0.0" - acorn "^7.1.0" - acorn-globals "^4.3.2" + acorn "^5.5.3" + acorn-globals "^4.1.0" array-equal "^1.0.0" - cssom "^0.4.1" - cssstyle "^2.0.0" - data-urls "^1.1.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" domexception "^1.0.1" - escodegen "^1.11.1" + escodegen "^1.9.1" html-encoding-sniffer "^1.0.2" - nwsapi "^2.2.0" - parse5 "5.1.0" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" pn "^1.1.0" - request "^2.88.0" - request-promise-native "^1.0.7" - saxes "^3.1.9" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" symbol-tree "^3.2.2" - tough-cookie "^3.0.1" + tough-cookie "^2.3.4" w3c-hr-time "^1.0.1" - w3c-xmlserializer "^1.1.2" webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + +jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" whatwg-encoding "^1.0.5" whatwg-mimetype "^2.3.0" - whatwg-url "^7.0.0" - ws "^7.0.0" + whatwg-url "^8.5.0" + ws "^7.4.6" xml-name-validator "^3.0.0" -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" - integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -10975,13 +12578,6 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-bigint@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.3.0.tgz#0ccd912c4b8270d05f056fbd13814b53d3825b1e" - integrity sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4= - dependencies: - bignumber.js "^7.0.0" - json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -10992,16 +12588,21 @@ json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-bet resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -11012,24 +12613,31 @@ json-stable-stringify-without-jsonify@^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, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stable-stringify@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" + integrity sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U= + dependencies: + jsonify "~0.0.0" + +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" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json3@^3.3.2: +json3@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== -json5@2.x, json5@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" - integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== +json5@2.x, json5@^2.1.0, json5@^2.1.1, json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: - minimist "^1.2.0" + minimist "^1.2.5" -json5@^0.5.0, json5@^0.5.1: +json5@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= @@ -11041,20 +12649,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" - -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -11071,6 +12665,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -11086,28 +12685,6 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -jwa@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" - integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" - integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== - dependencies: - jwa "^2.0.0" - safe-buffer "^5.0.1" - -kew@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b" - integrity sha1-edk9LTM2PW/dKXCzNdkUGtWR15s= - keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -11139,23 +12716,29 @@ kind-of@^5.0.0: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== -kind-of@^6.0.0, kind-of@^6.0.2: +kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= - optionalDependencies: - graceful-fs "^4.1.9" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +klona@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + +labeled-stream-splicer@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz#42a41a16abcd46fd046306cf4f2c3576fffb1c21" + integrity sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw== + dependencies: + inherits "^2.0.1" + stream-splicer "^2.0.0" + last-call-webpack-plugin@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" @@ -11164,13 +12747,6 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" -latest-version@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= - dependencies: - package-json "^4.0.0" - latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -11178,56 +12754,57 @@ latest-version@^5.0.0: dependencies: package-json "^6.3.0" -lazy-ass@1.6.0: +launch-editor-middleware@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/launch-editor-middleware/-/launch-editor-middleware-2.2.1.tgz#e14b07e6c7154b0a4b86a0fd345784e45804c157" + integrity sha512-s0UO2/gEGiCgei3/2UN3SMuUj1phjQN8lcpnvgLSz26fAzNWPQ6Nf/kF5IFClnfU2ehp6LrmKdMU/beveO+2jg== + dependencies: + launch-editor "^2.2.1" + +launch-editor@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.2.1.tgz#871b5a3ee39d6680fcc26d37930b6eeda89db0ca" + integrity sha512-On+V7K2uZK6wK7x691ycSUbLD/FyKKelArkbaAMSSJU8JmqmhwN2+mnJDNINuJWSrh2L0kDk+ZQtbC/gOWUwLw== + dependencies: + chalk "^2.3.0" + shell-quote "^1.6.1" + +lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= -lazy@~1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690" - integrity sha1-2qBoIGKCVCwIgojpdcKXwa53tpA= - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" +leaflet@^1.5.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19" + integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw== -lean-he@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/lean-he/-/lean-he-2.1.2.tgz#50c2422f0ca42f6737c9c1988b555cf00ebc712f" - integrity sha512-g/cq01j/rnv7JWoxFmeLgJdd/CucksyDtS+pyepO89EdT0O4KfHJokOVz/xQ4mvjKJzcrj87Q3/s2ESou90WCQ== - -lerna@^3.14.1: - version "3.20.2" - resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.20.2.tgz#abf84e73055fe84ee21b46e64baf37b496c24864" - integrity sha512-bjdL7hPLpU3Y8CBnw/1ys3ynQMUjiK6l9iDWnEGwFtDy48Xh5JboR9ZJwmKGCz9A/sarVVIGwf1tlRNKUG9etA== - dependencies: - "@lerna/add" "3.20.0" - "@lerna/bootstrap" "3.20.0" - "@lerna/changed" "3.20.0" - "@lerna/clean" "3.20.0" +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" + integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== + +lerna@^3.15.0: + version "3.22.1" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62" + integrity sha512-vk1lfVRFm+UuEFA7wkLKeSF7Iz13W+N/vFd48aW2yuS7Kv0RbNm2/qcDPV863056LMfkRlsEe+QYOw3palj5Lg== + dependencies: + "@lerna/add" "3.21.0" + "@lerna/bootstrap" "3.21.0" + "@lerna/changed" "3.21.0" + "@lerna/clean" "3.21.0" "@lerna/cli" "3.18.5" - "@lerna/create" "3.18.5" - "@lerna/diff" "3.18.5" - "@lerna/exec" "3.20.0" - "@lerna/import" "3.18.5" - "@lerna/info" "3.20.0" - "@lerna/init" "3.18.5" - "@lerna/link" "3.18.5" - "@lerna/list" "3.20.0" - "@lerna/publish" "3.20.2" - "@lerna/run" "3.20.0" - "@lerna/version" "3.20.2" + "@lerna/create" "3.22.0" + "@lerna/diff" "3.21.0" + "@lerna/exec" "3.21.0" + "@lerna/import" "3.22.0" + "@lerna/info" "3.21.0" + "@lerna/init" "3.21.0" + "@lerna/link" "3.21.0" + "@lerna/list" "3.21.0" + "@lerna/publish" "3.22.1" + "@lerna/run" "3.21.0" + "@lerna/version" "3.22.1" import-local "^2.0.0" npmlog "^4.1.2" @@ -11236,14 +12813,15 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levenary@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" - integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== +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: - leven "^3.1.0" + prelude-ls "^1.2.1" + type-check "~0.4.0" -levn@^0.3.0, levn@~0.3.0: +levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= @@ -11251,13 +12829,6 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lie@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" - integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4= - dependencies: - immediate "~3.0.5" - lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -11270,54 +12841,31 @@ linkify-it@^2.0.0: dependencies: uc.micro "^1.0.1" -lint-staged@^8.2.1: - version "8.2.1" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.2.1.tgz#752fcf222d9d28f323a3b80f1e668f3654ff221f" - integrity sha512-n0tDGR/rTCgQNwXnUf/eWIpPNddGWxC32ANTNYsj2k02iZb7Cz5ox2tytwBu+2r0zDXMEMKw7Y9OD/qsav561A== - dependencies: - chalk "^2.3.1" - commander "^2.14.1" - cosmiconfig "^5.2.0" - debug "^3.1.0" - dedent "^0.7.0" - del "^3.0.0" - execa "^1.0.0" - g-status "^2.0.2" - is-glob "^4.0.0" - is-windows "^1.0.2" - listr "^0.14.2" - listr-update-renderer "^0.5.0" - lodash "^4.17.11" - log-symbols "^2.2.0" - micromatch "^3.1.8" - npm-which "^3.0.1" - p-map "^1.1.1" - path-is-inside "^1.0.2" - pify "^3.0.0" - please-upgrade-node "^3.0.2" - staged-git-files "1.1.2" - string-argv "^0.0.2" - stringify-object "^3.2.2" - yup "^0.27.0" - -listr-silent-renderer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" - integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= - -listr-update-renderer@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9" - integrity sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk= +lint-staged@^10.0.7: + version "10.5.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" + integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== dependencies: - chalk "^1.1.3" - cli-truncate "^0.2.1" - elegant-spinner "^1.0.1" - figures "^1.7.0" - indent-string "^3.0.0" - log-symbols "^1.0.2" - log-update "^1.0.2" - strip-ansi "^3.0.1" + chalk "^4.1.0" + cli-truncate "^2.1.0" + commander "^6.2.0" + cosmiconfig "^7.0.0" + debug "^4.2.0" + dedent "^0.7.0" + enquirer "^2.3.6" + execa "^4.1.0" + listr2 "^3.2.2" + log-symbols "^4.0.0" + micromatch "^4.0.2" + normalize-path "^3.0.0" + please-upgrade-node "^3.2.0" + string-argv "0.3.1" + stringify-object "^3.3.0" + +listr-silent-renderer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" + integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= listr-update-renderer@^0.5.0: version "0.5.0" @@ -11333,16 +12881,6 @@ listr-update-renderer@^0.5.0: log-update "^2.3.0" strip-ansi "^3.0.1" -listr-verbose-renderer@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35" - integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU= - dependencies: - chalk "^1.1.3" - cli-cursor "^1.0.2" - date-fns "^1.27.2" - figures "^1.7.0" - listr-verbose-renderer@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db" @@ -11353,29 +12891,20 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" -listr@0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a" - integrity sha1-a84sD1YD+klYDqF81qAMwOX6RRo= +listr2@^3.2.2: + version "3.11.0" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.11.0.tgz#9771b02407875aa78e73d6e0ff6541bbec0aaee9" + integrity sha512-XLJVe2JgXCyQTa3FbSv11lkKExYmEyA4jltVo8z4FX10Vt1Yj8IMekBfwim0BSOM9uj1QMTJvDQQpHyuPbB/dQ== dependencies: - chalk "^1.1.3" - cli-truncate "^0.2.1" - figures "^1.7.0" - indent-string "^2.1.0" - is-promise "^2.1.0" - is-stream "^1.1.0" - listr-silent-renderer "^1.1.1" - listr-update-renderer "^0.2.0" - listr-verbose-renderer "^0.4.0" - log-symbols "^1.0.2" - log-update "^1.0.2" - ora "^0.2.3" - p-map "^1.1.1" - rxjs "^5.0.0-beta.11" - stream-to-observable "^0.1.0" - strip-ansi "^3.0.1" + cli-truncate "^2.1.0" + colorette "^1.2.2" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.6.7" + through "^2.3.8" + wrap-ansi "^7.0.0" -listr@^0.14.2, listr@^0.14.3: +listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== @@ -11401,16 +12930,6 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" - load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -11437,27 +12956,15 @@ load-script@^1.0.0: resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4" integrity sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ= -loader-fs-cache@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.2.tgz#54cedf6b727e1779fd8f01205f05f6e88706f086" - integrity sha512-70IzT/0/L+M20jUlEqZhZyArTU6VKLRTYRDAYN26g4jfzpJqjipLL3/hgYpySqI9PwsVRHHFja0LfEmsx9X2Cw== - dependencies: - find-cache-dir "^0.1.1" - mkdirp "0.5.1" - loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" +loader-runner@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== loader-utils@^0.2.16: version "0.2.17" @@ -11469,7 +12976,7 @@ loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: +loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -11478,12 +12985,14 @@ loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2 emojis-list "^3.0.0" json5 "^1.0.1" -localforage@^1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204" - integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ== +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== dependencies: - lie "3.1.1" + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" locate-path@^2.0.0: version "2.0.0" @@ -11508,17 +13017,24 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" - integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= -lodash.clonedeep@^4.5.0: +lodash.clonedeep@4.5.0, lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= @@ -11528,47 +13044,67 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= - -lodash.findindex@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" - integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY= - -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= +lodash.defaultsdeep@^4.6.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" + integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== -lodash.get@^4.4.2: +lodash.get@^4.0.0, lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= + lodash.isequal@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.isfunction@^3.0.8, lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc= +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= -lodash.memoize@4.x, lodash.memoize@^4.1.2: +lodash.map@^4.5.1: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" + integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= + +lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.merge@^4.6.0: +lodash.memoize@~3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" + integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= + +lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== @@ -11583,12 +13119,17 @@ lodash.set@^4.3.2: resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= +lodash.snakecase@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" + integrity sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= -lodash.template@^4.0.2, lodash.template@^4.4.0, lodash.template@^4.5.0: +lodash.template@^4.0.2, lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== @@ -11603,38 +13144,26 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" -lodash.unescape@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" - integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.12: +lodash@4.17.21, lodash@4.x, lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.3, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.7.0, lodash@~4.17.10, lodash@~4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-driver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" - integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg== - -log-symbols@2.2.0, log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -11642,13 +13171,13 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-update@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" - integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE= +log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: - ansi-escapes "^1.0.0" - cli-cursor "^1.0.2" + chalk "^4.1.0" + is-unicode-supported "^0.1.0" log-update@^2.3.0: version "2.3.0" @@ -11659,19 +13188,27 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" -loglevel@^1.6.6: - version "1.6.7" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.7.tgz#b3e034233188c68b889f5b862415306f565e2c56" - integrity sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A== - -lolex@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" - integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== +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: - "@sinonjs/commons" "^1.7.0" + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +loglevel@^1.6.2, loglevel@^1.6.7, loglevel@^1.6.8: + version "1.7.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" + integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw== + +longest@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8" + integrity sha1-eB4YMpaqlPbU2RbcM10NF676I/g= -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -11691,6 +13228,13 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -11709,7 +13253,7 @@ lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache@^4.1.5: pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@^5.0.0, lru-cache@^5.1.1: +lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== @@ -11724,18 +13268,16 @@ lru-cache@^6.0.0: yallist "^4.0.0" macos-release@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" - integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== + version "2.5.0" + resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.5.0.tgz#067c2c88b5f3fb3c56a375b2ec93826220fa1ff2" + integrity sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g== -"magento2-rest-client@github:DivanteLtd/magento2-rest-client": - version "0.0.12" - resolved "https://codeload.github.com/vuestorefront/magento2-rest-client/tar.gz/29cfac4c4f9982f99b7832621820f93dcf6f9dd4" +magic-string@^0.25.7: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== dependencies: - humps "^1.1.0" - oauth-1.0a "^1.0.1" - request "^2.72.0" - winston "^2.2.0" + sourcemap-codec "^1.4.4" make-dir@^1.0.0: version "1.3.0" @@ -11752,10 +13294,10 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" - integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: semver "^6.0.0" @@ -11788,12 +13330,7 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -mamacro@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" - integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== - -map-age-cleaner@^0.1.1: +map-age-cleaner@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== @@ -11815,6 +13352,11 @@ map-obj@^2.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk= +map-obj@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.1.tgz#e4ea399dbc979ae735c83c863dd31bdf364277b7" + integrity sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ== + map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -11823,9 +13365,9 @@ map-visit@^1.0.0: object-visit "^1.0.0" markdown-it-anchor@^5.0.2: - version "5.2.5" - resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-5.2.5.tgz#dbf13cfcdbffd16a510984f1263e1d479a47d27a" - integrity sha512-xLIjLQmtym3QpoY9llBgApknl7pxAcN3WDRc2d3rwpl+/YvDZHPmKscGs+L6E05xf2KrCXPBvosWt7MZukwSpQ== + version "5.3.0" + resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz#d549acd64856a8ecd1bea58365ef385effbac744" + integrity sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA== markdown-it-chain@^1.3.0: version "1.3.0" @@ -11860,13 +13402,6 @@ markdown-it@^8.4.1: mdurl "^1.0.1" uc.micro "^1.0.5" -matcher@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.1.1.tgz#51d8301e138f840982b338b116bb0c09af62c1c2" - integrity sha512-+BmqxWIubKTRKNWx/ahnCkk3mG8m7OturVlqq6HiojGJTd5hVYbgZm6WzcYPCoB+KBT4Vd6R7WSRG2OADNaCjg== - dependencies: - escape-string-regexp "^1.0.4" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -11876,6 +13411,11 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -11891,23 +13431,22 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== +mem@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/mem/-/mem-8.1.1.tgz#cf118b357c65ab7b7e0817bdf00c8062297c0122" + integrity sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA== dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" + map-age-cleaner "^0.1.3" + mimic-fn "^3.1.0" -memfs@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.0.tgz#f9438e622b5acd1daa8a4ae160c496fdd1325b26" - integrity sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A== +memfs@^3.1.2, memfs@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.2.tgz#5de461389d596e3f23d48bb7c2afb6161f4df40e" + integrity sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q== dependencies: - fs-monkey "1.0.1" + fs-monkey "1.0.3" -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -11923,11 +13462,6 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -memorystream@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" - integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= - meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -11959,20 +13493,22 @@ meow@^4.0.0: redent "^2.0.0" trim-newlines "^2.0.0" -meow@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4" - integrity sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig== - dependencies: - camelcase-keys "^4.0.0" - decamelize-keys "^1.0.0" - loud-rejection "^1.0.0" - minimist-options "^3.0.1" - normalize-package-data "^2.3.4" - read-pkg-up "^3.0.0" - redent "^2.0.0" - trim-newlines "^2.0.0" - yargs-parser "^10.0.0" +meow@^8.0.0: + version "8.1.2" + resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" + integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^3.0.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.18.0" + yargs-parser "^20.2.3" merge-descriptors@1.0.1: version "1.0.1" @@ -11991,22 +13527,27 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" - integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== +merge2@^1.2.3, merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -merge@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" - integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== +merge@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" + integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== -methods@^1.1.1, methods@~1.1.2: +meros@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/meros/-/meros-1.1.4.tgz#c17994d3133db8b23807f62bec7f0cb276cfd948" + integrity sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ== + +methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: +micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -12025,13 +13566,13 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== +micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" - picomatch "^2.0.5" + picomatch "^2.2.3" miller-rabin@^4.0.0: version "4.0.1" @@ -12041,43 +13582,53 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.49.0, "mime-db@>= 1.43.0 < 2": + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== +mime-types@^2.1.12, mime-types@^2.1.19, mime-types@^2.1.27, mime-types@^2.1.30, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== dependencies: - mime-db "1.43.0" + mime-db "1.49.0" mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.0.3, mime@^2.2.0, mime@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" - integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== +mime@^2.0.3, mime@^2.3.1, mime@^2.4.4: + version "2.5.2" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" + integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -12085,6 +13636,11 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + mini-css-extract-plugin@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.6.0.tgz#a3f13372d6fcde912f3ee4cd039665704801e3b9" @@ -12100,18 +13656,27 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + minimist-options@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" @@ -12120,27 +13685,40 @@ minimist-options@^3.0.1: arrify "^1.0.1" is-plain-obj "^1.1.0" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minimist@^1.2.5: +minimist@1.2.5, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= +minimisted@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minimisted/-/minimisted-2.0.1.tgz#d059fb905beecf0774bc3b308468699709805cb1" + integrity sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA== + dependencies: + minimist "^1.2.5" + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" -minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: +minipass@^2.3.5, minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== @@ -12148,13 +13726,28 @@ minipass@^2.3.5, minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.2.1: +minipass@^3.0.0, minipass@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: minipass "^2.9.0" +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -12179,6 +13772,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp-promise@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" @@ -12186,44 +13784,97 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*: - version "1.0.3" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" - integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== +mkdirp@*, mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mkdirp@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4= -mkdirp@0.5.1, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1, mkdirp@~0.5.x: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= +"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mochawesome-merge@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/mochawesome-merge/-/mochawesome-merge-4.2.0.tgz#970ea141165039bfdd6772b1232a1ef6ba99af1a" + integrity sha512-FSMzagh+8hTShhFXdBLE4/zS2WALcDruoD0bmtiwHEjfyQszR/iEGFTgbuM5ewA5At3qeSGwGsT0k2Stt64NdQ== + dependencies: + fs-extra "^7.0.1" + glob "^7.1.6" + uuid "^3.3.2" + yargs "^15.3.1" + +mochawesome-report-generator@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mochawesome-report-generator/-/mochawesome-report-generator-5.2.0.tgz#ffd29f8d610863e48e63c15c4e83b5689d8dbd04" + integrity sha512-DDY/3jSkM/VrWy0vJtdYOf6qBLdaPaLcI7rQmBVbnclIX7AKniE1Rhz3T/cMT/7u54W5EHNo1z84z7efotq/Eg== + dependencies: + chalk "^2.4.2" + dateformat "^3.0.2" + escape-html "^1.0.3" + fs-extra "^7.0.0" + fsu "^1.0.2" + lodash.isfunction "^3.0.8" + opener "^1.5.2" + prop-types "^15.7.2" + tcomb "^3.2.17" + tcomb-validation "^3.3.0" + validator "^10.11.0" + yargs "^13.2.2" + +mochawesome@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/mochawesome/-/mochawesome-6.2.2.tgz#2847b4cccb254201e8dad89384f760fe9cdbae15" + integrity sha512-NuIxYo8zczmL5XWLNFiud21OsAJHXrflt2lcRY2u8a3TilGwglhzTPjUHZCLqJvbqj2CnIHX2ueqOh1ViUNDPw== dependencies: - minimist "0.0.8" + chalk "^4.1.0" + diff "^5.0.0" + json-stringify-safe "^5.0.1" + lodash.isempty "^4.4.0" + lodash.isfunction "^3.0.9" + lodash.isobject "^3.0.2" + lodash.isstring "^4.0.1" + mochawesome-report-generator "^5.2.0" + strip-ansi "^6.0.0" + uuid "^8.3.2" modify-values@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -module-details-from-path@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" - integrity sha1-EUyUlnPiqKNenTV4hSeqN7Z52is= - -moment-timezone@^0.5.x: - version "0.5.28" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.28.tgz#f093d789d091ed7b055d82aa81a82467f72e4338" - integrity sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw== - dependencies: - moment ">= 2.9.0" +module-deps@^6.0.0, module-deps@^6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.2.3.tgz#15490bc02af4b56cf62299c7c17cba32d71a96ee" + integrity sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA== + dependencies: + JSONStream "^1.0.3" + browser-resolve "^2.0.0" + cached-path-relative "^1.0.2" + concat-stream "~1.6.0" + defined "^1.0.0" + detective "^5.2.0" + duplexer2 "^0.1.2" + inherits "^2.0.1" + parents "^1.0.0" + readable-stream "^2.0.2" + resolve "^1.4.0" + stream-combiner2 "^1.1.1" + subarg "^1.0.0" + through2 "^2.0.0" + xtend "^4.0.0" -moment@2.24.0, "moment@>= 2.9.0", moment@^2.19: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +moment@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== move-concurrently@^1.0.1: version "1.0.1" @@ -12247,11 +13898,16 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.0.0, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -12275,12 +13931,17 @@ multimatch@^3.0.0: arrify "^1.0.1" minimatch "^3.0.4" +mustache@^2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" + integrity sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ== + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -mute-stream@~0.0.4: +mute-stream@0.0.8, mute-stream@~0.0.4: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== @@ -12295,9 +13956,14 @@ mz@^2.5.0: thenify-all "^1.0.0" nan@^2.12.1, nan@^2.13.2: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.15.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== + +nanoid@^3.1.23: + version "3.1.25" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" + integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== nanomatch@^1.2.9: version "1.2.13" @@ -12321,24 +13987,15 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -needle@^2.1.0: - version "2.3.3" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.3.tgz#a041ad1d04a871b0ebb666f40baaf1fb47867117" - integrity sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" - integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== nice-try@^1.0.4: version "1.0.5" @@ -12352,32 +14009,27 @@ no-case@^2.2.0: dependencies: lower-case "^1.1.1" -nocache@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" - integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q== - -node-cache@^4.1.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-4.2.1.tgz#efd8474dee4edec4138cdded580f5516500f7334" - integrity sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A== +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== dependencies: - clone "2.x" - lodash "^4.17.15" + lower-case "^2.0.2" + tslib "^2.0.3" node-fetch-npm@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" - integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz#6507d0e17a9ec0be3bec516958a497cec54bf5a4" + integrity sha512-iOuIQDWDyjhv9qSDrj9aq/klt6F9z1p2otB3AV7v3zBDcL/x+OfGsvGQZZCcMZbUf4Ujw1xGNQkjvGnVT22cKg== dependencies: encoding "^0.1.11" json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.6.0, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== +node-fetch@2.6.1, node-fetch@^2.5.0, node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== node-fetch@^1.0.1: version "1.7.3" @@ -12387,15 +14039,10 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -node-forge@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" - integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== - -node-forge@^0.9.0: - version "0.9.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5" - integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ== +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== node-gyp@^3.8.0: version "3.8.0" @@ -12416,9 +14063,9 @@ node-gyp@^3.8.0: which "1" node-gyp@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.0.tgz#8e31260a7af4a2e2f994b0673d4e0b3866156332" - integrity sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw== + version "5.1.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" + integrity sha512-WH0WKGi+a4i4DUt2mHnvocex/xPLp9pYt5R6M2JdFB7pJ7Z34hveZ4nDTGTiLXCkitA9T8HFZjhinBCiVHYcWw== dependencies: env-paths "^2.2.0" glob "^7.1.4" @@ -12432,6 +14079,14 @@ node-gyp@^5.0.2: tar "^4.4.12" which "^1.3.1" +node-html-parser@^3.2.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-3.3.6.tgz#fdbb3ba16d1252d7197ec39f0260d9c10ef33590" + integrity sha512-VkWDHvNgFGB3mbQGMyzqRE1i/BG7TKX9wRXC8e/v8kL0kZR/Oy6RjYxXH91K6/+m3g8iQ8dTqRy75lTYoA2Cjg== + dependencies: + css-select "^4.1.3" + he "1.2.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -12471,33 +14126,42 @@ node-modules-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" - integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== +node-notifier@^5.4.2: + version "5.4.5" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.5.tgz#0cbc1a2b0f658493b4025775a13ad938e96091ef" + integrity sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ== dependencies: growly "^1.3.0" - is-wsl "^2.1.1" - semver "^6.3.0" + is-wsl "^1.1.0" + semver "^5.5.0" shellwords "^0.1.1" - which "^1.3.1" + which "^1.3.0" -node-releases@^1.1.50: - version "1.1.50" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.50.tgz#803c40d2c45db172d0410e4efec83aa8c6ad0592" - integrity sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ== - dependencies: - semver "^6.3.0" +node-object-hash@^1.2.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94" + integrity sha512-UdS4swXs85fCGWWf6t6DMGgpN/vnlKeSGEQ7hJcrs7PBFoxoKLmibc3QRb7fwiYsjdL7PX8iI/TMSlZ90dgHhQ== -node-releases@^1.1.53: - version "1.1.57" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.57.tgz#f6754ce225fad0611e61228df3e09232e017ea19" - integrity sha512-ZQmnWS7adi61A9JsllJ2gdj2PauElcjnOwTp2O011iGzoakTxUsDGSe+6vD7wXbKdqhSFymC0OSx35aAMhrSdw== +node-releases@^1.1.75: + version "1.1.75" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" + integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== + +node-res@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/node-res/-/node-res-5.0.1.tgz#ffaa462e206509d66d0ba28a4daf1f032daa6460" + integrity sha512-YOleO9c7MAqoHC+Ccu2vzvV1fL6Ku49gShq3PIMKWHRgrMSih3XcwL05NbLBi6oU2J471gTBfdpVVxwT6Pfhxg== + dependencies: + destroy "^1.0.4" + etag "^1.8.1" + mime-types "^2.1.19" + on-finished "^2.3.0" + vary "^1.1.2" -node-sass@^4.12.0: - version "4.13.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3" - integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw== +node-sass@^4.13.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" + integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -12513,7 +14177,7 @@ node-sass@^4.12.0: node-gyp "^3.8.0" npmlog "^4.0.0" request "^2.88.0" - sass-graph "^2.2.4" + sass-graph "2.2.5" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" @@ -12531,14 +14195,21 @@ nopt@1.0.10: dependencies: abbrev "1" -nopt@^4.0.1, nopt@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== dependencies: abbrev "1" osenv "^0.1.4" +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -12549,6 +14220,16 @@ normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package- semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-package-data@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" + integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== + dependencies: + hosted-git-info "^4.0.1" + is-core-module "^2.5.0" + semver "^7.3.4" + validate-npm-package-license "^3.0.1" + normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -12566,6 +14247,16 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + normalize-url@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" @@ -12575,27 +14266,37 @@ normalize-url@^2.0.1: query-string "^5.0.1" sort-keys "^2.0.0" -normalize-url@^3.0.0, normalize-url@^3.3.0: +normalize-url@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + +normalize-url@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +nouislider@^15.2.0: + version "15.4.0" + resolved "https://registry.yarnpkg.com/nouislider/-/nouislider-15.4.0.tgz#ac0d988e9ba59366062e5712e7cd37eb2e48630d" + integrity sha512-AV7UMhGhZ4Mj6ToMT812Ib8OJ4tAXR2/Um7C4l4ZvvsqujF0WpQTpqqHJ+9xt4174R7ueQOUrBR4yakJpAIPCA== npm-bundled@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" - integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + version "1.1.2" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1" + integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ== dependencies: npm-normalize-package-bin "^1.0.1" npm-lifecycle@^3.1.2: - version "3.1.4" - resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.4.tgz#de6975c7d8df65f5150db110b57cce498b0b604c" - integrity sha512-tgs1PaucZwkxECGKhC/stbEgFyc3TGh2TJcg2CDr6jbvQRdteHNhmMeljRzpe4wgFAXQADoy1cSqqi7mtiAa5A== + version "3.1.5" + resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz#9882d3642b8c82c815782a12e6a1bfeed0026309" + integrity sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g== dependencies: byline "^5.0.0" graceful-fs "^4.1.15" @@ -12630,13 +14331,6 @@ npm-packlist@^1.4.4: npm-bundled "^1.0.1" npm-normalize-package-bin "^1.0.1" -npm-path@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64" - integrity sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw== - dependencies: - which "^1.2.10" - npm-pick-manifest@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz#f4d9e5fd4be2153e5f4e5f9b7be8dc419a99abb7" @@ -12646,21 +14340,6 @@ npm-pick-manifest@^3.0.0: npm-package-arg "^6.0.0" semver "^5.4.1" -npm-run-all@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" - integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== - dependencies: - ansi-styles "^3.2.1" - chalk "^2.4.1" - cross-spawn "^6.0.5" - memorystream "^0.3.1" - minimatch "^3.0.4" - pidtree "^0.3.0" - read-pkg "^3.0.0" - shell-quote "^1.6.1" - string.prototype.padend "^3.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -12668,22 +14347,13 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.0: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" -npm-which@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" - integrity sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo= - dependencies: - commander "^2.9.0" - npm-path "^2.0.2" - which "^1.2.10" - "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -12699,21 +14369,25 @@ nprogress@^0.2.0: resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E= -nssocket@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/nssocket/-/nssocket-0.6.0.tgz#59f96f6ff321566f33c70f7dbeeecdfdc07154fa" - integrity sha1-Wflvb/MhVm8zxw99vu7N/cBxVPo= - dependencies: - eventemitter2 "~0.4.14" - lazy "~1.0.11" - -nth-check@^1.0.2, nth-check@~1.0.1: +nth-check@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== dependencies: boolbase "~1.0.0" +nth-check@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" + integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== + dependencies: + boolbase "^1.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -12724,24 +14398,67 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -nwsapi@^2.2.0: +nuxt-i18n@^6.5.0: + version "6.28.1" + resolved "https://registry.yarnpkg.com/nuxt-i18n/-/nuxt-i18n-6.28.1.tgz#63e3e809fa41d855c5c7c443c0074eecaa97d273" + integrity sha512-JKRs8AmixVZ7k90Rrwq468McfnInP1ymuejYHRGA4VV0nZCLYsdDQXZxXl3JXaER9VatM9C24GM3ArAYFOtUhg== + dependencies: + "@babel/parser" "^7.14.9" + "@babel/traverse" "^7.14.9" + "@intlify/vue-i18n-extensions" "^1.0.2" + "@intlify/vue-i18n-loader" "^1.1.0" + cookie "^0.4.1" + devalue "^2.0.1" + is-https "^4.0.0" + js-cookie "^3.0.0" + klona "^2.0.4" + lodash.merge "^4.6.2" + ufo "^0.7.7" + vue-i18n "^8.25.0" + +nuxt-purgecss@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nuxt-purgecss/-/nuxt-purgecss-1.0.0.tgz#7c275205f0b727a5822781908d684f2e094ff5e7" + integrity sha512-gu6CUd+oA738Gqyl8P16HmnvreuslKVrn8tTJnjCut6OOC3pRrxqct6sAad0sxDk2fULVdeGb7V/mxo8qKisPQ== + dependencies: + "@fullhuman/postcss-purgecss" "^2.0.5" + consola "^1.4.5" + glob-all "^3.1.0" + purgecss "^2.0.5" + purgecss-webpack-plugin "^2.0.5" + +nuxt@^2.13.3: + version "2.15.8" + resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-2.15.8.tgz#946cba46bdaaf0e3918aa27fd9ea0fed8ed303b0" + integrity sha512-ceK3qLg/Baj7J8mK9bIxqw9AavrF+LXqwYEreBdY/a4Sj8YV4mIvhqea/6E7VTCNNGvKT2sJ/TTJjtfQ597lTA== + dependencies: + "@nuxt/babel-preset-app" "2.15.8" + "@nuxt/builder" "2.15.8" + "@nuxt/cli" "2.15.8" + "@nuxt/components" "^2.1.8" + "@nuxt/config" "2.15.8" + "@nuxt/core" "2.15.8" + "@nuxt/generator" "2.15.8" + "@nuxt/loading-screen" "^2.0.3" + "@nuxt/opencollective" "^0.3.2" + "@nuxt/server" "2.15.8" + "@nuxt/telemetry" "^1.3.3" + "@nuxt/utils" "2.15.8" + "@nuxt/vue-app" "2.15.8" + "@nuxt/vue-renderer" "2.15.8" + "@nuxt/webpack" "2.15.8" + +nwsapi@^2.0.7, nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -oauth-1.0a@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/oauth-1.0a/-/oauth-1.0a-1.0.1.tgz#29a9b580d7cf5de29573e2269fb05eb3efb1d819" - integrity sha1-Kam1gNfPXeKVc+Imn7Bes++x2Bk= - dependencies: - crypto-js "~3.1.2-2" - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -12755,20 +14472,18 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^1.1.4: - version "1.3.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" - integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== - -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-is@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" - integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.0, object-keys@^1.1.1: version "1.1.1" @@ -12782,23 +14497,24 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== +object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0, object.getownpropertydescriptors@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" + integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.0-next.2" object.pick@^1.3.0: version "1.3.0" @@ -12807,15 +14523,14 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== +object.values@^1.1.0, object.values@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" + es-abstract "^1.18.2" obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" @@ -12827,14 +14542,14 @@ octokit-pagination-methods@^1.1.0: resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== -on-finished@~2.3.0: +on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= dependencies: ee-first "1.1.1" -on-headers@~1.0.2: +on-headers@^1.0.2, on-headers@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== @@ -12858,22 +14573,22 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" opencollective-postinstall@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" - integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== -opener@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" - integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA== +opener@1.5.2, opener@^1.5.1, opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== opn@^5.5.0: version "5.5.0" @@ -12889,23 +14604,23 @@ optimism@^0.10.0: dependencies: "@wry/context" "^0.4.0" -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= +optimism@^0.16.0, optimism@^0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.1.tgz#7c8efc1f3179f18307b887e18c15c5b7133f6e7d" + integrity sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg== dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" + "@wry/context" "^0.6.0" + "@wry/trie" "^0.3.0" -optimize-css-assets-webpack-plugin@^5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572" - integrity sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA== +optimize-css-assets-webpack-plugin@^5.0.1, optimize-css-assets-webpack-plugin@^5.0.4: + version "5.0.8" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.8.tgz#cbccdcf5a6ef61d4f8cc78cf083a67446e5f402a" + integrity sha512-mgFS1JdOtEGzD8l+EuISqL57cKO+We9GcoiQEmdCWRqqck+FGNmYJtx9qfAPzEz+lRrlThWMuGDaRkI/yWNx/Q== dependencies: cssnano "^4.1.10" last-call-webpack-plugin "^3.0.0" -optionator@^0.8.1, optionator@^0.8.2: +optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== @@ -12917,15 +14632,17 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" word-wrap "~1.2.3" -ora@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" - integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q= +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: - chalk "^1.1.1" - cli-cursor "^1.0.2" - cli-spinners "^0.1.2" - object-assign "^4.0.1" + 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" @@ -12934,32 +14651,16 @@ original@^1.0.0: dependencies: url-parse "^1.4.3" -os-browserify@^0.3.0: +os-browserify@^0.3.0, os-browserify@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -os-locale@^3.0.0, os-locale@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - os-name@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" @@ -12968,7 +14669,7 @@ os-name@^3.1.0: macos-release "^2.2.0" windows-release "^3.1.0" -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -12981,6 +14682,18 @@ osenv@0, osenv@^0.1.4, osenv@^0.1.5: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +ospath@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" + integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= + +outpipe@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/outpipe/-/outpipe-1.1.1.tgz#50cf8616365e87e031e29a5ec9339a3da4725fa2" + integrity sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I= + dependencies: + shell-quote "^1.4.2" + p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -12991,25 +14704,29 @@ p-defer@^1.0.0: resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + dependencies: + p-reduce "^1.0.0" + p-each-series@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" - integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" - integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== - -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== +p-limit@3.1.0, p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" p-limit@^1.1.0: version "1.3.0" @@ -13019,9 +14736,9 @@ p-limit@^1.1.0: p-try "^1.0.0" p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" @@ -13046,6 +14763,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-map-series/-/p-map-series-1.0.0.tgz#bf98fe575705658a9e1351befb85ae4c1f07bdca" @@ -13053,16 +14777,18 @@ p-map-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-map@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" - integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== - p-map@^2.0.0, p-map@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-pipe@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" @@ -13104,16 +14830,6 @@ p-waterfall@^1.0.0: dependencies: p-reduce "^1.0.0" -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= - dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" - package-json@^6.3.0: version "6.5.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" @@ -13124,7 +14840,7 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -pako@~1.0.5: +pako@^1.0.10, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -13145,6 +14861,14 @@ param-case@2.1.x, param-case@^2.1.1: dependencies: no-case "^2.2.0" +param-case@^3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -13152,18 +14876,32 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-asn1@^5.0.0: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== +parents@^1.0.0, parents@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" + integrity sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E= + dependencies: + path-platform "~0.11.15" + +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== dependencies: - asn1.js "^4.0.0" + asn1.js "^5.2.0" browserify-aes "^1.0.0" - create-hash "^1.1.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-git-config@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/parse-git-config/-/parse-git-config-3.0.0.tgz#4a2de08c7b74a2555efa5ae94d40cd44302a6132" + integrity sha512-wXoQGL1D+2COYWCD35/xbiKma1Z15xvZL8cI25wvxzled58V51SJM04Urt/uznS900iQor7QO04SgdfT/XlbuA== + dependencies: + git-config-path "^2.0.0" + ini "^1.3.5" + parse-github-repo-url@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" @@ -13185,13 +14923,13 @@ parse-json@^4.0.0: json-parse-better-errors "^1.0.1" parse-json@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" - integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" parse-passwd@^1.0.0: @@ -13200,39 +14938,54 @@ parse-passwd@^1.0.0: integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= parse-path@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.1.tgz#0ec769704949778cb3b8eda5e994c32073a1adff" - integrity sha512-d7yhga0Oc+PwNXDvQ0Jv1BuWkLVPXcAoQ/WREgd6vNNoKYaW52KI+RdOFjI63wjkmps9yUE8VS4veP+AgpQ/hA== + version "4.0.3" + resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-4.0.3.tgz#82d81ec3e071dcc4ab49aa9f2c9c0b8966bb22bf" + integrity sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA== dependencies: is-ssh "^1.3.0" protocols "^1.4.0" + qs "^6.9.4" + query-string "^6.13.8" -parse-url@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f" - integrity sha512-flNUPP27r3vJpROi0/R3/2efgKkyXqnXwyP1KQ2U0SfFRgdizOdWfvrrvJg1LuOoxs7GQhmxJlq23IpQ/BkByg== +parse-url@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-6.0.0.tgz#f5dd262a7de9ec00914939220410b66cff09107d" + integrity sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw== dependencies: is-ssh "^1.3.0" - normalize-url "^3.3.0" + normalize-url "^6.1.0" parse-path "^4.0.0" protocols "^1.4.0" -parse5@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" - integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== + +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@0.0.1: +path-browserify@0.0.1, path-browserify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== @@ -13264,7 +15017,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -13280,22 +15033,20 @@ path-key@^3.0.0, path-key@^3.1.0: integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-platform@~0.11.15: + version "0.11.15" + resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" + integrity sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I= path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-to-regexp@^1.0.1: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -13305,13 +15056,6 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" - path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -13325,9 +15069,9 @@ path-type@^4.0.0: integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pbkdf2@^3.0.3: - version "3.0.17" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" - integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -13345,40 +15089,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -phantomjs-prebuilt@^2.1.10: - version "2.1.16" - resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz#efd212a4a3966d3647684ea8ba788549be2aefef" - integrity sha1-79ISpKOWbTZHaE6ouniFSb4q7+8= - dependencies: - es6-promise "^4.0.3" - extract-zip "^1.6.5" - fs-extra "^1.0.0" - hasha "^2.2.0" - kew "^0.7.0" - progress "^1.1.8" - request "^2.81.0" - request-progress "^2.0.1" - which "^1.2.10" - -picomatch@^2.0.4, picomatch@^2.0.5: - version "2.2.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" - integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== - -picomatch@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" - integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== - -pidtree@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" - integrity sha512-9CT4NFlDcosssyg8KVFltgokyKZIFjoBxw8CTGy+5F38Y1eQWrt8tRayiUOXE+zVKQnYu5BR8JjCtvK3BcnBhg== - -pidusage@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.2.0.tgz#65ee96ace4e08a4cd3f9240996c85b367171ee92" - integrity sha512-OGo+iSOk44HRJ8q15AyG570UYxcm5u+R99DI8Khu8P3tKGkVu5EZX4ywHglWSTMNNXQ274oeGpYrvFEhDIFGPg== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^2.0.0, pify@^2.2.0, pify@^2.3.0: version "2.3.0" @@ -13395,6 +15109,11 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -13414,13 +15133,6 @@ pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" -pkg-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" - integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q= - dependencies: - find-up "^1.0.0" - pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -13442,6 +15154,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" @@ -13449,125 +15168,98 @@ pkg-up@^2.0.0: dependencies: find-up "^2.1.0" -please-upgrade-node@^3.0.2, please-upgrade-node@^3.1.1: +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" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - -pm2-axon-rpc@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/pm2-axon-rpc/-/pm2-axon-rpc-0.5.1.tgz#ad3c43c43811c71f13e5eee2821194d03ceb03fe" - integrity sha512-hT8gN3/j05895QLXpwg+Ws8PjO4AVID6Uf9StWpud9HB2homjc1KKCcI0vg9BNOt56FmrqKDT1NQgheIz35+sA== - dependencies: - debug "^3.0" - -pm2-axon@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/pm2-axon/-/pm2-axon-3.1.0.tgz#1b4527f3385e203adc1a5b0488bb52f0322731da" - integrity sha512-5sBM+vHw0Cp2K9CJ9ZOYhKtNCCcgQ0eKOyFrSo5Jusbq9FfvuelsMG4WDaxkqosaQbf8N5YfyHhD7eOUcnm5rQ== - dependencies: - amp "~0.3.1" - amp-message "~0.1.1" - debug "^3.0" - escape-regexp "0.0.1" - -pm2-deploy@^0.3.9: - version "0.3.10" - resolved "https://registry.yarnpkg.com/pm2-deploy/-/pm2-deploy-0.3.10.tgz#5b6689df8db2390589244b7c15c563bd467093f6" - integrity sha512-WagPKsX+LDCe8wLCL5nzu8RQvVUQ5GlFdJRVYCL0ogFnHfYRym91qNU4PkNSWSq11pdvG8la7DTjdW6FWXc8lw== - dependencies: - async "^2.6" - tv4 "^1.3" - -pm2-multimeter@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz#1a1e55153d41a05534cea23cfe860abaa0eb4ace" - integrity sha1-Gh5VFT1BoFU0zqI8/oYKuqDrSs4= - dependencies: - charm "~0.1.1" - -pm2@^2.10.4: - version "2.10.4" - resolved "https://registry.yarnpkg.com/pm2/-/pm2-2.10.4.tgz#dd292fd26aed882f6e9f7b9652191387d2debe6a" - integrity sha512-AuAA6DoF/R3L9zSuYtKzaEd6UFvhCKqfW49dgLe0Q4SQtYmQMmXmyEAp5tr1iduJrqGRwpb5ytVm2rWZ56/4Vg== - dependencies: - async "^2.5" - blessed "^0.1.81" - chalk "^1.1" - chokidar "^2" - cli-table-redemption "^1.0.0" - commander "2.13.0" - cron "^1.3" - debug "^3.0" - eventemitter2 "1.0.5" - fclone "1.0.11" - mkdirp "0.5.1" - moment "^2.19" - needle "^2.1.0" - nssocket "0.6.0" - pidusage "^1.2.0" - pm2-axon "3.1.0" - pm2-axon-rpc "^0.5.1" - pm2-deploy "^0.3.9" - pm2-multimeter "^0.1.2" - pmx "^1.6" - promptly "2.2.0" - semver "^5.3" - shelljs "0.7.8" - source-map-support "^0.5" - sprintf-js "1.1.1" - v8-compile-cache "^1.1.0" - vizion "^0.2" - yamljs "^0.3.0" - optionalDependencies: - gkt "https://tgz.pm2.io/gkt-1.0.0.tgz" - -pmx@^1.6: - version "1.6.7" - resolved "https://registry.yarnpkg.com/pmx/-/pmx-1.6.7.tgz#b0fc8061bc8343a4069d18e4ee4f031de0af890a" - integrity sha512-CoyZD1EWj/fvpuEPnndB11s5onzN5p/0bxGsBuwbyb8uFtg3lMxXys1pXs88gReiRnMSYCSt25J3GCc6AnxoFQ== - dependencies: - debug "^3" - deep-metrics "^0.0.1" - json-stringify-safe "^5.0" - semver "5.*" - vxx "^1.2.0" - pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== -portfinder@^1.0.13, portfinder@^1.0.25: - version "1.0.25" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" - integrity sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg== +pnp-webpack-plugin@^1.6.4: + version "1.7.0" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz#65741384f6d8056f36e2255a8d67ffc20866f5c9" + integrity sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg== + dependencies: + ts-pnp "^1.1.6" + +portfinder@^1.0.13, portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== dependencies: async "^2.6.2" debug "^3.1.1" - mkdirp "^0.5.1" + mkdirp "^0.5.5" posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + postcss-calc@^7.0.1: - version "7.0.2" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" - integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== + version "7.0.5" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" + integrity sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg== dependencies: postcss "^7.0.27" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.0.2" +postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + +postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + postcss-colormin@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" @@ -13587,6 +15279,37 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + +postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + +postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + postcss-discard-comments@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" @@ -13615,38 +15338,99 @@ postcss-discard-overridden@^4.0.1: dependencies: postcss "^7.0.0" -postcss-flexbugs-fixes@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-3.3.1.tgz#0783cc7212850ef707f97f8bc8b6fb624e00c75d" - integrity sha512-9y9kDDf2F9EjKX6x9ueNa5GARvsUbXw4ezH8vXItXHwKzljbu8awP7t5dCaabKYm18Vs1lo5bKQcnc0HkISt+w== +postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== dependencies: - postcss "^6.0.1" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" -postcss-flexbugs-fixes@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.0.tgz#662b3dcb6354638b9213a55eed8913bcdc8d004a" - integrity sha512-QRE0n3hpkxxS/OGvzOa+PDuy4mh/Jg4o9ui22/ko5iGYOG3M5dfJabjnAZjTdh2G9F85c7Hv8hWcEDEKW/xceQ== +postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== dependencies: - postcss "^7.0.26" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + +postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + +postcss-font-variant@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz#42d4c0ab30894f60f98b17561eb5c0321f502641" + integrity sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA== + dependencies: + postcss "^7.0.2" + +postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + +postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-import-resolver@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-import-resolver/-/postcss-import-resolver-2.0.0.tgz#95c61ac5489047bd93ff42a9cd405cfe9041e2c0" + integrity sha512-y001XYgGvVwgxyxw9J1a5kqM/vtmIQGzx34g0A0Oy44MFcy/ZboZw1hu/iN3VYFjSTRzbvd7zZJJz0Kh0AGkTw== + dependencies: + enhanced-resolve "^4.1.1" + +postcss-import@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153" + integrity sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw== + dependencies: + postcss "^7.0.1" + postcss-value-parser "^3.2.3" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-initial@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.4.tgz#9d32069a10531fe2ecafa0b6ac750ee0bc7efc53" + integrity sha512-3RLn6DIpMsK1l5UUy9jxQvoDeUN4gP939tDcKUHD/kM8SGSKbFAnvkpFpj3Bhtz3HGk1jWY5ZNWX6mPta5M9fg== + dependencies: + postcss "^7.0.2" + +postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" postcss-load-config@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" - integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.2.tgz#c5ea504f2c4aef33c7359a34de3573772ad7502a" + integrity sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw== dependencies: cosmiconfig "^5.0.0" import-cwd "^2.0.0" -postcss-loader@^2.1.5: - version "2.1.6" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.1.6.tgz#1d7dd7b17c6ba234b9bed5af13e0bea40a42d740" - integrity sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg== - dependencies: - loader-utils "^1.1.0" - postcss "^6.0.0" - postcss-load-config "^2.0.0" - schema-utils "^0.4.0" - postcss-loader@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" @@ -13657,6 +15441,20 @@ postcss-loader@^3.0.0: postcss-load-config "^2.0.0" schema-utils "^1.0.0" +postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + +postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" + postcss-merge-longhand@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" @@ -13719,13 +15517,6 @@ postcss-minify-selectors@^4.0.2: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -postcss-modules-extract-imports@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz#dc87e34148ec7eab5f791f7cd5849833375b741a" - integrity sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw== - dependencies: - postcss "^6.0.1" - postcss-modules-extract-imports@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" @@ -13733,14 +15524,6 @@ postcss-modules-extract-imports@^2.0.0: dependencies: postcss "^7.0.5" -postcss-modules-local-by-default@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" - integrity sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk= - dependencies: - css-selector-tokenizer "^0.7.0" - postcss "^6.0.1" - postcss-modules-local-by-default@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz#dd9953f6dd476b5fd1ef2d8830c8929760b56e63" @@ -13750,30 +15533,24 @@ postcss-modules-local-by-default@^2.0.6: postcss-selector-parser "^6.0.0" postcss-value-parser "^3.3.1" -postcss-modules-scope@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" - integrity sha1-1upkmUx5+XtipytCb75gVqGUu5A= +postcss-modules-local-by-default@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== dependencies: - css-selector-tokenizer "^0.7.0" - postcss "^6.0.1" + icss-utils "^4.1.1" + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" -postcss-modules-scope@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz#33d4fc946602eb5e9355c4165d68a10727689dba" - integrity sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ== +postcss-modules-scope@^2.1.0, postcss-modules-scope@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== dependencies: postcss "^7.0.6" postcss-selector-parser "^6.0.0" -postcss-modules-values@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" - integrity sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA= - dependencies: - icss-replace-symbols "^1.1.0" - postcss "^6.0.1" - postcss-modules-values@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz#479b46dc0c5ca3dc7fa5270851836b9ec7152f64" @@ -13782,6 +15559,21 @@ postcss-modules-values@^2.0.0: icss-replace-symbols "^1.1.0" postcss "^7.0.6" +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + +postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== + dependencies: + postcss "^7.0.2" + postcss-normalize-charset@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" @@ -13872,6 +15664,79 @@ postcss-ordered-values@^4.1.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + +postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + +postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-preset-env@^6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + +postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + postcss-reduce-initial@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" @@ -13892,6 +15757,13 @@ postcss-reduce-transforms@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" +postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== + dependencies: + postcss "^7.0.2" + postcss-safe-parser@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" @@ -13899,6 +15771,22 @@ postcss-safe-parser@^4.0.1: dependencies: postcss "^7.0.26" +postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-not@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.1.tgz#263016eef1cf219e0ade9a913780fc1f48204cbf" + integrity sha512-YolvBgInEK5/79C+bdFMyzqTg6pkYqDbzZIST/PDMqa/o3qtXenD05apBG2jLgT0/BQ77d4U2UK12jWpilqMAQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + postcss-selector-parser@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" @@ -13908,21 +15796,28 @@ postcss-selector-parser@^3.0.0: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" - integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== +postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== dependencies: - cssesc "^3.0.0" + cssesc "^2.0.0" indexes-of "^1.0.1" uniq "^1.0.1" -postcss-svgo@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" - integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.6" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" + integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-svgo@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e" + integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw== dependencies: - is-svg "^3.0.0" postcss "^7.0.0" postcss-value-parser "^3.0.0" svgo "^1.0.0" @@ -13936,40 +15831,65 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" +postcss-url@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-8.0.0.tgz#7b10059bd12929cdbb1971c60f61a0e5af86b4ca" + integrity sha512-E2cbOQ5aii2zNHh8F6fk1cxls7QVFZjLPSrqvmiza8OuXLzIpErij8BDS5Y3STPfJgpIMNCPEr8JlKQWEoozUw== + dependencies: + mime "^2.3.1" + minimatch "^3.0.4" + mkdirp "^0.5.0" + postcss "^7.0.2" + xxhashjs "^0.2.1" + postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== -postcss-value-parser@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz#651ff4593aa9eda8d5d0d66593a2417aeaeb325d" - integrity sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg== +postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.23: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== +postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== dependencies: - chalk "^2.4.1" + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss@7.0.32: + version "7.0.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== + dependencies: + chalk "^2.4.2" source-map "^0.6.1" - supports-color "^5.4.0" + supports-color "^6.1.0" -postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.5, postcss@^7.0.6: - version "7.0.27" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" - integrity sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ== +postcss@7.x.x, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.36" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb" + integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw== dependencies: chalk "^2.4.2" source-map "^0.6.1" supports-color "^6.1.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" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.1: +prepend-http@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= @@ -13984,35 +15904,55 @@ prettier@^1.18.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -pretty-bytes@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" - integrity sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk= +pretty-bytes@^5.4.1, pretty-bytes@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -pretty-error@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" - integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= +pretty-error@^2.0.2, pretty-error@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" + integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== dependencies: - renderkid "^2.0.1" - utila "~0.4" + lodash "^4.17.20" + renderkid "^2.0.4" + +pretty-format@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" + integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== + dependencies: + "@jest/types" "^24.9.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" -pretty-format@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8" - integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ== +pretty-format@^26.0.0, pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== dependencies: - "@jest/types" "^25.1.0" + "@jest/types" "^26.6.2" ansi-regex "^5.0.0" ansi-styles "^4.0.0" - react-is "^16.12.0" + react-is "^17.0.1" + +pretty-format@^27.0.6: + version "27.0.6" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.0.6.tgz#ab770c47b2c6f893a21aefc57b75da63ef49a11f" + integrity sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ== + dependencies: + "@jest/types" "^27.0.6" + ansi-regex "^5.0.0" + ansi-styles "^5.0.0" + react-is "^17.0.1" pretty-time@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== -pretty@2.0.0, pretty@^2.0.0: +pretty@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pretty/-/pretty-2.0.0.tgz#adbc7960b7bbfe289a557dc5f737619a220d06a5" integrity sha1-rbx5YLe7/iiaVX3F9zdhmiINBqU= @@ -14021,40 +15961,26 @@ pretty@2.0.0, pretty@^2.0.0: extend-shallow "^2.0.1" js-beautify "^1.6.12" -print-message@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/print-message/-/print-message-2.1.0.tgz#b5588ed08b0e1bf77ac7bcb5cb78004afaf9a891" - integrity sha1-tViO0IsOG/d6x7y1y3gASvr5qJE= - dependencies: - chalk "1.1.1" +printj@~1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" + integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== prismjs@^1.13.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.21.0.tgz#36c086ec36b45319ec4218ee164c110f9fc015a3" - integrity sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw== - optionalDependencies: - clipboard "^2.0.0" - -private@^0.1.6, private@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + version "1.24.1" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.1.tgz#c4d7895c4d6500289482fa8936d9cdd192684036" + integrity sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow== process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: +process@^0.11.10, process@~0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" - integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= - progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -14065,11 +15991,6 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise-polyfill@^8.1.3: - version "8.1.3" - resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" - integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== - promise-retry@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" @@ -14078,20 +15999,20 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" -promptly@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/promptly/-/promptly-2.2.0.tgz#2a13fa063688a2a5983b161fff0108a07d26fc74" - integrity sha1-KhP6BjaIoqWYOxYf/wEIoH0m/HQ= +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== dependencies: - read "^1.0.4" + asap "~2.0.3" prompts@^2.0.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.1.tgz#b63a9ce2809f106fa9ae1277c275b167af46ea05" - integrity sha512-qIP2lQyCwYbdzcqHIUi2HAxiWixhoM9OdLCWf8txXsapC/X9YdsCoeyRIXE/GP+Q0J37Q7+XN/MFqbUa7IzXNA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" + integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== dependencies: kleur "^3.0.3" - sisteransi "^1.0.4" + sisteransi "^1.0.5" promzard@^0.3.0: version "0.3.0" @@ -14100,10 +16021,23 @@ promzard@^0.3.0: dependencies: read "1" -property-expr@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" - integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== +prop-types@^15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +proper-lockfile@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" proto-list@~1.2.1: version "1.2.4" @@ -14111,9 +16045,9 @@ proto-list@~1.2.1: integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= protocols@^1.1.0, protocols@^1.4.0: - version "1.4.7" - resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.7.tgz#95f788a4f0e979b291ffefcf5636ad113d037d32" - integrity sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg== + version "1.4.8" + resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8" + integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg== protoduck@^5.0.1: version "5.0.1" @@ -14123,18 +16057,13 @@ protoduck@^5.0.1: genfun "^5.0.0" proxy-addr@~2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" - integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== dependencies: - forwarded "~0.1.2" + forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-polyfill@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/proxy-polyfill/-/proxy-polyfill-0.3.1.tgz#163d5283cf928dd8ddb5c5e88528e4ccd233496f" - integrity sha512-jywE1NIksgIGqZc4uF0QLbXGz2RcHQobsCkAW+8F0nr/6agap+TWksEAKyLnIBafPD88HT9qZR2ec0oomHdjcQ== - prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -14145,10 +16074,10 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24, psl@^1.1.28: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== +psl@^1.1.28, psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== public-encrypt@^4.0.0: version "4.0.3" @@ -14192,7 +16121,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.4.1: +punycode@^1.2.4, punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -14203,12 +16132,31 @@ punycode@^2.1.0, punycode@^2.1.1: integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== pupa@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.0.1.tgz#dbdc9ff48ffbea4a26a069b6f9f7abb051008726" - integrity sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA== + version "2.1.1" + resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== dependencies: escape-goat "^2.0.0" +purgecss-webpack-plugin@^2.0.5: + version "2.3.0" + resolved "https://registry.yarnpkg.com/purgecss-webpack-plugin/-/purgecss-webpack-plugin-2.3.0.tgz#ba50bc25524ddbc27b5ed47ef30af65ad3a10bb2" + integrity sha512-I9IMWFq4sHRg/FuuD38upRZdQ0EHuyrv+dFTKcPRjugJWkIU6S5lzKdZqO4cFo+ApcA5gqHnAEIk1ncPabZITg== + dependencies: + purgecss "^2.3.0" + webpack "^4.42.1" + webpack-sources "^1.4.3" + +purgecss@^2.0.5, purgecss@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-2.3.0.tgz#5327587abf5795e6541517af8b190a6fb5488bb3" + integrity sha512-BE5CROfVGsx2XIhxGuZAT7rTH9lLeQx/6M0P7DTXQH4IUc3BBzs9JUzt4yzGf3JrH9enkeq6YJBe9CTtkm1WmQ== + dependencies: + commander "^5.0.0" + glob "^7.0.0" + postcss "7.0.32" + postcss-selector-parser "^6.0.2" + q@^1.1.2, q@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -14219,19 +16167,30 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.9.4: + version "6.10.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@6.13.2: - version "6.13.2" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.2.tgz#3585aa9412c957cbd358fd5eaca7466f05586dda" - integrity sha512-BMmDaUiLDFU1hlM38jTFcRt7HYiGP/zt1sRzrIWm5zpeEuO1rkbPS0ELI3uehoLuuhHDCS8u8lhFN3fEN4JzPQ== +qss@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/qss/-/qss-2.0.3.tgz#630b38b120931b52d04704f3abfb0f861604a9ec" + integrity sha512-j48ZBT5IZbSqJiSU8EX4XrN8nXiflHvmMvv2XpFc31gh7n6EpSs75bNr6+oj3FOLWyT8m09pTmqLNl34L7/uPQ== + +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= dependencies: - decode-uri-component "^0.2.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" query-string@^5.0.1: version "5.1.1" @@ -14242,32 +16201,57 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -querystring-es3@^0.2.0, querystring-es3@^0.2.1: +query-string@^6.13.8: + version "6.14.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" + integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== + dependencies: + decode-uri-component "^0.2.0" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + +querystring-es3@^0.2.0, querystring-es3@^0.2.1, querystring-es3@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= -querystring@0.2.0, querystring@^0.2.0: +querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== quick-lru@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= -ramda@0.24.1: - version "0.24.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857" - integrity sha1-w7d1UZfzW43DUCIoJixMkd22uFc= +quick-lru@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" + integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + +ramda@~0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -14297,7 +16281,16 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: +rc9@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/rc9/-/rc9-1.2.0.tgz#ef098181fdde714efc4c426383d6e46c14b1254a" + integrity sha512-/jknmhG0USFAx5uoKkAKhtG40sONds9RWhFHrP1UzJ3OvVfqFWOypSUpmsQD0fFwAV7YtzHhsn3QNasfAoxgcQ== + dependencies: + defu "^2.0.4" + destr "^1.0.0" + flat "^5.0.0" + +rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -14307,10 +16300,22 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-is@^16.12.0: - version "16.13.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527" - integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA== +react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha1-5mTvMRYRZsl1HNvo28+GtftY93Q= + dependencies: + pify "^2.3.0" read-cmd-shim@^1.0.1: version "1.0.5" @@ -14319,17 +16324,22 @@ read-cmd-shim@^1.0.1: dependencies: graceful-fs "^4.1.2" +read-only-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" + integrity sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A= + dependencies: + readable-stream "^2.0.2" + "read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13: - version "2.1.1" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.1.tgz#16aa66c59e7d4dad6288f179dd9295fd59bb98f1" - integrity sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A== + version "2.1.2" + resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" + integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== dependencies: glob "^7.1.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.0" normalize-package-data "^2.0.0" npm-normalize-package-bin "^1.0.0" - optionalDependencies: - graceful-fs "^4.1.2" read-package-tree@^5.1.6: version "5.3.1" @@ -14348,14 +16358,6 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= - dependencies: - find-up "^2.0.0" - read-pkg "^2.0.0" - read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -14372,6 +16374,15 @@ read-pkg-up@^4.0.0: find-up "^3.0.0" read-pkg "^3.0.0" +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -14381,15 +16392,6 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -14399,7 +16401,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -read-pkg@^5.1.1: +read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== @@ -14409,7 +16411,7 @@ read-pkg@^5.1.1: parse-json "^5.0.0" type-fest "^0.6.0" -read@1, read@^1.0.4, read@~1.0.1: +read@1, read@~1.0.1: version "1.0.7" resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= @@ -14429,7 +16431,7 @@ read@1, read@^1.0.4, read@~1.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -14457,10 +16459,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -14471,13 +16473,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= - dependencies: - resolve "^1.1.6" - redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -14494,29 +16489,13 @@ redent@^2.0.0: indent-string "^3.0.0" strip-indent "^2.0.0" -redis-commands@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785" - integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg== - -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= - -redis-parser@^3.0.0: +redent@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= - dependencies: - redis-errors "^1.0.0" - -redis-tag-cache@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/redis-tag-cache/-/redis-tag-cache-1.2.1.tgz#07d6e1f945d9d5c8186602129cb10573167d7534" - integrity sha512-0F+rLtoIkz4NOWGt9k3qaPcbpvr6+wcEC9aWV78newMKTRN/nIYHgvfyO1Q0ySEepiEYbqj9DhjvQ4CUAtT9/g== + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== dependencies: - ioredis "^4.0.0" + indent-string "^4.0.0" + strip-indent "^3.0.0" reduce@^1.0.1: version "1.0.2" @@ -14525,23 +16504,6 @@ reduce@^1.0.1: dependencies: object-keys "^1.1.0" -referrer-policy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" - integrity sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA== - -reflect-metadata@^0.1.12: - version "0.1.13" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" - integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== - -regenerate-unicode-properties@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" - integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -14549,49 +16511,27 @@ regenerate-unicode-properties@^8.2.0: dependencies: regenerate "^1.4.0" -regenerate@^1.2.1, regenerate@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" - integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== - -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerate@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.12.1: +regenerator-runtime@^0.12.0: version "0.12.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== -regenerator-runtime@^0.13.2: - version "0.13.3" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" - integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== - -regenerator-transform@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" - integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== - dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" - private "^0.1.6" - -regenerator-transform@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb" - integrity sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ== - dependencies: - private "^0.1.6" +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== regenerator-transform@^0.14.2: - version "0.14.4" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" - integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + version "0.14.5" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" + integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw== dependencies: "@babel/runtime" "^7.8.4" - private "^0.1.8" regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" @@ -14602,48 +16542,22 @@ regex-not@^1.0.0, regex-not@^1.0.2: safe-regex "^1.1.0" regexp.prototype.flags@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" - integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -regexpp@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" - integrity sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw== - -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - -regexpu-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" - integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" -regexpu-core@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" - integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== - dependencies: - regenerate "^1.4.0" - regenerate-unicode-properties "^8.1.0" - regjsgen "^0.5.0" - regjsparser "^0.6.0" - unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.1.0" +regexpp@^3.0.0, regexpp@^3.1.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: - version "4.7.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" - integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== +regexpu-core@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.1.tgz#2dea5a9a07233298fbf0db91fa9abc4c6e0f8ad6" + integrity sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ== dependencies: regenerate "^1.4.0" regenerate-unicode-properties "^8.2.0" @@ -14652,33 +16566,13 @@ regexpu-core@^4.7.0: unicode-match-property-ecmascript "^1.0.4" unicode-match-property-value-ecmascript "^1.2.0" -register-service-worker@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/register-service-worker/-/register-service-worker-1.6.2.tgz#9297e54c205c371c6e49bfa88f6997e8dd315f4c" - integrity sha512-I8L87fX2TK29LDx+wgyOUh2BJ3rDIRC1FtRZEHeP3rivzDv6p1DDZLGGtPucqjEkm45+2crtFIFssEWv56+9Wg== - -registry-auth-token@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" - integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== - dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" - registry-auth-token@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" - integrity sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA== + version "4.2.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== dependencies: rc "^1.2.8" -registry-url@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= - dependencies: - rc "^1.0.1" - registry-url@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" @@ -14686,39 +16580,15 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= - -regjsgen@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" - integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== - regjsgen@^0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== -regjsparser@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" - integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= - dependencies: - jsesc "~0.5.0" - -regjsparser@^0.6.0: - version "0.6.3" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.3.tgz#74192c5805d35e9f5ebe3c1fb5b40d40a8a38460" - integrity sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA== - dependencies: - jsesc "~0.5.0" - regjsparser@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" - integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + version "0.6.9" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.9.tgz#b489eef7c9a2ce43727627011429cf833a7183e6" + integrity sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ== dependencies: jsesc "~0.5.0" @@ -14727,31 +16597,58 @@ relateurl@0.2.x, relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -remove-accents@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= +relay-compiler@11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-11.0.2.tgz#e1e09a1c881d169a7a524ead728ad6a89c7bd4af" + integrity sha512-nDVAURT1YncxSiDOKa39OiERkAr0DUcPmlHlg+C8zD+EiDo2Sgczf2R6cDsN4UcDvucYtkLlDLFErPwgLs8WzA== + dependencies: + "@babel/core" "^7.0.0" + "@babel/generator" "^7.5.0" + "@babel/parser" "^7.0.0" + "@babel/runtime" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + babel-preset-fbjs "^3.3.0" + chalk "^4.0.0" + fb-watchman "^2.0.0" + fbjs "^3.0.0" + glob "^7.1.1" + immutable "~3.7.6" + invariant "^2.2.4" + nullthrows "^1.1.1" + relay-runtime "11.0.2" + signedsource "^1.0.0" + yargs "^15.3.1" + +relay-runtime@11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-11.0.2.tgz#c3650477d45665b9628b852b35f203e361ad55e8" + integrity sha512-xxZkIRnL8kNE1cxmwDXX8P+wSeWLR+0ACFyAiAhvfWWAyjXb+bhjJ2FSsRGlNYfkqaTNEuDqpnodQV1/fF7Idw== + dependencies: + "@babel/runtime" "^7.0.0" + fbjs "^3.0.0" + invariant "^2.2.4" remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= -renderkid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" - integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA== +renderkid@^2.0.4: + version "2.0.7" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.7.tgz#464f276a6bdcee606f4a15993f9b29fc74ca8609" + integrity sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ== dependencies: - css-select "^1.1.0" - dom-converter "^0.2" - htmlparser2 "^3.3.0" - strip-ansi "^3.0.0" - utila "^0.4.0" + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^3.0.1" repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + version "1.1.4" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== repeat-string@^1.6.1: version "1.6.1" @@ -14765,72 +16662,30 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -replace-in-file@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-4.3.1.tgz#c67c92178b38052008e379197cc0d86ca927f7b0" - integrity sha512-FqVvfmpqGTD2JRGI1JjJ86b24P17x/WWwGdxExeyJxnh/2rVQz2+jXfD1507UnnhEQw092X0u0DPCBf1WC4ooQ== - dependencies: - chalk "^2.4.2" - glob "^7.1.6" - yargs "^15.0.2" - -request-progress@3.0.0: +request-progress@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" integrity sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4= dependencies: throttleit "^1.0.0" -request-progress@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-2.0.1.tgz#5d36bb57961c673aa5b788dbc8141fdf23b44e08" - integrity sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg= - dependencies: - throttleit "^1.0.0" - -request-promise-core@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" - integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== dependencies: - lodash "^4.17.15" + lodash "^4.17.19" -request-promise-native@^1.0.7: - version "1.0.8" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" - integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== +request-promise-native@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== dependencies: - request-promise-core "1.1.3" + request-promise-core "1.1.4" stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -request@^2.72.0, request@^2.81.0, request@^2.87.0, request@^2.88.0: +request@^2.87.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -14861,38 +16716,16 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-in-the-middle@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.0.3.tgz#ef8bfd771760db573bc86d1341d8ae411a04c600" - integrity sha512-p/ICV8uMlqC4tjOYabLMxAWCIKa0YUQgZZ6KDM0xgXJNgdGQ1WmL2A07TwmrZw+wi6ITUFKzH5v3n+ENEyXVkA== - dependencies: - debug "^4.1.1" - module-details-from-path "^1.0.3" - resolve "^1.12.0" - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -require-uncached@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - -requireindex@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -14920,10 +16753,10 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve-from@^3.0.0: version "3.0.0" @@ -14935,10 +16768,19 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve-global@1.0.0, resolve-global@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-global/-/resolve-global-1.0.0.tgz#a2a79df4af2ca3f49bf77ef9ddacd322dad19255" + integrity sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw== + dependencies: + global-dirs "^0.1.1" + +resolve-pkg@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-2.0.0.tgz#ac06991418a7623edc119084edc98b0e6bf05a41" + integrity sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ== + dependencies: + resolve-from "^5.0.0" resolve-url@^0.2.1: version "0.2.1" @@ -14950,11 +16792,27 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.2.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.8.1: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== +resolve@1.20.0, resolve@^1.1.4, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.2.0, resolve@^1.20.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +resolve@~1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +resolve@~1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== dependencies: + is-core-module "^2.1.0" path-parse "^1.0.6" responselike@^1.0.2: @@ -14980,19 +16838,19 @@ 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" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry-request@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.1.1.tgz#f676d0db0de7a6f122c048626ce7ce12101d2bd8" - integrity sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ== - dependencies: - debug "^4.1.1" - through2 "^3.0.1" - retry@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" @@ -15003,6 +16861,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -15013,21 +16876,14 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: +rimraf@2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" -rimraf@2.6.3, rimraf@~2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -15042,22 +16898,60 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rollup-plugin-terser@^5.1.2: + version "5.3.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz#8c650062c22a8426c64268548957463bf981b413" + integrity sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w== + dependencies: + "@babel/code-frame" "^7.5.5" + jest-worker "^24.9.0" + rollup-pluginutils "^2.8.2" + serialize-javascript "^4.0.0" + terser "^4.6.2" + +rollup-plugin-typescript2@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.30.0.tgz#1cc99ac2309bf4b9d0a3ebdbc2002aecd56083d3" + integrity sha512-NUFszIQyhgDdhRS9ya/VEmsnpTe+GERDMmFo0Y+kf8ds51Xy57nPNGglJY+W6x1vcouA7Au7nsTgsLFj2I0PxQ== + dependencies: + "@rollup/pluginutils" "^4.1.0" + find-cache-dir "^3.3.1" + fs-extra "8.1.0" + resolve "1.20.0" + tslib "2.1.0" + +rollup-pluginutils@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" + integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== + dependencies: + estree-walker "^0.6.1" + +rollup@^1.25.2: + version "1.32.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4" + integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A== + dependencies: + "@types/estree" "*" + "@types/node" "*" + acorn "^7.1.0" + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== -run-async@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" +run-async@^2.2.0, run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== -run-node@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" - integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" @@ -15066,34 +16960,10 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rw@1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" - integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= - -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" - integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= - -rxjs@^5.0.0-beta.11: - version "5.5.12" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" - integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== - dependencies: - symbol-observable "1.0.1" - -rxjs@^6.3.3, rxjs@^6.4.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== +rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.6.0, 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== dependencies: tslib "^1.9.0" @@ -15102,10 +16972,10 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-regex@^1.1.0: version "1.1.0" @@ -15114,7 +16984,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -15134,38 +17004,48 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sass-graph@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" - integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= +sass-graph@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" + integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== dependencies: glob "^7.0.0" lodash "^4.0.0" scss-tokenizer "^0.2.3" - yargs "^7.0.0" + yargs "^13.3.2" -sass-loader@^7.0.3, sass-loader@^7.1.0: - version "7.3.1" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f" - integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA== +sass-loader@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" + integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== dependencies: clone-deep "^4.0.1" - loader-utils "^1.0.1" - neo-async "^2.5.0" - pify "^4.0.1" + loader-utils "^1.2.3" + neo-async "^2.6.1" + schema-utils "^2.6.1" semver "^6.3.0" +sass-resources-loader@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/sass-resources-loader/-/sass-resources-loader-2.2.4.tgz#1a86fba499e74a88cb7ce95f0c98449f348d360e" + integrity sha512-hIQhBygYky+rLf+4cuoGYONZ623CEH4Swo1fs1WRJkukbqdvN1VIu2KCL59du6vX92bNELzNkGPLx+NorN73xA== + dependencies: + async "^3.2.0" + chalk "^4.1.0" + glob "^7.1.6" + loader-utils "^2.0.0" + sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^3.1.9: - version "3.1.11" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" - integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: - xmlchars "^2.1.1" + xmlchars "^2.2.0" schema-utils@2.7.0: version "2.7.0" @@ -15176,14 +17056,6 @@ schema-utils@2.7.0: ajv "^6.12.2" ajv-keywords "^3.4.1" -schema-utils@^0.4.0, schema-utils@^0.4.5: - version "0.4.7" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" - integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== - dependencies: - ajv "^6.1.0" - ajv-keywords "^3.1.0" - schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -15193,6 +17065,24 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" +schema-utils@^2.0.0, schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -15201,6 +17091,11 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" +scule@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/scule/-/scule-0.2.1.tgz#0c1dc847b18e07219ae9a3832f2f83224e2079dc" + integrity sha512-M9gnWtn3J0W+UhJOHmBxBTwv8mZCan5i1Himp60t6vvZcor0wr+IM0URKmIglsWJ7bRujNAVVN77fp+uZaWoKg== + section-matter@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" @@ -15214,30 +17109,18 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -select@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" - integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= - -selfsigned@^1.10.7: - version "1.10.7" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" - integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== +selfsigned@^1.10.8: + version "1.10.11" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9" + integrity sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA== dependencies: - node-forge "0.9.0" + 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@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= - dependencies: - semver "^5.0.3" - semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -15245,43 +17128,33 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@5.*, semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +semver-regex@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807" + integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA== + +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.1.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== - semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@7.3.5, semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.0.0, semver@^7.1.1: - version "7.1.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" - integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== - -semver@^7.1.3: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== - -semver@^7.3.2: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== - dependencies: - lru-cache "^6.0.0" - semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -15306,10 +17179,26 @@ send@0.17.1: range-parser "~1.2.1" statuses "~1.5.0" -serialize-javascript@^2.1.0, serialize-javascript@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" - integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== +serialize-javascript@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" + integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" serve-index@^1.9.1: version "1.9.1" @@ -15324,7 +17213,14 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.14.1: +serve-placeholder@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/serve-placeholder/-/serve-placeholder-1.2.4.tgz#513eac9c435272c7fe9a86612c852ae9b1467fd4" + integrity sha512-jWD9cZXLcr4vHTTL5KEPIUBUYyOWN/z6v/tn0l6XxFhi9iqV3Fc5Y1aFeduUyz+cx8sALzGCUczkPfeOlrq9jg== + dependencies: + defu "^5.0.0" + +serve-static@1.14.1, serve-static@^1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== @@ -15334,10 +17230,10 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -serviceworker-cache-polyfill@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/serviceworker-cache-polyfill/-/serviceworker-cache-polyfill-4.0.0.tgz#de19ee73bef21ab3c0740a37b33db62464babdeb" - integrity sha1-3hnuc77yGrPAdAo3sz22JGS6ves= +server-destroy@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" + integrity sha1-8Tv5KOQrnD55OD5hzDmYtdFObN0= set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -15354,7 +17250,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -15369,7 +17265,7 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -sha.js@^2.4.0, sha.js@^2.4.8: +sha.js@^2.4.0, sha.js@^2.4.8, sha.js@^2.4.9, sha.js@~2.4.4: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -15384,6 +17280,21 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shasum-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shasum-object/-/shasum-object-1.0.0.tgz#0b7b74ff5b66ecf9035475522fa05090ac47e29e" + integrity sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg== + dependencies: + fast-safe-stringify "^2.0.7" + +shasum@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" + integrity sha1-5wEjENj0F/TetXEhUOVni4euVl8= + dependencies: + json-stable-stringify "~0.0.0" + sha.js "~2.4.4" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -15408,55 +17319,53 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.6.1: +shell-quote@^1.4.2, shell-quote@^1.6.1: version "1.7.2" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -shelljs@0.7.8: - version "0.7.8" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" - integrity sha1-3svPh0sNHl+3LhSxZKloMEjprLM= - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - -shelljs@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" - integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -shimmer@^1.0.0, shimmer@^1.1.0, shimmer@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" sigmund@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -simple-git@^1.85.0: - version "1.131.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.131.0.tgz#68d85bf6a706e418b8a92cae765d2ad358781e21" - integrity sha512-z/art7YYtmPnnLItT/j+nKwJt6ap6nHZ4D8sYo9PdCKK/ug56SN6m/evfxJk7uDV3e9JuCa8qIyDU2P3cxmiNQ== +signedsource@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" + integrity sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo= + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== dependencies: - debug "^4.0.1" + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" simple-swizzle@^0.2.2: version "0.2.2" @@ -15465,10 +17374,39 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -sisteransi@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.4.tgz#386713f1ef688c7c0304dc4c0632898941cad2e3" - integrity sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig== +simplebar-vue@^1.4.0: + version "1.6.7" + resolved "https://registry.yarnpkg.com/simplebar-vue/-/simplebar-vue-1.6.7.tgz#fb1e2502dec6c87b38dc39787908438d22e13f2b" + integrity sha512-URrEV7Y2XqWFmYTxNeDHluC+FCybksHvaace6ItflTl5WXqniQFnZVtUax2AsJK7+n48wp8wpfqBlFv+BR4MAw== + dependencies: + core-js "^3.0.1" + simplebar "^5.3.5" + +simplebar@^5.3.5: + version "5.3.5" + resolved "https://registry.yarnpkg.com/simplebar/-/simplebar-5.3.5.tgz#799d14cdc8bb8ed245789745b9f3741d05403944" + integrity sha512-mcTlXEiva8pSMdNEzeV3C1KyBHk7Sn2Pe46U1Uwo53dCuQqdJIK0bEJrIPs8W4/RqJquKIsC8Y1h7+aqOl8ccA== + dependencies: + "@juggle/resize-observer" "^3.3.1" + can-use-dom "^0.1.0" + core-js "^3.0.1" + lodash.debounce "^4.0.8" + lodash.memoize "^4.1.2" + lodash.throttle "^4.1.1" + +sirv@^1.0.7: + version "1.0.16" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.16.tgz#9caadfc46c264a38ad1c38c99259692fbf76ed10" + integrity sha512-x56DISeIgSUGVJrQS3mwu+UvtnzHenKDFBQL+UlAswxwk9b2Cpc0KGVvftoIJZgweOOXbMZzyXFYgVElOuSI1Q== + dependencies: + "@polka/url" "^1.0.0-next.19" + mime "^2.3.1" + totalist "^1.0.0" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^1.0.0: version "1.0.0" @@ -15490,21 +17428,23 @@ slice-ansi@0.0.4: resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" - integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== +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: - is-fullwidth-code-point "^2.0.0" + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== +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 "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" slide@^1.1.6: version "1.1.6" @@ -15512,9 +17452,9 @@ slide@^1.1.6: integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= smart-buffer@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.1.0.tgz#91605c25d91652f4661ea69ccf45f1b331ca21ba" - integrity sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw== + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== smoothscroll-polyfill@^0.4.3: version "0.4.4" @@ -15551,25 +17491,26 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== +sockjs-client@^1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.2.tgz#4bc48c2da9ce4769f19dc723396b50f5c12330a3" + integrity sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ== dependencies: - debug "^3.2.5" + debug "^3.2.6" eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" + faye-websocket "^0.11.3" + inherits "^2.0.4" + json3 "^3.3.3" + url-parse "^1.5.3" -sockjs@0.3.19: - version "0.3.19" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== +sockjs@^0.3.21: + version "0.3.21" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" + integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== dependencies: - faye-websocket "^0.10.0" - uuid "^3.0.1" + faye-websocket "^0.11.3" + uuid "^3.4.0" + websocket-driver "^0.7.4" socks-proxy-agent@^4.0.0: version "4.0.2" @@ -15587,6 +17528,13 @@ socks@~2.3.2: ip "1.1.5" smart-buffer "^4.1.0" +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + sort-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" @@ -15610,25 +17558,18 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - -source-map-support@^0.5, source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== +source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== source-map@0.5.6: version "0.5.6" @@ -15642,7 +17583,7 @@ source-map@^0.4.2: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -15652,36 +17593,41 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: +source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + spdx-correct@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" - integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q== + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg== + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + version "3.0.10" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== spdy-transport@^3.0.0: version "3.0.0" @@ -15695,10 +17641,10 @@ spdy-transport@^3.0.0: readable-stream "^3.0.6" wbuf "^1.7.3" -spdy@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.1.tgz#6f12ed1c5db7ea4f24ebb8b89ba58c87c08257f2" - integrity sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA== +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== dependencies: debug "^4.1.0" handle-thing "^2.0.0" @@ -15725,6 +17671,13 @@ split2@^2.0.0: dependencies: through2 "^2.0.2" +split2@^3.0.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" + integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== + dependencies: + readable-stream "^3.0.0" + split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" @@ -15732,11 +17685,6 @@ split@^1.0.0: dependencies: through "2" -sprintf-js@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" - integrity sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw= - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -15758,36 +17706,47 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^6.0.0, ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + version "6.0.2" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" +ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-trace@0.0.x: +stack-trace@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= stack-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" - integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.5.tgz#a19b0b01947e0029c8e451d5d61a498f5bb1471b" + integrity sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ== + dependencies: + escape-string-regexp "^2.0.0" -staged-git-files@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.2.tgz#4326d33886dc9ecfa29a6193bf511ba90a46454b" - integrity sha512-0Eyrk6uXW6tg9PYkhi/V/J4zHp33aNyi2hOCmhFLqLTIhbgqWn5jlSzI+IU0VqrZq6+DbHcabQl/WP6P3BG0QA== +stack-utils@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277" + integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw== + dependencies: + escape-string-regexp "^2.0.0" -standard-as-callback@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126" - integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg== +stackframe@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" + integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== static-extend@^0.1.1: version "0.1.2" @@ -15802,12 +17761,19 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -std-env@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-2.2.1.tgz#2ffa0fdc9e2263e0004c1211966e960948a40f6b" - integrity sha512-IjYQUinA3lg5re/YMlwlfhqNRTzMZMqE+pezevdcTaHceqx8ngEi1alX9nNCk9Sc81fy1fLDeQoaCzeiW1yBOQ== +std-env@^1.1.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-1.3.1.tgz#4e1758412439e9ece1d437b1b098551911aa44ee" + integrity sha512-KI2F2pPJpd3lHjng+QLezu0eq+QDtXcv1um016mhOPAJFHKL+09ykK5PUBWta2pZDC8BVV0VPya08A15bUXSLQ== + dependencies: + is-ci "^1.1.0" + +std-env@^2.2.1, std-env@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-2.3.0.tgz#66d4a4a4d5224242ed8e43f5d65cfa9095216eee" + integrity sha512-4qT5B45+Kjef2Z6pE0BkskzsH0GO7GrND0wGlTM1ioUe3v0dGYx9ZJH0Aro/YyA8fqQ5EyIKDRjZojJYMFTflw== dependencies: - ci-info "^1.6.0" + ci-info "^3.0.0" stdout-stream@^1.4.0: version "1.4.1" @@ -15821,14 +17787,7 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= -"storefront-query-builder@https://github.com/vuestorefront/storefront-query-builder.git": - version "1.0.1" - resolved "https://github.com/vuestorefront/storefront-query-builder.git#0ef1f9760eb44edce5b9464a9145ce0a7517a419" - dependencies: - "@typescript-eslint/parser" "^2.26.0" - clone-deep "^4.0.1" - -stream-browserify@^2.0.1: +stream-browserify@^2.0.0, stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== @@ -15836,6 +17795,14 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" +stream-combiner2@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -15844,14 +17811,7 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" -stream-events@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" - integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== - dependencies: - stubs "^3.0.0" - -stream-http@^2.7.2: +stream-http@^2.0.0, stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== @@ -15862,15 +17822,28 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-http@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.2.0.tgz#1872dfcf24cb15752677e40e5c3f9cc1926028b5" + integrity sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.4" + readable-stream "^3.6.0" + xtend "^4.0.2" + stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -stream-to-observable@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" - integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4= +stream-splicer@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.1.tgz#0b13b7ee2b5ac7e0609a7463d83899589a363fcd" + integrity sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.2" strict-uri-encode@^1.0.0: version "1.1.0" @@ -15882,20 +17855,28 @@ 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.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736" - integrity sha1-2sMECGkMIfPDYwo/86BYd73L1zY= +string-argv@0.3.1, 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-length@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" - integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= dependencies: astral-regex "^1.0.0" - strip-ansi "^5.2.0" + strip-ansi "^4.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" -string-width@^1.0.1, string-width@^1.0.2: +string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= @@ -15922,37 +17903,29 @@ string-width@^3.0.0, string-width@^3.1.0: strip-ansi "^5.1.0" string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== dependencies: emoji-regex "^8.0.0" is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.padend@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" - integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - function-bind "^1.1.1" -string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - function-bind "^1.1.1" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -15968,7 +17941,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -stringify-object@^3.2.2: +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== @@ -16010,6 +17983,11 @@ strip-bom-string@^1.0.0: resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI= +strip-bom@4.0.0, strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -16022,11 +18000,6 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -16049,7 +18022,24 @@ strip-indent@^2.0.0: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= -strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" + integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, 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" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -16063,10 +18053,14 @@ strong-log-transformer@^2.0.0: minimist "^1.2.0" through "^2.3.4" -stubs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" - integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= +style-resources-loader@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/style-resources-loader/-/style-resources-loader-1.4.1.tgz#87f520e6c8120a71e756726c1c53a78c544ca7db" + integrity sha512-UaAoQXq20relw6B633z4QZDxDyW7gevTt1e0y3MZtzdZfnvB90UL658czAgNc609Y7Kn5ErdthK9bSVhnykBUA== + dependencies: + glob "^7.1.6" + loader-utils "^2.0.0" + schema-utils "^3.0.0" stylehacks@^4.0.0: version "4.0.3" @@ -16086,50 +18080,75 @@ stylus-loader@^3.0.2: lodash.clonedeep "^4.5.0" when "~3.6.x" -stylus@^0.54.5: - version "0.54.7" - resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.7.tgz#c6ce4793965ee538bcebe50f31537bfc04d88cd2" - integrity sha512-Yw3WMTzVwevT6ZTrLCYNHAFmanMxdylelL3hkWNgPMeTCpMwpV3nXjpOHuBXtFv7aiO2xRuQS6OoAdgkNcSNug== +stylus@^0.54.8: + version "0.54.8" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" + integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg== dependencies: css-parse "~2.0.0" debug "~3.1.0" - glob "^7.1.3" - mkdirp "~0.5.x" + glob "^7.1.6" + mkdirp "~1.0.4" safer-buffer "^2.1.2" sax "~1.2.4" - semver "^6.0.0" + semver "^6.3.0" source-map "^0.7.3" -supports-color@5.5.0, supports-color@^5.3.0, supports-color@^5.4.0: +subarg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" + integrity sha1-9izxdYHplrSPyWVpn1TAauJouNI= + dependencies: + minimist "^1.1.0" + +subscriptions-transport-ws@^0.9.18: + version "0.9.19" + resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz#10ca32f7e291d5ee8eb728b9c02e43c52606cdcf" + integrity sha512-dxdemxFFB0ppCLg10FTtRqH/31FNRL1y1BQv8209MK5I4CwALb7iihQg+7p65lFcIl8MHatINWBLOqpgU4Kyyw== + dependencies: + backo2 "^1.0.2" + eventemitter3 "^3.1.0" + iterall "^1.2.1" + symbol-observable "^1.0.4" + ws "^5.2.0 || ^6.0.0 || ^7.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" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" -supports-color@6.1.0, supports-color@^6.1.0: +supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== dependencies: has-flag "^3.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" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= +supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== +supports-color@^8.0.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: - version "2.1.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" - integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== dependencies: has-flag "^4.0.0" supports-color "^7.0.0" @@ -16158,82 +18177,49 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -sw-precache-webpack-plugin@^0.11.5: - version "0.11.5" - resolved "https://registry.yarnpkg.com/sw-precache-webpack-plugin/-/sw-precache-webpack-plugin-0.11.5.tgz#9b53f65a4966e3adc298e256b3cef7a55c73fdfd" - integrity sha512-K6E52DbYyzGNXGyv2LhI2Duomr3t/2FFMmnGdHZ1Ruk3ulFHDMASJtg3WpA3CXlWODZx189tTaOIO5mWkSKyVg== - dependencies: - del "^3.0.0" - sw-precache "^5.2.1" - uglify-es "^3.3.9" - -sw-precache@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.1.tgz#06134f319eec68f3b9583ce9a7036b1c119f7179" - integrity sha512-8FAy+BP/FXE+ILfiVTt+GQJ6UEf4CVHD9OfhzH0JX+3zoy2uFk7Vn9EfXASOtVmmIVbL3jE/W8Z66VgPSZcMhw== - dependencies: - dom-urls "^1.1.0" - es6-promise "^4.0.5" - glob "^7.1.1" - lodash.defaults "^4.2.0" - lodash.template "^4.4.0" - meow "^3.7.0" - mkdirp "^0.5.1" - pretty-bytes "^4.0.2" - sw-toolbox "^3.4.0" - update-notifier "^2.3.0" - -sw-toolbox@^3.4.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/sw-toolbox/-/sw-toolbox-3.6.0.tgz#26df1d1c70348658e4dea2884319149b7b3183b5" - integrity sha1-Jt8dHHA0hljk3qKIQxkUm3sxg7U= - dependencies: - path-to-regexp "^1.0.1" - serviceworker-cache-polyfill "^4.0.0" - -symbol-observable@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" - integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= - -symbol-observable@^1.0.2, symbol-observable@^1.1.0: +symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== -symbol-tree@^3.2.2: +symbol-observable@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" + integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== + +symbol-tree@^3.2.2, symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -synchronous-promise@^2.0.6: - version "2.0.10" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.10.tgz#e64c6fd3afd25f423963353043f4a68ebd397fd8" - integrity sha512-6PC+JRGmNjiG3kJ56ZMNWDPL8hjyghF5cMXIFOKg+NiwwEZZIvxTWd0pinWKyD227odg9ygF8xVhhz7gb8Uq7A== +sync-fetch@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/sync-fetch/-/sync-fetch-0.3.0.tgz#77246da949389310ad978ab26790bb05f88d1335" + integrity sha512-dJp4qg+x4JwSEW1HibAuMi0IIrBI3wuQr2GimmqB7OXR50wmwzfdusG+p39R9w3R6aFtZ2mzvxvWKQ3Bd/vx3g== + dependencies: + buffer "^5.7.0" + node-fetch "^2.6.1" -table@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" - integrity sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA== +syntax-error@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" + integrity sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w== dependencies: - ajv "^5.2.3" - ajv-keywords "^2.1.0" - chalk "^2.1.0" - lodash "^4.17.4" - slice-ansi "1.0.0" - string-width "^2.1.1" + acorn-node "^1.2.0" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +table@^6.0.9: + version "6.7.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" + integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" -tapable@^1.0.0, tapable@^1.1.3: +tapable@^1.0.0, tapable@^1.0.0-beta.5, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== @@ -16248,28 +18234,41 @@ tar@^2.0.0: inherits "2" tar@^4.4.10, tar@^4.4.12, tar@^4.4.8: - version "4.4.13" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" - integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + version "4.4.19" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.19.tgz#2e4d7263df26f2b914dee10c825ab132123742f3" + integrity sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA== dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.8.6" - minizlib "^1.2.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.3" + chownr "^1.1.4" + fs-minipass "^1.2.7" + minipass "^2.9.0" + minizlib "^1.3.3" + mkdirp "^0.5.5" + safe-buffer "^5.2.1" + yallist "^3.1.1" + +tar@^6.0.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" -teeny-request@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.2.tgz#da0a6ba6fce4a9dab772ef84b04e417157c56fe7" - integrity sha512-B6fxA0fSnY/bul06NggdN1nywtr5U5Uvt96pHfTi8pi4MNe6++VUWcAAFBrcMeha94s+gULwA5WvagoSZ+AcYg== +tcomb-validation@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tcomb-validation/-/tcomb-validation-3.4.1.tgz#a7696ec176ce56a081d9e019f8b732a5a8894b65" + integrity sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA== dependencies: - http-proxy-agent "^4.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.2.0" - stream-events "^1.0.5" - uuid "^3.3.2" + tcomb "^3.0.0" + +tcomb@^3.0.0, tcomb@^3.2.17: + version "3.2.29" + resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-3.2.29.tgz#32404fe9456d90c2cf4798682d37439f1ccc386c" + integrity sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ== temp-dir@^1.0.0: version "1.0.0" @@ -16288,17 +18287,10 @@ temp-write@^3.4.0: temp-dir "^1.0.0" uuid "^3.0.1" -term-size@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" - integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk= - dependencies: - execa "^0.7.0" - term-size@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.0.tgz#1f16adedfe9bdc18800e1776821734086fcc6753" - integrity sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw== + version "2.2.1" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" + integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== terminal-link@^2.0.0: version "2.1.1" @@ -16309,29 +18301,53 @@ terminal-link@^2.0.0: supports-hyperlinks "^2.0.0" terser-webpack-plugin@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" - integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== + version "1.4.5" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" + integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== dependencies: cacache "^12.0.2" find-cache-dir "^2.1.0" is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^2.1.2" + serialize-javascript "^4.0.0" source-map "^0.6.1" terser "^4.1.2" webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser@^4.1.2: - version "4.6.6" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.6.tgz#da2382e6cafbdf86205e82fb9a115bd664d54863" - integrity sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g== +terser-webpack-plugin@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" + integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + jest-worker "^26.5.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.4" + webpack-sources "^1.4.3" + +terser@^4.1.2, terser@^4.3.9, terser@^4.6.2, terser@^4.6.3: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== dependencies: commander "^2.20.0" source-map "~0.6.1" source-map-support "~0.5.12" +terser@^5.3.4: + version "5.7.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.2.tgz#d4d95ed4f8bf735cb933e802f2a1829abf545e3f" + integrity sha512-0Omye+RD4X7X69O0eql3lC4Heh/5iLj3ggxR/B5ketZLOtLiOqukUgjw3q4PDnNQbsrkKr3UMypqStQG3XKRvw== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -16356,7 +18372,7 @@ text-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== -text-table@^0.2.0, text-table@~0.2.0: +text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= @@ -16369,21 +18385,32 @@ thenify-all@^1.0.0: thenify ">= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": - version "3.3.0" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" - integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: any-promise "^1.0.0" -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== +thread-loader@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/thread-loader/-/thread-loader-3.0.4.tgz#c392e4c0241fbc80430eb680e4886819b504a31b" + integrity sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA== + dependencies: + json-parse-better-errors "^1.0.2" + loader-runner "^4.1.0" + loader-utils "^2.0.0" + neo-async "^2.6.2" + schema-utils "^3.0.0" -throttle-debounce@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" - integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg== +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= + +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== throttleit@^1.0.0: version "1.0.0" @@ -16398,14 +18425,22 @@ through2@^2.0.0, through2@^2.0.2: readable-stream "~2.3.6" xtend "~4.0.1" -through2@^3.0.0, through2@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" - integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== +through2@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" + integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== dependencies: + inherits "^2.0.4" readable-stream "2 || 3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3.4: +through2@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" + integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== + dependencies: + readable-stream "3" + +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -16415,35 +18450,30 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -timed-out@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= +time-fix-plugin@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/time-fix-plugin/-/time-fix-plugin-2.0.7.tgz#4ba70ae2e40cedf34dabe505eda7b71b1b244f50" + integrity sha512-uVFet1LQToeUX0rTcSiYVYVoGuBpc8gP/2jnlUzuHMHe+gux6XLsNzxLUweabMwiUj5ejhoIMsUI55nVSEa/Vw== + +timers-browserify@^1.0.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" + integrity sha1-ycWLV1voQHN1y14kYtrO50NZ9B0= + dependencies: + process "~0.11.0" timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== dependencies: setimmediate "^1.0.4" -timsort@^0.3.0: +timsort@^0.3.0, timsort@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-emitter@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" - integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== - -tmp@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" - integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== - dependencies: - rimraf "^2.6.3" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -16451,6 +18481,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -16466,11 +18503,6 @@ to-factory@^1.0.0: resolved "https://registry.yarnpkg.com/to-factory/-/to-factory-1.0.0.tgz#8738af8bd97120ad1d4047972ada5563bf9479b1" integrity sha1-hzivi9lxIK0dQEeXKtpVY7+UebE= -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" - integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -16528,12 +18560,12 @@ toposort@^1.0.0: resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= -toposort@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" - integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= +totalist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" + integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== -tough-cookie@^2.3.3, tough-cookie@~2.5.0: +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -16541,22 +18573,14 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" - integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== dependencies: - ip-regex "^2.1.0" - psl "^1.1.28" + psl "^1.1.33" punycode "^2.1.1" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" + universalify "^0.1.2" tr46@^1.0.1: version "1.0.1" @@ -16565,6 +18589,13 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -16575,16 +18606,16 @@ trim-newlines@^2.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" integrity sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA= +trim-newlines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== + trim-off-newlines@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - "true-case-path@^1.0.2": version "1.0.3" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" @@ -16604,63 +18635,100 @@ ts-invariant@^0.4.0: dependencies: tslib "^1.9.3" -ts-jest@^25.2.1: - version "25.2.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.2.1.tgz#49bf05da26a8b7fbfbc36b4ae2fcdc2fef35c85d" - integrity sha512-TnntkEEjuXq/Gxpw7xToarmHbAafgCaAzOpnajnFC6jI7oo1trMzAHA04eWpc3MhV6+yvhE8uUBAmN+teRJh0A== +ts-invariant@^0.8.0: + version "0.8.2" + resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.8.2.tgz#62af654ebfb8b1eeb55bc9adc2f40c6b93b0ff7e" + integrity sha512-VI1ZSMW8soizP5dU8DsMbj/TncHf7bIUqavuE7FTeYeQat454HHurJ8wbfCnVWcDOMkyiBUWOW2ytew3xUxlRw== + dependencies: + tslib "^2.1.0" + +ts-invariant@^0.9.0: + version "0.9.1" + resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.9.1.tgz#87dfde9894a4ce3c7711b02b1b449e7fd7384b13" + integrity sha512-hSeYibh29ULlHkuEfukcoiyTct+s2RzczMLTv4x3NWC/YrBy7x7ps5eYq/b4Y3Sb9/uAlf54+/5CAEMVxPhuQw== + dependencies: + tslib "^2.1.0" + +ts-jest@^27.0.3: + version "27.0.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.5.tgz#0b0604e2271167ec43c12a69770f0bb65ad1b750" + integrity sha512-lIJApzfTaSSbtlksfFNHkWOzLJuuSm4faFAfo5kvzOiRAuoN4/eKxVJ2zEAho8aecE04qX6K1pAzfH5QHL1/8w== dependencies: bs-logger "0.x" - buffer-from "1.x" fast-json-stable-stringify "2.x" + jest-util "^27.0.0" json5 "2.x" - lodash.memoize "4.x" + lodash "4.x" make-error "1.x" - mkdirp "0.x" - resolve "1.x" - semver "^5.5" - yargs-parser "^16.1.0" + semver "7.x" + yargs-parser "20.x" -ts-loader@^5.3.0: - version "5.4.5" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-5.4.5.tgz#a0c1f034b017a9344cef0961bfd97cc192492b8b" - integrity sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw== +ts-loader@^8.0.17, ts-loader@^8.0.3: + version "8.3.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.3.0.tgz#83360496d6f8004fab35825279132c93412edf33" + integrity sha512-MgGly4I6cStsJy27ViE32UoqxPTN9Xly4anxxVyaIWR+9BGxboV4EyJBGfR3RePV7Ksjj3rHmPZJeIt+7o4Vag== dependencies: - chalk "^2.3.0" + chalk "^4.1.0" enhanced-resolve "^4.0.0" - loader-utils "^1.0.2" - micromatch "^3.1.4" - semver "^5.0.1" + loader-utils "^2.0.0" + micromatch "^4.0.0" + semver "^7.3.4" -ts-node@^8.6.2: - version "8.6.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35" - integrity sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg== +ts-node@^8.4.1: + version "8.10.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" + integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== dependencies: arg "^4.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.6" + source-map-support "^0.5.17" yn "3.1.1" -tsconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" - integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== +ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + +tsconfig-paths@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" + integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== dependencies: - "@types/strip-bom" "^3.0.0" - "@types/strip-json-comments" "0.0.30" + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" strip-bom "^3.0.0" - strip-json-comments "^2.0.0" + +tslib@2.1.0, tslib@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@~2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tslib@~2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" + integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== -tsutils@^3.17.1, tsutils@^3.7.0: - version "3.17.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== +tslib@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + +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" @@ -16669,6 +18737,11 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= +tty-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" + integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -16676,16 +18749,18 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tv4@^1.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.3.0.tgz#d020c846fadd50c855abb25ebaecc68fc10f7963" - integrity sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM= - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" 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" @@ -16698,10 +18773,20 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== +type-fest@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" + integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type-fest@^0.3.0: version "0.3.1" @@ -16713,7 +18798,7 @@ type-fest@^0.6.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -type-fest@^0.8.1: +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -16738,23 +18823,40 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.1.6: - version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" - integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== +typescript@^3.6.4: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== + +typescript@^4.2.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86" + integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ== + +typescript@~4.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== + +typescript@~4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" + integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== + +ua-parser-js@^0.7.18, ua-parser-js@^0.7.28: + version "0.7.28" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" + integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -uglify-es@^3.3.9: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" - integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ== - dependencies: - commander "~2.13.0" - source-map "~0.6.1" +ufo@^0.7.4, ufo@^0.7.7: + version "0.7.9" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.7.9.tgz#0268e3734b413c9ed6f3510201f42372821b875c" + integrity sha512-6t9LrLk3FhqTS+GW3IqlITtfRB5JAVr5MMNjpBECfK827W+Vh5Ilw/LhTcHWrt6b3hkeBvcbjx4Ti7QVFzmcww== uglify-js@3.4.x: version "3.4.10" @@ -16765,12 +18867,9 @@ uglify-js@3.4.x: source-map "~0.6.1" uglify-js@^3.1.4, uglify-js@^3.5.1: - version "3.8.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.8.0.tgz#f3541ae97b2f048d7e7e3aa4f39fd8a1f5d7a805" - integrity sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ== - dependencies: - commander "~2.20.3" - source-map "~0.6.1" + version "3.14.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.1.tgz#e2cb9fe34db9cb4cf7e35d1d26dfea28e09a7d06" + integrity sha512-JhS3hmcVaXlp/xSo3PKY5R0JqKs5M3IV+exdLHW99qKvKivPO4Z8qbej6mte17SOPqAOVMjt/XGgWacnFSzM3g== uid-number@0.0.6: version "0.0.6" @@ -16782,6 +18881,37 @@ umask@^1.1.0: resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= +umd@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" + integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +undeclared-identifiers@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz#9254c1d37bdac0ac2b52de4b6722792d2a91e30f" + integrity sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw== + dependencies: + acorn-node "^1.3.0" + dash-ast "^1.0.0" + get-assigned-identifiers "^1.2.0" + simple-concat "^1.0.0" + xtend "^4.0.1" + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -16795,20 +18925,15 @@ unicode-match-property-ecmascript@^1.0.4: unicode-canonical-property-names-ecmascript "^1.0.4" unicode-property-aliases-ecmascript "^1.0.4" -unicode-match-property-value-ecmascript@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" - integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== - unicode-match-property-value-ecmascript@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== unicode-property-aliases-ecmascript@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" - integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== union-value@^1.0.0: version "1.0.1" @@ -16844,13 +18969,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" - integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= - dependencies: - crypto-random-string "^1.0.0" - unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -16865,23 +18983,33 @@ universal-user-agent@^4.0.0: dependencies: os-name "^3.1.0" -universal-user-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-5.0.0.tgz#a3182aa758069bf0e79952570ca757de3579c1d9" - integrity sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q== - dependencies: - os-name "^3.1.0" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== -universalify@^0.1.0: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +unixify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090" + integrity sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA= + dependencies: + normalize-path "^2.1.1" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -16900,41 +19028,25 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -untildify@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" - integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA== - -unzip-response@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== upath@^1.1.0, upath@^1.1.1, upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@^2.3.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" - integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== - dependencies: - boxen "^1.2.1" - chalk "^2.0.1" - configstore "^3.0.0" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" +upath@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" + integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== update-notifier@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" - integrity sha512-w3doE1qtI0/ZmgeoDoARmI5fjDoT93IfKgEGqm26dGUOh8oNpaSTsGNdYRN/SjOuo10jcJGwkEL3mroKzktkew== + version "4.1.3" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" + integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== dependencies: boxen "^4.2.0" chalk "^3.0.0" @@ -16956,23 +19068,18 @@ upper-case@^1.1.1: integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" -urijs@^1.16.1: - version "1.19.2" - resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.2.tgz#f9be09f00c4c5134b7cb3cf475c1dd394526265a" - integrity sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w== - urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-loader@^1.0.1, url-loader@^1.1.2: +url-loader@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-1.1.2.tgz#b971d191b83af693c5e3fea4064be9e1f2d7f8d8" integrity sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg== @@ -16981,12 +19088,14 @@ url-loader@^1.0.1, url-loader@^1.1.2: mime "^2.0.3" schema-utils "^1.0.0" -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= +url-loader@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== dependencies: - prepend-http "^1.0.1" + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" url-parse-lax@^3.0.0: version "3.0.0" @@ -16995,15 +19104,15 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3, url-parse@^1.4.4: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== +url-parse@^1.4.3, url-parse@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" -url@0.11.0, url@^0.11.0: +url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= @@ -17016,7 +19125,7 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -17036,7 +19145,18 @@ util.promisify@1.0.0: define-properties "^1.1.2" object.getownpropertydescriptors "^2.0.3" -util.promisify@^1.0.0, util.promisify@~1.0.0: +util.promisify@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b" + integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + for-each "^0.3.3" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.1" + +util.promisify@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== @@ -17060,7 +19180,14 @@ util@^0.11.0: dependencies: inherits "2.0.3" -utila@^0.4.0, utila@~0.4: +util@~0.10.1: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + +utila@~0.4: version "0.4.0" resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= @@ -17070,35 +19197,35 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.2.tgz#7ff5c203467e91f5e0d85cfcbaaf7d2ebbca9be6" - integrity sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw== - -v8-compile-cache@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" - integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-1.1.2.tgz#8d32e4f16974654657e676e0e467a348e89b0dc4" - integrity sha512-ejdrifsIydN1XDH7EuR2hn8ZrkRKUYF7tUcBjBy/lhrCvs2K+zRlbW9UHc0IQ9RsYFZJFqJrieoIHfkCa0DBRA== +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@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.2.tgz#387d173be5383dbec209d21af033dcb892e3ac82" - integrity sha512-G9R+Hpw0ITAmPSr47lSlc5A1uekSYzXxTMlFxso2xoffwo4jQnzbv1p9yXIinO8UMZKfAFewaCHwWvnH4Jb4Ug== +v8-to-istanbul@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz#4229f2a99e367f3f018fa1d5c2b8ec684667c69c" + integrity sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" source-map "^0.7.3" +valid-url@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" + integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= + validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -17114,11 +19241,36 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vary@~1.1.2: +validator@^10.11.0: + version "10.11.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" + integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== + +validator@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-8.2.0.tgz#3c1237290e37092355344fef78c231249dab77b9" + integrity sha512-Yw5wW34fSv5spzTXNkokD6S6/Oq92d8q/t14TqsS3fAiA1RYnxSFSIZ+CY3n6PGGRCq5HhJTSepQvFUS2QUDxA== + +value-or-promise@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.10.tgz#5bf041f1e9a8e7043911875547636768a836e446" + integrity sha512-1OwTzvcfXkAfabk60UVr5NdjtjJ0Fg0T5+B1bhxtrOEwSH2fe8y4DnLgoksfCyd8yZCOQQHB0qLMQnwgCjbXLQ== + +value-or-promise@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.6.tgz#218aa4794aa2ee24dcf48a29aba4413ed584747f" + integrity sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg== + +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +vee-validate@^3.2.3: + version "3.4.11" + resolved "https://registry.yarnpkg.com/vee-validate/-/vee-validate-3.4.11.tgz#2015f75a4aa33c615418e6aa6b03bd778ed173d1" + integrity sha512-508zDZkxqzlXgOo3Rb+djjA1HxQjkUJzM2FKzxWIiwlarzYPb3J9seMifJZ+AfaWXraErZ+L0/6ncUBP7MYx2A== + vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" @@ -17133,110 +19285,71 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vizion@^0.2: - version "0.2.13" - resolved "https://registry.yarnpkg.com/vizion/-/vizion-0.2.13.tgz#1314cdee2b34116f9f5b1248536f95dbfcd6ef5f" - integrity sha1-ExTN7is0EW+fWxJIU2+V2/zW718= - dependencies: - async "1.5" - -vm-browserify@^1.0.1: +vm-browserify@^1.0.0, vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vue-analytics@^5.16.1: - version "5.22.1" - resolved "https://registry.yarnpkg.com/vue-analytics/-/vue-analytics-5.22.1.tgz#9d6b32da56daee1b9dfb23a267b50349a03f710f" - integrity sha512-HPKQMN7gfcUqS5SxoO0VxqLRRSPkG1H1FqglsHccz6BatBatNtm/Vyy8brApktZxNCfnAkrSVDpxg3/FNDeOgQ== - -vue-apollo@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vue-apollo/-/vue-apollo-3.0.3.tgz#7f29558df76eec0f03251847eef153816a261827" - integrity sha512-WJaQ1v/i46/oIPlKv7J0Tx6tTlbuaeCdhrAbL06h+Zca2gzr5ywjUFpl8ijMTGJsQ+Ph/U4xEpBFBOMxQmL+7g== - dependencies: - chalk "^2.4.2" - serialize-javascript "^2.1.0" - throttle-debounce "^2.1.0" - -vue-carousel@^0.18: - version "0.18.0" - resolved "https://registry.yarnpkg.com/vue-carousel/-/vue-carousel-0.18.0.tgz#478dfcad3abe2ee44c227020b6e60fb8484dc9f1" - integrity sha512-a2zxh7QJioDxNMguqcuJ7TPbfgK5bGDaAXIia7NWxPAWsEvNE4ZtHgsGu40L5Aha4uyjmNKXvleB14QAXFoKig== - dependencies: - global "^4.3.2" - regenerator-runtime "^0.12.1" - vue "^2.5.17" +vue-client-only@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/vue-client-only/-/vue-client-only-2.1.0.tgz#1a67a47b8ecacfa86d75830173fffee3bf8a4ee3" + integrity sha512-vKl1skEKn8EK9f8P2ZzhRnuaRHLHrlt1sbRmazlvsx6EiC3A8oWF8YCBrMJzoN+W3OnElwIGbVjsx6/xelY1AA== -vue-eslint-parser@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz#c268c96c6d94cfe3d938a5f7593959b0ca3360d1" - integrity sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw== +vue-drag-drop@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/vue-drag-drop/-/vue-drag-drop-1.1.4.tgz#6e94aeb10304b91d651614b7307d0b90f0227e89" + integrity sha512-zrpvKcD5LPVbht7cJ7ZBY0poS8GDLibRck18Zx0polkoSSpTPNMAv/m/b98xtZ3I1jdWGyiPybfPgKFFkygjhQ== dependencies: - debug "^3.1.0" - eslint-scope "^3.7.1" - eslint-visitor-keys "^1.0.0" - espree "^3.5.2" - esquery "^1.0.0" - lodash "^4.17.4" + core-js "^2.5.3" -vue-eslint-parser@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz#00f4e4da94ec974b821a26ff0ed0f7a78402b8a1" - integrity sha512-JlHVZwBBTNVvzmifwjpZYn0oPWH2SgWv5dojlZBsrhablDu95VFD+hriB1rQGwbD+bms6g+rAFhQHk6+NyiS6g== +vue-eslint-parser@^7.0.0, vue-eslint-parser@^7.10.0: + version "7.10.0" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.10.0.tgz#ea4e4b10fd10aa35c8a79ac783488d8abcd29be8" + integrity sha512-7tc/ewS9Vq9Bn741pvpg8op2fWJPH3k32aL+jcIcWGCTzh/zXSdh7pZ5FV3W2aJancP9+ftPAv292zY5T5IPCg== dependencies: - debug "^4.1.0" - eslint-scope "^4.0.0" - eslint-visitor-keys "^1.0.0" - espree "^4.1.0" - esquery "^1.0.1" - lodash "^4.17.11" + debug "^4.1.1" + eslint-scope "^5.1.1" + eslint-visitor-keys "^1.1.0" + espree "^6.2.1" + esquery "^1.4.0" + lodash "^4.17.21" + semver "^6.3.0" -vue-gtm@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/vue-gtm/-/vue-gtm-2.2.0.tgz#7b96cce86eb1318d37f55e761cb3253b5369c312" - integrity sha512-DsypU2nqN7xc8GbdAxOatzj+LCCkM9F4Lz5m0FhGLw1Cs+ENgHt7ieHIRD41pu8g0QbJiIMkpcnwPQEBe2Sg0w== +vue-fragment@^1.5.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/vue-fragment/-/vue-fragment-1.5.2.tgz#310017170c564c4aad95da14c185c92c6784fd3c" + integrity sha512-KEW0gkeNOLJjtXN4jqJhTazez5jtrwimHkE5Few/VxblH4F9EcvJiEsahrV5kg5uKd5U8du4ORKS6QjGE0piYA== vue-hot-reload-api@^2.3.0: version "2.3.4" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog== -vue-i18n@^8.0.0: - version "8.15.4" - resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.15.4.tgz#1bfba2b6a6cb6de7b44f0f0aa89ad775fc902bc2" - integrity sha512-brhbJRB/gyWlroAhQZU0TNTQzNonbkHmzH4HlJzs7c+DsVIhB5OlRHg3zAl+85kkT8mpxzvBE6Bm1slqnRRmsg== +vue-i18n@^8.25.0: + version "8.25.0" + resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.25.0.tgz#1037d9295fa2845a230b771de473481edb2cfc4c" + integrity sha512-ynhcL+PmTxuuSE1T10htiSXzjBozxYIE3ffbM1RfgAkVbr/v1SP+9Mi/7/uv8ZVV1yGuKjFAYp9BXq+X7op6MQ== -vue-jest@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.5.tgz#d6f124b542dcbff207bf9296c19413f4c40b70c9" - integrity sha512-xWDxde91pDqYBGDlODENZ3ezPgw+IQFoVDtf+5Awlg466w3KvMSqWzs8PxcTeTr+wmAHi0j+a+Lm3R7aUJa1jA== +vue-jest@^4.0.0-0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-4.0.1.tgz#683efc351c24456865b1356bae69d5bb663dafb5" + integrity sha512-4jewjN8HVgpIW0ZdEwzCWz5vcRLIs1PxMs+5IqJ/6f9KRbEQ+DEqEKHUzIjoNzW2UJOUYBZzWpBnVHakpc/k5w== dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.26.0" + "@babel/plugin-transform-modules-commonjs" "^7.2.0" + "@vue/component-compiler-utils" "^3.1.0" chalk "^2.1.0" extract-from-css "^0.4.4" - find-babel-config "^1.1.0" - js-beautify "^1.6.14" - node-cache "^4.1.1" - object-assign "^4.1.1" - source-map "^0.5.6" - tsconfig "^7.0.0" - vue-template-es2015-compiler "^1.6.0" - -vue-lazy-hydration@^1.0.0-beta.9: - version "1.0.0-beta.12" - resolved "https://registry.yarnpkg.com/vue-lazy-hydration/-/vue-lazy-hydration-1.0.0-beta.12.tgz#d578dc86059eb0a147e6e553585dd22c41dfbae2" - integrity sha512-0innXtjaqNodh0aw1/3dgGdEfL76eqDzo3CETSKolyjm3Z++hqBS7GQkrAAfp4JYBE12mb5yP2YnJxK6tJSGIA== + source-map "0.5.6" -vue-lazyload@^1.2.6: - version "1.3.3" - resolved "https://registry.yarnpkg.com/vue-lazyload/-/vue-lazyload-1.3.3.tgz#4df50a271bde9b74c3caf7a228d6e0af50d5682f" - integrity sha512-uHnq0FTEeNmqnbBC2aRKlmtd9LofMZ6Q3mWvgfLa+i9vhxU8fDK+nGs9c1iVT85axSua/AUnMttIq3xPaU9G3A== +vue-lazy-hydration@^2.0.0-beta.4: + version "2.0.0-beta.4" + resolved "https://registry.yarnpkg.com/vue-lazy-hydration/-/vue-lazy-hydration-2.0.0-beta.4.tgz#11e7021a9bac003ea6ed74f132dcd0d025fafc1f" + integrity sha512-bhr7AxzrSEPed4cOawIeCxJmR8pglberR78x1zs0886xR+47/EXE9s0Ezss90CPINo8ApzUfA/r+SbNffn+t9w== -vue-loader@^15.4.2, vue-loader@^15.5.1, vue-loader@^15.7.1: - version "15.9.0" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.0.tgz#5d4b0378a4606188fc83e587ed23c94bc3a10998" - integrity sha512-FeDHvTSpwyLeF7LIV1PYkvqUQgTJ8UmOxhSlCyRSxaXCKk+M6NF4tDQsLsPPNeDPyR7TfRQ8MLg6v+8PsDV9xQ== +vue-loader@^15.7.1, vue-loader@^15.9.7: + version "15.9.8" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.8.tgz#4b0f602afaf66a996be1e534fb9609dc4ab10e61" + integrity sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog== dependencies: "@vue/component-compiler-utils" "^3.1.0" hash-sum "^1.0.2" @@ -17244,37 +19357,39 @@ vue-loader@^15.4.2, vue-loader@^15.5.1, vue-loader@^15.7.1: vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" -vue-meta@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/vue-meta/-/vue-meta-2.3.3.tgz#2a097f62817204b0da78be4d62aee0cb566eaee0" - integrity sha512-FmeekLkd5+BI7NFBnU4bEnTpVZqmu3q8ztW4R4U3GBQljgDM7QAuwMXs8/aJP0U1tjtxobX8T6HcyZXimGj1DQ== +vue-meta@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/vue-meta/-/vue-meta-2.4.0.tgz#a419fb4b4135ce965dab32ec641d1989c2ee4845" + integrity sha512-XEeZUmlVeODclAjCNpWDnjgw+t3WA6gdzs6ENoIAgwO1J1d5p1tezDhtteLUFwcaQaTtayRrsx7GL6oXp/m2Jw== dependencies: deepmerge "^4.2.2" -vue-no-ssr@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/vue-no-ssr/-/vue-no-ssr-0.2.2.tgz#e1a0ef0ba39fc54972edd71a2ae11a7988312b19" - integrity sha512-JRiKLrefDdFCMDkDiOIGXd2kLCeLDVcUIihnMydxGtGlIphOvIyz1HolTAlD4yBYXvpVZest2Im0ku2LBFf1GA== +vue-multiselect@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/vue-multiselect/-/vue-multiselect-2.1.6.tgz#5be5d811a224804a15c43a4edbb7485028a89c7f" + integrity sha512-s7jmZPlm9FeueJg1RwJtnE9KNPtME/7C8uRWSfp9/yEN4M8XcS/d+bddoyVwVnvFyRh9msFo0HWeW0vTL8Qv+w== -vue-observe-visibility@^0.4.1: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-0.4.6.tgz#878cb8ebcf3078e40807af29774e97105ebd519e" - integrity sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q== +vue-no-ssr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/vue-no-ssr/-/vue-no-ssr-1.1.1.tgz#875f3be6fb0ae41568a837f3ac1a80eaa137b998" + integrity sha512-ZMjqRpWabMPqPc7gIrG0Nw6vRf1+itwf0Itft7LbMXs2g3Zs/NFmevjZGN1x7K3Q95GmIjWbQZTVerxiBxI+0g== -vue-offline@^1.0.8: - version "1.0.204" - resolved "https://registry.yarnpkg.com/vue-offline/-/vue-offline-1.0.204.tgz#9c9c0e98fc29cb3334b57848dd4c6f6cd9607d6d" - integrity sha512-4dYUE81k9tLSPbx7qlJDyKw2QNdNL7aJMuBAfrAhtCjZdZsiXoaS56QApZaTh8BV3yG6sbTdbAD6hDaEYHe3EQ== +vue-router@^3.4.5, vue-router@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.5.2.tgz#5f55e3f251970e36c3e8d88a7cd2d67a350ade5c" + integrity sha512-807gn82hTnjCYGrnF3eNmIw/dk7/GE4B5h69BlyCK9KHASwSloD1Sjcn06zg9fVG4fYH2DrsNBZkpLtb25WtaQ== -vue-router@3.1.6, vue-router@^3.1.3: - version "3.1.6" - resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.1.6.tgz#45f5a3a3843e31702c061dd829393554e4328f89" - integrity sha512-GYhn2ynaZlysZMkFE5oCHRUTqE8BWs/a9YbKpNLi0i7xD6KG1EzDqpHQmv1F5gXjr8kL5iIVS8EOtRaVUEXTqA== +vue-scrollto@^2.17.1: + version "2.20.0" + resolved "https://registry.yarnpkg.com/vue-scrollto/-/vue-scrollto-2.20.0.tgz#3ba52239a62710c97003d12d4393f1f3393cd5cc" + integrity sha512-7i+AGKJTThZnMEkhIPgrZjyAX+fXV7/rGdg+CV283uZZwCxwiMXaBLTmIc5RTA4uwGnT+E6eJle3mXQfM2OD3A== + dependencies: + bezier-easing "2.1.0" -vue-server-renderer@^2.6.10, vue-server-renderer@^2.6.11: - version "2.6.11" - resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.11.tgz#be8c9abc6aacc309828a755c021a05fc474b4bc3" - integrity sha512-V3faFJHr2KYfdSIalL+JjinZSHYUhlrvJ9pzCIjjwSh77+pkrsXpK4PucdPcng57+N77pd1LrKqwbqjQdktU1A== +vue-server-renderer@^2.6.10, vue-server-renderer@^2.6.12: + version "2.6.14" + resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.14.tgz#c8bffff152df6b47b858818ef8d524d2fc351654" + integrity sha512-HifYRa/LW7cKywg9gd4ZtvtRuBlstQBao5ZCWlg40fyB4OPoGfEXAzxb0emSLv4pBDOHYx0UjpqvxpiQFEuoLA== dependencies: chalk "^1.1.3" hash-sum "^1.0.2" @@ -17282,47 +19397,39 @@ vue-server-renderer@^2.6.10, vue-server-renderer@^2.6.11: lodash.template "^4.5.0" lodash.uniq "^4.5.0" resolve "^1.2.0" - serialize-javascript "^2.1.2" + serialize-javascript "^3.1.0" source-map "0.5.6" -vue-ssr-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/vue-ssr-webpack-plugin/-/vue-ssr-webpack-plugin-3.0.0.tgz#db47769ed8e71c8eb53aa9ae7be9ff04baf546fa" - integrity sha1-20d2ntjnHI61Oqmue+n/BLr1Rvo= - dependencies: - chalk "^1.1.3" - hash-sum "^1.0.2" - -vue-style-loader@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8" - integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ== +vue-style-loader@^4.1.0, vue-style-loader@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35" + integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg== dependencies: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@^2.6.10, vue-template-compiler@^2.6.11: - version "2.6.11" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080" - integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA== +vue-template-compiler@^2.6.10, vue-template-compiler@^2.6.12, vue-template-compiler@^2.6.14, vue-template-compiler@^2.6.x: + version "2.6.14" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763" + integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g== dependencies: de-indent "^1.0.2" he "^1.1.0" -vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0: +vue-template-es2015-compiler@^1.9.0: version "1.9.1" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== -vue@^2.5.17, vue@^2.6.10, vue@^2.6.11: - version "2.6.11" - resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5" - integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ== +vue2-leaflet@^2.5.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/vue2-leaflet/-/vue2-leaflet-2.7.1.tgz#2f95c287621bf778f10804c88223877f5c049257" + integrity sha512-K7HOlzRhjt3Z7+IvTqEavIBRbmCwSZSCVUlz9u4Rc+3xGCLsHKz4TAL4diAmfHElCQdPPVdZdJk8wPUt2fu6WQ== -vuelidate@^0.7.5: - version "0.7.5" - resolved "https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.7.5.tgz#ff48c75ae9d24ea24c24e9ea08065eda0a0cba0a" - integrity sha512-GAAG8QAFVp7BFeQlNaThpTbimq3+HypBPNwdkCkHZZeVaD5zmXXfhp357dcUJXHXTZjSln0PvP6wiwLZXkFTwg== +vue@^2.6.10, vue@^2.6.11, vue@^2.6.12, vue@^2.6.x: + version "2.6.14" + resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235" + integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ== vuepress-html-webpack-plugin@^3.2.0: version "3.2.0" @@ -17338,10 +19445,11 @@ vuepress-html-webpack-plugin@^3.2.0: util.promisify "1.0.0" vuepress-plugin-container@^2.0.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/vuepress-plugin-container/-/vuepress-plugin-container-2.1.2.tgz#688c1e5a9709a1b8658605bcaee580b3abb19d16" - integrity sha512-Df5KoIDMYiFg45GTfFw2hIiLGSsjhms4f3ppl2UIBf5nWMxi2lfifcoo8MooMSfxboxRZjoDccqQfu0fypaKrQ== + version "2.1.5" + resolved "https://registry.yarnpkg.com/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz#37fff05662fedbd63ffd3a5463b2592c7a7f3133" + integrity sha512-TQrDX/v+WHOihj3jpilVnjXu9RcTm6m8tzljNJwYhxnJUW0WWQ0hFLcDTqTBwgKIFdEiSxVOmYE+bJX/sq46MA== dependencies: + "@vuepress/shared-utils" "^1.2.0" markdown-it-container "^2.0.0" vuepress-plugin-smooth-scroll@^0.0.3: @@ -17351,59 +19459,35 @@ vuepress-plugin-smooth-scroll@^0.0.3: dependencies: smoothscroll-polyfill "^0.4.3" -vuepress@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/vuepress/-/vuepress-1.3.1.tgz#59355081a2c66ba3c9432ffd6e1d4025f9cf8e24" - integrity sha512-i0f0JB0zdmdVH8P8cO4w7PljPQpf8ObiVk/1pOidvMQCMEhFmIpYz+730Wlf0rtB/GG4QUsqQ27Ckp5Rfob+hQ== +vuepress@^1.2.0: + version "1.8.2" + resolved "https://registry.yarnpkg.com/vuepress/-/vuepress-1.8.2.tgz#97e8bf979630611fc7b621fc4cc35b798ee5e847" + integrity sha512-BU1lUDwsA3ghf7a9ga4dsf0iTc++Z/l7BR1kUagHWVBHw7HNRgRDfAZBDDQXhllMILVToIxaTifpne9mSi94OA== dependencies: - "@vuepress/core" "^1.3.1" - "@vuepress/theme-default" "^1.3.1" + "@vuepress/core" "1.8.2" + "@vuepress/theme-default" "1.8.2" cac "^6.5.6" envinfo "^7.2.0" opencollective-postinstall "^2.0.2" update-notifier "^4.0.0" -vuex-router-sync@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vuex-router-sync/-/vuex-router-sync-5.0.0.tgz#1a225c17a1dd9e2f74af0a1b2c62072e9492b305" - integrity sha512-Mry2sO4kiAG64714X1CFpTA/shUH1DmkZ26DFDtwoM/yyx6OtMrc+MxrU+7vvbNLO9LSpgwkiJ8W+rlmRtsM+w== - -vuex@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.2.tgz#a2863f4005aa73f2587e55c3fadf3f01f69c7d4d" - integrity sha512-ha3jNLJqNhhrAemDXcmMJMKf1Zu4sybMPr9KxJIuOpVcsDQlTBYLLladav2U+g1AvdYDG5Gs0xBTb0M5pXXYFQ== - -vxx@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/vxx/-/vxx-1.2.2.tgz#741fb51c6f11d3383da6f9b92018a5d7ba807611" - integrity sha1-dB+1HG8R0zg9pvm5IBil17qAdhE= - dependencies: - continuation-local-storage "^3.1.4" - debug "^2.6.3" - extend "^3.0.0" - is "^3.2.0" - lodash.findindex "^4.4.0" - lodash.isequal "^4.0.0" - lodash.merge "^4.6.0" - methods "^1.1.1" - semver "^5.0.1" - shimmer "^1.0.0" - uuid "^3.0.1" +vuex@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.6.2.tgz#236bc086a870c3ae79946f107f16de59d5895e71" + integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw== -w3c-hr-time@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" - integrity sha1-gqwr/2PZUOqeMYmlimViX+3xkEU= +w3c-hr-time@^1.0.1, w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: - browser-process-hrtime "^0.1.2" + browser-process-hrtime "^1.0.0" -w3c-xmlserializer@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" - integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== dependencies: - domexception "^1.0.1" - webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" walker@^1.0.7, walker@~1.0.5: @@ -17413,14 +19497,36 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -watchpack@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== +watchify@3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/watchify/-/watchify-3.11.1.tgz#8e4665871fff1ef64c0430d1a2c9d084d9721881" + integrity sha512-WwnUClyFNRMB2NIiHgJU9RQPQNqVeFk7OmZaWf5dC5EnNa0Mgr7imBydbaJ7tGTuPM2hz1Cb4uiBvK9NVxMfog== + dependencies: + anymatch "^2.0.0" + browserify "^16.1.0" + chokidar "^2.1.1" + defined "^1.0.0" + outpipe "^1.1.0" + through2 "^2.0.0" + xtend "^4.0.0" + +watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== + dependencies: + chokidar "^2.1.8" + +watchpack@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== dependencies: - chokidar "^2.0.2" graceful-fs "^4.1.2" neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.1" wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" @@ -17441,13 +19547,23 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-bundle-analyzer@^3.3.2: - version "3.6.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd" - integrity sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g== +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +webpack-bundle-analyzer@^3.5.2: + version "3.9.0" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz#f6f94db108fb574e415ad313de41a2707d33ef3c" + integrity sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA== dependencies: - acorn "^6.0.7" - acorn-walk "^6.1.1" + acorn "^7.1.1" + acorn-walk "^7.1.1" bfj "^6.1.1" chalk "^2.4.1" commander "^2.18.0" @@ -17455,11 +19571,26 @@ webpack-bundle-analyzer@^3.3.2: express "^4.16.3" filesize "^3.6.1" gzip-size "^5.0.0" - lodash "^4.17.15" + lodash "^4.17.19" mkdirp "^0.5.1" opener "^1.5.1" ws "^6.0.0" +webpack-bundle-analyzer@^4.4.1: + version "4.4.2" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.2.tgz#39898cf6200178240910d629705f0f3493f7d666" + integrity sha512-PIagMYhlEzFfhMYOzs5gFT55DkUdkyrJi/SxJp8EF3YMWhS+T9vvs2EoTetpk5qb6VsCq02eXTlRDOydRhDFAQ== + dependencies: + acorn "^8.0.4" + acorn-walk "^8.0.0" + chalk "^4.1.0" + commander "^6.2.0" + gzip-size "^6.0.0" + lodash "^4.17.20" + opener "^1.5.2" + sirv "^1.0.7" + ws "^7.3.1" + webpack-chain@^4.9.0: version "4.12.1" resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-4.12.1.tgz#6c8439bbb2ab550952d60e1ea9319141906c02a6" @@ -17469,34 +19600,17 @@ webpack-chain@^4.9.0: javascript-stringify "^1.6.0" webpack-chain@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-6.4.0.tgz#22f0b27b6a9bc9ee3cba4f9e6513cf66394034e2" - integrity sha512-f97PYqxU+9/u0IUqp/ekAHRhBD1IQwhBv3wlJo2nvyELpr2vNnUqO3XQEk+qneg0uWGP54iciotszpjfnEExFA== + version "6.5.1" + resolved "https://registry.yarnpkg.com/webpack-chain/-/webpack-chain-6.5.1.tgz#4f27284cbbb637e3c8fbdef43eef588d4d861206" + integrity sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA== dependencies: deepmerge "^1.5.2" javascript-stringify "^2.0.1" -webpack-cli@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631" - integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g== - dependencies: - chalk "2.4.2" - cross-spawn "6.0.5" - enhanced-resolve "4.1.0" - findup-sync "3.0.0" - global-modules "2.0.0" - import-local "2.0.0" - interpret "1.2.0" - loader-utils "1.2.3" - supports-color "6.1.0" - v8-compile-cache "2.0.3" - yargs "13.2.4" - -webpack-dev-middleware@^3.4.0, webpack-dev-middleware@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" - integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== +webpack-dev-middleware@^3.7.2: + version "3.7.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== dependencies: memory-fs "^0.4.1" mime "^2.4.4" @@ -17504,10 +19618,22 @@ webpack-dev-middleware@^3.4.0, webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" +webpack-dev-middleware@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz#179cc40795882cae510b1aa7f3710cbe93c9333e" + integrity sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w== + dependencies: + colorette "^1.2.2" + mem "^8.1.1" + memfs "^3.2.2" + mime-types "^2.1.30" + range-parser "^1.2.1" + schema-utils "^3.0.0" + webpack-dev-server@^3.5.1: - version "3.10.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz#f35945036813e57ef582c2420ef7b470e14d3af0" - integrity sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ== + version "3.11.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#695ebced76a4929f0d5de7fd73fafe185fe33708" + integrity sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" @@ -17517,33 +19643,33 @@ webpack-dev-server@^3.5.1: debug "^4.1.1" del "^4.1.1" express "^4.17.1" - html-entities "^1.2.1" + html-entities "^1.3.1" http-proxy-middleware "0.19.1" import-local "^2.0.0" internal-ip "^4.3.0" ip "^1.1.5" is-absolute-url "^3.0.3" killable "^1.0.1" - loglevel "^1.6.6" + loglevel "^1.6.8" opn "^5.5.0" p-retry "^3.0.1" - portfinder "^1.0.25" + portfinder "^1.0.26" schema-utils "^1.0.0" - selfsigned "^1.10.7" + selfsigned "^1.10.8" semver "^6.3.0" serve-index "^1.9.1" - sockjs "0.3.19" - sockjs-client "1.4.0" - spdy "^4.0.1" + sockjs "^0.3.21" + sockjs-client "^1.5.0" + spdy "^4.0.2" strip-ansi "^3.0.1" supports-color "^6.1.0" url "^0.11.0" webpack-dev-middleware "^3.7.2" webpack-log "^2.0.0" ws "^6.2.1" - yargs "12.0.5" + yargs "^13.3.2" -webpack-hot-middleware@^2.24.3: +webpack-hot-middleware@^2.25.0: version "2.25.0" resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.0.tgz#4528a0a63ec37f8f8ef565cf9e534d57d09fe706" integrity sha512-xs5dPOrGPCzuRXNi8F6rwhawWvQQkeli5Ro48PRuQh8pYPCPmNnltP9itiUPT4xI8oW+y0m59lyyeQk54s5VgA== @@ -17561,14 +19687,19 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@^4.1.2, webpack-merge@^4.2.2: +webpack-merge@^4.1.2: version "4.2.2" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== dependencies: lodash "^4.17.15" -webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: +webpack-node-externals@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz#1a3407c158d547a9feb4229a9e3385b7b60c9917" + integrity sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ== + +webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -17576,33 +19707,33 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.25.1, webpack@^4.8.1: - version "4.42.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.0.tgz#b901635dd6179391d90740a63c93f76f39883eb8" - integrity sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w== +webpack@^4.42.1, webpack@^4.46.0, webpack@^4.8.1: + version "4.46.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" + integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/wasm-edit" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.2.1" + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.4.1" ajv "^6.10.2" ajv-keywords "^3.4.1" chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" + enhanced-resolve "^4.5.0" eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" loader-utils "^1.2.3" memory-fs "^0.4.1" micromatch "^3.1.10" - mkdirp "^0.5.1" + mkdirp "^0.5.3" neo-async "^2.6.1" node-libs-browser "^2.2.1" schema-utils "^1.0.0" tapable "^1.1.3" terser-webpack-plugin "^1.4.3" - watchpack "^1.6.0" + watchpack "^1.7.4" webpack-sources "^1.4.1" webpackbar@3.2.0: @@ -17619,37 +19750,60 @@ webpackbar@3.2.0: text-table "^0.2.0" wrap-ansi "^5.1.0" -websocket-driver@>=0.5.1: - version "0.7.3" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" - integrity sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg== +webpackbar@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-4.0.0.tgz#ee7a87f16077505b5720551af413c8ecd5b1f780" + integrity sha512-k1qRoSL/3BVuINzngj09nIwreD8wxV4grcuhHTD8VJgUbGcy8lQSPqv+bM00B7F+PffwIsQ8ISd4mIwRbr23eQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^2.4.2" + consola "^2.10.0" + figures "^3.0.0" + pretty-time "^1.1.0" + std-env "^2.2.1" + text-table "^0.2.0" + wrap-ansi "^6.0.0" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== dependencies: - http-parser-js ">=0.4.0 <0.4.11" + http-parser-js ">=0.5.1" safe-buffer ">=5.1.0" websocket-extensions ">=0.1.1" websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" - integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== -whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" -whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" - integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== +whatwg-fetch@>=0.10.0, whatwg-fetch@^3.4.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" + integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== -whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-url@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" @@ -17659,22 +19813,42 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + when@~3.6.x: version "3.6.4" resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e" integrity sha1-RztRfsFZ4rhQBUl6E5g/CVQS404= -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1: +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +which@1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -17695,13 +19869,6 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -widest-line@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" - integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA== - dependencies: - string-width "^2.1.1" - widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -17710,33 +19877,26 @@ widest-line@^3.1.0: string-width "^4.0.0" windows-release@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" - integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA== + version "3.3.3" + resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" + integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg== dependencies: execa "^1.0.0" -winston@^2.2.0: - version "2.4.4" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.4.tgz#a01e4d1d0a103cf4eada6fc1f886b3110d71c34b" - integrity sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q== - dependencies: - async "~1.0.0" - colors "1.0.x" - cycle "1.0.x" - eyes "0.1.x" - isstream "0.1.x" - stack-trace "0.0.x" - -word-wrap@~1.2.3: +word-wrap@^1.0.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== -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + +workbox-cdn@^5.1.4: + version "5.1.4" + resolved "https://registry.yarnpkg.com/workbox-cdn/-/workbox-cdn-5.1.4.tgz#dbd8acee70b1978be70106207590bbb76af935cf" + integrity sha512-04gM3mi8QGutokkSaA9xunVfjURnLbo9TTWyi8+pSDCEW5cD8u5GbJiliLK1vB9CShk/9OY1UDfW+XcmD+d6KQ== worker-farm@^1.7.0: version "1.7.0" @@ -17745,14 +19905,6 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" @@ -17770,7 +19922,7 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" -wrap-ansi@^6.2.0: +wrap-ansi@^6.0.0, wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== @@ -17779,6 +19931,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -17812,7 +19973,7 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -write-json-file@^2.2.0: +write-json-file@^2.2.0, write-json-file@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" integrity sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8= @@ -17844,42 +20005,30 @@ write-pkg@^3.1.0: sort-keys "^2.0.0" write-json-file "^2.2.0" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" +ws@7.4.5: + version "7.4.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" + integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= +ws@^5.2.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.3.tgz#05541053414921bc29c63bee14b8b0dd50b07b3d" + integrity sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA== dependencies: - mkdirp "^0.5.1" + async-limiter "~1.0.0" + +"ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.3.1, ws@^7.4.6: + version "7.5.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" + integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg== ws@^6.0.0, ws@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" -ws@^7.0.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e" - integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A== - -x-xss-protection@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.3.0.tgz#3e3a8dd638da80421b0e9fff11a2dbe168f6d52c" - integrity sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg== - -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -17890,32 +20039,39 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xmlchars@^2.1.1: +xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= +xxhashjs@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" + integrity sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw== + dependencies: + cuint "^0.2.2" -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== @@ -17925,104 +20081,60 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.7.2: +yaml@^1.10.0, yaml@^1.7.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yamljs@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.3.0.tgz#dc060bf267447b39f7304e9b2bfbe8b5a7ddb03b" - integrity sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ== - dependencies: - argparse "^1.0.7" - glob "^7.0.5" - -yargs-parser@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" - integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== - dependencies: - camelcase "^4.1.0" - -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^13.1.0: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.0.tgz#cdd7a97490ec836195f59f3f4dbe5ea9e8f75f08" - integrity sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ== +yargs-parser@^15.0.1: + version "15.0.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.3.tgz#316e263d5febe8b38eef61ac092b33dfcc9b1115" + integrity sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^16.1.0: - version "16.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1" - integrity sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" - integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= - dependencies: - camelcase "^3.0.0" - -yargs@12.0.5: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== +yargs@^13.2.2, yargs@^13.3.0, yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: cliui "^5.0.0" find-up "^3.0.0" get-caller-file "^2.0.1" - os-locale "^3.1.0" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.0" + yargs-parser "^13.1.2" yargs@^14.2.2: - version "14.2.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.2.tgz#2769564379009ff8597cdd38fba09da9b493c4b5" - integrity sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA== + version "14.2.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.3.tgz#1a1c3edced1afb2a2fea33604bc6d1d8d688a414" + integrity sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg== dependencies: cliui "^5.0.0" decamelize "^1.2.0" @@ -18034,12 +20146,12 @@ yargs@^14.2.2: string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^15.0.0" + yargs-parser "^15.0.1" -yargs@^15.0.0, yargs@^15.0.2: - version "15.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219" - integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg== +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: cliui "^6.0.0" decamelize "^1.2.0" @@ -18051,28 +20163,35 @@ yargs@^15.0.0, yargs@^15.0.2: string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^16.1.0" + yargs-parser "^18.1.2" -yargs@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" - integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" +yargs@^16.0.3: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "^5.0.0" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" -yauzl@2.10.0: +yargs@^17.0.0: + version "17.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.1.1.tgz#c2a8091564bdb196f7c0a67c1d12e5b85b8067ba" + integrity sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yauzl@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= @@ -18080,39 +20199,44 @@ yauzl@2.10.0: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" -yauzl@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" - integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= - dependencies: - fd-slicer "~1.0.1" - yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== -yup@^0.27.0: - version "0.27.0" - resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7" - integrity sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ== +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +z-schema@~3.18.3: + version "3.18.4" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.18.4.tgz#ea8132b279533ee60be2485a02f7e3e42541a9a2" + integrity sha512-DUOKC/IhbkdLKKiV89gw9DUauTV8U/8yJl1sjf6MtDmzevLKOF2duNJ495S3MFVjqZarr+qNGCPbkg4mu4PpLw== dependencies: - "@babel/runtime" "^7.0.0" - fn-name "~2.0.1" - lodash "^4.17.11" - property-expr "^1.5.0" - synchronous-promise "^2.0.6" - toposort "^2.0.2" + lodash.get "^4.0.0" + lodash.isequal "^4.0.0" + validator "^8.0.0" + optionalDependencies: + commander "^2.7.1" -zen-observable-ts@^0.8.20: - version "0.8.20" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.20.tgz#44091e335d3fcbc97f6497e63e7f57d5b516b163" - integrity sha512-2rkjiPALhOtRaDX6pWyNqK1fnP5KkJJybYebopNSn6wDG1lxBoFs2+nwwXKoA6glHIrtwrfBBy6da0stkKtTAA== +zen-observable-ts@^0.8.21: + version "0.8.21" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d" + integrity sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg== dependencies: tslib "^1.9.3" zen-observable "^0.8.0" -zen-observable@^0.8.0: +zen-observable-ts@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz#2d1aa9d79b87058e9b75698b92791c1838551f83" + integrity sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA== + dependencies: + "@types/zen-observable" "0.8.3" + zen-observable "0.8.15" + +zen-observable@0.8.15, zen-observable@^0.8.0, zen-observable@^0.8.14: version "0.8.15" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==