Merge branch 'features/deits' into deits/provider-results

This commit is contained in:
Ben Irvin 2022-11-16 10:55:37 +01:00
commit 7cf2ae8d05
21 changed files with 193 additions and 351 deletions

View File

@ -0,0 +1,15 @@
'use strict';
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
/**
* TODO: this should extend @strapi/eslint-config/typescript but doing so requires configuring parserOption.project, which requires tsconfig.json configuration
*/
// extends: ['plugin:@typescript-eslint/recommended'],
globals: {
strapi: false,
},
// Instead of extending (which includes values that interfere with this configuration), only take the rules field
rules: require('./.eslintrc.back.js').rules,
};

View File

@ -13,10 +13,20 @@ module.exports = {
}, },
overrides: [ overrides: [
{ {
// Backend javascript
files: ['packages/**/*.js', 'test/**/*.js', 'scripts/**/*.js'], files: ['packages/**/*.js', 'test/**/*.js', 'scripts/**/*.js'],
excludedFiles: frontPaths, excludedFiles: frontPaths,
...require('./.eslintrc.back.js'), ...require('./.eslintrc.back.js'),
}, },
// Backend typescript
{
files: ['packages/**/*.ts', 'test/**/*.ts', 'scripts/**/*.ts'],
excludedFiles: frontPaths,
...require('./.eslintrc.back.typescript.js'),
},
// Frontend
{ {
files: frontPaths, files: frontPaths,
...require('./.eslintrc.front.js'), ...require('./.eslintrc.front.js'),

View File

@ -41,7 +41,7 @@
"format": "npm-run-all -p format:*", "format": "npm-run-all -p format:*",
"format:code": "npm run prettier:code -- --write", "format:code": "npm run prettier:code -- --write",
"format:other": "npm run prettier:other -- --write", "format:other": "npm run prettier:other -- --write",
"prettier:code": "prettier \"**/*.js\"", "prettier:code": "prettier \"**/*.{js,ts}\"",
"prettier:other": "prettier \"**/*.{md,css,scss,yaml,yml}\"", "prettier:other": "prettier \"**/*.{md,css,scss,yaml,yml}\"",
"test:clean": "rimraf ./coverage", "test:clean": "rimraf ./coverage",
"test:front": "npm run test:clean && cross-env IS_EE=true jest --config ./jest.config.front.js", "test:front": "npm run test:clean && cross-env IS_EE=true jest --config ./jest.config.front.js",
@ -56,10 +56,10 @@
"doc:api": "node scripts/open-api/serve.js" "doc:api": "node scripts/open-api/serve.js"
}, },
"lint-staged": { "lint-staged": {
"*.{js,md,css,scss,yaml,yml}": [ "*.{js,ts,md,css,scss,yaml,yml}": [
"prettier --write" "prettier --write"
], ],
"*.js": [ "*.{js,ts}": [
"eslint --fix" "eslint --fix"
] ]
}, },
@ -70,12 +70,22 @@
"@strapi/eslint-config": "0.1.2", "@strapi/eslint-config": "0.1.2",
"@swc/core": "1.2.224", "@swc/core": "1.2.224",
"@swc/jest": "0.2.22", "@swc/jest": "0.2.22",
"@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/parser": "5.42.1",
"babel-eslint": "10.1.0", "babel-eslint": "10.1.0",
"chalk": "4.1.2", "chalk": "4.1.2",
"chokidar": "3.5.3", "chokidar": "3.5.3",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"dotenv": "14.2.0", "dotenv": "14.2.0",
"eslint": "8.21.0", "eslint": "8.21.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin": "1.0.1",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jsx-a11y": "6.6.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.31.10",
"eslint-plugin-react-hooks": "4.6.0",
"execa": "1.0.0", "execa": "1.0.0",
"fs-extra": "10.1.0", "fs-extra": "10.1.0",
"get-port": "5.1.1", "get-port": "5.1.1",
@ -105,6 +115,7 @@
"stylelint-processor-styled-components": "1.10.0", "stylelint-processor-styled-components": "1.10.0",
"supertest": "6.2.4", "supertest": "6.2.4",
"ts-jest": "29.0.3", "ts-jest": "29.0.3",
"typescript": "4.6.2",
"yargs": "17.6.0" "yargs": "17.6.0"
}, },
"engines": { "engines": {

View File

@ -9,6 +9,7 @@ const FilterSelect = ({ message, value, onChange, possibleFilters, onClear, cust
return ( return (
<Select <Select
data-testid={`${message}-button`}
aria-label={message} aria-label={message}
placeholder={message} placeholder={message}
size="M" size="M"
@ -20,7 +21,7 @@ const FilterSelect = ({ message, value, onChange, possibleFilters, onClear, cust
> >
{Object.entries(possibleFilters).map(([filterName, count]) => { {Object.entries(possibleFilters).map(([filterName, count]) => {
return ( return (
<Option key={filterName} value={filterName}> <Option data-testid={`${filterName}-${count}`} key={filterName} value={filterName}>
{computeFilterMessage(filterName, count)} {computeFilterMessage(filterName, count)}
</Option> </Option>
); );

View File

@ -51,6 +51,7 @@ const NpmPackagesFilters = ({
<ButtonToggle <ButtonToggle
variant="tertiary" variant="tertiary"
ref={buttonRef} ref={buttonRef}
data-testid="filters-button"
startIcon={<Filter />} startIcon={<Filter />}
onClick={handleToggle} onClick={handleToggle}
size="S" size="S"

View File

@ -1378,6 +1378,7 @@ exports[`Marketplace page - layout renders the online layout 1`] = `
<button <button
aria-disabled="false" aria-disabled="false"
class="c52 c53 c54" class="c52 c53 c54"
data-testid="filters-button"
type="button" type="button"
> >
<div <div

View File

@ -1378,6 +1378,7 @@ exports[`Marketplace page - plugins tab renders and matches the plugin tab snaps
<button <button
aria-disabled="false" aria-disabled="false"
class="c52 c53 c54" class="c52 c53 c54"
data-testid="filters-button"
type="button" type="button"
> >
<div <div

View File

@ -1373,6 +1373,7 @@ exports[`Marketplace page - providers tab renders and matches the providers tab
<button <button
aria-disabled="false" aria-disabled="false"
class="c52 c53 c54" class="c52 c53 c54"
data-testid="filters-button"
type="button" type="button"
> >
<div <div

View File

@ -66,7 +66,6 @@ const App = (
const waitForReload = async () => { const waitForReload = async () => {
await waitFor(() => { await waitFor(() => {
expect(screen.queryByTestId('loader')).toBe(null);
expect(screen.getByRole('heading', { name: /marketplace/i })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /marketplace/i })).toBeInTheDocument();
}); });
}; };

View File

@ -54,7 +54,6 @@ const waitForReload = async () => {
await waitFor( await waitFor(
() => { () => {
expect(screen.queryByTestId('loader')).toBe(null); expect(screen.queryByTestId('loader')).toBe(null);
expect(screen.getByRole('heading', { name: /marketplace/i })).toBeInTheDocument();
}, },
{ timeout: 5000 } { timeout: 5000 }
); );
@ -151,21 +150,21 @@ describe('Marketplace page - plugins tab', () => {
}); });
it('shows plugins filters popover', () => { it('shows plugins filters popover', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' }); const categoriesButton = screen.getByTestId('Categories-button');
expect(collectionsButton).toBeVisible(); expect(collectionsButton).toBeVisible();
expect(categoriesButton).toBeVisible(); expect(categoriesButton).toBeVisible();
}); });
it('shows the collections filter options', () => { it('shows the collections filter options', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
@ -177,16 +176,16 @@ describe('Marketplace page - plugins tab', () => {
}; };
Object.entries(mockedServerCollections).forEach(([collectionName, count]) => { Object.entries(mockedServerCollections).forEach(([collectionName, count]) => {
const option = screen.getByRole('option', { name: `${collectionName} (${count})` }); const option = screen.getByTestId(`${collectionName}-${count}`);
expect(option).toBeVisible(); expect(option).toBeVisible();
}); });
}); });
it('shows the categories filter options', () => { it('shows the categories filter options', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' }); const categoriesButton = screen.getByTestId('Categories-button');
userEvent.click(categoriesButton); userEvent.click(categoriesButton);
@ -197,16 +196,16 @@ describe('Marketplace page - plugins tab', () => {
}; };
Object.entries(mockedServerCategories).forEach(([categoryName, count]) => { Object.entries(mockedServerCategories).forEach(([categoryName, count]) => {
const option = screen.getByRole('option', { name: `${categoryName} (${count})` }); const option = screen.getByTestId(`${categoryName}-${count}`);
expect(option).toBeVisible(); expect(option).toBeVisible();
}); });
}); });
it('filters a collection option', async () => { it('filters a collection option', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (13)` }); const option = screen.getByRole('option', { name: `Made by Strapi (13)` });
@ -227,10 +226,10 @@ describe('Marketplace page - plugins tab', () => {
}); });
it('filters a category option', async () => { it('filters a category option', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' }); const categoriesButton = screen.getByTestId('Categories-button');
userEvent.click(categoriesButton); userEvent.click(categoriesButton);
const option = screen.getByRole('option', { name: `Custom fields (4)` }); const option = screen.getByRole('option', { name: `Custom fields (4)` });
@ -252,9 +251,9 @@ describe('Marketplace page - plugins tab', () => {
it('filters a category and a collection option', async () => { it('filters a category and a collection option', async () => {
// When a user clicks the filters button // When a user clicks the filters button
userEvent.click(screen.getByRole('button', { name: /filters/i })); userEvent.click(screen.getByTestId('filters-button'));
// They should see a select button for collections with no options selected // They should see a select button for collections with no options selected
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
// When they click the select button // When they click the select button
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
// They should see a Made by Strapi option // They should see a Made by Strapi option
@ -264,11 +263,11 @@ describe('Marketplace page - plugins tab', () => {
// The page should reload // The page should reload
await waitForReload(); await waitForReload();
// When they click the filters button again // When they click the filters button again
userEvent.click(screen.getByRole('button', { name: 'Filters' })); userEvent.click(screen.getByTestId('filters-button'));
// They should see the collections button indicating 1 option selected // They should see the collections button indicating 1 option selected
userEvent.click(screen.getByRole('button', { name: '1 collection selected Made by Strapi' })); userEvent.click(screen.getByRole('button', { name: '1 collection selected Made by Strapi' }));
// They should the categories button with no options selected // They should the categories button with no options selected
const categoriesButton = screen.getByRole('button', { name: 'No categories selected' }); const categoriesButton = screen.getByTestId('Categories-button');
userEvent.click(categoriesButton); userEvent.click(categoriesButton);
const categoryOption = screen.getByRole('option', { name: `Custom fields (4)` }); const categoryOption = screen.getByRole('option', { name: `Custom fields (4)` });
userEvent.click(categoryOption); userEvent.click(categoryOption);
@ -295,13 +294,13 @@ describe('Marketplace page - plugins tab', () => {
}); });
it('filters multiple collection options', async () => { it('filters multiple collection options', async () => {
userEvent.click(screen.getByRole('button', { name: /filters/i })); userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: 'No collections selected' })); userEvent.click(screen.getByTestId('Collections-button'));
userEvent.click(screen.getByRole('option', { name: `Made by Strapi (13)` })); userEvent.click(screen.getByRole('option', { name: `Made by Strapi (13)` }));
await waitForReload(); await waitForReload();
userEvent.click(screen.getByRole('button', { name: /filters/i })); userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: `1 collection selected Made by Strapi` })); userEvent.click(screen.getByRole('button', { name: `1 collection selected Made by Strapi` }));
userEvent.click(screen.getByRole('option', { name: `Verified (29)` })); userEvent.click(screen.getByRole('option', { name: `Verified (29)` }));
@ -318,13 +317,13 @@ describe('Marketplace page - plugins tab', () => {
}); });
it('filters multiple category options', async () => { it('filters multiple category options', async () => {
userEvent.click(screen.getByRole('button', { name: /filters/i })); userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: 'No categories selected' })); userEvent.click(screen.getByTestId('Categories-button'));
userEvent.click(screen.getByRole('option', { name: `Custom fields (4)` })); userEvent.click(screen.getByRole('option', { name: `Custom fields (4)` }));
await waitForReload(); await waitForReload();
userEvent.click(screen.getByRole('button', { name: /filters/i })); userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: `1 category selected Custom fields` })); userEvent.click(screen.getByRole('button', { name: `1 category selected Custom fields` }));
userEvent.click(screen.getByRole('option', { name: `Monitoring (1)` })); userEvent.click(screen.getByRole('option', { name: `Monitoring (1)` }));
@ -341,10 +340,10 @@ describe('Marketplace page - plugins tab', () => {
}); });
it('removes a filter option tag', async () => { it('removes a filter option tag', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (13)` }); const option = screen.getByRole('option', { name: `Made by Strapi (13)` });
@ -362,10 +361,10 @@ describe('Marketplace page - plugins tab', () => {
}); });
it('only filters in the plugins tab', async () => { it('only filters in the plugins tab', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (13)` }); const option = screen.getByRole('option', { name: `Made by Strapi (13)` });

View File

@ -53,7 +53,6 @@ const waitForReload = async () => {
await waitFor( await waitFor(
() => { () => {
expect(screen.queryByTestId('loader')).toBe(null); expect(screen.queryByTestId('loader')).toBe(null);
expect(screen.getByRole('heading', { name: /marketplace/i })).toBeInTheDocument();
}, },
{ timeout: 5000 } { timeout: 5000 }
); );
@ -158,7 +157,7 @@ describe('Marketplace page - providers tab', () => {
}); });
it('shows providers filters popover', () => { it('shows providers filters popover', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
// Only show collections filters on providers // Only show collections filters on providers
const providersTab = screen.getByRole('tab', { name: /providers/i }); const providersTab = screen.getByRole('tab', { name: /providers/i });
@ -168,10 +167,10 @@ describe('Marketplace page - providers tab', () => {
}); });
it('shows the collections filter options', () => { it('shows the collections filter options', () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
@ -183,16 +182,16 @@ describe('Marketplace page - providers tab', () => {
}; };
Object.entries(mockedServerCollections).forEach(([collectionName, count]) => { Object.entries(mockedServerCollections).forEach(([collectionName, count]) => {
const option = screen.getByRole('option', { name: `${collectionName} (${count})` }); const option = screen.getByTestId(`${collectionName}-${count}`);
expect(option).toBeVisible(); expect(option).toBeVisible();
}); });
}); });
it('filters a collection option', async () => { it('filters a collection option', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (6)` }); const option = screen.getByRole('option', { name: `Made by Strapi (6)` });
@ -213,13 +212,13 @@ describe('Marketplace page - providers tab', () => {
}); });
it('filters multiple collection options', async () => { it('filters multiple collection options', async () => {
userEvent.click(screen.getByRole('button', { name: /filters/i })); userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: 'No collections selected' })); userEvent.click(screen.getByTestId('Collections-button'));
userEvent.click(screen.getByRole('option', { name: `Made by Strapi (6)` })); userEvent.click(screen.getByRole('option', { name: `Made by Strapi (6)` }));
await waitForReload(); await waitForReload();
userEvent.click(screen.getByRole('button', { name: /filters/i })); userEvent.click(screen.getByTestId('filters-button'));
userEvent.click(screen.getByRole('button', { name: `1 collection selected Made by Strapi` })); userEvent.click(screen.getByRole('button', { name: `1 collection selected Made by Strapi` }));
userEvent.click(screen.getByRole('option', { name: `Verified (6)` })); userEvent.click(screen.getByRole('option', { name: `Verified (6)` }));
@ -236,10 +235,10 @@ describe('Marketplace page - providers tab', () => {
}); });
it('removes a filter option tag', async () => { it('removes a filter option tag', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (6)` }); const option = screen.getByRole('option', { name: `Made by Strapi (6)` });
@ -257,10 +256,10 @@ describe('Marketplace page - providers tab', () => {
}); });
it('only filters in the providers tab', async () => { it('only filters in the providers tab', async () => {
const filtersButton = screen.getByRole('button', { name: /filters/i }); const filtersButton = screen.getByTestId('filters-button');
userEvent.click(filtersButton); userEvent.click(filtersButton);
const collectionsButton = screen.getByRole('button', { name: 'No collections selected' }); const collectionsButton = screen.getByTestId('Collections-button');
userEvent.click(collectionsButton); userEvent.click(collectionsButton);
const option = screen.getByRole('option', { name: `Made by Strapi (6)` }); const option = screen.getByRole('option', { name: `Made by Strapi (6)` });

View File

@ -1,75 +0,0 @@
'use strict';
// Helpers.
const { createTestBuilder } = require('../../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../../test/helpers/strapi');
const form = require('../../../../../../test/helpers/generators');
const { createAuthRequest } = require('../../../../../../test/helpers/request');
const builder = createTestBuilder();
let strapi;
let rq;
const restart = async () => {
await strapi.destroy();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
};
describe('Content Manager - Hide relations', () => {
beforeAll(async () => {
await builder.addContentTypes([form.article]).build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Hide relations', async () => {
await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'PUT',
body: {
layouts: {
edit: [],
editRelations: [],
list: [],
},
},
});
const { body } = await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'GET',
});
expect(body.data.contentType.layouts.editRelations).toStrictEqual([]);
});
test('Hide relations after server restart', async () => {
await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'PUT',
body: {
layouts: {
edit: [],
editRelations: [],
list: [],
},
},
});
await restart();
const { body } = await rq({
url: '/content-manager/content-types/api::article.article/configuration',
method: 'GET',
});
expect(body.data.contentType.layouts.editRelations).toStrictEqual([]);
});
});

View File

@ -1,196 +0,0 @@
'use strict';
// Test a simple default API with no relations
const { omit, pick } = require('lodash/fp');
const { createTestBuilder } = require('../../../../../test/helpers/builder');
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
const { createAuthRequest } = require('../../../../../test/helpers/request');
let strapi;
let rq;
const data = {
products: [],
shops: [],
};
const productModel = {
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
description: '',
collectionName: '',
};
const productWithDPModel = {
attributes: {
name: {
type: 'string',
},
},
displayName: 'Product',
singularName: 'product',
pluralName: 'products',
draftAndPublish: true,
description: '',
collectionName: '',
};
const shopModel = {
attributes: {
name: {
type: 'string',
},
products: {
type: 'relation',
relation: 'manyToMany',
target: 'api::product.product',
targetAttribute: 'shops',
},
},
displayName: 'Shop',
singularName: 'shop',
pluralName: 'shops',
};
const shops = [
{
name: 'market',
},
];
const products =
({ withPublished = false }) =>
({ shop }) => {
const shops = [shop[0].id];
const entries = [
{
name: 'tomato',
shops,
publishedAt: new Date(),
},
{
name: 'apple',
shops,
publishedAt: null,
},
];
if (withPublished) {
return entries;
}
return entries.map(omit('publishedAt'));
};
describe('Relation-list route', () => {
describe('without draftAndPublish', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productModel, shopModel])
.addFixtures(shopModel.singularName, shops)
.addFixtures(productModel.singularName, products({ withPublished: false }))
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
data.shops = await builder.sanitizedFixturesFor(shopModel.singularName, strapi);
data.products = await builder.sanitizedFixturesFor(productModel.singularName, strapi);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Can get relation-list for products of a shop', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.body).toHaveLength(data.products.length);
data.products.forEach((product, index) => {
expect(res.body[index]).toStrictEqual(pick(['_id', 'id', 'name'], product));
});
});
test('Can get relation-list for products of a shop and omit some results', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
body: {
idsToOmit: [data.products[0].id],
},
});
expect(res.body).toHaveLength(1);
expect(res.body[0]).toStrictEqual(pick(['_id', 'id', 'name'], data.products[1]));
});
});
describe('with draftAndPublish', () => {
const builder = createTestBuilder();
beforeAll(async () => {
await builder
.addContentTypes([productWithDPModel, shopModel])
.addFixtures(shopModel.singularName, shops)
.addFixtures(productWithDPModel.singularName, products({ withPublished: true }))
.build();
strapi = await createStrapiInstance();
rq = await createAuthRequest({ strapi });
data.shops = await builder.sanitizedFixturesFor(shopModel.singularName, strapi);
data.products = await builder.sanitizedFixturesFor(productWithDPModel.singularName, strapi);
});
afterAll(async () => {
await strapi.destroy();
await builder.cleanup();
});
test('Can get relation-list for products of a shop', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
});
expect(res.body).toHaveLength(data.products.length);
const tomatoProductRes = res.body.find((p) => p.name === 'tomato');
const appleProductRes = res.body.find((p) => p.name === 'apple');
expect(tomatoProductRes).toMatchObject(pick(['_id', 'id', 'name'], data.products[0]));
expect(tomatoProductRes.publishedAt).toBeISODate();
expect(appleProductRes).toStrictEqual({
...pick(['_id', 'id', 'name'], data.products[1]),
publishedAt: null,
});
});
test('Can get relation-list for products of a shop and omit some results', async () => {
const res = await rq({
method: 'POST',
url: '/content-manager/relations/api::shop.shop/products',
body: {
idsToOmit: [data.products[1].id],
},
});
expect(res.body).toHaveLength(1);
expect(res.body[0]).toMatchObject(pick(['_id', 'id', 'name'], data.products[0]));
});
});
});

