mirror of
https://github.com/strapi/strapi.git
synced 2025-10-27 16:10:08 +00:00
Merge branch 'develop' into feature/add-mariadb-migration-support
This commit is contained in:
commit
9bc666db2f
4
.github/workflows/adminBundleSize.yml
vendored
4
.github/workflows/adminBundleSize.yml
vendored
@ -37,8 +37,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
build-script: 'build:size'
|
build-script: 'build:size'
|
||||||
pattern: '**/build/**/*.{js,css,html,svg}'
|
pattern: '**/build/**/*.{js,css,html,svg}'
|
||||||
strip-hash: "\\.(?:(\\w{8})\\.chunk)|(?:\\.(\\w{8}))"
|
strip-hash: "-([-\\w]{8})(\\.\\w+)$"
|
||||||
minimum-change-threshold: 10
|
minimum-change-threshold: '5%'
|
||||||
|
|
||||||
# FIXME: exclude unnamed webpack chunks - remove once webpack
|
# FIXME: exclude unnamed webpack chunks - remove once webpack
|
||||||
# does not create them anymore
|
# does not create them anymore
|
||||||
|
|||||||
37
.github/workflows/issues_handleLabel.yml
vendored
37
.github/workflows/issues_handleLabel.yml
vendored
@ -70,6 +70,43 @@ jobs:
|
|||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
close-reason: 'not_planned'
|
close-reason: 'not_planned'
|
||||||
|
|
||||||
|
# v4 Legacy Issues
|
||||||
|
- name: 'Comment: unsupported v4 issues'
|
||||||
|
if: "${{ github.event.label.name == 'flag: v4-unsupported' }}"
|
||||||
|
uses: actions-cool/issues-helper@v3
|
||||||
|
with:
|
||||||
|
actions: 'create-comment'
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
body: |
|
||||||
|
> This is a templated message
|
||||||
|
|
||||||
|
Hello @${{ github.event.issue.user.login }},
|
||||||
|
|
||||||
|
Thank you for reporting this potential bug report, please keep in mind that we are no longer accepting low/medium severity bug reports for Strapi v4.
|
||||||
|
We have confirmed that this issue is a low/medium severity bug and is not reproducable in Strapi 5. We advise upgrading to Strapi 5 to resolve this issue.
|
||||||
|
|
||||||
|
The recommended action we suggest for v4 users is to utilize the various migration resources to upgrade from Strapi 4 to Strapi 5:
|
||||||
|
|
||||||
|
- [Updating documentation](https://docs.strapi.io/dev-docs/migration/v4-to-v5/introduction-and-faq)
|
||||||
|
- [Step by Step Guide](https://docs.strapi.io/dev-docs/migration/v4-to-v5/step-by-step)
|
||||||
|
- [Upgrade Tool](https://docs.strapi.io/dev-docs/upgrade-tool)
|
||||||
|
- [Breaking Changes list](https://docs.strapi.io/dev-docs/migration/v4-to-v5/breaking-changes)
|
||||||
|
- [Specific Resources](https://docs.strapi.io/dev-docs/migration/v4-to-v5/additional-resources/introduction)
|
||||||
|
|
||||||
|
Please see our [Security file](https://github.com/strapi/strapi/security) for more information about supported versions.
|
||||||
|
For now this issue is marked as closed.
|
||||||
|
|
||||||
|
Thank You
|
||||||
|
- name: 'Close: unsupported v4 issues'
|
||||||
|
if: "${{ github.event.label.name == 'flag: v4-unsupported' }}"
|
||||||
|
uses: actions-cool/issues-helper@v3
|
||||||
|
with:
|
||||||
|
actions: 'close-issue'
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
close-reason: 'not_planned'
|
||||||
|
|
||||||
# Feature request redirections
|
# Feature request redirections
|
||||||
- name: 'Comment: redirect feature request to canny'
|
- name: 'Comment: redirect feature request to canny'
|
||||||
if: "${{ github.event.label.name == 'issue: feature request' }}"
|
if: "${{ github.event.label.name == 'issue: feature request' }}"
|
||||||
|
|||||||
24
SECURITY.md
24
SECURITY.md
@ -2,18 +2,20 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
As of April 2024 (and until this document is updated), only the v4.x.x _GA_ or _STABLE_ releases of Strapi are supported for updates and bug fixes. Any previous versions are currently not supported and users are advised to use them "at their own risk".
|
As of September 2024 (and until this document is updated), only the v4.x.x and v5.x.x _GA_ or _STABLE_ releases of Strapi are supported for updates and bug fixes. Any previous versions are currently not supported and users are advised to use them "at their own risk".
|
||||||
|
|
||||||
| Version | Release Tag | Support Starts | Support Ends | Security Updates Until | Notes |
|
**Note**: The v4.x.x LTS version will only receive high/critical severity fixes until April 2026. Any Medium/Low severity issues will not be fixed unless specific exceptions are made.
|
||||||
| ------- | ----------- | -------------- | -------------- | ---------------------- | -------------------- |
|
|
||||||
| 5.x.x | GA | October 2024 | Further Notice | Further Notice | LTS (Future) |
|
| Version | Release Tag | Support Starts | Support Ends | Security Updates Until | Notes |
|
||||||
| 5.x.x | RC | N/A | October 2024 | N/A | Non-Production Usage |
|
| ------- | ----------- | -------------- | -------------- | ---------------------- | ------------------------------ |
|
||||||
| 5.x.x | Beta | N/A | N/A | N/A | Not Supported |
|
| 5.x.x | GA / Stable | September 2024 | Further Notice | Further Notice | LTS |
|
||||||
| 5.x.x | Alpha | N/A | N/A | N/A | Not Supported |
|
| 5.x.x | RC | N/A | September 2024 | N/A | Not Supported |
|
||||||
| 4.x.x | GA | November 2021 | October 2025 | April 2026 | LTS |
|
| 5.x.x | Beta | N/A | N/A | N/A | Not Supported |
|
||||||
| 4.x.x | Beta | N/A | N/A | N/A | Not Supported |
|
| 5.x.x | Alpha | N/A | N/A | N/A | Not Supported |
|
||||||
| 4.x.x | Alpha | N/A | N/A | N/A | Not Supported |
|
| 4.x.x | GA / Stable | November 2021 | October 2025 | April 2026 | LTS (High/Critical fixes only) |
|
||||||
| 3.x.x | N/A | N/A | N/A | N/A | End Of Life |
|
| 4.x.x | Beta | N/A | N/A | N/A | Not Supported |
|
||||||
|
| 4.x.x | Alpha | N/A | N/A | N/A | Not Supported |
|
||||||
|
| 3.x.x | N/A | N/A | N/A | N/A | End Of Life |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
module.exports = ({ env }) => ({
|
module.exports = ({ env }) => ({
|
||||||
future: {
|
future: {
|
||||||
contentReleasesScheduling: env.bool('STRAPI_FUTURE_CONTENT_RELEASES_SCHEDULING', false),
|
preview: env.bool('STRAPI_FUTURE_PREVIEW', false),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -148,7 +148,7 @@
|
|||||||
"stream-chain": "2.2.5",
|
"stream-chain": "2.2.5",
|
||||||
"stream-json": "1.8.0",
|
"stream-json": "1.8.0",
|
||||||
"supertest": "6.3.3",
|
"supertest": "6.3.3",
|
||||||
"tar": "6.1.13",
|
"tar": "6.2.1",
|
||||||
"ts-jest": "29.1.0",
|
"ts-jest": "29.1.0",
|
||||||
"typescript": "5.3.2",
|
"typescript": "5.3.2",
|
||||||
"yalc": "1.0.0-pre.53",
|
"yalc": "1.0.0-pre.53",
|
||||||
|
|||||||
@ -61,7 +61,7 @@
|
|||||||
"open": "8.4.0",
|
"open": "8.4.0",
|
||||||
"ora": "5.4.1",
|
"ora": "5.4.1",
|
||||||
"pkg-up": "3.1.0",
|
"pkg-up": "3.1.0",
|
||||||
"tar": "6.1.13",
|
"tar": "6.2.1",
|
||||||
"xdg-app-paths": "8.3.0",
|
"xdg-app-paths": "8.3.0",
|
||||||
"yup": "0.32.9"
|
"yup": "0.32.9"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { isStderrError } from './types';
|
|||||||
import type { Scope } from './types';
|
import type { Scope } from './types';
|
||||||
import { logger } from './utils/logger';
|
import { logger } from './utils/logger';
|
||||||
import { gitIgnore } from './utils/gitignore';
|
import { gitIgnore } from './utils/gitignore';
|
||||||
|
import { getInstallArgs } from './utils/get-package-manager-args';
|
||||||
|
|
||||||
async function createStrapi(scope: Scope) {
|
async function createStrapi(scope: Scope) {
|
||||||
const { rootPath } = scope;
|
const { rootPath } = scope;
|
||||||
@ -239,29 +240,27 @@ async function createApp(scope: Scope) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const installArguments = ['install'];
|
async function runInstall({ rootPath, packageManager }: Scope) {
|
||||||
|
// include same cwd and env to ensure version check returns same version we use below
|
||||||
|
const { envArgs, cmdArgs } = await getInstallArgs(packageManager, {
|
||||||
|
cwd: rootPath,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const installArgumentsMap = {
|
|
||||||
npm: ['--legacy-peer-deps'],
|
|
||||||
yarn: ['--network-timeout 1000000'],
|
|
||||||
pnpm: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
function runInstall({ rootPath, packageManager }: Scope) {
|
|
||||||
const options: execa.Options = {
|
const options: execa.Options = {
|
||||||
cwd: rootPath,
|
cwd: rootPath,
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
|
...envArgs,
|
||||||
NODE_ENV: 'development',
|
NODE_ENV: 'development',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (packageManager in installArgumentsMap) {
|
const proc = execa(packageManager, cmdArgs, options);
|
||||||
installArguments.push(...(installArgumentsMap[packageManager] ?? []));
|
|
||||||
}
|
|
||||||
|
|
||||||
const proc = execa(packageManager, installArguments, options);
|
|
||||||
|
|
||||||
return proc;
|
return proc;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,144 @@
|
|||||||
|
import execa from 'execa';
|
||||||
|
import semver from 'semver';
|
||||||
|
|
||||||
|
const installArguments = ['install'];
|
||||||
|
|
||||||
|
type VersionedArgumentsMap = {
|
||||||
|
[key: string]: string[]; // Maps semver ranges to argument arrays
|
||||||
|
};
|
||||||
|
|
||||||
|
type VersionedEnvMap = {
|
||||||
|
[key: string]: Record<string, string>; // Maps semver ranges to environment variables
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set command line options for specific package managers, with full semver ranges
|
||||||
|
const installArgumentsMap: {
|
||||||
|
[key: string]: VersionedArgumentsMap;
|
||||||
|
} = {
|
||||||
|
npm: {
|
||||||
|
'*': ['--legacy-peer-deps'],
|
||||||
|
},
|
||||||
|
yarn: {
|
||||||
|
'<4': ['--network-timeout', '1000000'],
|
||||||
|
'*': [],
|
||||||
|
},
|
||||||
|
pnpm: {
|
||||||
|
'*': [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set environment variables for specific package managers, with full semver ranges
|
||||||
|
const installEnvMap: {
|
||||||
|
[key: string]: VersionedEnvMap;
|
||||||
|
} = {
|
||||||
|
yarn: {
|
||||||
|
'>=4': { YARN_HTTP_TIMEOUT: '1000000' },
|
||||||
|
'*': {},
|
||||||
|
},
|
||||||
|
npm: {
|
||||||
|
'*': {},
|
||||||
|
},
|
||||||
|
pnpm: {
|
||||||
|
'*': {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the version of the specified package manager.
|
||||||
|
*
|
||||||
|
* Executes the package manager's `--version` command to determine its version.
|
||||||
|
*
|
||||||
|
* @param packageManager - The name of the package manager (e.g., 'npm', 'yarn', 'pnpm').
|
||||||
|
* @param options - Optional execution options to pass to `execa`.
|
||||||
|
* @returns A promise that resolves to the trimmed version string of the package manager.
|
||||||
|
*
|
||||||
|
* @throws Will throw an error if the package manager's version cannot be determined.
|
||||||
|
*/
|
||||||
|
export const getPackageManagerVersion = async (
|
||||||
|
packageManager: string,
|
||||||
|
options?: execa.Options
|
||||||
|
): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const { stdout } = await execa(packageManager, ['--version'], options);
|
||||||
|
return stdout.trim();
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Error detecting ${packageManager} version: ${err}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges all matching semver ranges using a custom merge function.
|
||||||
|
*
|
||||||
|
* Iterates over the `versionMap`, checking if the provided `version` satisfies each semver range.
|
||||||
|
* If it does, the corresponding value is merged using the provided `mergeFn`.
|
||||||
|
* The merging starts with the value associated with the wildcard '*' key.
|
||||||
|
*
|
||||||
|
* @param version - The package manager version to check against the ranges.
|
||||||
|
* @param versionMap - A map of semver ranges to corresponding values (arguments or environment variables).
|
||||||
|
* @param mergeFn - A function that defines how to merge two values (accumulated and current).
|
||||||
|
* @returns The merged result of all matching ranges.
|
||||||
|
*/
|
||||||
|
function mergeMatchingVersionRanges<T>(
|
||||||
|
version: string,
|
||||||
|
versionMap: { [key: string]: T },
|
||||||
|
mergeFn: (acc: T, curr: T) => T
|
||||||
|
): T {
|
||||||
|
return Object.keys(versionMap).reduce((acc, range) => {
|
||||||
|
if (semver.satisfies(version, range) || range === '*') {
|
||||||
|
return mergeFn(acc, versionMap[range]);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, versionMap['*']); // Start with the wildcard entry
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeArguments(acc: string[], curr: string[]): string[] {
|
||||||
|
return [...acc, ...curr];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeEnvVars(
|
||||||
|
acc: Record<string, string>,
|
||||||
|
curr: Record<string, string>
|
||||||
|
): Record<string, string> {
|
||||||
|
return { ...acc, ...curr };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the install arguments and environment variables for a given package manager.
|
||||||
|
*
|
||||||
|
* This function determines the correct command line arguments and environment variables
|
||||||
|
* based on the package manager's version. It uses predefined semver ranges to match
|
||||||
|
* the package manager's version and merges all applicable settings.
|
||||||
|
*
|
||||||
|
* The arguments and environment variables are sourced from:
|
||||||
|
* - `installArgumentsMap` for command line arguments.
|
||||||
|
* - `installEnvMap` for environment variables.
|
||||||
|
*
|
||||||
|
* The function ensures that all matching semver ranges are considered and merged appropriately.
|
||||||
|
* It always includes the base `installArguments` (e.g., `['install']`) and applies any additional
|
||||||
|
* arguments or environment variables as defined by the matched version ranges.
|
||||||
|
*
|
||||||
|
* @param packageManager - The name of the package manager (e.g., 'npm', 'yarn', 'pnpm').
|
||||||
|
* @param options - Optional execution options to pass to `execa`.
|
||||||
|
* @returns An object containing:
|
||||||
|
* - `cmdArgs`: The full array of install arguments for the given package manager and version.
|
||||||
|
* - `envArgs`: The merged environment variables applicable to the package manager and version.
|
||||||
|
*
|
||||||
|
* @throws Will throw an error if the package manager version cannot be determined.
|
||||||
|
*/
|
||||||
|
export const getInstallArgs = async (packageManager: string, options?: execa.Options) => {
|
||||||
|
const packageManagerVersion = await getPackageManagerVersion(packageManager, options);
|
||||||
|
|
||||||
|
// Get environment variables
|
||||||
|
const envMap = installEnvMap[packageManager];
|
||||||
|
const envArgs = packageManagerVersion
|
||||||
|
? mergeMatchingVersionRanges(packageManagerVersion, envMap, mergeEnvVars)
|
||||||
|
: envMap['*'];
|
||||||
|
|
||||||
|
// Get install arguments
|
||||||
|
const argsMap = installArgumentsMap[packageManager];
|
||||||
|
const cmdArgs = packageManagerVersion
|
||||||
|
? mergeMatchingVersionRanges(packageManagerVersion, argsMap, mergeArguments)
|
||||||
|
: argsMap['*'];
|
||||||
|
|
||||||
|
return { envArgs, cmdArgs: [...installArguments, ...cmdArgs], version: packageManagerVersion };
|
||||||
|
};
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
TextInput,
|
TextInput,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@strapi/design-system';
|
} from '@strapi/design-system';
|
||||||
|
import { Check } from '@strapi/icons';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { Formik, Form, FormikHelpers } from 'formik';
|
import { Formik, Form, FormikHelpers } from 'formik';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
@ -197,14 +198,13 @@ const CreatePage = () => {
|
|||||||
handleReset();
|
handleReset();
|
||||||
permissionsRef.current?.resetForm();
|
permissionsRef.current?.resetForm();
|
||||||
}}
|
}}
|
||||||
size="L"
|
|
||||||
>
|
>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'app.components.Button.reset',
|
id: 'app.components.Button.reset',
|
||||||
defaultMessage: 'Reset',
|
defaultMessage: 'Reset',
|
||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" loading={isSubmitting} size="L">
|
<Button type="submit" loading={isSubmitting} startIcon={<Check />}>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'global.save',
|
id: 'global.save',
|
||||||
defaultMessage: 'Save',
|
defaultMessage: 'Save',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { Box, Button, Flex, Main } from '@strapi/design-system';
|
import { Box, Button, Flex, Main } from '@strapi/design-system';
|
||||||
|
import { Check } from '@strapi/icons';
|
||||||
import { Formik, FormikHelpers } from 'formik';
|
import { Formik, FormikHelpers } from 'formik';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { Navigate, useMatch } from 'react-router-dom';
|
import { Navigate, useMatch } from 'react-router-dom';
|
||||||
@ -191,9 +192,9 @@ const EditPage = () => {
|
|||||||
<Flex gap={2}>
|
<Flex gap={2}>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
startIcon={<Check />}
|
||||||
disabled={role.code === 'strapi-super-admin'}
|
disabled={role.code === 'strapi-super-admin'}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
size="L"
|
|
||||||
>
|
>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'global.save',
|
id: 'global.save',
|
||||||
|
|||||||
@ -191,7 +191,6 @@ const EditPage = () => {
|
|||||||
startIcon={<Check />}
|
startIcon={<Check />}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
type="submit"
|
type="submit"
|
||||||
size="L"
|
|
||||||
>
|
>
|
||||||
{formatMessage({ id: 'global.save', defaultMessage: 'Save' })}
|
{formatMessage({ id: 'global.save', defaultMessage: 'Save' })}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -98,7 +98,6 @@ const WebhookForm = ({
|
|||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
startIcon={<Publish />}
|
startIcon={<Publish />}
|
||||||
disabled={isCreating || isTriggering}
|
disabled={isCreating || isTriggering}
|
||||||
size="L"
|
|
||||||
>
|
>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'Settings.webhooks.trigger',
|
id: 'Settings.webhooks.trigger',
|
||||||
@ -108,7 +107,6 @@ const WebhookForm = ({
|
|||||||
<Button
|
<Button
|
||||||
startIcon={<Check />}
|
startIcon={<Check />}
|
||||||
type="submit"
|
type="submit"
|
||||||
size="L"
|
|
||||||
disabled={!modified}
|
disabled={!modified}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -144,7 +144,6 @@ export const SingleSignOnPage = () => {
|
|||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
startIcon={<Check />}
|
startIcon={<Check />}
|
||||||
type="submit"
|
type="submit"
|
||||||
size="L"
|
|
||||||
>
|
>
|
||||||
{formatMessage({
|
{formatMessage({
|
||||||
id: 'global.save',
|
id: 'global.save',
|
||||||
|
|||||||
@ -49,7 +49,10 @@ const sendUpdateProjectInformation = async (strapi: Core.Strapi) => {
|
|||||||
|
|
||||||
const startCron = (strapi: Core.Strapi) => {
|
const startCron = (strapi: Core.Strapi) => {
|
||||||
strapi.cron.add({
|
strapi.cron.add({
|
||||||
'0 0 0 * * *': () => sendUpdateProjectInformation(strapi),
|
sendProjectInformation: {
|
||||||
|
task: () => sendUpdateProjectInformation(strapi),
|
||||||
|
options: '0 0 0 * * *',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,10 @@ const sendUpdateProjectInformation = async (strapi: Core.Strapi) => {
|
|||||||
|
|
||||||
const startCron = (strapi: Core.Strapi) => {
|
const startCron = (strapi: Core.Strapi) => {
|
||||||
strapi.cron.add({
|
strapi.cron.add({
|
||||||
'0 0 0 * * *': () => sendUpdateProjectInformation(strapi),
|
sendProjectInformation: {
|
||||||
|
task: () => sendUpdateProjectInformation(strapi),
|
||||||
|
options: '0 0 0 * * *',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -118,7 +118,7 @@ export const VersionHeader = ({ headerId }: VersionHeaderProps) => {
|
|||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
})}
|
})}
|
||||||
subtitle={
|
subtitle={
|
||||||
<Typography variant="epsilon">
|
<Typography variant="epsilon" textColor="neutral600">
|
||||||
{formatMessage(
|
{formatMessage(
|
||||||
{
|
{
|
||||||
id: 'content-manager.history.version.subtitle',
|
id: 'content-manager.history.version.subtitle',
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { PLUGIN_ID } from './constants/plugin';
|
|||||||
import { ContentManagerPlugin } from './content-manager';
|
import { ContentManagerPlugin } from './content-manager';
|
||||||
import { historyAdmin } from './history';
|
import { historyAdmin } from './history';
|
||||||
import { reducer } from './modules/reducers';
|
import { reducer } from './modules/reducers';
|
||||||
|
import { previewAdmin } from './preview';
|
||||||
import { routes } from './router';
|
import { routes } from './router';
|
||||||
import { prefixPluginTranslations } from './utils/translations';
|
import { prefixPluginTranslations } from './utils/translations';
|
||||||
|
|
||||||
@ -45,6 +46,9 @@ export default {
|
|||||||
if (typeof historyAdmin.bootstrap === 'function') {
|
if (typeof historyAdmin.bootstrap === 'function') {
|
||||||
historyAdmin.bootstrap(app);
|
historyAdmin.bootstrap(app);
|
||||||
}
|
}
|
||||||
|
if (typeof previewAdmin.bootstrap === 'function') {
|
||||||
|
previewAdmin.bootstrap(app);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async registerTrads({ locales }: { locales: string[] }) {
|
async registerTrads({ locales }: { locales: string[] }) {
|
||||||
const importedTrads = await Promise.all(
|
const importedTrads = await Promise.all(
|
||||||
|
|||||||
@ -151,7 +151,7 @@ const Panel = React.forwardRef<any, PanelProps>(({ children, title }, ref) => {
|
|||||||
justifyContent="stretch"
|
justifyContent="stretch"
|
||||||
alignItems="flex-start"
|
alignItems="flex-start"
|
||||||
>
|
>
|
||||||
<Typography tag="h2" variant="sigma" textTransform="uppercase">
|
<Typography tag="h2" variant="sigma" textTransform="uppercase" textColor="neutral600">
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
17
packages/core/content-manager/admin/src/preview/LICENSE
Normal file
17
packages/core/content-manager/admin/src/preview/LICENSE
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Copyright (c) 2015-present Strapi Solutions SAS
|
||||||
|
|
||||||
|
* All software that resides within this directory and its subdirectories, is licensed under the license defined below.
|
||||||
|
|
||||||
|
Enterprise License
|
||||||
|
|
||||||
|
If you or the company you represent has entered into a written agreement referencing the Enterprise Edition of the Strapi source code available at
|
||||||
|
https://github.com/strapi/strapi, then such agreement applies to your use of the Enterprise Edition of the Strapi Software. If you or the company you
|
||||||
|
represent is using the Enterprise Edition of the Strapi Software in connection with a subscription to our cloud offering, then the agreement you have
|
||||||
|
agreed to with respect to our cloud offering and the licenses included in such agreement apply to your use of the Enterprise Edition of the Strapi Software.
|
||||||
|
Otherwise, the Strapi Enterprise Software License Agreement (found here https://strapi.io/enterprise-terms) applies to your use of the Enterprise Edition of the Strapi Software.
|
||||||
|
|
||||||
|
BY ACCESSING OR USING THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE RELEVANT REFERENCED AGREEMENT.
|
||||||
|
IF YOU ARE NOT AUTHORIZED TO ACCEPT THESE TERMS ON BEHALF OF THE COMPANY YOU REPRESENT OR IF YOU DO NOT AGREE TO ALL OF THE RELEVANT TERMS AND CONDITIONS REFERENCED AND YOU
|
||||||
|
HAVE NOT OTHERWISE EXECUTED A WRITTEN AGREEMENT WITH STRAPI, YOU ARE NOT AUTHORIZED TO ACCESS OR USE OR ALLOW ANY USER TO ACCESS OR USE ANY PART OF
|
||||||
|
THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE. YOUR ACCESS RIGHTS ARE CONDITIONAL ON YOUR CONSENT TO THE RELEVANT REFERENCED TERMS TO THE EXCLUSION OF ALL OTHER TERMS;
|
||||||
|
IF THE RELEVANT REFERENCED TERMS ARE CONSIDERED AN OFFER BY YOU, ACCEPTANCE IS EXPRESSLY LIMITED TO THE RELEVANT REFERENCED TERMS.
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export const FEATURE_ID = 'preview';
|
||||||
18
packages/core/content-manager/admin/src/preview/index.ts
Normal file
18
packages/core/content-manager/admin/src/preview/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-disable check-file/no-index */
|
||||||
|
|
||||||
|
import { FEATURE_ID } from './constants';
|
||||||
|
|
||||||
|
import type { PluginDefinition } from '@strapi/admin/strapi-admin';
|
||||||
|
|
||||||
|
const previewAdmin = {
|
||||||
|
bootstrap(app) {
|
||||||
|
// TODO: Add license registry check when it's available
|
||||||
|
if (!window.strapi.future.isEnabled(FEATURE_ID)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-console -- TODO remove when we have real functionality
|
||||||
|
console.log('Bootstrapping preview admin');
|
||||||
|
},
|
||||||
|
} satisfies Partial<PluginDefinition>;
|
||||||
|
|
||||||
|
export { previewAdmin };
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { getService } from './utils';
|
import { getService } from './utils';
|
||||||
import { ALLOWED_WEBHOOK_EVENTS } from './constants';
|
import { ALLOWED_WEBHOOK_EVENTS } from './constants';
|
||||||
import history from './history';
|
import history from './history';
|
||||||
|
import preview from './preview';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
||||||
@ -13,4 +14,5 @@ export default async () => {
|
|||||||
await getService('permission').registerPermissions();
|
await getService('permission').registerPermissions();
|
||||||
|
|
||||||
await history.bootstrap?.({ strapi });
|
await history.bootstrap?.({ strapi });
|
||||||
|
await preview.bootstrap?.({ strapi });
|
||||||
};
|
};
|
||||||
|
|||||||
17
packages/core/content-manager/server/src/preview/LICENSE
Normal file
17
packages/core/content-manager/server/src/preview/LICENSE
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Copyright (c) 2015-present Strapi Solutions SAS
|
||||||
|
|
||||||
|
* All software that resides within this directory and its subdirectories, is licensed under the license defined below.
|
||||||
|
|
||||||
|
Enterprise License
|
||||||
|
|
||||||
|
If you or the company you represent has entered into a written agreement referencing the Enterprise Edition of the Strapi source code available at
|
||||||
|
https://github.com/strapi/strapi, then such agreement applies to your use of the Enterprise Edition of the Strapi Software. If you or the company you
|
||||||
|
represent is using the Enterprise Edition of the Strapi Software in connection with a subscription to our cloud offering, then the agreement you have
|
||||||
|
agreed to with respect to our cloud offering and the licenses included in such agreement apply to your use of the Enterprise Edition of the Strapi Software.
|
||||||
|
Otherwise, the Strapi Enterprise Software License Agreement (found here https://strapi.io/enterprise-terms) applies to your use of the Enterprise Edition of the Strapi Software.
|
||||||
|
|
||||||
|
BY ACCESSING OR USING THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE RELEVANT REFERENCED AGREEMENT.
|
||||||
|
IF YOU ARE NOT AUTHORIZED TO ACCEPT THESE TERMS ON BEHALF OF THE COMPANY YOU REPRESENT OR IF YOU DO NOT AGREE TO ALL OF THE RELEVANT TERMS AND CONDITIONS REFERENCED AND YOU
|
||||||
|
HAVE NOT OTHERWISE EXECUTED A WRITTEN AGREEMENT WITH STRAPI, YOU ARE NOT AUTHORIZED TO ACCESS OR USE OR ALLOW ANY USER TO ACCESS OR USE ANY PART OF
|
||||||
|
THE ENTERPRISE EDITION OF THE STRAPI SOFTWARE. YOUR ACCESS RIGHTS ARE CONDITIONAL ON YOUR CONSENT TO THE RELEVANT REFERENCED TERMS TO THE EXCLUSION OF ALL OTHER TERMS;
|
||||||
|
IF THE RELEVANT REFERENCED TERMS ARE CONSIDERED AN OFFER BY YOU, ACCEPTANCE IS EXPRESSLY LIMITED TO THE RELEVANT REFERENCED TERMS.
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export const FEATURE_ID = 'preview';
|
||||||
22
packages/core/content-manager/server/src/preview/index.ts
Normal file
22
packages/core/content-manager/server/src/preview/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import type { Plugin } from '@strapi/types';
|
||||||
|
import { FEATURE_ID } from './constants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check once if the feature is enabled before loading it,
|
||||||
|
* so that we can assume it is enabled in the other files.
|
||||||
|
*/
|
||||||
|
const getFeature = (): Partial<Plugin.LoadedPlugin> => {
|
||||||
|
// TODO: Add license registry check when it's available
|
||||||
|
if (!strapi.features.future.isEnabled(FEATURE_ID)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bootstrap() {
|
||||||
|
// eslint-disable-next-line no-console -- TODO remove when we have real functionality
|
||||||
|
console.log('Bootstrapping preview server');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getFeature();
|
||||||
@ -16,6 +16,7 @@ interface SelectCategoryProps {
|
|||||||
onChange: (value: { target: { name: string; value: any; type: string } }) => void;
|
onChange: (value: { target: { name: string; value: any; type: string } }) => void;
|
||||||
value?: string;
|
value?: string;
|
||||||
isCreating?: boolean;
|
isCreating?: boolean;
|
||||||
|
dynamicZoneTarget?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectCategory = ({
|
export const SelectCategory = ({
|
||||||
@ -25,6 +26,7 @@ export const SelectCategory = ({
|
|||||||
onChange,
|
onChange,
|
||||||
value = undefined,
|
value = undefined,
|
||||||
isCreating,
|
isCreating,
|
||||||
|
dynamicZoneTarget,
|
||||||
}: SelectCategoryProps) => {
|
}: SelectCategoryProps) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { allComponentsCategories } = useDataManager();
|
const { allComponentsCategories } = useDataManager();
|
||||||
@ -48,7 +50,7 @@ export const SelectCategory = ({
|
|||||||
<Combobox
|
<Combobox
|
||||||
// TODO: re-enable category edits, renaming categories of already existing components currently breaks other functionality
|
// TODO: re-enable category edits, renaming categories of already existing components currently breaks other functionality
|
||||||
// See https://github.com/strapi/strapi/issues/20356
|
// See https://github.com/strapi/strapi/issues/20356
|
||||||
disabled={!isCreating}
|
disabled={!isCreating && !dynamicZoneTarget}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onCreateOption={handleCreateOption}
|
onCreateOption={handleCreateOption}
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@ -205,7 +205,13 @@ const checkLicense = async ({ strapi }: { strapi: Core.Strapi }) => {
|
|||||||
|
|
||||||
if (!shouldStayOffline) {
|
if (!shouldStayOffline) {
|
||||||
await onlineUpdate({ strapi });
|
await onlineUpdate({ strapi });
|
||||||
strapi.cron.add({ [shiftCronExpression('0 0 */12 * * *')]: onlineUpdate });
|
|
||||||
|
strapi.cron.add({
|
||||||
|
onlineUpdate: {
|
||||||
|
task: () => onlineUpdate({ strapi }),
|
||||||
|
options: shiftCronExpression('0 0 */12 * * *'),
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!ee.licenseInfo.expireAt) {
|
if (!ee.licenseInfo.expireAt) {
|
||||||
return disable('Your license does not have offline support.');
|
return disable('Your license does not have offline support.');
|
||||||
|
|||||||
@ -86,8 +86,6 @@ const sync = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
await strapi.db.transaction(async ({ trx }) => {
|
await strapi.db.transaction(async ({ trx }) => {
|
||||||
const con = strapi.db.getConnection();
|
|
||||||
|
|
||||||
// Iterate old relations that are deleted and insert the new ones
|
// Iterate old relations that are deleted and insert the new ones
|
||||||
for (const { joinTable, relations } of oldRelations) {
|
for (const { joinTable, relations } of oldRelations) {
|
||||||
// Update old ids with the new ones
|
// Update old ids with the new ones
|
||||||
@ -98,7 +96,7 @@ const sync = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Insert those relations into the join table
|
// Insert those relations into the join table
|
||||||
await con.batchInsert(joinTable.name, newRelations).transacting(trx);
|
await trx.batchInsert(joinTable.name, newRelations, 1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -54,7 +54,7 @@
|
|||||||
"semver": "7.5.4",
|
"semver": "7.5.4",
|
||||||
"stream-chain": "2.2.5",
|
"stream-chain": "2.2.5",
|
||||||
"stream-json": "1.8.0",
|
"stream-json": "1.8.0",
|
||||||
"tar": "6.1.13",
|
"tar": "6.2.1",
|
||||||
"tar-stream": "2.2.0",
|
"tar-stream": "2.2.0",
|
||||||
"ws": "8.17.1"
|
"ws": "8.17.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -259,11 +259,6 @@ const createHelpers = (db: Database) => {
|
|||||||
dropForeignKey(tableBuilder, updatedForeignKey.object);
|
dropForeignKey(tableBuilder, updatedForeignKey.object);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const removedColumn of table.columns.removed) {
|
|
||||||
debug(`Dropping column ${removedColumn.name} on ${table.name}`);
|
|
||||||
dropColumn(tableBuilder, removedColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for mysql only, dropForeignKey also removes the index, so don't drop it twice
|
// for mysql only, dropForeignKey also removes the index, so don't drop it twice
|
||||||
const isMySQL = db.config.connection.client === 'mysql';
|
const isMySQL = db.config.connection.client === 'mysql';
|
||||||
const ignoreForeignKeyNames = isMySQL
|
const ignoreForeignKeyNames = isMySQL
|
||||||
@ -287,7 +282,13 @@ const createHelpers = (db: Database) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update existing columns / foreign keys / indexes
|
// We drop columns after indexes to ensure that it doesn't cascade delete any indexes we expect to exist
|
||||||
|
for (const removedColumn of table.columns.removed) {
|
||||||
|
debug(`Dropping column ${removedColumn.name} on ${table.name}`);
|
||||||
|
dropColumn(tableBuilder, removedColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update existing columns
|
||||||
for (const updatedColumn of table.columns.updated) {
|
for (const updatedColumn of table.columns.updated) {
|
||||||
debug(`Updating column ${updatedColumn.name} on ${table.name}`);
|
debug(`Updating column ${updatedColumn.name} on ${table.name}`);
|
||||||
|
|
||||||
@ -300,16 +301,7 @@ const createHelpers = (db: Database) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const updatedForeignKey of table.foreignKeys.updated) {
|
// Add any new columns
|
||||||
debug(`Recreating updated foreign key ${updatedForeignKey.name} on ${table.name}`);
|
|
||||||
createForeignKey(tableBuilder, updatedForeignKey.object);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const updatedIndex of table.indexes.updated) {
|
|
||||||
debug(`Recreating updated index ${updatedIndex.name} on ${table.name}`);
|
|
||||||
createIndex(tableBuilder, updatedIndex.object);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const addedColumn of table.columns.added) {
|
for (const addedColumn of table.columns.added) {
|
||||||
debug(`Creating column ${addedColumn.name} on ${table.name}`);
|
debug(`Creating column ${addedColumn.name} on ${table.name}`);
|
||||||
|
|
||||||
@ -321,6 +313,17 @@ const createHelpers = (db: Database) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// once the columns have all been updated, we can create indexes again
|
||||||
|
for (const updatedForeignKey of table.foreignKeys.updated) {
|
||||||
|
debug(`Recreating updated foreign key ${updatedForeignKey.name} on ${table.name}`);
|
||||||
|
createForeignKey(tableBuilder, updatedForeignKey.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const updatedIndex of table.indexes.updated) {
|
||||||
|
debug(`Recreating updated index ${updatedIndex.name} on ${table.name}`);
|
||||||
|
createIndex(tableBuilder, updatedIndex.object);
|
||||||
|
}
|
||||||
|
|
||||||
for (const addedForeignKey of table.foreignKeys.added) {
|
for (const addedForeignKey of table.foreignKeys.added) {
|
||||||
debug(`Creating foreign keys ${addedForeignKey.name} on ${table.name}`);
|
debug(`Creating foreign keys ${addedForeignKey.name} on ${table.name}`);
|
||||||
createForeignKey(tableBuilder, addedForeignKey);
|
createForeignKey(tableBuilder, addedForeignKey);
|
||||||
|
|||||||
@ -335,7 +335,6 @@ const EditPage = () => {
|
|||||||
<Button
|
<Button
|
||||||
startIcon={<Check />}
|
startIcon={<Check />}
|
||||||
type="submit"
|
type="submit"
|
||||||
size="M"
|
|
||||||
disabled={!modified || isSubmitting || values.stages.length === 0}
|
disabled={!modified || isSubmitting || values.stages.length === 0}
|
||||||
// if the confirm dialog is open the loading state is on
|
// if the confirm dialog is open the loading state is on
|
||||||
// the confirm button already
|
// the confirm button already
|
||||||
|
|||||||
@ -70,7 +70,12 @@ export default ({ strapi }: { strapi: Core.Strapi }) => {
|
|||||||
async registerCron() {
|
async registerCron() {
|
||||||
const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
|
const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
|
||||||
|
|
||||||
strapi.cron.add({ [weeklySchedule]: this.sendMetrics.bind(this) });
|
strapi.cron.add({
|
||||||
|
reviewWorkflowsWeekly: {
|
||||||
|
task: this.sendMetrics.bind(this),
|
||||||
|
options: weeklySchedule,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -108,7 +108,7 @@
|
|||||||
"watch": "pack-up watch"
|
"watch": "pack-up watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
|
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
|
||||||
"@strapi/admin": "workspace:*",
|
"@strapi/admin": "workspace:*",
|
||||||
"@strapi/cloud-cli": "workspace:*",
|
"@strapi/cloud-cli": "workspace:*",
|
||||||
"@strapi/content-manager": "workspace:*",
|
"@strapi/content-manager": "workspace:*",
|
||||||
@ -169,7 +169,7 @@
|
|||||||
"vite": "5.1.7",
|
"vite": "5.1.7",
|
||||||
"webpack": "^5.90.3",
|
"webpack": "^5.90.3",
|
||||||
"webpack-bundle-analyzer": "^4.10.1",
|
"webpack-bundle-analyzer": "^4.10.1",
|
||||||
"webpack-dev-middleware": "6.1.1",
|
"webpack-dev-middleware": "6.1.2",
|
||||||
"webpack-hot-middleware": "2.26.1",
|
"webpack-hot-middleware": "2.26.1",
|
||||||
"yalc": "1.0.0-pre.53",
|
"yalc": "1.0.0-pre.53",
|
||||||
"yup": "0.32.9"
|
"yup": "0.32.9"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export interface FeaturesConfig {
|
export interface FeaturesConfig {
|
||||||
future?: {
|
future?: {
|
||||||
contentReleases?: boolean;
|
preview?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
packages/core/upload/admin/.eslintrc
Normal file
12
packages/core/upload/admin/.eslintrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["**/*.js", "**/*.jsx"],
|
||||||
|
"extends": ["custom/front"],
|
||||||
|
"rules": {
|
||||||
|
"import/extensions": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,8 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import { useQueryParams } from '@strapi/admin/strapi-admin';
|
import { useQueryParams } from '@strapi/admin/strapi-admin';
|
||||||
import { Loader } from '@strapi/design-system';
|
import { CrumbSimpleMenu, Loader, MenuItem } from '@strapi/design-system';
|
||||||
import { CrumbSimpleMenu, MenuItem } from '@strapi/design-system';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { NavLink, useLocation } from 'react-router-dom';
|
import { NavLink, useLocation } from 'react-router-dom';
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const ComponentFixture = ({ to, ...props }) => {
|
|||||||
<FolderCard
|
<FolderCard
|
||||||
id={ID_FIXTURE}
|
id={ID_FIXTURE}
|
||||||
ariaLabel="Folder 1"
|
ariaLabel="Folder 1"
|
||||||
startAction={<></>}
|
startAction={null}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
to={to}
|
to={to}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -5,7 +5,12 @@ import set from 'lodash/set';
|
|||||||
import { ON_CHANGE, SET_LOADED } from './actionTypes';
|
import { ON_CHANGE, SET_LOADED } from './actionTypes';
|
||||||
import { init, initialState } from './init';
|
import { init, initialState } from './init';
|
||||||
|
|
||||||
const reducer = (state = initialState, action) =>
|
const reducer = (
|
||||||
|
state = initialState,
|
||||||
|
action = {
|
||||||
|
type: '',
|
||||||
|
}
|
||||||
|
) =>
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
produce(state, (draftState) => {
|
produce(state, (draftState) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import getTrad from './getTrad';
|
|||||||
export const urlSchema = yup.object().shape({
|
export const urlSchema = yup.object().shape({
|
||||||
urls: yup.string().test({
|
urls: yup.string().test({
|
||||||
name: 'isUrlValid',
|
name: 'isUrlValid',
|
||||||
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
message: '${path}',
|
message: '${path}',
|
||||||
test(values = '') {
|
test(values = '') {
|
||||||
const urls = values.split(/\r?\n/);
|
const urls = values.split(/\r?\n/);
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
import { rest } from 'msw';
|
import { rest } from 'msw';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
|
|
||||||
|
// Define the expected structure of your query parameters
|
||||||
|
interface CustomQuery extends qs.ParsedQs {
|
||||||
|
filters?: {
|
||||||
|
$and?: Array<{ parent: { id: string } }>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
rest.get('/upload/configuration', async (req, res, ctx) => {
|
rest.get('/upload/configuration', async (req, res, ctx) => {
|
||||||
return res(
|
return res(
|
||||||
@ -48,7 +55,7 @@ const handlers = [
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
rest.get('/upload/folders', async (req, res, ctx) => {
|
rest.get('/upload/folders', async (req, res, ctx) => {
|
||||||
const query = qs.parse(req.url.search.slice(1));
|
const query: CustomQuery = qs.parse(req.url.search.slice(1));
|
||||||
|
|
||||||
if (query._q) {
|
if (query._q) {
|
||||||
return res(
|
return res(
|
||||||
@ -183,13 +190,13 @@ const handlers = [
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
rest.get('*/an-image.png', (req, res, ctx) =>
|
rest.get('*/an-image.png', (req, res, ctx) =>
|
||||||
res(ctx.set('Content-Type', 'image/png'), ctx.body())
|
res(ctx.set('Content-Type', 'image/png'), ctx.body('Successful response'))
|
||||||
),
|
),
|
||||||
rest.get('*/a-pdf.pdf', (req, res, ctx) =>
|
rest.get('*/a-pdf.pdf', (req, res, ctx) =>
|
||||||
res(ctx.set('Content-Type', 'application/pdf'), ctx.body())
|
res(ctx.set('Content-Type', 'application/pdf'), ctx.body('Successful response'))
|
||||||
),
|
),
|
||||||
rest.get('*/a-video.mp4', (req, res, ctx) =>
|
rest.get('*/a-video.mp4', (req, res, ctx) =>
|
||||||
res(ctx.set('Content-Type', 'video/mp4'), ctx.body())
|
res(ctx.set('Content-Type', 'video/mp4'), ctx.body('Successful response'))
|
||||||
),
|
),
|
||||||
rest.get('*/not-working-like-cors.lutin', (req, res, ctx) => res(ctx.json({}))),
|
rest.get('*/not-working-like-cors.lutin', (req, res, ctx) => res(ctx.json({}))),
|
||||||
rest.get('*/some-where-not-existing.jpg', (req, res) => res.networkError('Failed to fetch')),
|
rest.get('*/some-where-not-existing.jpg', (req, res) => res.networkError('Failed to fetch')),
|
||||||
10
packages/core/upload/admin/tsconfig.build.json
Normal file
10
packages/core/upload/admin/tsconfig.build.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "tsconfig/client.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "../",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "./dist"
|
||||||
|
},
|
||||||
|
"include": ["./src", "../shared", "../package.json"],
|
||||||
|
"exclude": ["**/__mocks__", "./src/**/tests", "**/*.test.*"]
|
||||||
|
}
|
||||||
11
packages/core/upload/admin/tsconfig.json
Normal file
11
packages/core/upload/admin/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extends": "tsconfig/client.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "../",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@tests/*": ["./tests/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["../package.json", "./src", "../shared", "./tests"]
|
||||||
|
}
|
||||||
@ -6,5 +6,5 @@ module.exports = {
|
|||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^@tests/(.*)$': '<rootDir>/admin/tests/$1',
|
'^@tests/(.*)$': '<rootDir>/admin/tests/$1',
|
||||||
},
|
},
|
||||||
setupFilesAfterEnv: ['./admin/tests/setup.js'],
|
setupFilesAfterEnv: ['./admin/tests/setup.ts'],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@tests/*": ["./admin/tests/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -17,11 +17,19 @@
|
|||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
"./strapi-admin": {
|
"./strapi-admin": {
|
||||||
|
"types": "./dist/admin/src/index.d.ts",
|
||||||
"source": "./admin/src/index.js",
|
"source": "./admin/src/index.js",
|
||||||
"import": "./dist/admin/index.mjs",
|
"import": "./dist/admin/index.mjs",
|
||||||
"require": "./dist/admin/index.js",
|
"require": "./dist/admin/index.js",
|
||||||
"default": "./dist/admin/index.js"
|
"default": "./dist/admin/index.js"
|
||||||
},
|
},
|
||||||
|
"./_internal/shared": {
|
||||||
|
"types": "./dist/shared/index.d.ts",
|
||||||
|
"source": "./shared/index.ts",
|
||||||
|
"import": "./dist/shared/index.mjs",
|
||||||
|
"require": "./dist/shared/index.js",
|
||||||
|
"default": "./dist/shared/index.js"
|
||||||
|
},
|
||||||
"./strapi-server": {
|
"./strapi-server": {
|
||||||
"types": "./dist/server/src/index.d.ts",
|
"types": "./dist/server/src/index.d.ts",
|
||||||
"source": "./server/src/index.ts",
|
"source": "./server/src/index.ts",
|
||||||
@ -41,6 +49,9 @@
|
|||||||
"lint": "run -T eslint .",
|
"lint": "run -T eslint .",
|
||||||
"test:front": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js",
|
"test:front": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js",
|
||||||
"test:unit": "run -T jest",
|
"test:unit": "run -T jest",
|
||||||
|
"test:ts:back": "run -T tsc --noEmit -p server/tsconfig.json",
|
||||||
|
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
||||||
|
"test:front:watch": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js --watch",
|
||||||
"test:unit:watch": "run -T jest --watch",
|
"test:unit:watch": "run -T jest --watch",
|
||||||
"watch": "pack-up watch"
|
"watch": "pack-up watch"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,9 +3,11 @@ import { Config, defineConfig } from '@strapi/pack-up';
|
|||||||
const config: Config = defineConfig({
|
const config: Config = defineConfig({
|
||||||
bundles: [
|
bundles: [
|
||||||
{
|
{
|
||||||
source: './admin/src/index.js',
|
types: './dist/admin/src/index.d.ts',
|
||||||
|
source: './admin/src/index.js', // TODO: change it with the .ts file
|
||||||
import: './dist/admin/index.mjs',
|
import: './dist/admin/index.mjs',
|
||||||
require: './dist/admin/index.js',
|
require: './dist/admin/index.js',
|
||||||
|
tsconfig: './admin/tsconfig.build.json',
|
||||||
runtime: 'web',
|
runtime: 'web',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export async function bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
|||||||
config &&
|
config &&
|
||||||
Object.keys(defaultValue).every((key) => Object.prototype.hasOwnProperty.call(config, key))
|
Object.keys(defaultValue).every((key) => Object.prototype.hasOwnProperty.call(config, key))
|
||||||
) {
|
) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const validateStructureMoveManyFoldersFilesSchema = yup
|
|||||||
|
|
||||||
const validateDuplicatesMoveManyFoldersFilesSchema = yup
|
const validateDuplicatesMoveManyFoldersFilesSchema = yup
|
||||||
.object()
|
.object()
|
||||||
.test('are-folders-unique', 'some folders already exist', async function (value) {
|
.test('are-folders-unique', 'some folders already exist', async function areFoldersUnique(value) {
|
||||||
const { folderIds, destinationFolderId } = value;
|
const { folderIds, destinationFolderId } = value;
|
||||||
if (isEmpty(folderIds)) return true;
|
if (isEmpty(folderIds)) return true;
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ const validateMoveFoldersNotInsideThemselvesSchema = yup
|
|||||||
.test(
|
.test(
|
||||||
'dont-move-inside-self',
|
'dont-move-inside-self',
|
||||||
'folders cannot be moved inside themselves or one of its children',
|
'folders cannot be moved inside themselves or one of its children',
|
||||||
async function (value) {
|
async function validateMoveFoldersNotInsideThemselves(value) {
|
||||||
const { folderIds, destinationFolderId } = value;
|
const { folderIds, destinationFolderId } = value;
|
||||||
if (destinationFolderId === null || isEmpty(folderIds)) return true;
|
if (destinationFolderId === null || isEmpty(folderIds)) return true;
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,7 @@ const getFileData = (filePath: string) => ({
|
|||||||
alternativeText: 'image.png',
|
alternativeText: 'image.png',
|
||||||
caption: 'image.png',
|
caption: 'image.png',
|
||||||
ext: '.png',
|
ext: '.png',
|
||||||
folder: null,
|
folder: undefined,
|
||||||
folderPath: '/',
|
folderPath: '/',
|
||||||
filepath: filePath,
|
filepath: filePath,
|
||||||
getStream: () => fs.createReadStream(filePath),
|
getStream: () => fs.createReadStream(filePath),
|
||||||
@ -56,6 +56,7 @@ const getFileData = (filePath: string) => ({
|
|||||||
size: 4,
|
size: 4,
|
||||||
width: 1500,
|
width: 1500,
|
||||||
tmpWorkingDirectory,
|
tmpWorkingDirectory,
|
||||||
|
name: 'image.png',
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Upload image', () => {
|
describe('Upload image', () => {
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
|
import type { UID } from '@strapi/types';
|
||||||
import { signEntityMedia } from '../utils';
|
import { signEntityMedia } from '../utils';
|
||||||
import { getService } from '../../../utils';
|
import { getService } from '../../../utils';
|
||||||
|
|
||||||
jest.mock('../../../utils');
|
jest.mock('../../../utils');
|
||||||
|
|
||||||
describe('Upload | extensions | entity-manager', () => {
|
describe('Upload | extensions | entity-manager', () => {
|
||||||
const modelUID = 'model';
|
const modelUID = 'model' as UID.Schema;
|
||||||
const componentUID = 'component';
|
const componentUID = 'component';
|
||||||
|
|
||||||
const models = {
|
const models = {
|
||||||
@ -71,6 +72,18 @@ describe('Upload | extensions | entity-manager', () => {
|
|||||||
spySignFileUrls = jest.fn();
|
spySignFileUrls = jest.fn();
|
||||||
jest.mocked(getService).mockImplementation(() => ({
|
jest.mocked(getService).mockImplementation(() => ({
|
||||||
signFileUrls: spySignFileUrls,
|
signFileUrls: spySignFileUrls,
|
||||||
|
getFolderPath: jest.fn(),
|
||||||
|
deleteByIds: jest.fn(),
|
||||||
|
computeMetrics: jest.fn().mockResolvedValue({
|
||||||
|
assetNumber: 0,
|
||||||
|
folderNumber: 0,
|
||||||
|
averageDepth: 0,
|
||||||
|
maxDepth: 0,
|
||||||
|
averageDeviationDepth: 0,
|
||||||
|
}),
|
||||||
|
sendMetrics: jest.fn().mockResolvedValue(undefined),
|
||||||
|
ensureWeeklyStoredCronSchedule: jest.fn().mockResolvedValue(undefined),
|
||||||
|
registerCron: jest.fn().mockResolvedValue(undefined),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
global.strapi = {
|
global.strapi = {
|
||||||
|
|||||||
@ -129,6 +129,11 @@ export default ({ strapi }: { strapi: Core.Strapi }) => ({
|
|||||||
async registerCron() {
|
async registerCron() {
|
||||||
const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
|
const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
|
||||||
|
|
||||||
strapi.cron.add({ [weeklySchedule]: this.sendMetrics.bind(this) });
|
strapi.cron.add({
|
||||||
|
uploadWeekly: {
|
||||||
|
task: this.sendMetrics.bind(this),
|
||||||
|
options: weeklySchedule,
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,11 @@ const { factory } = require('typescript');
|
|||||||
const { models } = require('../common');
|
const { models } = require('../common');
|
||||||
const { emitDefinitions, format, generateSharedExtensionDefinition } = require('../utils');
|
const { emitDefinitions, format, generateSharedExtensionDefinition } = require('../utils');
|
||||||
|
|
||||||
|
const NO_COMPONENT_PLACEHOLDER_COMMENT = `/*
|
||||||
|
* The app doesn't have any components yet.
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate type definitions for Strapi Components
|
* Generate type definitions for Strapi Components
|
||||||
*
|
*
|
||||||
@ -23,6 +28,12 @@ const generateComponentsDefinitions = async (options = {}) => {
|
|||||||
definition: models.schema.generateSchemaDefinition(contentType),
|
definition: models.schema.generateSchemaDefinition(contentType),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
options.logger.debug(`Found ${componentsDefinitions.length} components.`);
|
||||||
|
|
||||||
|
if (componentsDefinitions.length === 0) {
|
||||||
|
return { output: NO_COMPONENT_PLACEHOLDER_COMMENT, stats: {} };
|
||||||
|
}
|
||||||
|
|
||||||
const formattedSchemasDefinitions = componentsDefinitions.reduce((acc, def) => {
|
const formattedSchemasDefinitions = componentsDefinitions.reduce((acc, def) => {
|
||||||
acc.push(
|
acc.push(
|
||||||
// Definition
|
// Definition
|
||||||
|
|||||||
@ -5,6 +5,11 @@ const { factory } = require('typescript');
|
|||||||
const { models } = require('../common');
|
const { models } = require('../common');
|
||||||
const { emitDefinitions, format, generateSharedExtensionDefinition } = require('../utils');
|
const { emitDefinitions, format, generateSharedExtensionDefinition } = require('../utils');
|
||||||
|
|
||||||
|
const NO_CONTENT_TYPE_PLACEHOLDER_COMMENT = `/*
|
||||||
|
* The app doesn't have any content-types yet.
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate type definitions for Strapi Content-Types
|
* Generate type definitions for Strapi Content-Types
|
||||||
*
|
*
|
||||||
@ -23,6 +28,12 @@ const generateContentTypesDefinitions = async (options = {}) => {
|
|||||||
definition: models.schema.generateSchemaDefinition(contentType),
|
definition: models.schema.generateSchemaDefinition(contentType),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
options.logger.debug(`Found ${contentTypesDefinitions.length} content-types.`);
|
||||||
|
|
||||||
|
if (contentTypesDefinitions.length === 0) {
|
||||||
|
return { output: NO_CONTENT_TYPE_PLACEHOLDER_COMMENT, stats: {} };
|
||||||
|
}
|
||||||
|
|
||||||
const formattedSchemasDefinitions = contentTypesDefinitions.reduce((acc, def) => {
|
const formattedSchemasDefinitions = contentTypesDefinitions.reduce((acc, def) => {
|
||||||
acc.push(
|
acc.push(
|
||||||
// Definition
|
// Definition
|
||||||
|
|||||||
@ -52,7 +52,7 @@ const createConfig = ({ port, testDir, appDir }) => ({
|
|||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 3 : 0,
|
retries: process.env.CI ? 3 : 1,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: 1,
|
workers: 1,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
|||||||
@ -10,28 +10,56 @@ import {
|
|||||||
skipCtbTour,
|
skipCtbTour,
|
||||||
} from '../../../utils/shared';
|
} from '../../../utils/shared';
|
||||||
|
|
||||||
// TODO: fix the test so that it doesn't fail on CI
|
test.describe('Edit collection type', () => {
|
||||||
describeOnCondition(!process.env.CI)('Edit collection type', () => {
|
// very long timeout for these tests because they restart the server multiple times
|
||||||
|
test.describe.configure({ timeout: 300000 });
|
||||||
|
|
||||||
// use a name with a capital and a space to ensure we also test the kebab-casing conversion for api ids
|
// use a name with a capital and a space to ensure we also test the kebab-casing conversion for api ids
|
||||||
const ctName = 'Secret Document';
|
const ctName = 'Secret Document';
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
let dependentTestsInitialized = false;
|
||||||
await resetFiles();
|
|
||||||
await resetDatabaseAndImportDataFromPath('with-admin.tar');
|
|
||||||
await page.goto('/admin');
|
|
||||||
|
|
||||||
await login({ page });
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// TODO: optimize and duplicate this logic in a standardized way other tests
|
||||||
|
// reduce each test run by having a fake `beforeAll` to create the content types only once
|
||||||
|
let loggedIn = false;
|
||||||
|
if (!dependentTestsInitialized) {
|
||||||
|
await resetFiles();
|
||||||
|
await resetDatabaseAndImportDataFromPath('with-admin.tar');
|
||||||
|
|
||||||
|
await page.goto('/admin');
|
||||||
|
|
||||||
|
await login({ page });
|
||||||
|
loggedIn = true;
|
||||||
|
await page.getByRole('link', { name: 'Content-Type Builder' }).click();
|
||||||
|
|
||||||
|
await skipCtbTour(page);
|
||||||
|
|
||||||
|
// create a collection type to be used
|
||||||
|
await createCollectionType(page, {
|
||||||
|
name: ctName,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createCollectionType(page, {
|
||||||
|
name: 'dog',
|
||||||
|
});
|
||||||
|
await createCollectionType(page, {
|
||||||
|
name: 'owner',
|
||||||
|
});
|
||||||
|
|
||||||
|
dependentTestsInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loggedIn) {
|
||||||
|
await page.goto('/admin');
|
||||||
|
|
||||||
|
await login({ page });
|
||||||
|
loggedIn = true;
|
||||||
|
}
|
||||||
|
|
||||||
await page.getByRole('link', { name: 'Content-Type Builder' }).click();
|
await page.getByRole('link', { name: 'Content-Type Builder' }).click();
|
||||||
|
|
||||||
await skipCtbTour(page);
|
await skipCtbTour(page);
|
||||||
|
|
||||||
// TODO: create a "saveFileState" mechanism to be used so we don't have to do a full server restart before each test
|
|
||||||
// create a collection type to be used
|
|
||||||
await createCollectionType(page, {
|
|
||||||
name: ctName,
|
|
||||||
});
|
|
||||||
|
|
||||||
await navToHeader(page, ['Content-Type Builder', ctName], ctName);
|
await navToHeader(page, ['Content-Type Builder', ctName], ctName);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -41,6 +69,33 @@ describeOnCondition(!process.env.CI)('Edit collection type', () => {
|
|||||||
await resetFiles();
|
await resetFiles();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Tests for GH#21398
|
||||||
|
test('Can update relation of type manyToOne to oneToOne', async ({ page }) => {
|
||||||
|
// Create dog owner relation in Content-Type Builder
|
||||||
|
await navToHeader(page, ['Content-Type Builder', 'Dog'], 'Dog');
|
||||||
|
await page.getByRole('button', { name: /add another field to this/i }).click();
|
||||||
|
await page.getByRole('button', { name: /relation/i }).click();
|
||||||
|
await page.getByLabel('Basic settings').getByRole('button').nth(3).click();
|
||||||
|
await page.getByRole('button', { name: /article/i }).click();
|
||||||
|
await page.getByRole('menuitem', { name: /owner/i }).click();
|
||||||
|
await page.getByRole('button', { name: 'Finish' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
|
||||||
|
await waitForRestart(page);
|
||||||
|
|
||||||
|
await expect(page.getByRole('cell', { name: 'owner', exact: true })).toBeVisible();
|
||||||
|
|
||||||
|
// update dog owner relation in Content-Type Builder to oneToOne
|
||||||
|
await page.getByRole('button', { name: /edit owner/i }).click();
|
||||||
|
await page.getByLabel('Basic settings').getByRole('button').nth(0).click();
|
||||||
|
await page.getByRole('button', { name: 'Finish' }).click();
|
||||||
|
await page.getByRole('button', { name: 'Save' }).click();
|
||||||
|
|
||||||
|
await waitForRestart(page);
|
||||||
|
|
||||||
|
await expect(page.getByRole('cell', { name: 'owner', exact: true })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('Can toggle internationalization', async ({ page }) => {
|
test('Can toggle internationalization', async ({ page }) => {
|
||||||
await page.getByRole('button', { name: 'Edit' }).first().click();
|
await page.getByRole('button', { name: 'Edit' }).first().click();
|
||||||
await page.getByRole('tab', { name: 'Advanced settings' }).click();
|
await page.getByRole('tab', { name: 'Advanced settings' }).click();
|
||||||
|
|||||||
97
yarn.lock
97
yarn.lock
@ -5677,18 +5677,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@pmmmwh/react-refresh-webpack-plugin@npm:0.5.11":
|
"@pmmmwh/react-refresh-webpack-plugin@npm:0.5.15":
|
||||||
version: 0.5.11
|
version: 0.5.15
|
||||||
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.11"
|
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.15"
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-html-community: "npm:^0.0.8"
|
ansi-html: "npm:^0.0.9"
|
||||||
common-path-prefix: "npm:^3.0.0"
|
|
||||||
core-js-pure: "npm:^3.23.3"
|
core-js-pure: "npm:^3.23.3"
|
||||||
error-stack-parser: "npm:^2.0.6"
|
error-stack-parser: "npm:^2.0.6"
|
||||||
find-up: "npm:^5.0.0"
|
|
||||||
html-entities: "npm:^2.1.0"
|
html-entities: "npm:^2.1.0"
|
||||||
loader-utils: "npm:^2.0.4"
|
loader-utils: "npm:^2.0.4"
|
||||||
schema-utils: "npm:^3.0.0"
|
schema-utils: "npm:^4.2.0"
|
||||||
source-map: "npm:^0.7.3"
|
source-map: "npm:^0.7.3"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
"@types/webpack": 4.x || 5.x
|
"@types/webpack": 4.x || 5.x
|
||||||
@ -5696,7 +5694,7 @@ __metadata:
|
|||||||
sockjs-client: ^1.4.0
|
sockjs-client: ^1.4.0
|
||||||
type-fest: ">=0.17.0 <5.0.0"
|
type-fest: ">=0.17.0 <5.0.0"
|
||||||
webpack: ">=4.43.0 <6.0.0"
|
webpack: ">=4.43.0 <6.0.0"
|
||||||
webpack-dev-server: 3.x || 4.x
|
webpack-dev-server: 3.x || 4.x || 5.x
|
||||||
webpack-hot-middleware: 2.x
|
webpack-hot-middleware: 2.x
|
||||||
webpack-plugin-serve: 0.x || 1.x
|
webpack-plugin-serve: 0.x || 1.x
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@ -5712,7 +5710,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
webpack-plugin-serve:
|
webpack-plugin-serve:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/a9c8468417a14a23339e313cff6ddb8029e0637748973070e61d83a2534620b3492b9a42ecf9eb9d63cb709f53c17fe814bc7dd68d64c300db338e9fd7287bc4
|
checksum: 10c0/ba310aa4d53070f59c8a374d1d256c5965c044c0c3fb1ff6b55353fb5e86de08a490a7bd59a31f0d4951f8f29f81864c7df224fe1342543a95d048b7413ff171
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -8473,7 +8471,7 @@ __metadata:
|
|||||||
open: "npm:8.4.0"
|
open: "npm:8.4.0"
|
||||||
ora: "npm:5.4.1"
|
ora: "npm:5.4.1"
|
||||||
pkg-up: "npm:3.1.0"
|
pkg-up: "npm:3.1.0"
|
||||||
tar: "npm:6.1.13"
|
tar: "npm:6.2.1"
|
||||||
tsconfig: "workspace:*"
|
tsconfig: "workspace:*"
|
||||||
xdg-app-paths: "npm:8.3.0"
|
xdg-app-paths: "npm:8.3.0"
|
||||||
yup: "npm:0.32.9"
|
yup: "npm:0.32.9"
|
||||||
@ -8747,7 +8745,7 @@ __metadata:
|
|||||||
semver: "npm:7.5.4"
|
semver: "npm:7.5.4"
|
||||||
stream-chain: "npm:2.2.5"
|
stream-chain: "npm:2.2.5"
|
||||||
stream-json: "npm:1.8.0"
|
stream-json: "npm:1.8.0"
|
||||||
tar: "npm:6.1.13"
|
tar: "npm:6.2.1"
|
||||||
tar-stream: "npm:2.2.0"
|
tar-stream: "npm:2.2.0"
|
||||||
typescript: "npm:5.3.2"
|
typescript: "npm:5.3.2"
|
||||||
ws: "npm:8.17.1"
|
ws: "npm:8.17.1"
|
||||||
@ -9474,7 +9472,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@strapi/strapi@workspace:packages/core/strapi"
|
resolution: "@strapi/strapi@workspace:packages/core/strapi"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.11"
|
"@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.15"
|
||||||
"@strapi/admin": "workspace:*"
|
"@strapi/admin": "workspace:*"
|
||||||
"@strapi/cloud-cli": "workspace:*"
|
"@strapi/cloud-cli": "workspace:*"
|
||||||
"@strapi/content-manager": "workspace:*"
|
"@strapi/content-manager": "workspace:*"
|
||||||
@ -9547,7 +9545,7 @@ __metadata:
|
|||||||
vite: "npm:5.1.7"
|
vite: "npm:5.1.7"
|
||||||
webpack: "npm:^5.90.3"
|
webpack: "npm:^5.90.3"
|
||||||
webpack-bundle-analyzer: "npm:^4.10.1"
|
webpack-bundle-analyzer: "npm:^4.10.1"
|
||||||
webpack-dev-middleware: "npm:6.1.1"
|
webpack-dev-middleware: "npm:6.1.2"
|
||||||
webpack-hot-middleware: "npm:2.26.1"
|
webpack-hot-middleware: "npm:2.26.1"
|
||||||
yalc: "npm:1.0.0-pre.53"
|
yalc: "npm:1.0.0-pre.53"
|
||||||
yup: "npm:0.32.9"
|
yup: "npm:0.32.9"
|
||||||
@ -12408,7 +12406,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ajv-keywords@npm:^5.0.0":
|
"ajv-keywords@npm:^5.0.0, ajv-keywords@npm:^5.1.0":
|
||||||
version: 5.1.0
|
version: 5.1.0
|
||||||
resolution: "ajv-keywords@npm:5.1.0"
|
resolution: "ajv-keywords@npm:5.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12467,6 +12465,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"ajv@npm:^8.9.0":
|
||||||
|
version: 8.17.1
|
||||||
|
resolution: "ajv@npm:8.17.1"
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal: "npm:^3.1.3"
|
||||||
|
fast-uri: "npm:^3.0.1"
|
||||||
|
json-schema-traverse: "npm:^1.0.0"
|
||||||
|
require-from-string: "npm:^2.0.2"
|
||||||
|
checksum: 10c0/ec3ba10a573c6b60f94639ffc53526275917a2df6810e4ab5a6b959d87459f9ef3f00d5e7865b82677cb7d21590355b34da14d1d0b9c32d75f95a187e76fff35
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ansi-align@npm:^3.0.0":
|
"ansi-align@npm:^3.0.0":
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
resolution: "ansi-align@npm:3.0.1"
|
resolution: "ansi-align@npm:3.0.1"
|
||||||
@ -12510,7 +12520,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ansi-html-community@npm:0.0.8, ansi-html-community@npm:^0.0.8":
|
"ansi-html-community@npm:0.0.8":
|
||||||
version: 0.0.8
|
version: 0.0.8
|
||||||
resolution: "ansi-html-community@npm:0.0.8"
|
resolution: "ansi-html-community@npm:0.0.8"
|
||||||
bin:
|
bin:
|
||||||
@ -12519,6 +12529,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"ansi-html@npm:^0.0.9":
|
||||||
|
version: 0.0.9
|
||||||
|
resolution: "ansi-html@npm:0.0.9"
|
||||||
|
bin:
|
||||||
|
ansi-html: bin/ansi-html
|
||||||
|
checksum: 10c0/4a5de9802fb50193e32b51a9ea48dc0d7e4436b860cb819d7110c62f2bfb1410288e1a2f9a848269f5eab8f903797a7f0309fe4c552f92a92b61a5b759ed52bd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ansi-regex@npm:^2.0.0":
|
"ansi-regex@npm:^2.0.0":
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
resolution: "ansi-regex@npm:2.1.1"
|
resolution: "ansi-regex@npm:2.1.1"
|
||||||
@ -14613,13 +14632,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"common-path-prefix@npm:^3.0.0":
|
|
||||||
version: 3.0.0
|
|
||||||
resolution: "common-path-prefix@npm:3.0.0"
|
|
||||||
checksum: 10c0/c4a74294e1b1570f4a8ab435285d185a03976c323caa16359053e749db4fde44e3e6586c29cd051100335e11895767cbbd27ea389108e327d62f38daf4548fdb
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"common-tags@npm:^1.8.0":
|
"common-tags@npm:^1.8.0":
|
||||||
version: 1.8.2
|
version: 1.8.2
|
||||||
resolution: "common-tags@npm:1.8.2"
|
resolution: "common-tags@npm:1.8.2"
|
||||||
@ -18077,6 +18089,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fast-uri@npm:^3.0.1":
|
||||||
|
version: 3.0.1
|
||||||
|
resolution: "fast-uri@npm:3.0.1"
|
||||||
|
checksum: 10c0/3cd46d6006083b14ca61ffe9a05b8eef75ef87e9574b6f68f2e17ecf4daa7aaadeff44e3f0f7a0ef4e0f7e7c20fc07beec49ff14dc72d0b500f00386592f2d10
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fast-xml-parser@npm:4.2.5":
|
"fast-xml-parser@npm:4.2.5":
|
||||||
version: 4.2.5
|
version: 4.2.5
|
||||||
resolution: "fast-xml-parser@npm:4.2.5"
|
resolution: "fast-xml-parser@npm:4.2.5"
|
||||||
@ -28670,7 +28689,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0":
|
"schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0":
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
resolution: "schema-utils@npm:3.3.0"
|
resolution: "schema-utils@npm:3.3.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -28693,6 +28712,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"schema-utils@npm:^4.2.0":
|
||||||
|
version: 4.2.0
|
||||||
|
resolution: "schema-utils@npm:4.2.0"
|
||||||
|
dependencies:
|
||||||
|
"@types/json-schema": "npm:^7.0.9"
|
||||||
|
ajv: "npm:^8.9.0"
|
||||||
|
ajv-formats: "npm:^2.1.1"
|
||||||
|
ajv-keywords: "npm:^5.1.0"
|
||||||
|
checksum: 10c0/8dab7e7800316387fd8569870b4b668cfcecf95ac551e369ea799bbcbfb63fb0365366d4b59f64822c9f7904d8c5afcfaf5a6124a4b08783e558cd25f299a6b4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"scripts-front@workspace:scripts/front":
|
"scripts-front@workspace:scripts/front":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "scripts-front@workspace:scripts/front"
|
resolution: "scripts-front@workspace:scripts/front"
|
||||||
@ -29799,7 +29830,7 @@ __metadata:
|
|||||||
stream-chain: "npm:2.2.5"
|
stream-chain: "npm:2.2.5"
|
||||||
stream-json: "npm:1.8.0"
|
stream-json: "npm:1.8.0"
|
||||||
supertest: "npm:6.3.3"
|
supertest: "npm:6.3.3"
|
||||||
tar: "npm:6.1.13"
|
tar: "npm:6.2.1"
|
||||||
ts-jest: "npm:29.1.0"
|
ts-jest: "npm:29.1.0"
|
||||||
typescript: "npm:5.3.2"
|
typescript: "npm:5.3.2"
|
||||||
yalc: "npm:1.0.0-pre.53"
|
yalc: "npm:1.0.0-pre.53"
|
||||||
@ -30450,17 +30481,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tar@npm:6.1.13":
|
"tar@npm:6.2.1":
|
||||||
version: 6.1.13
|
version: 6.2.1
|
||||||
resolution: "tar@npm:6.1.13"
|
resolution: "tar@npm:6.2.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
chownr: "npm:^2.0.0"
|
chownr: "npm:^2.0.0"
|
||||||
fs-minipass: "npm:^2.0.0"
|
fs-minipass: "npm:^2.0.0"
|
||||||
minipass: "npm:^4.0.0"
|
minipass: "npm:^5.0.0"
|
||||||
minizlib: "npm:^2.1.1"
|
minizlib: "npm:^2.1.1"
|
||||||
mkdirp: "npm:^1.0.3"
|
mkdirp: "npm:^1.0.3"
|
||||||
yallist: "npm:^4.0.0"
|
yallist: "npm:^4.0.0"
|
||||||
checksum: 10c0/eee5f264f3f3c27cd8d4934f80c568470f92811c416144ab671bb36b45a8ed55fbfbbd31f0146f3eddaca91fd564c9a7ec4d2086940968b836f4a2c54146c060
|
checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -32204,9 +32235,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"webpack-dev-middleware@npm:6.1.1":
|
"webpack-dev-middleware@npm:6.1.2":
|
||||||
version: 6.1.1
|
version: 6.1.2
|
||||||
resolution: "webpack-dev-middleware@npm:6.1.1"
|
resolution: "webpack-dev-middleware@npm:6.1.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
colorette: "npm:^2.0.10"
|
colorette: "npm:^2.0.10"
|
||||||
memfs: "npm:^3.4.12"
|
memfs: "npm:^3.4.12"
|
||||||
@ -32218,7 +32249,7 @@ __metadata:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
webpack:
|
webpack:
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/f8f5b7f7591fa3e4d4008b28ab2b5c13367a24587257e3e37cff31e2d8a6c859de5294af83c79e8faf3137db194377f392fffacdf5010b5c1311eba6f9b71568
|
checksum: 10c0/90c415a770c7db493f4a7d8f3308d761ff63249f628fa8a133eac5a61e849cdf658398e189fc2d95ce0ea884641363f964db6b269c6cea877765321dd7f14b9a
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user