mirror of
https://github.com/strapi/strapi.git
synced 2025-12-14 16:51:55 +00:00
Merge branch 'main' into chore/user-level-data
This commit is contained in:
commit
b51b0f4616
@ -26,14 +26,16 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'prefer-destructuring': ['error', { AssignmentExpression: { array: false } }],
|
||||
eqeqeq: 'warn',
|
||||
'no-underscore-dangle': 'warn',
|
||||
'no-use-before-define': 'warn',
|
||||
'no-param-reassign': 'warn',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'no-continue': 'warn',
|
||||
'no-process-exit': 'off',
|
||||
'no-plusplus': 'warn',
|
||||
'no-loop-func': 'warn',
|
||||
'guard-for-in': 'warn',
|
||||
'no-loop-func': 'off',
|
||||
'no-param-reassign': [
|
||||
'error',
|
||||
{
|
||||
props: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@ -20,7 +20,7 @@ https://github.com/strapi/strapi/blob/main/CONTRIBUTING.md#reporting-an-issue
|
||||
|
||||
### Required System information
|
||||
|
||||
<!-- Please ensure you are using the Node LTS version (v14 or v16) -->
|
||||
<!-- Please ensure you are using the Node LTS version (v14 or v16 or v18) -->
|
||||
<!-- Strapi v3 is not supported unless it is a critical/high security issue -->
|
||||
|
||||
- Node.js version:
|
||||
|
||||
2
.github/actions/check-pr-status/package.json
vendored
2
.github/actions/check-pr-status/package.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "check-pr-status",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
|
||||
20
.github/workflows/tests.yml
vendored
20
.github/workflows/tests.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
CODECOV_TOKEN: ${{ secrets.codecov }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
CODECOV_TOKEN: ${{ secrets.codecov }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
name: '[CE] E2E (postgres, node: ${{ matrix.node }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
services:
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
@ -116,7 +116,7 @@ jobs:
|
||||
name: '[CE] E2E (mysql, node: ${{ matrix.node }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
services:
|
||||
mysql:
|
||||
image: mysql
|
||||
@ -151,7 +151,7 @@ jobs:
|
||||
name: '[CE] E2E (mysql:5 , node: ${{ matrix.node }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:5
|
||||
@ -186,7 +186,7 @@ jobs:
|
||||
name: '[CE] E2E (sqlite: ${{ matrix.sqlite_pkg }}, node: ${{ matrix.node }})'
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
sqlite_pkg: ['better-sqlite3', 'sqlite3', '@vscode/sqlite3']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -210,7 +210,7 @@ jobs:
|
||||
STRAPI_LICENSE: ${{ secrets.strapiLicense }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
services:
|
||||
postgres:
|
||||
# Docker Hub image
|
||||
@ -250,7 +250,7 @@ jobs:
|
||||
STRAPI_LICENSE: ${{ secrets.strapiLicense }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
services:
|
||||
mysql:
|
||||
image: mysql
|
||||
@ -289,7 +289,7 @@ jobs:
|
||||
STRAPI_LICENSE: ${{ secrets.strapiLicense }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16]
|
||||
node: [14, 16, 18]
|
||||
sqlite_pkg: ['better-sqlite3', 'sqlite3', '@vscode/sqlite3']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@ -46,7 +46,7 @@ The Strapi core team will review your pull request and either merge it, request
|
||||
|
||||
## Contribution Prerequisites
|
||||
|
||||
- You have [Node.js](https://nodejs.org/en/) at version >= v14 and <= v16 and [Yarn](https://yarnpkg.com/en/) at v1.2.0+ installed.
|
||||
- You have [Node.js](https://nodejs.org/en/) at version >= v14 and <= v18 and [Yarn](https://yarnpkg.com/en/) at v1.2.0+ installed.
|
||||
- You are familiar with [Git](https://git-scm.com).
|
||||
|
||||
**Before submitting your pull request** make sure the following requirements are fulfilled:
|
||||
|
||||
@ -80,13 +80,13 @@ Complete installation requirements can be found in the documentation under <a hr
|
||||
- CentOS/RHEL 8
|
||||
- macOS Mojave
|
||||
- Windows 10
|
||||
- Docker - [Docker-Repo](https://github.com/strapi/strapi-docker)
|
||||
- Docker
|
||||
|
||||
(Please note that Strapi may work on other operating systems, but these are not tested nor officially supported at this time.)
|
||||
|
||||
**Node:**
|
||||
|
||||
- NodeJS >= 14 <= 16
|
||||
- NodeJS >= 14 <= 18
|
||||
- NPM >= 6.x
|
||||
|
||||
**Database:**
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "getstarted",
|
||||
"private": true,
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "A Strapi application.",
|
||||
"scripts": {
|
||||
"develop": "strapi develop",
|
||||
@ -12,15 +12,15 @@
|
||||
"strapi": "strapi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/plugin-documentation": "4.3.6",
|
||||
"@strapi/plugin-graphql": "4.3.6",
|
||||
"@strapi/plugin-i18n": "4.3.6",
|
||||
"@strapi/plugin-sentry": "4.3.6",
|
||||
"@strapi/plugin-users-permissions": "4.3.6",
|
||||
"@strapi/provider-email-mailgun": "4.3.6",
|
||||
"@strapi/provider-upload-aws-s3": "4.3.6",
|
||||
"@strapi/provider-upload-cloudinary": "4.3.6",
|
||||
"@strapi/strapi": "4.3.6",
|
||||
"@strapi/plugin-documentation": "4.3.8",
|
||||
"@strapi/plugin-graphql": "4.3.8",
|
||||
"@strapi/plugin-i18n": "4.3.8",
|
||||
"@strapi/plugin-sentry": "4.3.8",
|
||||
"@strapi/plugin-users-permissions": "4.3.8",
|
||||
"@strapi/provider-email-mailgun": "4.3.8",
|
||||
"@strapi/provider-upload-aws-s3": "4.3.8",
|
||||
"@strapi/provider-upload-cloudinary": "4.3.8",
|
||||
"@strapi/strapi": "4.3.8",
|
||||
"@vscode/sqlite3": "5.0.8",
|
||||
"better-sqlite3": "7.4.6",
|
||||
"lodash": "4.17.21",
|
||||
@ -33,7 +33,7 @@
|
||||
"uuid": "getstarted"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE"
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
beforeUpdate() {
|
||||
const ctx = strapi.requestContext.get();
|
||||
|
||||
console.log('User info in service: ', ctx.state.user);
|
||||
},
|
||||
};
|
||||
@ -5,7 +5,7 @@ const { createCoreRouter } = require('@strapi/strapi').factories;
|
||||
module.exports = createCoreRouter('api::address.address', {
|
||||
config: {
|
||||
find: {
|
||||
auth: false,
|
||||
// auth: false,
|
||||
},
|
||||
},
|
||||
only: ['find', 'findOne'],
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kitchensink-ts",
|
||||
"private": true,
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "A Strapi application",
|
||||
"scripts": {
|
||||
"develop": "strapi develop",
|
||||
@ -10,9 +10,9 @@
|
||||
"strapi": "strapi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/plugin-i18n": "4.3.6",
|
||||
"@strapi/plugin-users-permissions": "4.3.6",
|
||||
"@strapi/strapi": "4.3.6",
|
||||
"@strapi/plugin-i18n": "4.3.8",
|
||||
"@strapi/plugin-users-permissions": "4.3.8",
|
||||
"@strapi/strapi": "4.3.8",
|
||||
"better-sqlite3": "7.4.6"
|
||||
},
|
||||
"author": {
|
||||
@ -22,7 +22,7 @@
|
||||
"uuid": "getstarted"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kitchensink",
|
||||
"private": true,
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "A Strapi application.",
|
||||
"scripts": {
|
||||
"develop": "strapi develop",
|
||||
@ -12,10 +12,10 @@
|
||||
"strapi": "strapi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/provider-email-mailgun": "4.3.6",
|
||||
"@strapi/provider-upload-aws-s3": "4.3.6",
|
||||
"@strapi/provider-upload-cloudinary": "4.3.6",
|
||||
"@strapi/strapi": "4.3.6",
|
||||
"@strapi/provider-email-mailgun": "4.3.8",
|
||||
"@strapi/provider-upload-aws-s3": "4.3.8",
|
||||
"@strapi/provider-upload-cloudinary": "4.3.8",
|
||||
"@strapi/strapi": "4.3.8",
|
||||
"lodash": "4.17.21",
|
||||
"mysql": "2.18.1",
|
||||
"passport-google-oauth2": "0.2.0",
|
||||
@ -26,7 +26,7 @@
|
||||
"uuid": "getstarted"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
"examples/*"
|
||||
|
||||
12
package.json
12
package.json
@ -87,11 +87,11 @@
|
||||
"husky": "3.1.0",
|
||||
"inquirer": "8.2.4",
|
||||
"istanbul": "~0.4.2",
|
||||
"jest": "29.0.0",
|
||||
"jest-circus": "29.0.0",
|
||||
"jest-cli": "29.0.0",
|
||||
"jest-environment-jsdom": "29.0.0",
|
||||
"jest-watch-typeahead": "0.6.5",
|
||||
"jest": "29.0.3",
|
||||
"jest-circus": "29.0.3",
|
||||
"jest-cli": "29.0.3",
|
||||
"jest-environment-jsdom": "29.0.3",
|
||||
"jest-watch-typeahead": "2.2.0",
|
||||
"lerna": "5.4.3",
|
||||
"lint-staged": "10.5.4",
|
||||
"lodash": "4.17.21",
|
||||
@ -111,7 +111,7 @@
|
||||
"yargs": "13.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/admin-test-utils",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"private": true,
|
||||
"description": "Test utilities for the Strapi administration panel",
|
||||
"license": "MIT",
|
||||
@ -33,6 +33,6 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x"
|
||||
"node": ">=14.19.1 <=18.x.x"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-strapi-app",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "Generate a new Strapi application.",
|
||||
"keywords": [
|
||||
"create-strapi-app",
|
||||
@ -38,12 +38,12 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/generate-new": "4.3.6",
|
||||
"@strapi/generate-new": "4.3.8",
|
||||
"commander": "6.1.0",
|
||||
"inquirer": "8.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-strapi-starter",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "Generate a new Strapi application.",
|
||||
"keywords": [
|
||||
"create-strapi-starter",
|
||||
@ -38,7 +38,7 @@
|
||||
"test": "echo \"no tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/generate-new": "4.3.6",
|
||||
"@strapi/generate-new": "4.3.8",
|
||||
"chalk": "4.1.1",
|
||||
"ci-info": "3.3.2",
|
||||
"commander": "7.1.0",
|
||||
@ -48,7 +48,7 @@
|
||||
"ora": "5.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
} from './exposedHooks';
|
||||
import injectionZones from './injectionZones';
|
||||
import favicon from './favicon.ico';
|
||||
import localStorageKey from './components/LanguageProvider/utils/localStorageKey';
|
||||
|
||||
class StrapiApp {
|
||||
constructor({ adminConfig, appPlugins, library, middlewares, reducers }) {
|
||||
@ -457,6 +458,7 @@ class StrapiApp {
|
||||
href: this.configurations.head.favicon,
|
||||
},
|
||||
]}
|
||||
htmlAttributes={{ lang: localStorage.getItem(localStorageKey) || 'en' }}
|
||||
/>
|
||||
<BrowserRouter basename={basename}>
|
||||
<App store={store} />
|
||||
|
||||
@ -65,11 +65,11 @@ const AuthenticatedApp = () => {
|
||||
if (userRoles) {
|
||||
const isUserSuperAdmin = userRoles.find(({ code }) => code === 'strapi-super-admin');
|
||||
|
||||
if (isUserSuperAdmin) {
|
||||
if (isUserSuperAdmin && appInfos?.autoReload) {
|
||||
setGuidedTourVisibilityRef.current(true);
|
||||
}
|
||||
}
|
||||
}, [userRoles]);
|
||||
}, [userRoles, appInfos]);
|
||||
|
||||
// We don't need to wait for the release query to be fetched before rendering the plugins
|
||||
// however, we need the appInfos and the permissions
|
||||
|
||||
@ -132,6 +132,7 @@ describe('Admin | components | AuthenticatedApp', () => {
|
||||
.c3 {
|
||||
-webkit-animation: gzYjWD 1s infinite linear;
|
||||
animation: gzYjWD 1s infinite linear;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
@ -177,8 +178,20 @@ describe('Admin | components | AuthenticatedApp', () => {
|
||||
await waitFor(() => expect(setGuidedTourVisibility).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('should call setGuidedTourVisibility when user is super admin', async () => {
|
||||
it('should not setGuidedTourVisibility when user is a super admin and autoReload is false ', async () => {
|
||||
fetchUserRoles.mockImplementationOnce(() => [{ code: 'strapi-super-admin' }]);
|
||||
fetchAppInfo.mockImplementationOnce(() => ({ autoReload: false }));
|
||||
const setGuidedTourVisibility = jest.fn();
|
||||
useGuidedTour.mockImplementation(() => ({ setGuidedTourVisibility }));
|
||||
|
||||
render(<App />);
|
||||
|
||||
await waitFor(() => expect(setGuidedTourVisibility).not.toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('should call setGuidedTourVisibility when user is super admin and autoReload is true', async () => {
|
||||
fetchUserRoles.mockImplementationOnce(() => [{ code: 'strapi-super-admin' }]);
|
||||
fetchAppInfo.mockImplementationOnce(() => ({ autoReload: true }));
|
||||
const setGuidedTourVisibility = jest.fn();
|
||||
useGuidedTour.mockImplementation(() => ({ setGuidedTourVisibility }));
|
||||
render(<App />);
|
||||
|
||||
@ -21,6 +21,7 @@ const LanguageProvider = ({ children, localeNames, messages }) => {
|
||||
useEffect(() => {
|
||||
// Set user language in local storage.
|
||||
window.localStorage.setItem(localStorageKey, locale);
|
||||
document.documentElement.setAttribute('lang', locale);
|
||||
}, [locale]);
|
||||
|
||||
const changeLocale = (locale) => {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ThemeToggleContext } from '../../contexts';
|
||||
|
||||
@ -14,22 +14,33 @@ const getDefaultTheme = () => {
|
||||
const browserTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
const persistedTheme = localStorage.getItem(THEME_KEY);
|
||||
|
||||
if (!persistedTheme) {
|
||||
localStorage.setItem(THEME_KEY, browserTheme);
|
||||
}
|
||||
|
||||
return persistedTheme || browserTheme;
|
||||
};
|
||||
|
||||
const ThemeToggleProvider = ({ children, themes }) => {
|
||||
const [currentTheme, setCurrentTheme] = useState(getDefaultTheme());
|
||||
|
||||
const handleChangeTheme = (nextTheme) => {
|
||||
setCurrentTheme(nextTheme);
|
||||
localStorage.setItem(THEME_KEY, nextTheme);
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeToggleContext.Provider value={{ currentTheme, onChangeTheme: handleChangeTheme, themes }}>
|
||||
{children}
|
||||
</ThemeToggleContext.Provider>
|
||||
const handleChangeTheme = useCallback(
|
||||
(nextTheme) => {
|
||||
setCurrentTheme(nextTheme);
|
||||
localStorage.setItem(THEME_KEY, nextTheme);
|
||||
},
|
||||
[setCurrentTheme]
|
||||
);
|
||||
|
||||
const themeValues = useMemo(() => {
|
||||
return {
|
||||
currentTheme,
|
||||
onChangeTheme: handleChangeTheme,
|
||||
themes,
|
||||
};
|
||||
}, [currentTheme, handleChangeTheme, themes]);
|
||||
|
||||
return <ThemeToggleContext.Provider value={themeValues}>{children}</ThemeToggleContext.Provider>;
|
||||
};
|
||||
|
||||
ThemeToggleProvider.propTypes = {
|
||||
|
||||
@ -47,7 +47,7 @@ const DynamicTable = ({
|
||||
...metadatas,
|
||||
label: formatMessage({
|
||||
id: getTrad(`containers.ListPage.table-headers.${header.name}`),
|
||||
defaultMessage: header.name,
|
||||
defaultMessage: metadatas.label,
|
||||
}),
|
||||
},
|
||||
name: sortFieldValue,
|
||||
@ -60,7 +60,7 @@ const DynamicTable = ({
|
||||
...metadatas,
|
||||
label: formatMessage({
|
||||
id: getTrad(`containers.ListPage.table-headers.${header.name}`),
|
||||
defaultMessage: header.name,
|
||||
defaultMessage: metadatas.label,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@ -79,6 +79,8 @@ class InputJSON extends React.Component {
|
||||
try {
|
||||
if (value === null) return this.codeMirror.setValue('');
|
||||
|
||||
if (value === 0) return this.codeMirror.setValue('0');
|
||||
|
||||
return this.codeMirror.setValue(value);
|
||||
} catch (err) {
|
||||
return this.setState({ error: true });
|
||||
|
||||
@ -121,7 +121,7 @@ SelectMany.propTypes = {
|
||||
displayNavigationLink: PropTypes.bool.isRequired,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
loadingMessage: PropTypes.string.isRequired,
|
||||
loadingMessage: PropTypes.func.isRequired,
|
||||
mainField: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
schema: PropTypes.shape({
|
||||
|
||||
@ -72,7 +72,7 @@ SelectOne.propTypes = {
|
||||
components: PropTypes.object,
|
||||
isDisabled: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
loadingMessage: PropTypes.string.isRequired,
|
||||
loadingMessage: PropTypes.func.isRequired,
|
||||
mainField: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
schema: PropTypes.shape({
|
||||
|
||||
@ -151,6 +151,7 @@ describe('Content manager | App | main', () => {
|
||||
.c35 {
|
||||
-webkit-animation: gzYjWD 1s infinite linear;
|
||||
animation: gzYjWD 1s infinite linear;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.c33 {
|
||||
|
||||
@ -125,7 +125,7 @@ describe('CONTENT MANAGER | EditView | Header', () => {
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c12 .sc-kBzgEd {
|
||||
.c12 .sc-gJbFto {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
|
||||
@ -52,7 +52,7 @@ const forms = {
|
||||
fieldsToDisable: ['email'],
|
||||
fieldsToOmit: ['userInfo.confirmPassword', 'userInfo.news', 'userInfo.email'],
|
||||
schema: yup.object().shape({
|
||||
firstname: yup.string().required(translatedErrors.required),
|
||||
firstname: yup.string().trim().required(translatedErrors.required),
|
||||
lastname: yup.string(),
|
||||
password: yup
|
||||
.string()
|
||||
@ -76,7 +76,7 @@ const forms = {
|
||||
fieldsToDisable: [],
|
||||
fieldsToOmit: ['confirmPassword', 'news'],
|
||||
schema: yup.object().shape({
|
||||
firstname: yup.string().required(translatedErrors.required),
|
||||
firstname: yup.string().trim().required(translatedErrors.required),
|
||||
lastname: yup.string(),
|
||||
password: yup
|
||||
.string()
|
||||
|
||||
@ -417,7 +417,7 @@ describe('SettingsPage || components || SettingsNav', () => {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c16 sc-iuqRDJ c25"
|
||||
class="c16 sc-eBTqsU c25"
|
||||
>
|
||||
<svg
|
||||
class="c26"
|
||||
|
||||
@ -853,7 +853,7 @@ exports[`ApplicationsInfosPage || LogoInput from computer should render upload m
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c29 .sc-iRFsWr {
|
||||
.c29 .sc-eZhRLC {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -923,7 +923,7 @@ exports[`ApplicationsInfosPage || LogoInput from computer should render upload m
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.c37 .sc-iRFsWr {
|
||||
.c37 .sc-eZhRLC {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -1980,7 +1980,7 @@ exports[`ApplicationsInfosPage || LogoInput from url should render upload modal
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c33 .sc-iRFsWr {
|
||||
.c33 .sc-eZhRLC {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -2050,7 +2050,7 @@ exports[`ApplicationsInfosPage || LogoInput from url should render upload modal
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.c30 .sc-iRFsWr {
|
||||
.c30 .sc-eZhRLC {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
|
||||
@ -102,7 +102,7 @@ exports[`<EditPage /> renders and matches the snapshot 1`] = `
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c13 .sc-kBzgEd {
|
||||
.c13 .sc-gJbFto {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -172,7 +172,7 @@ exports[`<EditPage /> renders and matches the snapshot 1`] = `
|
||||
background: #f0f0ff;
|
||||
}
|
||||
|
||||
.c25 .sc-kBzgEd {
|
||||
.c25 .sc-gJbFto {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -293,6 +293,7 @@ exports[`<EditPage /> renders and matches the snapshot 1`] = `
|
||||
.c46 {
|
||||
-webkit-animation: gzYjWD 1s infinite linear;
|
||||
animation: gzYjWD 1s infinite linear;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
|
||||
@ -2,7 +2,7 @@ import * as yup from 'yup';
|
||||
import { translatedErrors } from '@strapi/helper-plugin';
|
||||
|
||||
const schema = yup.object().shape({
|
||||
firstname: yup.string().required(translatedErrors.required),
|
||||
firstname: yup.string().trim().required(translatedErrors.required),
|
||||
lastname: yup.string(),
|
||||
email: yup.string().email(translatedErrors.email).required(translatedErrors.required),
|
||||
roles: yup.array().min(1, translatedErrors.required).required(translatedErrors.required),
|
||||
|
||||
@ -466,7 +466,7 @@ exports[`DynamicTable renders and matches the snapshot 1`] = `
|
||||
</div>
|
||||
<nav
|
||||
aria-label="pagination"
|
||||
class="sc-icMgfS"
|
||||
class="sc-cKVNtL"
|
||||
>
|
||||
<ul
|
||||
class="c17 c18"
|
||||
|
||||
@ -581,6 +581,7 @@ describe('ADMIN | Pages | USERS | ListPage', () => {
|
||||
.c44 {
|
||||
-webkit-animation: gzYjWD 1s infinite linear;
|
||||
animation: gzYjWD 1s infinite linear;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.c62 {
|
||||
|
||||
@ -2,8 +2,8 @@ import * as yup from 'yup';
|
||||
import { translatedErrors } from '@strapi/helper-plugin';
|
||||
|
||||
export const commonUserSchema = {
|
||||
firstname: yup.mixed().required(translatedErrors.required),
|
||||
lastname: yup.mixed(),
|
||||
firstname: yup.string().trim().required(translatedErrors.required),
|
||||
lastname: yup.string(),
|
||||
email: yup.string().email(translatedErrors.email).lowercase().required(translatedErrors.required),
|
||||
username: yup.string().nullable(),
|
||||
password: yup
|
||||
|
||||
@ -171,7 +171,7 @@ describe('Admin | UseCasePage', () => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c35 .sc-ezHhwS {
|
||||
.c35 .sc-kBzgEd {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
|
||||
@ -588,6 +588,38 @@
|
||||
"content-manager.success.record.save": "Sauvegardé",
|
||||
"content-manager.success.record.unpublish": "Publication annulée",
|
||||
"content-manager.utils.data-loaded": "{number, plural, =1 {L'entrée a été chargée} other {Les entrées on été chargées} avec succès",
|
||||
"content-manager.apiError.This attribute must be unique": "Le champ {field} doit être unique",
|
||||
"form.button.continue": "Continuer",
|
||||
"global.search": "Rechercher",
|
||||
"global.actions": "Actions",
|
||||
"global.back": "Retour",
|
||||
"global.cancel": "Annuler",
|
||||
"global.change-password": "Modifier le mot de passe",
|
||||
"global.content-manager": "Gestion du contenu",
|
||||
"global.continue": "Continuer",
|
||||
"global.delete": "Supprimer",
|
||||
"global.delete-target": "Supprimer {target}",
|
||||
"global.description": "Description",
|
||||
"global.details": "Détails",
|
||||
"global.disabled": "Désactivé",
|
||||
"global.documentation": "Documentation",
|
||||
"global.enabled": "Activé",
|
||||
"global.finish": "Terminer",
|
||||
"global.marketplace": "Marketplace",
|
||||
"global.name": "Nom",
|
||||
"global.none": "Aucun",
|
||||
"global.password": "Mot de passe",
|
||||
"global.plugins": "Plugins",
|
||||
"global.profile": "Profil",
|
||||
"global.reset-password": "Réinitialiser le mot de passe",
|
||||
"global.roles": "Rôles",
|
||||
"global.save": "Enregistrer",
|
||||
"global.see-more": "Voir plus",
|
||||
"global.select": "Sélectionner",
|
||||
"global.select-all-entries": "Sélectionner toutes les entrées",
|
||||
"global.settings": "Paramètres",
|
||||
"global.type": "Type",
|
||||
"global.users": "Utilisateurs",
|
||||
"form.button.done": "Terminer",
|
||||
"global.prompt.unsaved": "Êtes-vous sûr de vouloir quitter cette page? Toutes vos modifications seront perdues",
|
||||
"notification.contentType.relations.conflict": "Le Type de Contenu à des relations qui rentrent en conflit",
|
||||
|
||||
@ -356,7 +356,6 @@
|
||||
"components.FilterOptions.FILTER_TYPES.$lt": "小于",
|
||||
"components.FilterOptions.FILTER_TYPES.$lte": "小于或等于",
|
||||
"components.FilterOptions.FILTER_TYPES.$ne": "不等于",
|
||||
"components.FilterOptions.FILTER_TYPES.$neq": "不等于",
|
||||
"components.FilterOptions.FILTER_TYPES.$notContains": "不包含 (区分大小写)",
|
||||
"components.FilterOptions.FILTER_TYPES.$notNull": "不为空",
|
||||
"components.FilterOptions.FILTER_TYPES.$null": "为空",
|
||||
|
||||
@ -97,7 +97,7 @@ exports[`<CreatePage /> renders and matches the snapshot 1`] = `
|
||||
background: #f0f0ff;
|
||||
}
|
||||
|
||||
.c13 .sc-kBzgEd {
|
||||
.c13 .sc-gJbFto {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -177,7 +177,7 @@ exports[`<CreatePage /> renders and matches the snapshot 1`] = `
|
||||
border: 1px solid #4945ff;
|
||||
}
|
||||
|
||||
.c16 .sc-kBzgEd {
|
||||
.c16 .sc-gJbFto {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -286,6 +286,7 @@ exports[`<CreatePage /> renders and matches the snapshot 1`] = `
|
||||
.c46 {
|
||||
-webkit-animation: gzYjWD 1s infinite linear;
|
||||
animation: gzYjWD 1s infinite linear;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
|
||||
@ -93,6 +93,7 @@ describe('<ListPage />', () => {
|
||||
.c4 {
|
||||
-webkit-animation: gzYjWD 1s infinite linear;
|
||||
animation: gzYjWD 1s infinite linear;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/admin",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "Strapi Admin",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -51,12 +51,12 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.7",
|
||||
"@strapi/babel-plugin-switch-ee-ce": "4.3.6",
|
||||
"@strapi/design-system": "1.2.2",
|
||||
"@strapi/helper-plugin": "4.3.6",
|
||||
"@strapi/icons": "1.2.2",
|
||||
"@strapi/typescript-utils": "4.3.6",
|
||||
"@strapi/utils": "4.3.6",
|
||||
"@strapi/babel-plugin-switch-ee-ce": "4.3.8",
|
||||
"@strapi/design-system": "1.2.3",
|
||||
"@strapi/helper-plugin": "4.3.8",
|
||||
"@strapi/icons": "1.2.3",
|
||||
"@strapi/typescript-utils": "4.3.8",
|
||||
"@strapi/utils": "4.3.8",
|
||||
"axios": "0.27.2",
|
||||
"babel-loader": "8.2.5",
|
||||
"babel-plugin-styled-components": "2.0.2",
|
||||
@ -152,7 +152,7 @@
|
||||
"@strapi/strapi": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@ -80,12 +80,10 @@ const buildAdmin = async () => {
|
||||
new Error(
|
||||
messages.errors.reduce((acc, error) => {
|
||||
if (isObject(error)) {
|
||||
acc += error.message;
|
||||
} else {
|
||||
acc += error.join('\n\n');
|
||||
return acc + error.message;
|
||||
}
|
||||
|
||||
return acc;
|
||||
return acc + error.join('\n\n');
|
||||
}, '')
|
||||
)
|
||||
);
|
||||
|
||||
@ -426,7 +426,11 @@ describe('Role', () => {
|
||||
|
||||
const count = jest.fn(() => Promise.resolve(0));
|
||||
let id = 1;
|
||||
const create = jest.fn(({ data }) => ({ ...data, id: id++ }));
|
||||
const create = jest.fn(({ data }) => {
|
||||
const res = { ...data, id };
|
||||
id += 1;
|
||||
return res;
|
||||
});
|
||||
const values = jest.fn(() => actions);
|
||||
const createMany = jest.fn();
|
||||
const assignARoleToAll = jest.fn();
|
||||
|
||||
@ -49,9 +49,10 @@ const unwrapDeep = (obj) => {
|
||||
|
||||
if (_.isPlainObject(v)) {
|
||||
if ('$elemMatch' in v) {
|
||||
v = v.$elemMatch; // removing this key
|
||||
_.setWith(acc, key, unwrapDeep(v.$elemMatch));
|
||||
} else {
|
||||
_.setWith(acc, key, unwrapDeep(v));
|
||||
}
|
||||
_.setWith(acc, key, unwrapDeep(v));
|
||||
} else if (_.isArray(v)) {
|
||||
// prettier-ignore
|
||||
_.setWith(acc, key, v.map(v => unwrapDeep(v)));
|
||||
|
||||
@ -144,7 +144,7 @@ const cleanPermissionsInDatabase = async () => {
|
||||
const total = await strapi.query('admin::permission').count();
|
||||
const pageCount = Math.ceil(total / pageSize);
|
||||
|
||||
for (let page = 0; page < pageCount; page++) {
|
||||
for (let page = 0; page < pageCount; page += 1) {
|
||||
// 1. Find invalid permissions and collect their ID to delete them later
|
||||
const results = await strapi
|
||||
.query('admin::permission')
|
||||
|
||||
@ -84,7 +84,7 @@ if (edition === 'EE') {
|
||||
// Create users with the new role & create associated auth requests
|
||||
const users = [];
|
||||
|
||||
for (let i = 0; i < localTestData.users.length; ++i) {
|
||||
for (let i = 0; i < localTestData.users.length; i += 1) {
|
||||
const userFixture = localTestData.users[i];
|
||||
const userAttributes = {
|
||||
...userFixture,
|
||||
|
||||
@ -145,7 +145,7 @@ describe('Admin User CRUD (e2e)', () => {
|
||||
};
|
||||
};
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
const res = await rq({
|
||||
url: '/admin/users',
|
||||
method: 'POST',
|
||||
|
||||
@ -12,7 +12,7 @@ const checkFieldsAreCorrectlyNested = (fields) => {
|
||||
}
|
||||
|
||||
let failed = false;
|
||||
for (let indexA = 0; indexA < fields.length; indexA++) {
|
||||
for (let indexA = 0; indexA < fields.length; indexA += 1) {
|
||||
failed = fields
|
||||
.slice(indexA + 1)
|
||||
.some(
|
||||
|
||||
@ -16,7 +16,7 @@ const getActionFromProvider = (actionId) => {
|
||||
|
||||
const email = yup.string().email().lowercase();
|
||||
|
||||
const firstname = yup.string().min(1);
|
||||
const firstname = yup.string().trim().min(1);
|
||||
|
||||
const lastname = yup.string();
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-content-manager",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "A powerful UI to easily manage your data.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -24,11 +24,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
"@strapi/utils": "4.3.6",
|
||||
"@strapi/utils": "4.3.8",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -69,7 +69,7 @@ module.exports = ({ strapi }) => ({
|
||||
});
|
||||
};
|
||||
|
||||
for (const key in model.attributes) {
|
||||
for (const key of Object.keys(model.attributes)) {
|
||||
const attribute = model.attributes[key];
|
||||
|
||||
if (attribute.type === 'component') {
|
||||
|
||||
@ -62,6 +62,17 @@ const createContentTypeSchema = (
|
||||
return context.parent.singularName !== value;
|
||||
},
|
||||
})
|
||||
.test({
|
||||
name: 'pluralNameNotAllowed',
|
||||
message: getTrad('error.contentTypeName.reserved-name'),
|
||||
test(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !reservedNames.includes(toLower(trim(value)));
|
||||
},
|
||||
})
|
||||
.required(errorsTrads.required),
|
||||
singularName: yup
|
||||
.string()
|
||||
@ -87,6 +98,17 @@ const createContentTypeSchema = (
|
||||
return context.parent.pluralName !== value;
|
||||
},
|
||||
})
|
||||
.test({
|
||||
name: 'singularNameNotAllowed',
|
||||
message: getTrad('error.contentTypeName.reserved-name'),
|
||||
test(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !reservedNames.includes(toLower(trim(value)));
|
||||
},
|
||||
})
|
||||
.required(errorsTrads.required),
|
||||
draftAndPublish: yup.boolean(),
|
||||
kind: yup.string().oneOf(['singleType', 'collectionType']),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-content-type-builder",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "Strapi plugin to create content type",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -28,9 +28,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
"@strapi/generators": "4.3.6",
|
||||
"@strapi/helper-plugin": "4.3.6",
|
||||
"@strapi/utils": "4.3.6",
|
||||
"@strapi/generators": "4.3.8",
|
||||
"@strapi/helper-plugin": "4.3.8",
|
||||
"@strapi/utils": "4.3.8",
|
||||
"fs-extra": "10.0.0",
|
||||
"lodash": "4.17.21",
|
||||
"pluralize": "^8.0.0",
|
||||
@ -48,7 +48,7 @@
|
||||
"@testing-library/react": "12.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -130,7 +130,7 @@ module.exports = function createSchemaHandler(infos) {
|
||||
}
|
||||
|
||||
// set new Attributes
|
||||
for (const key in newAttributes) {
|
||||
for (const key of Object.keys(newAttributes)) {
|
||||
this.setAttribute(key, newAttributes[key]);
|
||||
}
|
||||
|
||||
|
||||
@ -157,7 +157,7 @@ describe('Content Type Builder - Content types', () => {
|
||||
const { uid } = createRes.body.data;
|
||||
|
||||
// create data
|
||||
for (let i = 0; i < 2; i++) {
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
await strapi.query(uid).create({ data: { title: 'Test' } });
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,10 @@ class SqliteDialect extends Dialect {
|
||||
fse.ensureDirSync(dbDir);
|
||||
}
|
||||
|
||||
useReturning() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
await this.db.connection.raw('pragma foreign_keys = on');
|
||||
}
|
||||
|
||||
@ -1,19 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash/fp');
|
||||
const types = require('./types');
|
||||
const { createField } = require('./fields');
|
||||
const { createQueryBuilder } = require('./query');
|
||||
const {
|
||||
isUndefined,
|
||||
castArray,
|
||||
isNil,
|
||||
has,
|
||||
isString,
|
||||
isInteger,
|
||||
pick,
|
||||
isPlainObject,
|
||||
isEmpty,
|
||||
isArray,
|
||||
isNull,
|
||||
} = require('lodash/fp');
|
||||
const types = require('../types');
|
||||
const { createField } = require('../fields');
|
||||
const { createQueryBuilder } = require('../query');
|
||||
const { createRepository } = require('./entity-repository');
|
||||
const { isBidirectional, isOneToAny } = require('./metadata/relations');
|
||||
const { isBidirectional, isOneToAny } = require('../metadata/relations');
|
||||
const { deleteRelatedMorphOneRelationsAfterMorphToManyUpdate } = require('./morph-relations');
|
||||
|
||||
const toId = (value) => value.id || value;
|
||||
const toIds = (value) => _.castArray(value || []).map(toId);
|
||||
const toIds = (value) => castArray(value || []).map(toId);
|
||||
|
||||
const isValidId = (value) => _.isString(value) || _.isInteger(value);
|
||||
const isValidId = (value) => isString(value) || isInteger(value);
|
||||
const toAssocs = (data) => {
|
||||
return _.castArray(data)
|
||||
.filter((datum) => !_.isNil(datum))
|
||||
return castArray(data)
|
||||
.filter((datum) => !isNil(datum))
|
||||
.map((datum) => {
|
||||
// if it is a string or an integer return an obj with id = to datum
|
||||
if (isValidId(datum)) {
|
||||
@ -21,7 +34,7 @@ const toAssocs = (data) => {
|
||||
}
|
||||
|
||||
// if it is an object check it has at least a valid id
|
||||
if (!_.has('id', datum) || !isValidId(datum.id)) {
|
||||
if (!has('id', datum) || !isValidId(datum.id)) {
|
||||
throw new Error(`Invalid id, expected a string or integer, got ${datum}`);
|
||||
}
|
||||
|
||||
@ -34,14 +47,14 @@ const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
|
||||
|
||||
const obj = {};
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
for (const attributeName of Object.keys(attributes)) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (types.isScalar(attribute.type)) {
|
||||
const field = createField(attribute);
|
||||
|
||||
if (_.isUndefined(data[attributeName])) {
|
||||
if (!_.isUndefined(attribute.default) && withDefaults) {
|
||||
if (isUndefined(data[attributeName])) {
|
||||
if (!isUndefined(attribute.default) && withDefaults) {
|
||||
if (typeof attribute.default === 'function') {
|
||||
obj[attributeName] = attribute.default();
|
||||
} else {
|
||||
@ -66,11 +79,11 @@ const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
|
||||
const joinColumnName = attribute.joinColumn.name;
|
||||
|
||||
// allow setting to null
|
||||
const attrValue = !_.isUndefined(data[attributeName])
|
||||
const attrValue = !isUndefined(data[attributeName])
|
||||
? data[attributeName]
|
||||
: data[joinColumnName];
|
||||
|
||||
if (!_.isUndefined(attrValue)) {
|
||||
if (!isUndefined(attrValue)) {
|
||||
obj[joinColumnName] = attrValue;
|
||||
}
|
||||
|
||||
@ -91,8 +104,8 @@ const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_.isUndefined(value)) {
|
||||
if (!_.has('id', value) || !_.has(typeField, value)) {
|
||||
if (!isUndefined(value)) {
|
||||
if (!has('id', value) || !has(typeField, value)) {
|
||||
throw new Error(`Expects properties ${typeField} an id to make a morph association`);
|
||||
}
|
||||
|
||||
@ -137,7 +150,7 @@ const createEntityManager = (db) => {
|
||||
const states = await db.lifecycles.run('beforeCount', uid, { params });
|
||||
|
||||
const res = await this.createQueryBuilder(uid)
|
||||
.init(_.pick(['_q', 'where', 'filters'], params))
|
||||
.init(pick(['_q', 'where', 'filters'], params))
|
||||
.count()
|
||||
.first()
|
||||
.execute();
|
||||
@ -155,7 +168,7 @@ const createEntityManager = (db) => {
|
||||
const metadata = db.metadata.get(uid);
|
||||
const { data } = params;
|
||||
|
||||
if (!_.isPlainObject(data)) {
|
||||
if (!isPlainObject(data)) {
|
||||
throw new Error('Create expects a data object');
|
||||
}
|
||||
|
||||
@ -187,7 +200,7 @@ const createEntityManager = (db) => {
|
||||
const metadata = db.metadata.get(uid);
|
||||
const { data } = params;
|
||||
|
||||
if (!_.isArray(data)) {
|
||||
if (!isArray(data)) {
|
||||
throw new Error('CreateMany expects data to be an array');
|
||||
}
|
||||
|
||||
@ -195,7 +208,7 @@ const createEntityManager = (db) => {
|
||||
processData(metadata, datum, { withDefaults: true })
|
||||
);
|
||||
|
||||
if (_.isEmpty(dataToInsert)) {
|
||||
if (isEmpty(dataToInsert)) {
|
||||
throw new Error('Nothing to insert');
|
||||
}
|
||||
|
||||
@ -214,11 +227,11 @@ const createEntityManager = (db) => {
|
||||
const metadata = db.metadata.get(uid);
|
||||
const { where, data } = params;
|
||||
|
||||
if (!_.isPlainObject(data)) {
|
||||
if (!isPlainObject(data)) {
|
||||
throw new Error('Update requires a data object');
|
||||
}
|
||||
|
||||
if (_.isEmpty(where)) {
|
||||
if (isEmpty(where)) {
|
||||
throw new Error('Update requires a where parameter');
|
||||
}
|
||||
|
||||
@ -232,7 +245,7 @@ const createEntityManager = (db) => {
|
||||
|
||||
const dataToUpdate = processData(metadata, data);
|
||||
|
||||
if (!_.isEmpty(dataToUpdate)) {
|
||||
if (!isEmpty(dataToUpdate)) {
|
||||
await this.createQueryBuilder(uid).where({ id }).update(dataToUpdate).execute();
|
||||
}
|
||||
|
||||
@ -259,7 +272,7 @@ const createEntityManager = (db) => {
|
||||
|
||||
const dataToUpdate = processData(metadata, data);
|
||||
|
||||
if (_.isEmpty(dataToUpdate)) {
|
||||
if (isEmpty(dataToUpdate)) {
|
||||
throw new Error('Update requires data');
|
||||
}
|
||||
|
||||
@ -280,7 +293,7 @@ const createEntityManager = (db) => {
|
||||
|
||||
const { where, select, populate } = params;
|
||||
|
||||
if (_.isEmpty(where)) {
|
||||
if (isEmpty(where)) {
|
||||
throw new Error('Delete requires a where parameter');
|
||||
}
|
||||
|
||||
@ -333,10 +346,10 @@ const createEntityManager = (db) => {
|
||||
async attachRelations(uid, id, data) {
|
||||
const { attributes } = db.metadata.get(uid);
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
for (const attributeName of Object.keys(attributes)) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
const isValidLink = _.has(attributeName, data) && !_.isNil(data[attributeName]);
|
||||
const isValidLink = has(attributeName, data) && !isNil(data[attributeName]);
|
||||
|
||||
if (attribute.type !== 'relation' || !isValidLink) {
|
||||
continue;
|
||||
@ -373,7 +386,7 @@ const createEntityManager = (db) => {
|
||||
};
|
||||
});
|
||||
|
||||
if (_.isEmpty(rows)) {
|
||||
if (isEmpty(rows)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -398,10 +411,18 @@ const createEntityManager = (db) => {
|
||||
...(data.__pivot || {}),
|
||||
}));
|
||||
|
||||
if (_.isEmpty(rows)) {
|
||||
if (isEmpty(rows)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// delete previous relations
|
||||
await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
|
||||
uid,
|
||||
attributeName,
|
||||
joinTable,
|
||||
db,
|
||||
});
|
||||
|
||||
await this.createQueryBuilder(joinTable.name).insert(rows).execute();
|
||||
|
||||
continue;
|
||||
@ -450,7 +471,7 @@ const createEntityManager = (db) => {
|
||||
if (isOneToAny(attribute) && isBidirectional(attribute)) {
|
||||
await this.createQueryBuilder(joinTable.name)
|
||||
.delete()
|
||||
.where({ [inverseJoinColumn.name]: _.castArray(data[attributeName]) })
|
||||
.where({ [inverseJoinColumn.name]: castArray(data[attributeName]) })
|
||||
.where(joinTable.on || {})
|
||||
.execute();
|
||||
}
|
||||
@ -487,10 +508,10 @@ const createEntityManager = (db) => {
|
||||
async updateRelations(uid, id, data) {
|
||||
const { attributes } = db.metadata.get(uid);
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
for (const attributeName of Object.keys(attributes)) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (attribute.type !== 'relation' || !_.has(attributeName, data)) {
|
||||
if (attribute.type !== 'relation' || !has(attributeName, data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -503,12 +524,14 @@ const createEntityManager = (db) => {
|
||||
// set columns
|
||||
const { idColumn, typeColumn } = targetAttribute.morphColumn;
|
||||
|
||||
// update instead of deleting because the relation is directly on the entity table
|
||||
// and not in a join table
|
||||
await this.createQueryBuilder(target)
|
||||
.update({ [idColumn.name]: null, [typeColumn.name]: null })
|
||||
.where({ [idColumn.name]: id, [typeColumn.name]: uid })
|
||||
.execute();
|
||||
|
||||
if (!_.isNull(data[attributeName])) {
|
||||
if (!isNull(data[attributeName])) {
|
||||
await this.createQueryBuilder(target)
|
||||
.update({ [idColumn.name]: id, [typeColumn.name]: uid })
|
||||
.where({ id: toId(data[attributeName]) })
|
||||
@ -540,7 +563,7 @@ const createEntityManager = (db) => {
|
||||
field: attributeName,
|
||||
}));
|
||||
|
||||
if (_.isEmpty(rows)) {
|
||||
if (isEmpty(rows)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -577,10 +600,18 @@ const createEntityManager = (db) => {
|
||||
...(data.__pivot || {}),
|
||||
}));
|
||||
|
||||
if (_.isEmpty(rows)) {
|
||||
if (isEmpty(rows)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// delete previous relations
|
||||
await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
|
||||
uid,
|
||||
attributeName,
|
||||
joinTable,
|
||||
db,
|
||||
});
|
||||
|
||||
await this.createQueryBuilder(joinTable.name).insert(rows).execute();
|
||||
|
||||
continue;
|
||||
@ -602,7 +633,7 @@ const createEntityManager = (db) => {
|
||||
.update({ [attribute.joinColumn.referencedColumn]: null })
|
||||
.execute();
|
||||
|
||||
if (!_.isNull(data[attributeName])) {
|
||||
if (!isNull(data[attributeName])) {
|
||||
await this.createQueryBuilder(target)
|
||||
// NOTE: works if it is an array or a single id
|
||||
.where({ id: data[attributeName] })
|
||||
@ -633,7 +664,7 @@ const createEntityManager = (db) => {
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (!_.isNull(data[attributeName])) {
|
||||
if (!isNull(data[attributeName])) {
|
||||
const insert = toAssocs(data[attributeName]).map((data) => {
|
||||
return {
|
||||
[joinColumn.name]: id,
|
||||
@ -667,7 +698,7 @@ const createEntityManager = (db) => {
|
||||
async deleteRelations(uid, id) {
|
||||
const { attributes } = db.metadata.get(uid);
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
for (const attributeName of Object.keys(attributes)) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (attribute.type !== 'relation') {
|
||||
@ -791,7 +822,7 @@ const createEntityManager = (db) => {
|
||||
async load(uid, entity, fields, params) {
|
||||
const { attributes } = db.metadata.get(uid);
|
||||
|
||||
const fieldsArr = _.castArray(fields);
|
||||
const fieldsArr = castArray(fields);
|
||||
fieldsArr.forEach((field) => {
|
||||
const attribute = attributes[field];
|
||||
|
||||
@ -814,7 +845,7 @@ const createEntityManager = (db) => {
|
||||
}
|
||||
|
||||
if (Array.isArray(fields)) {
|
||||
return _.pick(fields, entry);
|
||||
return pick(fields, entry);
|
||||
}
|
||||
|
||||
return entry[fields];
|
||||
59
packages/core/database/lib/entity-manager/morph-relations.js
Normal file
59
packages/core/database/lib/entity-manager/morph-relations.js
Normal file
@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
const { groupBy, pipe, mapValues, map, isEmpty } = require('lodash/fp');
|
||||
const { createQueryBuilder } = require('../query');
|
||||
|
||||
const getMorphToManyRowsLinkedToMorphOne = (rows, { uid, attributeName, typeColumn, db }) =>
|
||||
rows.filter((row) => {
|
||||
const relatedType = row[typeColumn.name];
|
||||
const field = row.field;
|
||||
|
||||
const targetAttribute = db.metadata.get(relatedType).attributes[field];
|
||||
|
||||
// ensure targeted field is the right one + check if it is a morphOne
|
||||
return (
|
||||
targetAttribute?.target === uid &&
|
||||
targetAttribute?.morphBy === attributeName &&
|
||||
targetAttribute?.relation === 'morphOne'
|
||||
);
|
||||
});
|
||||
|
||||
const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (
|
||||
rows,
|
||||
{ uid, attributeName, joinTable, db }
|
||||
) => {
|
||||
const { morphColumn } = joinTable;
|
||||
const { idColumn, typeColumn } = morphColumn;
|
||||
|
||||
const morphOneRows = getMorphToManyRowsLinkedToMorphOne(rows, {
|
||||
uid,
|
||||
attributeName,
|
||||
typeColumn,
|
||||
db,
|
||||
});
|
||||
|
||||
const groupByType = groupBy(typeColumn.name);
|
||||
const groupByField = groupBy('field');
|
||||
|
||||
const typeAndFieldIdsGrouped = pipe(groupByType, mapValues(groupByField))(morphOneRows);
|
||||
|
||||
const orWhere = [];
|
||||
|
||||
for (const [type, v] of Object.entries(typeAndFieldIdsGrouped)) {
|
||||
for (const [field, arr] of Object.entries(v)) {
|
||||
orWhere.push({
|
||||
[typeColumn.name]: type,
|
||||
field,
|
||||
[idColumn.name]: { $in: map(idColumn.name, arr) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEmpty(orWhere)) {
|
||||
await createQueryBuilder(joinTable.name, db).delete().where({ $or: orWhere }).execute();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
deleteRelatedMorphOneRelationsAfterMorphToManyUpdate,
|
||||
};
|
||||
@ -54,7 +54,7 @@ const createLifecyclesProvider = (db) => {
|
||||
* @param {Map<any, any>} states
|
||||
*/
|
||||
async run(action, uid, properties, states = new Map()) {
|
||||
for (let i = 0; i < subscribers.length; i++) {
|
||||
for (let i = 0; i < subscribers.length; i += 1) {
|
||||
const subscriber = subscribers[i];
|
||||
if (typeof subscriber === 'function') {
|
||||
const state = states.get(subscriber) || {};
|
||||
|
||||
@ -71,7 +71,7 @@ const applyJoin = (qb, join) => {
|
||||
inner.on(`${rootTable}.${rootColumn}`, `${alias}.${referencedColumn}`);
|
||||
|
||||
if (on) {
|
||||
for (const key in on) {
|
||||
for (const key of Object.keys(on)) {
|
||||
inner.onVal(`${alias}.${key}`, on[key]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ const { fromRow } = require('./transform');
|
||||
const getRootLevelPopulate = (meta) => {
|
||||
const populate = {};
|
||||
|
||||
for (const attributeName in meta.attributes) {
|
||||
for (const attributeName of Object.keys(meta.attributes)) {
|
||||
const attribute = meta.attributes[attributeName];
|
||||
if (attribute.type === 'relation') {
|
||||
populate[attributeName] = true;
|
||||
@ -72,7 +72,7 @@ const processPopulate = (populate, ctx) => {
|
||||
}
|
||||
|
||||
const finalPopulate = {};
|
||||
for (const key in populateMap) {
|
||||
for (const key of Object.keys(populateMap)) {
|
||||
const attribute = meta.attributes[key];
|
||||
|
||||
if (!attribute) {
|
||||
@ -119,7 +119,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (const key in populate) {
|
||||
for (const key of Object.keys(populate)) {
|
||||
const attribute = meta.attributes[key];
|
||||
const targetMeta = db.metadata.get(attribute.target);
|
||||
|
||||
@ -540,7 +540,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
||||
}, {});
|
||||
|
||||
const map = {};
|
||||
for (const type in idsByType) {
|
||||
for (const type of Object.keys(idsByType)) {
|
||||
const ids = idsByType[type];
|
||||
|
||||
// type was removed but still in morph relation
|
||||
@ -604,7 +604,7 @@ const applyPopulate = async (results, populate, ctx) => {
|
||||
}, {});
|
||||
|
||||
const map = {};
|
||||
for (const type in idsByType) {
|
||||
for (const type of Object.keys(idsByType)) {
|
||||
const ids = idsByType[type];
|
||||
|
||||
// type was removed but still in morph relation
|
||||
|
||||
@ -53,7 +53,7 @@ const toRow = (meta, data = {}) => {
|
||||
|
||||
const { attributes } = meta;
|
||||
|
||||
for (const key in data) {
|
||||
for (const key of Object.keys(data)) {
|
||||
const attribute = attributes[key];
|
||||
|
||||
if (!attribute || attribute.columnName === key) {
|
||||
|
||||
@ -76,7 +76,7 @@ const processAttributeWhere = (attribute, where, operator = '$eq') => {
|
||||
|
||||
const filters = {};
|
||||
|
||||
for (const key in where) {
|
||||
for (const key of Object.keys(where)) {
|
||||
const value = where[key];
|
||||
|
||||
if (!isOperator(key)) {
|
||||
@ -119,7 +119,7 @@ const processWhere = (where, ctx) => {
|
||||
const filters = {};
|
||||
|
||||
// for each key in where
|
||||
for (const key in where) {
|
||||
for (const key of Object.keys(where)) {
|
||||
const value = where[key];
|
||||
|
||||
// if operator $and $or then loop over them
|
||||
|
||||
@ -27,7 +27,13 @@ const createQueryBuilder = (uid, db) => {
|
||||
};
|
||||
|
||||
let counter = 0;
|
||||
const getAlias = () => `t${counter++}`;
|
||||
const getAlias = () => {
|
||||
const alias = `t${counter}`;
|
||||
|
||||
counter += 1;
|
||||
|
||||
return alias;
|
||||
};
|
||||
|
||||
return {
|
||||
alias: getAlias(),
|
||||
|
||||
@ -295,7 +295,11 @@ const createHelpers = (db) => {
|
||||
|
||||
const { object } = updatedColumn;
|
||||
|
||||
createColumn(tableBuilder, object).alter();
|
||||
if (object.type === 'increments') {
|
||||
createColumn(tableBuilder, { ...object, type: 'integer' }).alter();
|
||||
} else {
|
||||
createColumn(tableBuilder, object).alter();
|
||||
}
|
||||
}
|
||||
|
||||
for (const updatedForeignKey of table.foreignKeys.updated) {
|
||||
|
||||
@ -25,7 +25,7 @@ const createTable = (meta) => {
|
||||
columns: [],
|
||||
};
|
||||
|
||||
for (const key in meta.attributes) {
|
||||
for (const key of Object.keys(meta.attributes)) {
|
||||
const attribute = meta.attributes[key];
|
||||
|
||||
if (types.isRelation(attribute.type)) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/database",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "Strapi's database layer",
|
||||
"homepage": "https://strapi.io",
|
||||
"bugs": {
|
||||
@ -39,7 +39,7 @@
|
||||
"umzug": "3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/plugin-email",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "Easily configure your Strapi application to send emails.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -26,16 +26,16 @@
|
||||
"test:front:watch:ce": "cross-env IS_EE=false jest --config ./jest.config.front.js --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/provider-email-sendmail": "4.3.6",
|
||||
"@strapi/utils": "4.3.6",
|
||||
"@strapi/provider-email-sendmail": "4.3.8",
|
||||
"@strapi/utils": "4.3.8",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@strapi/helper-plugin": "4.3.6",
|
||||
"@strapi/helper-plugin": "4.3.8",
|
||||
"@testing-library/react": "12.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"strapi": {
|
||||
|
||||
@ -17,7 +17,7 @@ const getFilterList = ({ fieldSchema: { type: fieldType, mainField } }) => {
|
||||
value: '$eq',
|
||||
},
|
||||
{
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$neq', defaultMessage: 'is not' },
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$ne', defaultMessage: 'is not' },
|
||||
value: '$ne',
|
||||
},
|
||||
{
|
||||
@ -75,7 +75,7 @@ const getFilterList = ({ fieldSchema: { type: fieldType, mainField } }) => {
|
||||
value: '$eq',
|
||||
},
|
||||
{
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$neq', defaultMessage: 'is not' },
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$ne', defaultMessage: 'is not' },
|
||||
value: '$ne',
|
||||
},
|
||||
{
|
||||
@ -130,7 +130,7 @@ const getFilterList = ({ fieldSchema: { type: fieldType, mainField } }) => {
|
||||
value: '$eq',
|
||||
},
|
||||
{
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$neq', defaultMessage: 'is not' },
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$ne', defaultMessage: 'is not' },
|
||||
value: '$ne',
|
||||
},
|
||||
{
|
||||
@ -199,7 +199,7 @@ const getFilterList = ({ fieldSchema: { type: fieldType, mainField } }) => {
|
||||
value: '$eq',
|
||||
},
|
||||
{
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$neq', defaultMessage: 'is not' },
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$ne', defaultMessage: 'is not' },
|
||||
value: '$ne',
|
||||
},
|
||||
{
|
||||
@ -254,7 +254,7 @@ const getFilterList = ({ fieldSchema: { type: fieldType, mainField } }) => {
|
||||
value: '$eq',
|
||||
},
|
||||
{
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$neq', defaultMessage: 'is not' },
|
||||
intlLabel: { id: 'components.FilterOptions.FILTER_TYPES.$ne', defaultMessage: 'is not' },
|
||||
value: '$ne',
|
||||
},
|
||||
{
|
||||
|
||||
@ -171,7 +171,7 @@ exports[`<PaginationURLQuery /> should display the dots correctly 1`] = `
|
||||
|
||||
<nav
|
||||
aria-label="pagination"
|
||||
class="sc-iseIHH"
|
||||
class="sc-ezHhwS"
|
||||
>
|
||||
<ul
|
||||
class="c0 c1"
|
||||
@ -544,7 +544,7 @@ exports[`<PaginationURLQuery /> should work when the pageCount is inferior or eq
|
||||
|
||||
<nav
|
||||
aria-label="pagination"
|
||||
class="sc-iseIHH"
|
||||
class="sc-ezHhwS"
|
||||
>
|
||||
<ul
|
||||
class="c0 c1"
|
||||
|
||||
@ -190,7 +190,7 @@ describe('<PaginationURLQuery />', () => {
|
||||
|
||||
<nav
|
||||
aria-label="pagination"
|
||||
class="sc-iseIHH"
|
||||
class="sc-ezHhwS"
|
||||
>
|
||||
<ul
|
||||
class="c0 c1"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/helper-plugin",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "Helper for Strapi plugins development",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -77,8 +77,8 @@
|
||||
"@storybook/builder-webpack5": "6.5.9",
|
||||
"@storybook/manager-webpack5": "6.4.10",
|
||||
"@storybook/react": "^6.5.10",
|
||||
"@strapi/design-system": "1.2.2",
|
||||
"@strapi/icons": "1.2.2",
|
||||
"@strapi/design-system": "1.2.3",
|
||||
"@strapi/icons": "1.2.3",
|
||||
"@testing-library/react": "11.2.7",
|
||||
"@testing-library/react-hooks": "3.7.0",
|
||||
"babel-loader": "^8.2.5",
|
||||
@ -89,7 +89,7 @@
|
||||
"typescript": "4.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@ -86,7 +86,7 @@ Complete installation requirements can be found in the documentation under <a hr
|
||||
|
||||
**Node:**
|
||||
|
||||
- NodeJS >= 14 <= 16
|
||||
- NodeJS >= 14 <= 18
|
||||
- NPM >= 6.x
|
||||
|
||||
**Database:**
|
||||
|
||||
@ -21,6 +21,7 @@ const createEntityService = require('./services/entity-service');
|
||||
const createCronService = require('./services/cron');
|
||||
const entityValidator = require('./services/entity-validator');
|
||||
const createTelemetry = require('./services/metrics');
|
||||
const requestContext = require('./services/request-context');
|
||||
const createAuth = require('./services/auth');
|
||||
const createUpdateNotifier = require('./utils/update-notifier');
|
||||
const createStartupLogger = require('./utils/startup-logger');
|
||||
@ -108,6 +109,7 @@ class Strapi {
|
||||
this.log = createLogger(this.config.get('logger', {}));
|
||||
this.cron = createCronService();
|
||||
this.telemetry = createTelemetry(this);
|
||||
this.requestContext = requestContext;
|
||||
|
||||
createUpdateNotifier(this).notify();
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ const passwordValidator = yup
|
||||
const adminCreateSchema = yup.object().shape({
|
||||
email: emailValidator,
|
||||
password: passwordValidator,
|
||||
firstname: yup.string().required('First name is required'),
|
||||
firstname: yup.string().trim().required('First name is required'),
|
||||
lastname: yup.string(),
|
||||
});
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ const CHUNK_SIZE = 100;
|
||||
* Will dump configurations to a file or stdout
|
||||
* @param {string} file filepath to use as output
|
||||
*/
|
||||
module.exports = async function ({ file: filePath, pretty }) {
|
||||
module.exports = async ({ file: filePath, pretty }) => {
|
||||
const output = filePath ? fs.createWriteStream(filePath) : process.stdout;
|
||||
|
||||
const appContext = await strapi.compile();
|
||||
@ -21,7 +21,7 @@ module.exports = async function ({ file: filePath, pretty }) {
|
||||
|
||||
const pageCount = Math.ceil(count / CHUNK_SIZE);
|
||||
|
||||
for (let page = 0; page < pageCount; page++) {
|
||||
for (let page = 0; page < pageCount; page += 1) {
|
||||
const results = await app
|
||||
.query('strapi::core-store')
|
||||
.findMany({ limit: CHUNK_SIZE, offset: page * CHUNK_SIZE, orderBy: 'key' });
|
||||
|
||||
@ -56,7 +56,7 @@ const transformEntry = (entry, type) => {
|
||||
|
||||
const attributeValues = {};
|
||||
|
||||
for (const key in properties) {
|
||||
for (const key of Object.keys(properties)) {
|
||||
const property = properties[key];
|
||||
const attribute = type && type.attributes[key];
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ module.exports = async (strapi) => {
|
||||
|
||||
validateContentTypesUnicity(apis);
|
||||
|
||||
for (const apiName in apis) {
|
||||
for (const apiName of Object.keys(apis)) {
|
||||
strapi.container.get('apis').add(apiName, apis[apiName]);
|
||||
}
|
||||
};
|
||||
@ -131,11 +131,12 @@ const loadDir = async (dir) => {
|
||||
|
||||
const root = {};
|
||||
for (const fd of fds) {
|
||||
if (!fd.isFile()) {
|
||||
if (!fd.isFile() || extname(fd.name) === '.map') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = basename(fd.name, extname(fd.name));
|
||||
|
||||
root[normalizeName(key)] = await loadFile(join(dir, fd.name));
|
||||
}
|
||||
|
||||
|
||||
@ -59,7 +59,9 @@ const getEnabledPlugins = async (strapi) => {
|
||||
}
|
||||
|
||||
const installedPlugins = {};
|
||||
for (const dep in strapi.config.get('info.dependencies', {})) {
|
||||
const dependencies = strapi.config.get('info.dependencies', {});
|
||||
|
||||
for (const dep of Object.keys(dependencies)) {
|
||||
const packagePath = join(dep, 'package.json');
|
||||
let packageInfo;
|
||||
try {
|
||||
|
||||
@ -34,10 +34,10 @@ const applyUserExtension = async (plugins) => {
|
||||
const extendedSchemas = await loadFiles(extensionsDir, '**/content-types/**/schema.json');
|
||||
const strapiServers = await loadFiles(extensionsDir, '**/strapi-server.js');
|
||||
|
||||
for (const pluginName in plugins) {
|
||||
for (const pluginName of Object.keys(plugins)) {
|
||||
const plugin = plugins[pluginName];
|
||||
// first: load json schema
|
||||
for (const ctName in plugin.contentTypes) {
|
||||
for (const ctName of Object.keys(plugin.contentTypes)) {
|
||||
const extendedSchema = get([pluginName, 'content-types', ctName, 'schema'], extendedSchemas);
|
||||
if (extendedSchema) {
|
||||
plugin.contentTypes[ctName].schema = {
|
||||
@ -57,7 +57,7 @@ const applyUserExtension = async (plugins) => {
|
||||
const applyUserConfig = async (plugins) => {
|
||||
const userPluginsConfig = await getUserPluginsConfig();
|
||||
|
||||
for (const pluginName in plugins) {
|
||||
for (const pluginName of Object.keys(plugins)) {
|
||||
const plugin = plugins[pluginName];
|
||||
const userPluginConfig = getOr({}, `${pluginName}.config`, userPluginsConfig);
|
||||
const defaultConfig =
|
||||
@ -82,7 +82,7 @@ const loadPlugins = async (strapi) => {
|
||||
|
||||
strapi.config.set('enabledPlugins', enabledPlugins);
|
||||
|
||||
for (const pluginName in enabledPlugins) {
|
||||
for (const pluginName of Object.keys(enabledPlugins)) {
|
||||
const enabledPlugin = enabledPlugins[pluginName];
|
||||
|
||||
const serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');
|
||||
@ -100,7 +100,7 @@ const loadPlugins = async (strapi) => {
|
||||
await applyUserConfig(plugins);
|
||||
await applyUserExtension(plugins);
|
||||
|
||||
for (const pluginName in plugins) {
|
||||
for (const pluginName of Object.keys(plugins)) {
|
||||
strapi.container.get('plugins').add(pluginName, plugins[pluginName]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -5,7 +5,7 @@ const { createContentType } = require('../domain/content-type');
|
||||
const { addNamespace, hasNamespace } = require('../utils');
|
||||
|
||||
const validateKeySameToSingularName = (contentTypes) => {
|
||||
for (const ctName in contentTypes) {
|
||||
for (const ctName of Object.keys(contentTypes)) {
|
||||
const contentType = contentTypes[ctName];
|
||||
|
||||
if (ctName !== contentType.schema.info.singularName) {
|
||||
@ -63,7 +63,7 @@ const contentTypesRegistry = () => {
|
||||
add(namespace, newContentTypes) {
|
||||
validateKeySameToSingularName(newContentTypes);
|
||||
|
||||
for (const rawCtName in newContentTypes) {
|
||||
for (const rawCtName of Object.keys(newContentTypes)) {
|
||||
const uid = addNamespace(rawCtName, namespace);
|
||||
|
||||
if (has(uid, contentTypes)) {
|
||||
|
||||
@ -48,7 +48,7 @@ const controllersRegistry = () => {
|
||||
const filteredControllers = pickBy((_, uid) => hasNamespace(uid, namespace))(controllers);
|
||||
|
||||
const map = {};
|
||||
for (const uid in filteredControllers) {
|
||||
for (const uid of Object.keys(filteredControllers)) {
|
||||
Object.defineProperty(map, uid, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
@ -78,7 +78,7 @@ const controllersRegistry = () => {
|
||||
* @returns
|
||||
*/
|
||||
add(namespace, newControllers) {
|
||||
for (const controllerName in newControllers) {
|
||||
for (const controllerName of Object.keys(newControllers)) {
|
||||
const controller = newControllers[controllerName];
|
||||
const uid = addNamespace(controllerName, namespace);
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ const hooksRegistry = () => {
|
||||
* @returns
|
||||
*/
|
||||
add(namespace, hooks) {
|
||||
for (const hookName in hooks) {
|
||||
for (const hookName of Object.keys(hooks)) {
|
||||
const hook = hooks[hookName];
|
||||
const uid = addNamespace(hookName, namespace);
|
||||
|
||||
|
||||
@ -54,8 +54,8 @@ const middlewaresRegistry = () => {
|
||||
* @param {{ [key: string]: Middleware }} newMiddlewares
|
||||
* @returns
|
||||
*/
|
||||
add(namespace, rawMiddlewares) {
|
||||
for (const middlewareName in rawMiddlewares) {
|
||||
add(namespace, rawMiddlewares = {}) {
|
||||
for (const middlewareName of Object.keys(rawMiddlewares)) {
|
||||
const middleware = rawMiddlewares[middlewareName];
|
||||
const uid = addNamespace(middlewareName, namespace);
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ const policiesRegistry = () => {
|
||||
* @returns
|
||||
*/
|
||||
add(namespace, newPolicies) {
|
||||
for (const policyName in newPolicies) {
|
||||
for (const policyName of Object.keys(newPolicies)) {
|
||||
const policy = newPolicies[policyName];
|
||||
const uid = addNamespace(policyName, namespace);
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@ const servicesRegistry = (strapi) => {
|
||||
|
||||
// create lazy accessor to avoid instantiating the services;
|
||||
const map = {};
|
||||
for (const uid in filteredServices) {
|
||||
for (const uid of Object.keys(filteredServices)) {
|
||||
Object.defineProperty(map, uid, {
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
@ -78,7 +78,7 @@ const servicesRegistry = (strapi) => {
|
||||
* @returns
|
||||
*/
|
||||
add(namespace, newServices) {
|
||||
for (const serviceName in newServices) {
|
||||
for (const serviceName of Object.keys(newServices)) {
|
||||
const service = newServices[serviceName];
|
||||
const uid = addNamespace(serviceName, namespace);
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ const createCronService = () => {
|
||||
|
||||
return {
|
||||
add(tasks = {}) {
|
||||
for (const taskExpression in tasks) {
|
||||
for (const taskExpression of Object.keys(tasks)) {
|
||||
const taskValue = tasks[taskExpression];
|
||||
|
||||
let fn;
|
||||
|
||||
@ -18,11 +18,11 @@ const omitComponentData = (contentType, data) => {
|
||||
|
||||
// NOTE: we could generalize the logic to allow CRUD of relation directly in the DB layer
|
||||
const createComponents = async (uid, data) => {
|
||||
const { attributes } = strapi.getModel(uid);
|
||||
const { attributes = {} } = strapi.getModel(uid);
|
||||
|
||||
const componentBody = {};
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
for (const attributeName of Object.keys(attributes)) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (!has(attributeName, data) || !contentTypesUtils.isComponentAttribute(attribute)) {
|
||||
@ -118,11 +118,11 @@ const getComponents = async (uid, entity) => {
|
||||
create or update
|
||||
*/
|
||||
const updateComponents = async (uid, entityToUpdate, data) => {
|
||||
const { attributes } = strapi.getModel(uid);
|
||||
const { attributes = {} } = strapi.getModel(uid);
|
||||
|
||||
const componentBody = {};
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
for (const attributeName of Object.keys(attributes)) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (!has(attributeName, data)) {
|
||||
@ -275,9 +275,9 @@ const deleteOldDZComponents = async (uid, entityToUpdate, attributeName, dynamic
|
||||
};
|
||||
|
||||
const deleteComponents = async (uid, entityToDelete) => {
|
||||
const { attributes } = strapi.getModel(uid);
|
||||
const { attributes = {} } = strapi.getModel(uid);
|
||||
|
||||
for (const attributeName in attributes) {
|
||||
for (const attributeName of Object.keys(attributes)) {
|
||||
const attribute = attributes[attributeName];
|
||||
|
||||
if (attribute.type === 'component') {
|
||||
|
||||
@ -14,51 +14,56 @@ const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUti
|
||||
const { ValidationError } = strapiUtils.errors;
|
||||
|
||||
const addMinMax = (validator, { attr, updatedAttribute }) => {
|
||||
let nextValidator = validator;
|
||||
|
||||
if (
|
||||
Number.isInteger(attr.min) &&
|
||||
(attr.required || (Array.isArray(updatedAttribute.value) && updatedAttribute.value.length > 0))
|
||||
) {
|
||||
validator = validator.min(attr.min);
|
||||
nextValidator = nextValidator.min(attr.min);
|
||||
}
|
||||
if (Number.isInteger(attr.max)) {
|
||||
validator = validator.max(attr.max);
|
||||
nextValidator = nextValidator.max(attr.max);
|
||||
}
|
||||
return validator;
|
||||
return nextValidator;
|
||||
};
|
||||
|
||||
const addRequiredValidation =
|
||||
(createOrUpdate) =>
|
||||
(validator, { attr: { required } }) => {
|
||||
const addRequiredValidation = (createOrUpdate) => {
|
||||
return (validator, { attr: { required } }) => {
|
||||
let nextValidator = validator;
|
||||
if (required) {
|
||||
if (createOrUpdate === 'creation') {
|
||||
validator = validator.notNil();
|
||||
nextValidator = nextValidator.notNil();
|
||||
} else if (createOrUpdate === 'update') {
|
||||
validator = validator.notNull();
|
||||
nextValidator = nextValidator.notNull();
|
||||
}
|
||||
} else {
|
||||
validator = validator.nullable();
|
||||
nextValidator = nextValidator.nullable();
|
||||
}
|
||||
return validator;
|
||||
return nextValidator;
|
||||
};
|
||||
};
|
||||
|
||||
const addDefault = (createOrUpdate) => {
|
||||
return (validator, { attr }) => {
|
||||
let nextValidator = validator;
|
||||
|
||||
const addDefault =
|
||||
(createOrUpdate) =>
|
||||
(validator, { attr }) => {
|
||||
if (createOrUpdate === 'creation') {
|
||||
if (
|
||||
((attr.type === 'component' && attr.repeatable) || attr.type === 'dynamiczone') &&
|
||||
!attr.required
|
||||
) {
|
||||
validator = validator.default([]);
|
||||
nextValidator = nextValidator.default([]);
|
||||
} else {
|
||||
validator = validator.default(attr.default);
|
||||
nextValidator = nextValidator.default(attr.default);
|
||||
}
|
||||
} else {
|
||||
validator = validator.default(undefined);
|
||||
nextValidator = nextValidator.default(undefined);
|
||||
}
|
||||
|
||||
return validator;
|
||||
return nextValidator;
|
||||
};
|
||||
};
|
||||
|
||||
const preventCast = (validator) => validator.transform((val, originalVal) => originalVal);
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ describe('Metrics middleware', () => {
|
||||
const sendEvent = jest.fn();
|
||||
const middleware = createMiddleware({ sendEvent });
|
||||
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
for (let i = 0; i < 2000; i += 1) {
|
||||
await middleware(
|
||||
{
|
||||
request: {
|
||||
|
||||
@ -22,7 +22,7 @@ const createMiddleware = ({ sendEvent }) => {
|
||||
sendEvent('didReceiveRequest', { eventProperties: { url: ctx.request.url } });
|
||||
|
||||
// Increase counter.
|
||||
_state.counter++;
|
||||
_state.counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
packages/core/strapi/lib/services/request-context.js
Normal file
17
packages/core/strapi/lib/services/request-context.js
Normal file
@ -0,0 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
const { AsyncLocalStorage } = require('async_hooks');
|
||||
|
||||
const storage = new AsyncLocalStorage();
|
||||
|
||||
const requestCtx = {
|
||||
async run(store, cb) {
|
||||
return storage.run(store, cb);
|
||||
},
|
||||
|
||||
get() {
|
||||
return storage.getStore();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = requestCtx;
|
||||
@ -9,6 +9,7 @@ const { createContentAPI } = require('./content-api');
|
||||
const registerAllRoutes = require('./register-routes');
|
||||
const registerApplicationMiddlewares = require('./register-middlewares');
|
||||
const createKoaApp = require('./koa');
|
||||
const requestCtx = require('../request-context');
|
||||
|
||||
const healthCheck = async (ctx) => {
|
||||
ctx.set('strapi', 'You are so French!');
|
||||
@ -33,6 +34,8 @@ const createServer = (strapi) => {
|
||||
keys: strapi.config.get('server.app.keys'),
|
||||
});
|
||||
|
||||
app.use((ctx, next) => requestCtx.run(ctx, () => next()));
|
||||
|
||||
const router = new Router();
|
||||
|
||||
const routeManager = createRouteManager(strapi);
|
||||
|
||||
@ -50,7 +50,7 @@ const registerAdminRoutes = (strapi) => {
|
||||
* @param {import('../../').Strapi} strapi
|
||||
*/
|
||||
const registerPluginRoutes = (strapi) => {
|
||||
for (const pluginName in strapi.plugins) {
|
||||
for (const pluginName of Object.keys(strapi.plugins)) {
|
||||
const plugin = strapi.plugins[pluginName];
|
||||
|
||||
const generateRouteScope = createRouteScopeGenerator(`plugin::${pluginName}`);
|
||||
@ -86,7 +86,7 @@ const registerPluginRoutes = (strapi) => {
|
||||
* @param {import('../../').Strapi} strapi
|
||||
*/
|
||||
const registerAPIRoutes = (strapi) => {
|
||||
for (const apiName in strapi.api) {
|
||||
for (const apiName of Object.keys(strapi.api)) {
|
||||
const api = strapi.api[apiName];
|
||||
|
||||
const generateRouteScope = createRouteScopeGenerator(`api::${apiName}`);
|
||||
|
||||
@ -27,7 +27,7 @@ module.exports = async (uid, entity, files) => {
|
||||
let tmpModel = modelDef;
|
||||
let modelUID = uid;
|
||||
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
for (let i = 0; i < path.length; i += 1) {
|
||||
if (!tmpModel) return {};
|
||||
const part = path[i];
|
||||
const attr = tmpModel.attributes[part];
|
||||
|
||||
@ -26,7 +26,7 @@ module.exports = class WorkerQueue {
|
||||
enqueue(payload) {
|
||||
debug('Enqueue event in worker queue');
|
||||
if (this.running < this.concurrency) {
|
||||
this.running++;
|
||||
this.running += 1;
|
||||
this.execute(payload);
|
||||
} else {
|
||||
this.queue.unshift(payload);
|
||||
@ -40,7 +40,7 @@ module.exports = class WorkerQueue {
|
||||
if (payload) {
|
||||
this.execute(payload);
|
||||
} else {
|
||||
this.running--;
|
||||
this.running -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (path) => {
|
||||
if (typeof path !== 'string') throw new Error('admin.url must be a string');
|
||||
if (path === '' || path === '/') return '/';
|
||||
let tmpPath = path;
|
||||
if (typeof tmpPath !== 'string') throw new Error('admin.url must be a string');
|
||||
if (tmpPath === '' || tmpPath === '/') return '/';
|
||||
|
||||
if (path[0] != '/') path = `/${path}`;
|
||||
if (path[path.length - 1] != '/') path += '/';
|
||||
return path;
|
||||
if (tmpPath[0] !== '/') tmpPath = `/${tmpPath}`;
|
||||
if (tmpPath[tmpPath.length - 1] !== '/') tmpPath += '/';
|
||||
return tmpPath;
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@strapi/strapi",
|
||||
"version": "4.3.6",
|
||||
"version": "4.3.8",
|
||||
"description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
|
||||
"keywords": [
|
||||
"strapi",
|
||||
@ -80,17 +80,17 @@
|
||||
"dependencies": {
|
||||
"@koa/cors": "3.4.1",
|
||||
"@koa/router": "10.1.1",
|
||||
"@strapi/admin": "4.3.6",
|
||||
"@strapi/database": "4.3.6",
|
||||
"@strapi/generate-new": "4.3.6",
|
||||
"@strapi/generators": "4.3.6",
|
||||
"@strapi/logger": "4.3.6",
|
||||
"@strapi/plugin-content-manager": "4.3.6",
|
||||
"@strapi/plugin-content-type-builder": "4.3.6",
|
||||
"@strapi/plugin-email": "4.3.6",
|
||||
"@strapi/plugin-upload": "4.3.6",
|
||||
"@strapi/typescript-utils": "4.3.6",
|
||||
"@strapi/utils": "4.3.6",
|
||||
"@strapi/admin": "4.3.8",
|
||||
"@strapi/database": "4.3.8",
|
||||
"@strapi/generate-new": "4.3.8",
|
||||
"@strapi/generators": "4.3.8",
|
||||
"@strapi/logger": "4.3.8",
|
||||
"@strapi/plugin-content-manager": "4.3.8",
|
||||
"@strapi/plugin-content-type-builder": "4.3.8",
|
||||
"@strapi/plugin-email": "4.3.8",
|
||||
"@strapi/plugin-upload": "4.3.8",
|
||||
"@strapi/typescript-utils": "4.3.8",
|
||||
"@strapi/utils": "4.3.8",
|
||||
"bcryptjs": "2.4.3",
|
||||
"boxen": "5.1.2",
|
||||
"chalk": "4.1.2",
|
||||
@ -137,7 +137,7 @@
|
||||
"typescript": "4.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=16.x.x",
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1451,7 +1451,7 @@ exports[`BrowseStep renders and match snapshot 1`] = `
|
||||
</div>
|
||||
<nav
|
||||
aria-label="pagination"
|
||||
class="sc-fMfAsl"
|
||||
class="sc-ezDxBL"
|
||||
>
|
||||
<ul
|
||||
class="c17 c74"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user