View File

@ -20,6 +20,11 @@ export interface ILocalFileDestinationProviderOptions {
enabled: boolean; enabled: boolean;
}; };
// Archive
archive: {
enabled: boolean;
};
// File // File
file: { file: {
path: string; path: string;

View File

@ -38,7 +38,10 @@
}, },
"dependencies": { "dependencies": {
"@strapi/logger": "4.5.0", "@strapi/logger": "4.5.0",
"@strapi/strapi": "4.5.0",
"chalk": "4.1.2", "chalk": "4.1.2",
"fs-extra": "10.0.0",
"lodash": "4.17.21",
"prettier": "2.7.1", "prettier": "2.7.1",
"stream-chain": "2.2.5", "stream-chain": "2.2.5",
"stream-json": "1.7.4", "stream-json": "1.7.4",
@ -51,9 +54,8 @@
"@types/stream-chain": "2.0.1", "@types/stream-chain": "2.0.1",
"@types/stream-json": "1.7.2", "@types/stream-json": "1.7.2",
"@types/tar": "6.1.3", "@types/tar": "6.1.3",
"fs-extra": "10.0.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"typescript": "4.8.4" "typescript": "4.6.2"
}, },
"engines": { "engines": {
"node": ">=14.19.1 <=18.x.x", "node": ">=14.19.1 <=18.x.x",

View File

@ -277,7 +277,15 @@ program
.argParser(parseInputBool) .argParser(parseInputBool)
) )
.addOption( .addOption(
new Option('--compress [boolean]', 'Compress output file using gz') new Option('--compress [boolean]', 'Compress output file using gzip compression')
.default(true)
.argParser(parseInputBool)
)
.addOption(
new Option(
'--archive [boolean]',
'Export all backup files into a single tar archive instead of a folder'
)
.default(true) .default(true)
.argParser(parseInputBool) .argParser(parseInputBool)
) )
@ -310,7 +318,7 @@ program
.addOption( .addOption(
new Option( new Option(
'--schemaComparison <schemaComparison>', '--schemaComparison <schemaComparison>',
'exact requires every field to match, strict requires Strapi version and schemas to match, subset requires source schema to exist in destination, bypass skips checks', 'exact requires every field to match, strict requires Strapi version and content type schema fields do not break, subset requires source schema to exist in destination, bypass skips checks',
parseInputList parseInputList
) )
.choices(['exact', 'strict', 'subset', 'bypass']) .choices(['exact', 'strict', 'subset', 'bypass'])

View File

@ -78,6 +78,9 @@ module.exports = async (filename, opts) => {
compression: { compression: {
enabled: opts.compress, enabled: opts.compress,
}, },
archive: {
enabled: opts.archive,
},
}; };
const destination = createLocalFileDestinationProvider(destinationOptions); const destination = createLocalFileDestinationProvider(destinationOptions);

View File

@ -3,7 +3,7 @@ import { Attribute, ConfigurableOption, PrivateOption } from './base';
import { GetAttributesByType, GetAttributesValues } from './utils'; import { GetAttributesByType, GetAttributesValues } from './utils';
export type BasicRelationsType = 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany'; export type BasicRelationsType = 'oneToOne' | 'oneToMany' | 'manyToOne' | 'manyToMany';
export type PolymorphicRelationsType = 'morphToOne' | 'morphToMany' | 'morphOne' | 'morphMany'; export type PolymorphicRelationsType = 'morphToOne' | 'morphToMany' | 'morphOne' | 'morphMany';
export type RelationsType = BasicRelationsType | PolymorphicRelationsType; export type RelationsType = BasicRelationsType | PolymorphicRelationsType;
export interface BasicRelationAttributeProperties< export interface BasicRelationAttributeProperties<
@ -17,16 +17,14 @@ export interface BasicRelationAttributeProperties<
mappedBy?: RelationsKeysFromTo<T, S>; mappedBy?: RelationsKeysFromTo<T, S>;
} }
export interface PolymorphicRelationAttributeProperties< export interface PolymorphicRelationAttributeProperties<R extends RelationsType> {
R extends RelationsType,
> {
relation: R; relation: R;
} }
export type RelationAttribute< export type RelationAttribute<
S extends SchemaUID, S extends SchemaUID,
R extends RelationsType, R extends RelationsType,
T extends R extends PolymorphicRelationsType ? never: SchemaUID = never T extends R extends PolymorphicRelationsType ? never : SchemaUID = never
> = Attribute<'relation'> & > = Attribute<'relation'> &
// Properties // Properties
(R extends BasicRelationsType (R extends BasicRelationsType
@ -34,22 +32,21 @@ export type RelationAttribute<
: PolymorphicRelationAttributeProperties<R>) & : PolymorphicRelationAttributeProperties<R>) &
// Options // Options
ConfigurableOption & ConfigurableOption &
PrivateOption PrivateOption;
export type RelationsKeysFromTo< export type RelationsKeysFromTo<
TTarget extends SchemaUID, TTarget extends SchemaUID,
TSource extends SchemaUID TSource extends SchemaUID
> = keyof PickRelationsFromTo<TTarget, TSource>; > = keyof PickRelationsFromTo<TTarget, TSource>;
export type PickRelationsFromTo<TTarget extends SchemaUID, TSource extends SchemaUID> = GetAttributesByType< export type PickRelationsFromTo<
TTarget, TTarget extends SchemaUID,
'relation', TSource extends SchemaUID
{ target: TSource } > = GetAttributesByType<TTarget, 'relation', { target: TSource }>;
>;
export type RelationPluralityModifier< export type RelationPluralityModifier<
TRelation extends RelationsType, TRelation extends RelationsType,
TValue extends Object TValue extends Record<string, unknown>
> = TRelation extends `${string}Many` ? TValue[] : TValue; > = TRelation extends `${string}Many` ? TValue[] : TValue;
export type RelationValue< export type RelationValue<

