Merge branch 'develop' into feature/add-mariadb-migration-support

This commit is contained in:
Ben Irvin 2024-09-25 17:34:31 +02:00 committed by GitHub
commit 9bc666db2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 596 additions and 138 deletions

View File

@ -37,8 +37,8 @@ jobs:
with:
build-script: 'build:size'
pattern: '**/build/**/*.{js,css,html,svg}'
strip-hash: "\\.(?:(\\w{8})\\.chunk)|(?:\\.(\\w{8}))"
minimum-change-threshold: 10
strip-hash: "-([-\\w]{8})(\\.\\w+)$"
minimum-change-threshold: '5%'
# FIXME: exclude unnamed webpack chunks - remove once webpack
# does not create them anymore

View File

@ -70,6 +70,43 @@ jobs:
issue-number: ${{ github.event.issue.number }}
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
- name: 'Comment: redirect feature request to canny'
if: "${{ github.event.label.name == 'issue: feature request' }}"

View File

@ -2,18 +2,20 @@
## 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 |
| ------- | ----------- | -------------- | -------------- | ---------------------- | -------------------- |
| 5.x.x | GA | October 2024 | Further Notice | Further Notice | LTS (Future) |
| 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 | Alpha | N/A | N/A | N/A | Not Supported |
| 4.x.x | GA | November 2021 | October 2025 | April 2026 | LTS |
| 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 |
**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.
| Version | Release Tag | Support Starts | Support Ends | Security Updates Until | Notes |
| ------- | ----------- | -------------- | -------------- | ---------------------- | ------------------------------ |
| 5.x.x | GA / Stable | September 2024 | Further Notice | Further Notice | LTS |
| 5.x.x | RC | N/A | September 2024 | N/A | Not Supported |
| 5.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 | GA / Stable | November 2021 | October 2025 | April 2026 | LTS (High/Critical fixes only) |
| 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

View File

@ -1,5 +1,5 @@
module.exports = ({ env }) => ({
future: {
contentReleasesScheduling: env.bool('STRAPI_FUTURE_CONTENT_RELEASES_SCHEDULING', false),
preview: env.bool('STRAPI_FUTURE_PREVIEW', false),
},
});

View File

@ -148,7 +148,7 @@
"stream-chain": "2.2.5",
"stream-json": "1.8.0",
"supertest": "6.3.3",
"tar": "6.1.13",
"tar": "6.2.1",
"ts-jest": "29.1.0",
"typescript": "5.3.2",
"yalc": "1.0.0-pre.53",

View File

@ -61,7 +61,7 @@
"open": "8.4.0",
"ora": "5.4.1",
"pkg-up": "3.1.0",
"tar": "6.1.13",
"tar": "6.2.1",
"xdg-app-paths": "8.3.0",
"yup": "0.32.9"
},

View File

@ -14,6 +14,7 @@ import { isStderrError } from './types';
import type { Scope } from './types';
import { logger } from './utils/logger';
import { gitIgnore } from './utils/gitignore';
import { getInstallArgs } from './utils/get-package-manager-args';
async function createStrapi(scope: 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 = {
cwd: rootPath,
stdio: 'inherit',
env: {
...process.env,
...envArgs,
NODE_ENV: 'development',
},
};
if (packageManager in installArgumentsMap) {
installArguments.push(...(installArgumentsMap[packageManager] ?? []));
}
const proc = execa(packageManager, installArguments, options);
const proc = execa(packageManager, cmdArgs, options);
return proc;
}

View File

@ -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 };
};

View File

@ -11,6 +11,7 @@ import {
TextInput,
Typography,
} from '@strapi/design-system';
import { Check } from '@strapi/icons';
import { format } from 'date-fns';
import { Formik, Form, FormikHelpers } from 'formik';
import { useIntl } from 'react-intl';
@ -197,14 +198,13 @@ const CreatePage = () => {
handleReset();
permissionsRef.current?.resetForm();
}}
size="L"
>
{formatMessage({
id: 'app.components.Button.reset',
defaultMessage: 'Reset',
})}
</Button>
<Button type="submit" loading={isSubmitting} size="L">
<Button type="submit" loading={isSubmitting} startIcon={<Check />}>
{formatMessage({
id: 'global.save',
defaultMessage: 'Save',

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import { Box, Button, Flex, Main } from '@strapi/design-system';
import { Check } from '@strapi/icons';
import { Formik, FormikHelpers } from 'formik';
import { useIntl } from 'react-intl';
import { Navigate, useMatch } from 'react-router-dom';
@ -191,9 +192,9 @@ const EditPage = () => {
<Flex gap={2}>
<Button
type="submit"
startIcon={<Check />}
disabled={role.code === 'strapi-super-admin'}
loading={isSubmitting}
size="L"
>
{formatMessage({
id: 'global.save',

View File

@ -191,7 +191,6 @@ const EditPage = () => {
startIcon={<Check />}
loading={isSubmitting}
type="submit"
size="L"
>
{formatMessage({ id: 'global.save', defaultMessage: 'Save' })}
</Button>

View File

@ -98,7 +98,6 @@ const WebhookForm = ({
variant="tertiary"
startIcon={<Publish />}
disabled={isCreating || isTriggering}
size="L"
>
{formatMessage({
id: 'Settings.webhooks.trigger',
@ -108,7 +107,6 @@ const WebhookForm = ({
<Button
startIcon={<Check />}
type="submit"
size="L"
disabled={!modified}
loading={isSubmitting}
>

View File

@ -144,7 +144,6 @@ export const SingleSignOnPage = () => {
loading={isSubmitting}
startIcon={<Check />}
type="submit"
size="L"
>
{formatMessage({
id: 'global.save',

View File

@ -49,7 +49,10 @@ const sendUpdateProjectInformation = async (strapi: Core.Strapi) => {
const startCron = (strapi: Core.Strapi) => {
strapi.cron.add({
'0 0 0 * * *': () => sendUpdateProjectInformation(strapi),
sendProjectInformation: {
task: () => sendUpdateProjectInformation(strapi),
options: '0 0 0 * * *',
},
});
};

View File

@ -30,7 +30,10 @@ const sendUpdateProjectInformation = async (strapi: Core.Strapi) => {
const startCron = (strapi: Core.Strapi) => {
strapi.cron.add({
'0 0 0 * * *': () => sendUpdateProjectInformation(strapi),
sendProjectInformation: {
task: () => sendUpdateProjectInformation(strapi),
options: '0 0 0 * * *',
},
});
};

View File

@ -118,7 +118,7 @@ export const VersionHeader = ({ headerId }: VersionHeaderProps) => {
minute: 'numeric',
})}
subtitle={
<Typography variant="epsilon">
<Typography variant="epsilon" textColor="neutral600">
{formatMessage(
{
id: 'content-manager.history.version.subtitle',

View File

@ -4,6 +4,7 @@ import { PLUGIN_ID } from './constants/plugin';
import { ContentManagerPlugin } from './content-manager';
import { historyAdmin } from './history';
import { reducer } from './modules/reducers';
import { previewAdmin } from './preview';
import { routes } from './router';
import { prefixPluginTranslations } from './utils/translations';
@ -45,6 +46,9 @@ export default {
if (typeof historyAdmin.bootstrap === 'function') {
historyAdmin.bootstrap(app);
}
if (typeof previewAdmin.bootstrap === 'function') {
previewAdmin.bootstrap(app);
}
},
async registerTrads({ locales }: { locales: string[] }) {
const importedTrads = await Promise.all(

View File

@ -151,7 +151,7 @@ const Panel = React.forwardRef<any, PanelProps>(({ children, title }, ref) => {
justifyContent="stretch"
alignItems="flex-start"
>
<Typography tag="h2" variant="sigma" textTransform="uppercase">
<Typography tag="h2" variant="sigma" textTransform="uppercase" textColor="neutral600">
{title}
</Typography>
{children}

View 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.

View File

@ -0,0 +1 @@
export const FEATURE_ID = 'preview';

View 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 };

View File

@ -1,6 +1,7 @@
import { getService } from './utils';
import { ALLOWED_WEBHOOK_EVENTS } from './constants';
import history from './history';
import preview from './preview';
export default async () => {
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
@ -13,4 +14,5 @@ export default async () => {
await getService('permission').registerPermissions();
await history.bootstrap?.({ strapi });
await preview.bootstrap?.({ strapi });
};

View 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.

View File

@ -0,0 +1 @@
export const FEATURE_ID = 'preview';

View 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();

View File

@ -16,6 +16,7 @@ interface SelectCategoryProps {
onChange: (value: { target: { name: string; value: any; type: string } }) => void;
value?: string;
isCreating?: boolean;
dynamicZoneTarget?: string | null;
}
export const SelectCategory = ({
@ -25,6 +26,7 @@ export const SelectCategory = ({
onChange,
value = undefined,
isCreating,
dynamicZoneTarget,
}: SelectCategoryProps) => {
const { formatMessage } = useIntl();
const { allComponentsCategories } = useDataManager();
@ -48,7 +50,7 @@ export const SelectCategory = ({
<Combobox
// TODO: re-enable category edits, renaming categories of already existing components currently breaks other functionality
// See https://github.com/strapi/strapi/issues/20356
disabled={!isCreating}
disabled={!isCreating && !dynamicZoneTarget}
onChange={handleChange}
onCreateOption={handleCreateOption}
value={value}

View File

@ -205,7 +205,13 @@ const checkLicense = async ({ strapi }: { strapi: Core.Strapi }) => {
if (!shouldStayOffline) {
await onlineUpdate({ strapi });
strapi.cron.add({ [shiftCronExpression('0 0 */12 * * *')]: onlineUpdate });
strapi.cron.add({
onlineUpdate: {
task: () => onlineUpdate({ strapi }),
options: shiftCronExpression('0 0 */12 * * *'),
},
});
} else {
if (!ee.licenseInfo.expireAt) {
return disable('Your license does not have offline support.');

View File

@ -86,8 +86,6 @@ const sync = async (
);
await strapi.db.transaction(async ({ trx }) => {
const con = strapi.db.getConnection();
// Iterate old relations that are deleted and insert the new ones
for (const { joinTable, relations } of oldRelations) {
// Update old ids with the new ones
@ -98,7 +96,7 @@ const sync = async (
});
// Insert those relations into the join table
await con.batchInsert(joinTable.name, newRelations).transacting(trx);
await trx.batchInsert(joinTable.name, newRelations, 1000);
}
});
};

View File

@ -54,7 +54,7 @@
"semver": "7.5.4",
"stream-chain": "2.2.5",
"stream-json": "1.8.0",
"tar": "6.1.13",
"tar": "6.2.1",
"tar-stream": "2.2.0",
"ws": "8.17.1"
},

View File

@ -259,11 +259,6 @@ const createHelpers = (db: Database) => {
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
const isMySQL = db.config.connection.client === 'mysql';
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) {
debug(`Updating column ${updatedColumn.name} on ${table.name}`);
@ -300,16 +301,7 @@ const createHelpers = (db: Database) => {
}
}
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);
}
// Add any new columns
for (const addedColumn of table.columns.added) {
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) {
debug(`Creating foreign keys ${addedForeignKey.name} on ${table.name}`);
createForeignKey(tableBuilder, addedForeignKey);

View File

@ -335,7 +335,6 @@ const EditPage = () => {
<Button
startIcon={<Check />}
type="submit"
size="M"
disabled={!modified || isSubmitting || values.stages.length === 0}
// if the confirm dialog is open the loading state is on
// the confirm button already

View File

@ -70,7 +70,12 @@ export default ({ strapi }: { strapi: Core.Strapi }) => {
async registerCron() {
const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
strapi.cron.add({ [weeklySchedule]: this.sendMetrics.bind(this) });
strapi.cron.add({
reviewWorkflowsWeekly: {
task: this.sendMetrics.bind(this),
options: weeklySchedule,
},
});
},
};
};

View File

@ -108,7 +108,7 @@
"watch": "pack-up watch"
},
"dependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.15",
"@strapi/admin": "workspace:*",
"@strapi/cloud-cli": "workspace:*",
"@strapi/content-manager": "workspace:*",
@ -169,7 +169,7 @@
"vite": "5.1.7",
"webpack": "^5.90.3",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-dev-middleware": "6.1.1",
"webpack-dev-middleware": "6.1.2",
"webpack-hot-middleware": "2.26.1",
"yalc": "1.0.0-pre.53",
"yup": "0.32.9"

View File

@ -1,6 +1,6 @@
export interface FeaturesConfig {
future?: {
contentReleases?: boolean;
preview?: boolean;
};
}

View File

@ -0,0 +1,12 @@
{
"root": true,
"overrides": [
{
"files": ["**/*.js", "**/*.jsx"],
"extends": ["custom/front"],
"rules": {
"import/extensions": "off"
}
}
]
}

View File

@ -1,8 +1,7 @@
import React, { useState } from 'react';
import { useQueryParams } from '@strapi/admin/strapi-admin';
import { Loader } from '@strapi/design-system';
import { CrumbSimpleMenu, MenuItem } from '@strapi/design-system';
import { CrumbSimpleMenu, Loader, MenuItem } from '@strapi/design-system';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { NavLink, useLocation } from 'react-router-dom';

View File

@ -19,7 +19,7 @@ const ComponentFixture = ({ to, ...props }) => {
<FolderCard
id={ID_FIXTURE}
ariaLabel="Folder 1"
startAction={<></>}
startAction={null}
onClick={() => {}}
to={to}
{...props}

View File

@ -5,7 +5,12 @@ import set from 'lodash/set';
import { ON_CHANGE, SET_LOADED } from './actionTypes';
import { init, initialState } from './init';
const reducer = (state = initialState, action) =>
const reducer = (
state = initialState,
action = {
type: '',
}
) =>
// eslint-disable-next-line consistent-return
produce(state, (draftState) => {
switch (action.type) {

View File

@ -6,6 +6,7 @@ import getTrad from './getTrad';
export const urlSchema = yup.object().shape({
urls: yup.string().test({
name: 'isUrlValid',
// eslint-disable-next-line no-template-curly-in-string
message: '${path}',
test(values = '') {
const urls = values.split(/\r?\n/);

View File

@ -1,6 +1,13 @@
import { rest } from 'msw';
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 = [
rest.get('/upload/configuration', async (req, res, ctx) => {
return res(
@ -48,7 +55,7 @@ const handlers = [
);
}),
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) {
return res(
@ -183,13 +190,13 @@ const handlers = [
}),
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) =>
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) =>
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('*/some-where-not-existing.jpg', (req, res) => res.networkError('Failed to fetch')),

View File

@ -0,0 +1,10 @@
{
"extends": "tsconfig/client.json",
"compilerOptions": {
"rootDir": "../",
"baseUrl": ".",
"outDir": "./dist"
},
"include": ["./src", "../shared", "../package.json"],
"exclude": ["**/__mocks__", "./src/**/tests", "**/*.test.*"]
}

View File

@ -0,0 +1,11 @@
{
"extends": "tsconfig/client.json",
"compilerOptions": {
"rootDir": "../",
"baseUrl": ".",
"paths": {
"@tests/*": ["./tests/*"]
}
},
"include": ["../package.json", "./src", "../shared", "./tests"]
}

View File

@ -6,5 +6,5 @@ module.exports = {
moduleNameMapper: {
'^@tests/(.*)$': '<rootDir>/admin/tests/$1',
},
setupFilesAfterEnv: ['./admin/tests/setup.js'],
setupFilesAfterEnv: ['./admin/tests/setup.ts'],
};

View File

@ -1,8 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@tests/*": ["./admin/tests/*"]
}
}
}

View File

@ -17,11 +17,19 @@
],
"exports": {
"./strapi-admin": {
"types": "./dist/admin/src/index.d.ts",
"source": "./admin/src/index.js",
"import": "./dist/admin/index.mjs",
"require": "./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": {
"types": "./dist/server/src/index.d.ts",
"source": "./server/src/index.ts",
@ -41,6 +49,9 @@
"lint": "run -T eslint .",
"test:front": "run -T cross-env IS_EE=true jest --config ./jest.config.front.js",
"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",
"watch": "pack-up watch"
},

View File

@ -3,9 +3,11 @@ import { Config, defineConfig } from '@strapi/pack-up';
const config: Config = defineConfig({
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',
require: './dist/admin/index.js',
tsconfig: './admin/tsconfig.build.json',
runtime: 'web',
},
{

View File

@ -25,6 +25,7 @@ export async function bootstrap({ strapi }: { strapi: Core.Strapi }) {
config &&
Object.keys(defaultValue).every((key) => Object.prototype.hasOwnProperty.call(config, key))
) {
// eslint-disable-next-line no-continue
continue;
}

View File

@ -29,7 +29,7 @@ const validateStructureMoveManyFoldersFilesSchema = yup
const validateDuplicatesMoveManyFoldersFilesSchema = yup
.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;
if (isEmpty(folderIds)) return true;
@ -58,7 +58,7 @@ const validateMoveFoldersNotInsideThemselvesSchema = yup
.test(
'dont-move-inside-self',
'folders cannot be moved inside themselves or one of its children',
async function (value) {
async function validateMoveFoldersNotInsideThemselves(value) {
const { folderIds, destinationFolderId } = value;
if (destinationFolderId === null || isEmpty(folderIds)) return true;

View File

@ -47,7 +47,7 @@ const getFileData = (filePath: string) => ({
alternativeText: 'image.png',
caption: 'image.png',
ext: '.png',
folder: null,
folder: undefined,
folderPath: '/',
filepath: filePath,
getStream: () => fs.createReadStream(filePath),
@ -56,6 +56,7 @@ const getFileData = (filePath: string) => ({
size: 4,
width: 1500,
tmpWorkingDirectory,
name: 'image.png',
});
describe('Upload image', () => {

View File

@ -1,10 +1,11 @@
import type { UID } from '@strapi/types';
import { signEntityMedia } from '../utils';
import { getService } from '../../../utils';
jest.mock('../../../utils');
describe('Upload | extensions | entity-manager', () => {
const modelUID = 'model';
const modelUID = 'model' as UID.Schema;
const componentUID = 'component';
const models = {
@ -71,6 +72,18 @@ describe('Upload | extensions | entity-manager', () => {
spySignFileUrls = jest.fn();
jest.mocked(getService).mockImplementation(() => ({
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 = {

View File

@ -129,6 +129,11 @@ export default ({ strapi }: { strapi: Core.Strapi }) => ({
async registerCron() {
const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
strapi.cron.add({ [weeklySchedule]: this.sendMetrics.bind(this) });
strapi.cron.add({
uploadWeekly: {
task: this.sendMetrics.bind(this),
options: weeklySchedule,
},
});
},
});

View File

@ -5,6 +5,11 @@ const { factory } = require('typescript');
const { models } = require('../common');
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
*
@ -23,6 +28,12 @@ const generateComponentsDefinitions = async (options = {}) => {
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) => {
acc.push(
// Definition

View File

@ -5,6 +5,11 @@ const { factory } = require('typescript');
const { models } = require('../common');
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
*
@ -23,6 +28,12 @@ const generateContentTypesDefinitions = async (options = {}) => {
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) => {
acc.push(
// Definition

View File

@ -52,7 +52,7 @@ const createConfig = ({ port, testDir, appDir }) => ({
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 3 : 0,
retries: process.env.CI ? 3 : 1,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */

View File

@ -10,28 +10,56 @@ import {
skipCtbTour,
} from '../../../utils/shared';
// TODO: fix the test so that it doesn't fail on CI
describeOnCondition(!process.env.CI)('Edit collection type', () => {
test.describe('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
const ctName = 'Secret Document';
test.beforeEach(async ({ page }) => {
await resetFiles();
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
let dependentTestsInitialized = false;
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 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);
});
@ -41,6 +69,33 @@ describeOnCondition(!process.env.CI)('Edit collection type', () => {
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 }) => {
await page.getByRole('button', { name: 'Edit' }).first().click();
await page.getByRole('tab', { name: 'Advanced settings' }).click();

View File

@ -5677,18 +5677,16 @@ __metadata:
languageName: node
linkType: hard
"@pmmmwh/react-refresh-webpack-plugin@npm:0.5.11":
version: 0.5.11
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.11"
"@pmmmwh/react-refresh-webpack-plugin@npm:0.5.15":
version: 0.5.15
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.15"
dependencies:
ansi-html-community: "npm:^0.0.8"
common-path-prefix: "npm:^3.0.0"
ansi-html: "npm:^0.0.9"
core-js-pure: "npm:^3.23.3"
error-stack-parser: "npm:^2.0.6"
find-up: "npm:^5.0.0"
html-entities: "npm:^2.1.0"
loader-utils: "npm:^2.0.4"
schema-utils: "npm:^3.0.0"
schema-utils: "npm:^4.2.0"
source-map: "npm:^0.7.3"
peerDependencies:
"@types/webpack": 4.x || 5.x
@ -5696,7 +5694,7 @@ __metadata:
sockjs-client: ^1.4.0
type-fest: ">=0.17.0 <5.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-plugin-serve: 0.x || 1.x
peerDependenciesMeta:
@ -5712,7 +5710,7 @@ __metadata:
optional: true
webpack-plugin-serve:
optional: true
checksum: 10c0/a9c8468417a14a23339e313cff6ddb8029e0637748973070e61d83a2534620b3492b9a42ecf9eb9d63cb709f53c17fe814bc7dd68d64c300db338e9fd7287bc4
checksum: 10c0/ba310aa4d53070f59c8a374d1d256c5965c044c0c3fb1ff6b55353fb5e86de08a490a7bd59a31f0d4951f8f29f81864c7df224fe1342543a95d048b7413ff171
languageName: node
linkType: hard
@ -8473,7 +8471,7 @@ __metadata:
open: "npm:8.4.0"
ora: "npm:5.4.1"
pkg-up: "npm:3.1.0"
tar: "npm:6.1.13"
tar: "npm:6.2.1"
tsconfig: "workspace:*"
xdg-app-paths: "npm:8.3.0"
yup: "npm:0.32.9"
@ -8747,7 +8745,7 @@ __metadata:
semver: "npm:7.5.4"
stream-chain: "npm:2.2.5"
stream-json: "npm:1.8.0"
tar: "npm:6.1.13"
tar: "npm:6.2.1"
tar-stream: "npm:2.2.0"
typescript: "npm:5.3.2"
ws: "npm:8.17.1"
@ -9474,7 +9472,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@strapi/strapi@workspace:packages/core/strapi"
dependencies:
"@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.11"
"@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.15"
"@strapi/admin": "workspace:*"
"@strapi/cloud-cli": "workspace:*"
"@strapi/content-manager": "workspace:*"
@ -9547,7 +9545,7 @@ __metadata:
vite: "npm:5.1.7"
webpack: "npm:^5.90.3"
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"
yalc: "npm:1.0.0-pre.53"
yup: "npm:0.32.9"
@ -12408,7 +12406,7 @@ __metadata:
languageName: node
linkType: hard
"ajv-keywords@npm:^5.0.0":
"ajv-keywords@npm:^5.0.0, ajv-keywords@npm:^5.1.0":
version: 5.1.0
resolution: "ajv-keywords@npm:5.1.0"
dependencies:
@ -12467,6 +12465,18 @@ __metadata:
languageName: node
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":
version: 3.0.1
resolution: "ansi-align@npm:3.0.1"
@ -12510,7 +12520,7 @@ __metadata:
languageName: node
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
resolution: "ansi-html-community@npm:0.0.8"
bin:
@ -12519,6 +12529,15 @@ __metadata:
languageName: node
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":
version: 2.1.1
resolution: "ansi-regex@npm:2.1.1"
@ -14613,13 +14632,6 @@ __metadata:
languageName: node
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":
version: 1.8.2
resolution: "common-tags@npm:1.8.2"
@ -18077,6 +18089,13 @@ __metadata:
languageName: node
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":
version: 4.2.5
resolution: "fast-xml-parser@npm:4.2.5"
@ -28670,7 +28689,7 @@ __metadata:
languageName: node
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
resolution: "schema-utils@npm:3.3.0"
dependencies:
@ -28693,6 +28712,18 @@ __metadata:
languageName: node
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":
version: 0.0.0-use.local
resolution: "scripts-front@workspace:scripts/front"
@ -29799,7 +29830,7 @@ __metadata:
stream-chain: "npm:2.2.5"
stream-json: "npm:1.8.0"
supertest: "npm:6.3.3"
tar: "npm:6.1.13"
tar: "npm:6.2.1"
ts-jest: "npm:29.1.0"
typescript: "npm:5.3.2"
yalc: "npm:1.0.0-pre.53"
@ -30450,17 +30481,17 @@ __metadata:
languageName: node
linkType: hard
"tar@npm:6.1.13":
version: 6.1.13
resolution: "tar@npm:6.1.13"
"tar@npm:6.2.1":
version: 6.2.1
resolution: "tar@npm:6.2.1"
dependencies:
chownr: "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"
mkdirp: "npm:^1.0.3"
yallist: "npm:^4.0.0"
checksum: 10c0/eee5f264f3f3c27cd8d4934f80c568470f92811c416144ab671bb36b45a8ed55fbfbbd31f0146f3eddaca91fd564c9a7ec4d2086940968b836f4a2c54146c060
checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537
languageName: node
linkType: hard
@ -32204,9 +32235,9 @@ __metadata:
languageName: node
linkType: hard
"webpack-dev-middleware@npm:6.1.1":
version: 6.1.1
resolution: "webpack-dev-middleware@npm:6.1.1"
"webpack-dev-middleware@npm:6.1.2":
version: 6.1.2
resolution: "webpack-dev-middleware@npm:6.1.2"
dependencies:
colorette: "npm:^2.0.10"
memfs: "npm:^3.4.12"
@ -32218,7 +32249,7 @@ __metadata:
peerDependenciesMeta:
webpack:
optional: true
checksum: 10c0/f8f5b7f7591fa3e4d4008b28ab2b5c13367a24587257e3e37cff31e2d8a6c859de5294af83c79e8faf3137db194377f392fffacdf5010b5c1311eba6f9b71568
checksum: 10c0/90c415a770c7db493f4a7d8f3308d761ff63249f628fa8a133eac5a61e849cdf658398e189fc2d95ce0ea884641363f964db6b269c6cea877765321dd7f14b9a
languageName: node
linkType: hard