View File

@ -1,8 +1,8 @@
import { Service,GenericService } from '../core-api/service'; import { Service, GenericService } from '../core-api/service';
import { Controller, GenericController } from '../core-api/controller'; import { Controller, GenericController } from '../core-api/controller';
import { Middleware } from '../middlewares'; import { Middleware } from '../middlewares';
import { Policy } from '../core/registries/policies'; import { Policy } from '../core/registries/policies';
import { Strapi } from '@strapi/strapi'; import { Strapi } from './core/strapi';
type ControllerConfig<T extends Controller = Controller> = T; type ControllerConfig<T extends Controller = Controller> = T;

View File

@ -18,7 +18,7 @@ const createUserBodySchema = yup.object().shape({
connect: yup connect: yup
.array() .array()
.of(yup.object().shape({ id: yup.strapiID().required() })) .of(yup.object().shape({ id: yup.strapiID().required() }))
.min(1) .min(1, 'Users must have a role')
.required(), .required(),
}) })
.required() .required()
@ -36,7 +36,16 @@ const updateUserBodySchema = yup.object().shape({
connect: yup connect: yup
.array() .array()
.of(yup.object().shape({ id: yup.strapiID().required() })) .of(yup.object().shape({ id: yup.strapiID().required() }))
.min(1) .required(),
disconnect: yup
.array()
.test('CheckDisconnect', 'Cannot remove role', function test(disconnectValue) {
if (value.connect.length === 0 && disconnectValue.length > 0) {
return false;
}
return true;
})
.required(), .required(),
}) })
: yup.strapiID() : yup.strapiID()

View File

@ -6505,7 +6505,7 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^5.14.0": "@typescript-eslint/eslint-plugin@5.43.0", "@typescript-eslint/eslint-plugin@^5.14.0":
version "5.43.0" version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz#4a5248eb31b454715ddfbf8cfbf497529a0a78bc" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz#4a5248eb31b454715ddfbf8cfbf497529a0a78bc"
integrity sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA== integrity sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA==
@ -6520,6 +6520,16 @@
semver "^7.3.7" semver "^7.3.7"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/parser@5.42.1":
version "5.42.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.42.1.tgz#3e66156f2f74b11690b45950d8f5f28a62751d35"
integrity sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==
dependencies:
"@typescript-eslint/scope-manager" "5.42.1"
"@typescript-eslint/types" "5.42.1"
"@typescript-eslint/typescript-estree" "5.42.1"
debug "^4.3.4"
"@typescript-eslint/parser@^5.14.0": "@typescript-eslint/parser@^5.14.0":
version "5.43.0" version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.43.0.tgz#9c86581234b88f2ba406f0b99a274a91c11630fd" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.43.0.tgz#9c86581234b88f2ba406f0b99a274a91c11630fd"
@ -6530,6 +6540,14 @@
"@typescript-eslint/typescript-estree" "5.43.0" "@typescript-eslint/typescript-estree" "5.43.0"
debug "^4.3.4" debug "^4.3.4"
"@typescript-eslint/scope-manager@5.42.1":
version "5.42.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.42.1.tgz#05e5e1351485637d466464237e5259b49f609b18"
integrity sha512-QAZY/CBP1Emx4rzxurgqj3rUinfsh/6mvuKbLNMfJMMKYLRBfweus8brgXF8f64ABkIZ3zdj2/rYYtF8eiuksQ==
dependencies:
"@typescript-eslint/types" "5.42.1"
"@typescript-eslint/visitor-keys" "5.42.1"
"@typescript-eslint/scope-manager@5.43.0": "@typescript-eslint/scope-manager@5.43.0":
version "5.43.0" version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz#566e46303392014d5d163704724872e1f2dd3c15" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz#566e46303392014d5d163704724872e1f2dd3c15"
@ -6548,11 +6566,29 @@
debug "^4.3.4" debug "^4.3.4"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/types@5.42.1":
version "5.42.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.42.1.tgz#0d4283c30e9b70d2aa2391c36294413de9106df2"
integrity sha512-Qrco9dsFF5lhalz+lLFtxs3ui1/YfC6NdXu+RAGBa8uSfn01cjO7ssCsjIsUs484vny9Xm699FSKwpkCcqwWwA==
"@typescript-eslint/types@5.43.0": "@typescript-eslint/types@5.43.0":
version "5.43.0" version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.43.0.tgz#e4ddd7846fcbc074325293515fa98e844d8d2578" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.43.0.tgz#e4ddd7846fcbc074325293515fa98e844d8d2578"
integrity sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg== integrity sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg==
"@typescript-eslint/typescript-estree@5.42.1":
version "5.42.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.1.tgz#f9a223ecb547a781d37e07a5ac6ba9ff681eaef0"
integrity sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==
dependencies:
"@typescript-eslint/types" "5.42.1"
"@typescript-eslint/visitor-keys" "5.42.1"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.43.0": "@typescript-eslint/typescript-estree@5.43.0":
version "5.43.0" version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz#b6883e58ba236a602c334be116bfc00b58b3b9f2" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz#b6883e58ba236a602c334be116bfc00b58b3b9f2"
@ -6580,6 +6616,14 @@
eslint-utils "^3.0.0" eslint-utils "^3.0.0"
semver "^7.3.7" semver "^7.3.7"
"@typescript-eslint/visitor-keys@5.42.1":
version "5.42.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.1.tgz#df10839adf6605e1cdb79174cf21e46df9be4872"
integrity sha512-LOQtSF4z+hejmpUvitPlc4hA7ERGoj2BVkesOcG91HCn8edLGUXbTrErmutmPbl8Bo9HjAvOO/zBKQHExXNA2A==
dependencies:
"@typescript-eslint/types" "5.42.1"
eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@5.43.0": "@typescript-eslint/visitor-keys@5.43.0":
version "5.43.0" version "5.43.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz#cbbdadfdfea385310a20a962afda728ea106befa" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz#cbbdadfdfea385310a20a962afda728ea106befa"
@ -11072,7 +11116,7 @@ eslint-config-airbnb@^19.0.4:
object.assign "^4.1.2" object.assign "^4.1.2"
object.entries "^1.1.5" object.entries "^1.1.5"
eslint-config-prettier@^8.5.0: eslint-config-prettier@8.5.0, eslint-config-prettier@^8.5.0:
version "8.5.0" version "8.5.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1"
integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==
@ -11100,7 +11144,7 @@ eslint-plugin-es@^3.0.0:
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
regexpp "^3.0.0" regexpp "^3.0.0"
eslint-plugin-import@^2.25.4: eslint-plugin-import@2.26.0, eslint-plugin-import@^2.25.4:
version "2.26.0" version "2.26.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b"
integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==
@ -11119,7 +11163,7 @@ eslint-plugin-import@^2.25.4:
resolve "^1.22.0" resolve "^1.22.0"
tsconfig-paths "^3.14.1" tsconfig-paths "^3.14.1"
eslint-plugin-jsx-a11y@^6.5.1: eslint-plugin-jsx-a11y@6.6.1, eslint-plugin-jsx-a11y@^6.5.1:
version "6.6.1" version "6.6.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz#93736fc91b83fdc38cc8d115deedfc3091aef1ff" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz#93736fc91b83fdc38cc8d115deedfc3091aef1ff"
integrity sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q== integrity sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==
@ -11138,7 +11182,7 @@ eslint-plugin-jsx-a11y@^6.5.1:
minimatch "^3.1.2" minimatch "^3.1.2"
semver "^6.3.0" semver "^6.3.0"
eslint-plugin-node@^11.1.0: eslint-plugin-node@11.1.0, eslint-plugin-node@^11.1.0:
version "11.1.0" version "11.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d"
integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==
@ -11150,19 +11194,19 @@ eslint-plugin-node@^11.1.0:
resolve "^1.10.1" resolve "^1.10.1"
semver "^6.1.0" semver "^6.1.0"
eslint-plugin-prettier@^4.0.0: eslint-plugin-prettier@4.2.1, eslint-plugin-prettier@^4.0.0:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b"
integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==
dependencies: dependencies:
prettier-linter-helpers "^1.0.0" prettier-linter-helpers "^1.0.0"
eslint-plugin-react-hooks@^4.3.0: eslint-plugin-react-hooks@4.6.0, eslint-plugin-react-hooks@^4.3.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==
eslint-plugin-react@^7.29.3: eslint-plugin-react@7.31.10, eslint-plugin-react@^7.29.3:
version "7.31.10" version "7.31.10"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz#6782c2c7fe91c09e715d536067644bbb9491419a"
integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA== integrity sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==
@ -11182,6 +11226,13 @@ eslint-plugin-react@^7.29.3:
semver "^6.3.0" semver "^6.3.0"
string.prototype.matchall "^4.0.7" string.prototype.matchall "^4.0.7"
eslint-plugin@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin/-/eslint-plugin-1.0.1.tgz#24bf3136d167c90ed0d4d5702d8b9b1ed2beef79"
integrity sha512-ervp8C09On0fLA258TvE08AqAr/bhRYgHVZd3BrJjD4JfOA2JGANDLGs06j51oWqfPd7Feoo3OoqHD+fuI2sFQ==
dependencies:
requireindex "~1.1.0"
eslint-scope@5.1.1, eslint-scope@^5.1.1: eslint-scope@5.1.1, eslint-scope@^5.1.1:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
@ -19891,6 +19942,11 @@ require-from-string@2.0.2, require-from-string@^2.0.2:
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
requireindex@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162"
integrity sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==
requires-port@^1.0.0: requires-port@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
@ -22059,11 +22115,6 @@ typescript@4.6.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
typescript@4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
typescript@^4.6.2: typescript@^4.6.2:
version "4.9.3" version "4.9.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db"