Merge branch 'releases/v4' of github.com:strapi/strapi into v4/media-library-cm

This commit is contained in:
soupette 2021-11-04 08:00:43 +01:00
commit 4d907c873c
85 changed files with 370 additions and 388 deletions

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_basic_simples", "collectionName": "components_basic_simples",
"info": { "info": {
"name": "simple", "displayName": "simple",
"icon": "ambulance", "icon": "ambulance",
"description": "" "description": ""
}, },

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_blog_test_comos", "collectionName": "components_blog_test_comos",
"info": { "info": {
"name": "test comp", "displayName": "test comp",
"icon": "air-freshener", "icon": "air-freshener",
"description": "" "description": ""
}, },

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_default_apples", "collectionName": "components_default_apples",
"info": { "info": {
"name": "apple", "displayName": "apple",
"icon": "apple-alt", "icon": "apple-alt",
"description": "" "description": ""
}, },

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_default_cars", "collectionName": "components_default_cars",
"info": { "info": {
"name": "car", "displayName": "car",
"icon": "align-right" "icon": "align-right"
}, },
"options": {}, "options": {},

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_closingperiods", "collectionName": "components_closingperiods",
"info": { "info": {
"name": "closingperiod", "displayName": "closingperiod",
"description": "", "description": "",
"icon": "angry" "icon": "angry"
}, },

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_dishes", "collectionName": "components_dishes",
"info": { "info": {
"name": "dish", "displayName": "dish",
"description": "", "description": "",
"icon": "address-book" "icon": "address-book"
}, },

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_openingtimes", "collectionName": "components_openingtimes",
"info": { "info": {
"name": "openingtimes", "displayName": "openingtimes",
"description": "", "description": "",
"icon": "calendar" "icon": "calendar"
}, },

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_restaurantservices", "collectionName": "components_restaurantservices",
"info": { "info": {
"name": "restaurantservice", "displayName": "restaurantservice",
"description": "", "description": "",
"icon": "cannabis" "icon": "cannabis"
}, },

View File

@ -1,7 +1,7 @@
{ {
"collectionName": "components_default_temps", "collectionName": "components_default_temps",
"info": { "info": {
"name": "temp", "displayName": "temp",
"icon": "adjust", "icon": "adjust",
"description": "" "description": ""
}, },

View File

@ -1,3 +1,14 @@
module.exports = () => { 'use strict';
return (ctx, next) => next();
/**
* `test-middleware` middleware.
*/
module.exports = (config, { strapi }) => {
// Add your own logic here.
return async (ctx, next) => {
strapi.log.info('In test-middleware middleware.');
await next();
};
}; };

View File

@ -57,11 +57,11 @@ async function getTemplateQuestion() {
} }
/** /**
* * @param {string|null} projectName - The name of the project
* @param {string|null} template - The template the project should use
* @returns Array of prompt question objects * @returns Array of prompt question objects
*/ */
// TODO: re-enabled once the template have been migrated to V4 async function getPromptQuestions(projectName /*, template*/) {
async function getPromptQuestions(projectName /*, template */) {
return [ return [
{ {
type: 'input', type: 'input',
@ -85,7 +85,7 @@ async function getPromptQuestions(projectName /*, template */) {
}, },
], ],
}, },
// TODO: re-enabled once the template have been migrated to V4 // TODO: re-enable once we know where to list the official compatible templates
// { // {
// type: 'confirm', // type: 'confirm',
// name: 'useTemplate', // name: 'useTemplate',

View File

@ -4,23 +4,18 @@ import styled from 'styled-components';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import PlusCircle from '@strapi/icons/PlusCircle'; import PlusCircle from '@strapi/icons/PlusCircle';
import { Box } from '@strapi/design-system/Box'; import { Box } from '@strapi/design-system/Box';
import { BaseButton } from '@strapi/design-system/BaseButton';
import { Stack } from '@strapi/design-system/Stack'; import { Stack } from '@strapi/design-system/Stack';
import { Flex } from '@strapi/design-system/Flex'; import { Flex } from '@strapi/design-system/Flex';
import { Text } from '@strapi/design-system/Text'; import { Text } from '@strapi/design-system/Text';
import { pxToRem } from '@strapi/helper-plugin'; import { pxToRem } from '@strapi/helper-plugin';
import { getTrad } from '../../utils'; import { getTrad } from '../../utils';
const IconButton = styled(BaseButton)` const IconWrapper = styled.span`
border: none;
padding: 0;
background: transparent;
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
> svg { > svg {
width: ${pxToRem(24)}; width: ${pxToRem(24)};
height: ${pxToRem(24)}; height: ${pxToRem(24)};
> circle { > circle {
fill: ${({ theme }) => theme.colors.primary200}!important; fill: ${({ theme }) => theme.colors.primary200};
} }
> path { > path {
fill: ${({ theme }) => theme.colors.primary600}; fill: ${({ theme }) => theme.colors.primary600};
@ -33,17 +28,21 @@ const ComponentInitializer = ({ isReadOnly, onClick }) => {
return ( return (
<Box <Box
as="button"
background="neutral100" background="neutral100"
hasRadius
borderColor="neutral200" borderColor="neutral200"
disabled={isReadOnly}
hasRadius
onClick={onClick}
paddingTop={9} paddingTop={9}
paddingBottom={9} paddingBottom={9}
type="button"
> >
<Stack size={2}> <Stack size={2}>
<Flex justifyContent="center" style={{ cursor: isReadOnly ? 'not-allowed' : 'inherit' }}> <Flex justifyContent="center" style={{ cursor: isReadOnly ? 'not-allowed' : 'inherit' }}>
<IconButton disabled={isReadOnly} onClick={onClick}> <IconWrapper>
<PlusCircle /> <PlusCircle />
</IconButton> </IconWrapper>
</Flex> </Flex>
<Flex justifyContent="center"> <Flex justifyContent="center">
<Text textColor="primary600" small bold> <Text textColor="primary600" small bold>

View File

@ -26,12 +26,12 @@ const Category = ({ category, components, isOdd, isOpen, onAddComponent, onToggl
<AccordionContent> <AccordionContent>
<Box paddingTop={4} paddingBottom={4} paddingLeft={3} paddingRight={3}> <Box paddingTop={4} paddingBottom={4} paddingLeft={3} paddingRight={3}>
<Grid> <Grid>
{components.map(({ componentUid, info: { label, icon, name } }) => { {components.map(({ componentUid, info: { displayName, icon } }) => {
return ( return (
<ComponentCard <ComponentCard
key={componentUid} key={componentUid}
componentUid={componentUid} componentUid={componentUid}
intlLabel={{ id: label || name, defaultMessage: label || name }} intlLabel={{ id: displayName, defaultMessage: displayName }}
icon={icon} icon={icon}
onClick={onAddComponent} onClick={onAddComponent}
/> />

View File

@ -18,7 +18,13 @@ import Image from '@strapi/icons/Picture';
import Link from '@strapi/icons/Link'; import Link from '@strapi/icons/Link';
import Quote from '@strapi/icons/Quote'; import Quote from '@strapi/icons/Quote';
import More from '@strapi/icons/More'; import More from '@strapi/icons/More';
import { MainButtons, CustomIconButton, MoreButton, IconButtonGroupMargin } from './WysiwygStyles'; import {
MainButtons,
CustomIconButton,
MoreButton,
IconButtonGroupMargin,
CustomLinkIconButton,
} from './WysiwygStyles';
const WysiwygNav = ({ const WysiwygNav = ({
editorRef, editorRef,
@ -75,7 +81,7 @@ const WysiwygNav = ({
/> />
</MainButtons> </MainButtons>
<MoreButton disabled ref={buttonMoreRef} id="more" label="more" icon={<More />} /> <MoreButton disabled ref={buttonMoreRef} id="more" label="More" icon={<More />} />
</Flex> </Flex>
<Button onClick={onTogglePreviewMode} variant="tertiary" size="L" id="preview"> <Button onClick={onTogglePreviewMode} variant="tertiary" size="L" id="preview">
@ -135,7 +141,7 @@ const WysiwygNav = ({
ref={buttonMoreRef} ref={buttonMoreRef}
onClick={onTogglePopover} onClick={onTogglePopover}
id="more" id="more"
label="more" label="More"
icon={<More />} icon={<More />}
/> />
{visiblePopover && ( {visiblePopover && (
@ -179,7 +185,7 @@ const WysiwygNav = ({
name="Image" name="Image"
icon={<Image />} icon={<Image />}
/> />
<CustomIconButton <CustomLinkIconButton
onClick={() => onActionClick('Link', editorRef, onTogglePopover)} onClick={() => onActionClick('Link', editorRef, onTogglePopover)}
id="Link" id="Link"
label="Link" label="Link"

View File

@ -21,6 +21,13 @@ export const CustomIconButton = styled(IconButton)`
} }
`; `;
export const CustomLinkIconButton = styled(CustomIconButton)`
svg {
width: ${8 / 16}rem;
height: ${8 / 16}rem;
}
`;
export const MainButtons = styled(IconButtonGroup)` export const MainButtons = styled(IconButtonGroup)`
margin-left: ${({ theme }) => theme.spaces[4]}; margin-left: ${({ theme }) => theme.spaces[4]};
`; `;

View File

@ -73,7 +73,7 @@ const DynamicZoneList = ({ components }) => {
</CustomFlex> </CustomFlex>
<Box paddingTop={1}> <Box paddingTop={1}>
<Typography fontSize={1} textColor="neutral600" fontWeight="bold"> <Typography fontSize={1} textColor="neutral600" fontWeight="bold">
{get(componentLayouts, [componentUid, 'info', 'name'], '')} {get(componentLayouts, [componentUid, 'info', 'displayName'], '')}
</Typography> </Typography>
</Box> </Box>
</CustomLink> </CustomLink>

View File

@ -45,7 +45,7 @@ const EditSettingsView = ({ mainLayout, components, isContentTypeView, slug, upd
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
const { componentLayouts, initialData, modifiedData, metaToEdit, metaForm } = reducerState; const { componentLayouts, initialData, modifiedData, metaToEdit, metaForm } = reducerState;
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const modelName = get(mainLayout, ['info', isContentTypeView ? 'displayName' : 'name'], ''); const modelName = get(mainLayout, ['info', 'displayName'], '');
const attributes = get(modifiedData, ['attributes'], {}); const attributes = get(modifiedData, ['attributes'], {});
const entryTitleOptions = Object.keys(attributes).filter(attr => { const entryTitleOptions = Object.keys(attributes).filter(attr => {

View File

@ -3,9 +3,9 @@ import PropTypes from 'prop-types';
import { Box } from '@strapi/design-system/Box'; import { Box } from '@strapi/design-system/Box';
import { Flex } from '@strapi/design-system/Flex'; import { Flex } from '@strapi/design-system/Flex';
import { Td, Tr } from '@strapi/design-system/Table'; import { Td, Tr } from '@strapi/design-system/Table';
import { Text } from '@strapi/design-system/Text'; import { Text, EllipsisText } from '@strapi/design-system/Text';
import { IconButton } from '@strapi/design-system/IconButton'; import { IconButton } from '@strapi/design-system/IconButton';
import { stopPropagation, onRowClick } from '@strapi/helper-plugin'; import { stopPropagation, onRowClick, pxToRem } from '@strapi/helper-plugin';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
const RoleRow = ({ id, name, description, usersCount, icons }) => { const RoleRow = ({ id, name, description, usersCount, icons }) => {
@ -26,11 +26,11 @@ const RoleRow = ({ id, name, description, usersCount, icons }) => {
fn: icons[1].onClick, fn: icons[1].onClick,
})} })}
> >
<Td> <Td maxWidth={pxToRem(130)}>
<Text textColor="neutral800">{name}</Text> <EllipsisText textColor="neutral800">{name}</EllipsisText>
</Td> </Td>
<Td> <Td maxWidth={pxToRem(250)}>
<Text textColor="neutral800">{description}</Text> <EllipsisText textColor="neutral800">{description}</EllipsisText>
</Td> </Td>
<Td> <Td>
<Text textColor="neutral800">{usersCountText}</Text> <Text textColor="neutral800">{usersCountText}</Text>

View File

@ -331,7 +331,7 @@ const ListView = () => {
onValueChange={handleSelectAllCheckbox} onValueChange={handleSelectAllCheckbox}
/> />
</Th> </Th>
<Th> <Th width="20%">
<TableLabel textColor="neutral600"> <TableLabel textColor="neutral600">
{formatMessage({ {formatMessage({
id: 'Settings.webhooks.form.name', id: 'Settings.webhooks.form.name',
@ -339,7 +339,7 @@ const ListView = () => {
})} })}
</TableLabel> </TableLabel>
</Th> </Th>
<Th> <Th width="60%">
<TableLabel textColor="neutral600"> <TableLabel textColor="neutral600">
{formatMessage({ {formatMessage({
id: 'Settings.webhooks.form.url', id: 'Settings.webhooks.form.url',
@ -347,7 +347,7 @@ const ListView = () => {
})} })}
</TableLabel> </TableLabel>
</Th> </Th>
<Th width="30%"> <Th width="20%">
<TableLabel textColor="neutral600"> <TableLabel textColor="neutral600">
{formatMessage({ {formatMessage({
id: 'Settings.webhooks.list.th.status', id: 'Settings.webhooks.list.th.status',

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -16,7 +16,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -16,7 +16,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -16,7 +16,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -34,7 +34,7 @@ const productWithDP = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -38,7 +38,7 @@ const models = {
}, },
}, },
simpleCompo: { simpleCompo: {
name: 'simple-compo', displayName: 'simple-compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',
@ -46,7 +46,7 @@ const models = {
}, },
}, },
otherCompo: { otherCompo: {
name: 'compo-with-other-compo', displayName: 'compo-with-other-compo',
attributes: { attributes: {
compo: { compo: {
type: 'component', type: 'component',

View File

@ -23,7 +23,7 @@ const uploadImg = () => {
const components = { const components = {
singleMedia: { singleMedia: {
name: 'single-media', displayName: 'single-media',
attributes: { attributes: {
media: { media: {
type: 'media', type: 'media',
@ -31,7 +31,7 @@ const components = {
}, },
}, },
multipleMedia: { multipleMedia: {
name: 'multiple-media', displayName: 'multiple-media',
attributes: { attributes: {
media: { media: {
type: 'media', type: 'media',
@ -40,7 +40,7 @@ const components = {
}, },
}, },
withNested: { withNested: {
name: 'with-nested', displayName: 'with-nested',
attributes: { attributes: {
singleMedia: { singleMedia: {
type: 'component', type: 'component',

View File

@ -83,7 +83,7 @@ const ComponentBox = styled(Box)`
function ComponentCard({ component, dzName, index, isActive, isInDevelopmentMode, onClick }) { function ComponentCard({ component, dzName, index, isActive, isInDevelopmentMode, onClick }) {
const { modifiedData, removeComponentFromDynamicZone } = useDataManager(); const { modifiedData, removeComponentFromDynamicZone } = useDataManager();
const { const {
schema: { icon, name }, schema: { icon, displayName },
} = get(modifiedData, ['components', component], { } = get(modifiedData, ['components', component], {
schema: { icon: null }, schema: { icon: null },
}); });
@ -95,11 +95,16 @@ function ComponentCard({ component, dzName, index, isActive, isInDevelopmentMode
return ( return (
<button type="button" onClick={onClick}> <button type="button" onClick={onClick}>
<ComponentBox className={isActive ? 'active' : ''} borderRadius="borderRadius"> <ComponentBox
className={isActive ? 'active' : ''}
borderRadius="borderRadius"
paddingLeft={4}
paddingRight={4}
>
<Stack size={1} style={{ justifyContent: 'center', alignItems: 'center' }}> <Stack size={1} style={{ justifyContent: 'center', alignItems: 'center' }}>
<StyledFontAwesomeIcon icon={icon} /> <StyledFontAwesomeIcon icon={icon} />
<Text small bold> <Text small bold ellipsis style={{ width: `calc(${pxToRem(140)} - 32px)` }}>
{name} {displayName}
</Text> </Text>
</Stack> </Stack>
{isInDevelopmentMode && ( {isInDevelopmentMode && (

View File

@ -44,7 +44,7 @@ const ContentTypeBuilderNav = () => {
return ( return (
<React.Fragment key={section.name}> <React.Fragment key={section.name}>
<SubNavSection <SubNavSection
label={formatMessage({ id: title, defaultMessage: title.defaultMessage })} label={formatMessage({ id: title, defaultMessage: title })}
collapsable collapsable
badgeLabel={section.links.length.toString()} badgeLabel={section.links.length.toString()}
> >

View File

@ -1152,7 +1152,9 @@ exports[`<ContentTypeBuilderNav /> renders and matches the snapshot 1`] = `
</nav> </nav>
<div <div
class="c41 c42" class="c41 c42"
/> >
<div />
</div>
</div> </div>
<div <div
class="c43" class="c43"

View File

@ -32,7 +32,9 @@ const makeApp = () => {
<LanguageProvider messages={messages} localeNames={localeNames}> <LanguageProvider messages={messages} localeNames={localeNames}>
<Theme> <Theme>
<Router history={history}> <Router history={history}>
<Layout sideNav={<ContentTypeBuilderNav />} /> <Layout sideNav={<ContentTypeBuilderNav />}>
<div />
</Layout>
</Router> </Router>
</Theme> </Theme>
</LanguageProvider> </LanguageProvider>

View File

@ -43,7 +43,7 @@ const useContentTypeBuilderMenu = () => {
componentsGroupedByCategory[category].map(compo => ({ componentsGroupedByCategory[category].map(compo => ({
name: compo.uid, name: compo.uid,
to: `/plugins/${pluginId}/component-categories/${category}/${compo.uid}`, to: `/plugins/${pluginId}/component-categories/${category}/${compo.uid}`,
title: compo.schema.name, title: compo.schema.displayName,
})), })),
obj => obj.title obj => obj.title
), ),

View File

@ -545,13 +545,12 @@ const reducer = (state = initialState, action) =>
} }
case actions.UPDATE_SCHEMA: { case actions.UPDATE_SCHEMA: {
const { const {
data: { name, collectionName, category, icon, kind }, data: { displayName, category, icon, kind },
schemaType, schemaType,
uid, uid,
} = action; } = action;
draftState.modifiedData[schemaType].schema.collectionName = collectionName; draftState.modifiedData[schemaType].schema.displayName = displayName;
draftState.modifiedData[schemaType].schema.name = name;
if (action.schemaType === 'component') { if (action.schemaType === 'component') {
draftState.modifiedData.component.category = category; draftState.modifiedData.component.category = category;

View File

@ -715,8 +715,7 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
describe('UPDATE_SCHEMA', () => { describe('UPDATE_SCHEMA', () => {
it('Should update the modified data correctly if the schemaType is a content type', () => { it('Should update the modified data correctly if the schemaType is a content type', () => {
const data = { const data = {
name: 'test1', displayName: 'test1',
collectionName: 'newTest',
}; };
const state = { const state = {
@ -726,8 +725,7 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
contentType: { contentType: {
uid: 'test', uid: 'test',
schema: { schema: {
name: 'test', displayName: 'test',
collectionName: 'test',
attributes: [ attributes: [
{ {
name: 'something', name: 'something',
@ -751,8 +749,7 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
contentType: { contentType: {
uid: 'test', uid: 'test',
schema: { schema: {
name: 'test1', displayName: 'test1',
collectionName: 'newTest',
attributes: [ attributes: [
{ {
name: 'something', name: 'something',
@ -769,8 +766,7 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
it('Should update the modified data correctly if the schemaType is a component', () => { it('Should update the modified data correctly if the schemaType is a component', () => {
const data = { const data = {
name: 'newTest', displayName: 'newTest',
collectionName: 'newTest',
category: 'test', category: 'test',
icon: 'test', icon: 'test',
}; };
@ -782,9 +778,8 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
uid: 'test', uid: 'test',
category: 'default', category: 'default',
schema: { schema: {
name: 'test', displayName: 'test',
icon: 'book', icon: 'book',
collectionName: 'components_tests',
attributes: [ attributes: [
{ {
name: 'something', name: 'something',
@ -800,9 +795,8 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
uid: 'test', uid: 'test',
category: 'default', category: 'default',
schema: { schema: {
name: 'test', displayName: 'test',
icon: 'book', icon: 'book',
collectionName: 'components_tests',
attributes: [ attributes: [
{ {
name: 'something', name: 'something',
@ -827,9 +821,8 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
uid: 'test', uid: 'test',
category: 'test', category: 'test',
schema: { schema: {
name: 'newTest', displayName: 'newTest',
icon: 'test', icon: 'test',
collectionName: 'newTest',
attributes: [ attributes: [
{ {
name: 'something', name: 'something',
@ -845,9 +838,8 @@ describe('CTB | components | DataManagerProvider | reducer | basics actions ', (
uid: 'test', uid: 'test',
category: 'test', category: 'test',
schema: { schema: {
name: 'newTest', displayName: 'newTest',
icon: 'test', icon: 'test',
collectionName: 'newTest',
attributes: [ attributes: [
{ {
name: 'something', name: 'something',

View File

@ -7,7 +7,7 @@ import { CATEGORY_NAME_REGEX } from '../category';
const createComponentSchema = (usedComponentNames, reservedNames, category) => { const createComponentSchema = (usedComponentNames, reservedNames, category) => {
const shape = { const shape = {
name: yup displayName: yup
.string() .string()
.test({ .test({
name: 'nameAlreadyUsed', name: 'nameAlreadyUsed',

View File

@ -7,11 +7,11 @@ const componentForm = {
sectionTitle: null, sectionTitle: null,
items: [ items: [
{ {
name: `${prefix}name`, name: `${prefix}displayName`,
type: 'text', type: 'text',
intlLabel: { intlLabel: {
id: getTrad('modalForm.attribute.form.base.name'), id: getTrad('contentType.displayName.label'),
defaultMessage: 'Name', defaultMessage: 'Display Name',
}, },
}, },
{ {

View File

@ -221,10 +221,9 @@ const FormModal = () => {
actionType, actionType,
modalType, modalType,
data: { data: {
name: data.schema.name, displayName: data.schema.displayName,
category: data.category, category: data.category,
icon: data.schema.icon, icon: data.schema.icon,
collectionName: data.schema.collectionName,
}, },
}); });
} }
@ -496,10 +495,11 @@ const FormModal = () => {
return; return;
} }
} else if (isCreatingComponent) { // We are creating a component using the component modal from the left menu
} else if (modalType === 'component') {
if (isCreating) { if (isCreating) {
// Create the component schema // Create the component schema
const componentUid = createComponentUid(modifiedData.name, modifiedData.category); const componentUid = createComponentUid(modifiedData.displayName, modifiedData.category);
const { category, ...rest } = modifiedData; const { category, ...rest } = modifiedData;
createSchema(rest, 'component', componentUid, category); createSchema(rest, 'component', componentUid, category);
@ -652,7 +652,7 @@ const FormModal = () => {
const { category, type, ...rest } = componentToCreate; const { category, type, ...rest } = componentToCreate;
// Create a the component temp UID // Create a the component temp UID
// This could be refactored but I think it's more understandable to separate the logic // This could be refactored but I think it's more understandable to separate the logic
const componentUid = createComponentUid(componentToCreate.name, category); const componentUid = createComponentUid(componentToCreate.displayName, category);
// Create the component first and add it to the components data // Create the component first and add it to the components data
createSchema( createSchema(
// Component data // Component data
@ -686,7 +686,10 @@ const FormModal = () => {
if (isInFirstComponentStep) { if (isInFirstComponentStep) {
if (isCreatingComponentFromAView) { if (isCreatingComponentFromAView) {
const { category, type, ...rest } = modifiedData.componentToCreate; const { category, type, ...rest } = modifiedData.componentToCreate;
const componentUid = createComponentUid(modifiedData.componentToCreate.name, category); const componentUid = createComponentUid(
modifiedData.componentToCreate.displayName,
category
);
// Create the component first and add it to the components data // Create the component first and add it to the components data
createSchema( createSchema(
// Component data // Component data

View File

@ -189,11 +189,11 @@ const reducer = (state = initialState, action) =>
// This is run when the user has created a new component // This is run when the user has created a new component
const componentToCreate = state.modifiedData.componentToCreate; const componentToCreate = state.modifiedData.componentToCreate;
const modifiedData = { const modifiedData = {
name: componentToCreate.name, displayName: componentToCreate.displayName,
type: 'component', type: 'component',
repeatable: false, repeatable: false,
...action.options, ...action.options,
component: createComponentUid(componentToCreate.name, componentToCreate.category), component: createComponentUid(componentToCreate.displayName, componentToCreate.category),
}; };
const nextState = { const nextState = {

View File

@ -362,7 +362,7 @@ describe('CTB | components | FormModal | reducer | actions', () => {
createComponent: true, createComponent: true,
componentToCreate: { componentToCreate: {
type: 'component', type: 'component',
name: 'compo', displayName: 'compo',
icon: 'air-freshener', icon: 'air-freshener',
category: 'default', category: 'default',
}, },
@ -373,12 +373,12 @@ describe('CTB | components | FormModal | reducer | actions', () => {
...initialState, ...initialState,
componentToCreate: { componentToCreate: {
type: 'component', type: 'component',
name: 'compo', displayName: 'compo',
icon: 'air-freshener', icon: 'air-freshener',
category: 'default', category: 'default',
}, },
modifiedData: { modifiedData: {
name: 'compo', displayName: 'compo',
type: 'component', type: 'component',
repeatable: false, repeatable: false,
component: 'default.compo', component: 'default.compo',

View File

@ -36,7 +36,7 @@ const FormModalHeader = ({
let headers = []; let headers = [];
const schema = modifiedData?.[forTarget]?.[targetUid] || modifiedData?.[forTarget] || null; const schema = modifiedData?.[forTarget]?.[targetUid] || modifiedData?.[forTarget] || null;
let displayName = forTarget === 'contentType' ? schema?.schema.displayName : schema?.schema.name; let displayName = schema?.schema.displayName;
if (modalType === 'contentType') { if (modalType === 'contentType') {
icon = contentTypeKind; icon = contentTypeKind;
@ -78,7 +78,7 @@ const FormModalHeader = ({
headers = [ headers = [
{ {
label: displayName, label: displayName,
info: { category: schema?.category || null, name: schema?.schema.name }, info: { category: schema?.category || null, name: schema?.schema.displayName },
}, },
]; ];

View File

@ -39,7 +39,7 @@ const SelectComponent = ({
const compos = components.map(component => { const compos = components.map(component => {
return { return {
uid: component.uid, uid: component.uid,
label: component.schema.name, label: component.schema.displayName,
categoryName, categoryName,
}; };
}); });
@ -61,7 +61,11 @@ const SelectComponent = ({
if (isCreatingComponentWhileAddingAField) { if (isCreatingComponentWhileAddingAField) {
options = [ options = [
{ uid: value, label: componentToCreate.name, categoryName: componentToCreate.category }, {
uid: value,
label: componentToCreate.displayName,
categoryName: componentToCreate.category,
},
]; ];
} }

View File

@ -36,8 +36,8 @@ const SelectComponents = ({ dynamicZoneTarget, intlLabel, name, onChange, value
const [categoryName, components] = current; const [categoryName, components] = current;
const section = { const section = {
label: categoryName, label: categoryName,
children: components.map(({ uid, schema: { name } }) => { children: components.map(({ uid, schema: { displayName } }) => {
return { label: name, value: uid }; return { label: displayName, value: uid };
}), }),
}; };

View File

@ -53,7 +53,6 @@ const ListView = () => {
const contentTypeKind = get(modifiedData, [firstMainDataPath, 'schema', 'kind'], null); const contentTypeKind = get(modifiedData, [firstMainDataPath, 'schema', 'kind'], null);
const attributes = get(modifiedData, mainDataTypeAttributesPath, []); const attributes = get(modifiedData, mainDataTypeAttributesPath, []);
const currentDataName = get(initialData, [firstMainDataPath, 'schema', 'name'], '');
const isFromPlugin = has(initialData, [firstMainDataPath, 'plugin']); const isFromPlugin = has(initialData, [firstMainDataPath, 'plugin']);
const hasModelBeenModified = !isEqual(modifiedData, initialData); const hasModelBeenModified = !isEqual(modifiedData, initialData);
@ -76,10 +75,7 @@ const ListView = () => {
}); });
}; };
// TODO: fixme let label = get(modifiedData, [firstMainDataPath, 'schema', 'displayName'], '');
let label = isInContentTypeView
? get(modifiedData, [firstMainDataPath, 'schema', 'displayName'], '')
: get(modifiedData, [firstMainDataPath, 'schema', 'name'], '');
const kind = get(modifiedData, [firstMainDataPath, 'schema', 'kind'], ''); const kind = get(modifiedData, [firstMainDataPath, 'schema', 'kind'], '');
const isCreatingFirstContentType = match?.params.currentUID === 'create-content-type'; const isCreatingFirstContentType = match?.params.currentUID === 'create-content-type';
@ -192,9 +188,6 @@ const ListView = () => {
customRowComponent={props => <ListRow {...props} onClick={handleClickEditField} />} customRowComponent={props => <ListRow {...props} onClick={handleClickEditField} />}
addComponentToDZ={handleClickAddComponentToDZ} addComponentToDZ={handleClickAddComponentToDZ}
targetUid={targetUid} targetUid={targetUid}
dataType={forTarget}
dataTypeName={currentDataName}
mainTypeName={currentDataName}
editTarget={forTarget} editTarget={forTarget}
isMain isMain
/> />

View File

@ -47,6 +47,8 @@ exports[`<ListView /> renders and matches the snapshot 1`] = `
} }
.c63 { .c63 {
padding-right: 16px;
padding-left: 16px;
border-radius: borderRadius; border-radius: borderRadius;
} }
@ -678,6 +680,10 @@ exports[`<ListView /> renders and matches the snapshot 1`] = `
font-size: 0.75rem; font-size: 0.75rem;
line-height: 1.33; line-height: 1.33;
color: #32324d; color: #32324d;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.c33 { .c33 {
@ -3418,9 +3424,8 @@ exports[`<ListView /> renders and matches the snapshot 1`] = `
</svg> </svg>
<span <span
class="c31 c67" class="c31 c67"
> style="width: calc(8.75rem - 32px);"
Compo />
</span>
</div> </div>
<div <div
class="c68 c69" class="c68 c69"
@ -3469,9 +3474,8 @@ exports[`<ListView /> renders and matches the snapshot 1`] = `
</svg> </svg>
<span <span
class="c31 c67" class="c31 c67"
> style="width: calc(8.75rem - 32px);"
dish />
</span>
</div> </div>
<div <div
class="c68 c69" class="c68 c69"
@ -3520,9 +3524,8 @@ exports[`<ListView /> renders and matches the snapshot 1`] = `
</svg> </svg>
<span <span
class="c31 c67" class="c31 c67"
> style="width: calc(8.75rem - 32px);"
openingtimes />
</span>
</div> </div>
<div <div
class="c68 c69" class="c68 c69"
@ -3571,9 +3574,8 @@ exports[`<ListView /> renders and matches the snapshot 1`] = `
</svg> </svg>
<span <span
class="c31 c67" class="c31 c67"
> style="width: calc(8.75rem - 32px);"
restaurantservice />
</span>
</div> </div>
<div <div
class="c68 c69" class="c68 c69"

View File

@ -27,7 +27,7 @@ describe('Component validator', () => {
components: [], components: [],
component: { component: {
category: 'default', category: 'default',
name: 'mycompo', displayName: 'mycompo',
icon: 'american-sign-language-interpreting', icon: 'american-sign-language-interpreting',
attributes: { attributes: {
title: { title: {
@ -49,7 +49,7 @@ describe('Component validator', () => {
components: [], components: [],
component: { component: {
category: 'default', category: 'default',
name: 'mycompo', displayName: 'mycompo',
icon: 'american-sign-language-interpreting', icon: 'american-sign-language-interpreting',
attributes: { attributes: {
title: { title: {
@ -73,7 +73,7 @@ describe('Component validator', () => {
components: [], components: [],
component: { component: {
category: 'default', category: 'default',
name: 'mycompo', displayName: 'mycompo',
icon: 'american-sign-language-interpreting', icon: 'american-sign-language-interpreting',
attributes: { attributes: {
title: { title: {

View File

@ -44,7 +44,7 @@ describe('Content type validator', () => {
test('Throws when reserved names are used', async () => { test('Throws when reserved names are used', async () => {
const data = { const data = {
contentType: { contentType: {
name: 'test', displayName: 'test',
attributes: { attributes: {
thisIsReserved: { thisIsReserved: {
type: 'string', type: 'string',
@ -133,7 +133,7 @@ describe('Content type validator', () => {
{ {
uid: 'edit', uid: 'edit',
icon: 'star', icon: 'star',
name: 'test', displayName: 'test',
category: 'test', category: 'test',
attributes: { attributes: {
title: { title: {
@ -145,7 +145,7 @@ describe('Content type validator', () => {
{ {
tmpUID: 'random', tmpUID: 'random',
icon: 'star', icon: 'star',
name: 'test2', displayName: 'test2',
category: 'test', category: 'test',
attributes: { attributes: {
title: { title: {

View File

@ -16,10 +16,10 @@ const componentSchema = createSchema(VALID_TYPES, VALID_RELATIONS, {
modelType: modelTypes.COMPONENT, modelType: modelTypes.COMPONENT,
}) })
.shape({ .shape({
name: yup displayName: yup
.string() .string()
.min(1) .min(1)
.required('name.required'), .required('displayName.required'),
icon: yup icon: yup
.string() .string()
.nullable() .nullable()

View File

@ -1,11 +1,22 @@
'use strict'; 'use strict';
module.exports = () => ({ module.exports = () => ({
// TODO: Implement
getReservedNames() { getReservedNames() {
return { return {
models: [], models: ['boolean', 'date', 'date-time', 'dateTime', 'time', 'upload'],
attributes: [], attributes: [
'id',
'created_at',
'createdAt',
'updated_at',
'updatedAt',
'created_by',
'createdBy',
'updated_by',
'updatedBy',
'published_at',
'publishedAt',
],
}; };
// strapi.db.getReservedNames(); // strapi.db.getReservedNames();
}, },

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
const _ = require('lodash'); const _ = require('lodash');
const pluralize = require('pluralize');
const { formatAttributes, replaceTemporaryUIDs } = require('../utils/attributes'); const { formatAttributes, replaceTemporaryUIDs } = require('../utils/attributes');
const createBuilder = require('./schema-builder'); const createBuilder = require('./schema-builder');
@ -20,7 +19,7 @@ const formatComponent = component => {
apiId: modelName, apiId: modelName,
schema: { schema: {
icon: _.get(info, 'icon'), icon: _.get(info, 'icon'),
name: _.get(info, 'name') || _.upperFirst(pluralize(uid)), displayName: _.get(info, 'displayName'),
description: _.get(info, 'description', ''), description: _.get(info, 'description', ''),
connection, connection,
collectionName, collectionName,

View File

@ -14,10 +14,10 @@ module.exports = function createComponentBuilder() {
* Returns a uid from a component infos * Returns a uid from a component infos
* @param {Object} options options * @param {Object} options options
* @param {string} options.category component category * @param {string} options.category component category
* @param {string} options.name component name * @param {string} options.displayName component displayName
*/ */
createComponentUID({ category, name }) { createComponentUID({ category, displayName }) {
return `${nameToSlug(category)}.${nameToSlug(name)}`; return `${nameToSlug(category)}.${nameToSlug(displayName)}`;
}, },
createNewComponentUIDMap(components) { createNewComponentUIDMap(components) {
@ -39,17 +39,17 @@ module.exports = function createComponentBuilder() {
const handler = createSchemaHandler({ const handler = createSchemaHandler({
dir: path.join(strapi.dirs.components, nameToSlug(infos.category)), dir: path.join(strapi.dirs.components, nameToSlug(infos.category)),
filename: `${nameToSlug(infos.name)}.json`, filename: `${nameToSlug(infos.displayName)}.json`,
}); });
const collectionName = `components_${nameToCollectionName( const collectionName = `components_${nameToCollectionName(
infos.category infos.category
)}_${nameToCollectionName(pluralize(infos.name))}`; )}_${nameToCollectionName(pluralize(infos.displayName))}`;
handler handler
.setUID(uid) .setUID(uid)
.set('collectionName', collectionName) .set('collectionName', collectionName)
.set(['info', 'name'], infos.name) .set(['info', 'displayName'], infos.displayName)
.set(['info', 'icon'], infos.icon) .set(['info', 'icon'], infos.icon)
.set(['info', 'description'], infos.description) .set(['info', 'description'], infos.description)
.set('pluginOptions', infos.pluginOptions) .set('pluginOptions', infos.pluginOptions)
@ -98,7 +98,7 @@ module.exports = function createComponentBuilder() {
component component
.setUID(newUID) .setUID(newUID)
.setDir(newDir) .setDir(newDir)
.set(['info', 'name'], infos.name) .set(['info', 'displayName'], infos.displayName)
.set(['info', 'icon'], infos.icon) .set(['info', 'icon'], infos.icon)
.set(['info', 'description'], infos.description) .set(['info', 'description'], infos.description)
.set('pluginOptions', infos.pluginOptions) .set('pluginOptions', infos.pluginOptions)

View File

@ -38,7 +38,7 @@ describe('Content Type Builder - Components', () => {
'component.category': ['category.required'], 'component.category': ['category.required'],
'component.icon': ['icon.required'], 'component.icon': ['icon.required'],
'component.attributes': ['attributes.required'], 'component.attributes': ['attributes.required'],
'component.name': ['name.required'], 'component.displayName': ['displayName.required'],
}, },
}); });
}); });
@ -51,7 +51,7 @@ describe('Content Type Builder - Components', () => {
component: { component: {
category: 'default', category: 'default',
icon: 'default', icon: 'default',
name: 'Some Component', displayName: 'Some Component',
pluginOptions: { pluginOptions: {
pluginName: { pluginName: {
option: true, option: true,
@ -92,7 +92,7 @@ describe('Content Type Builder - Components', () => {
component: { component: {
category: 'default', category: 'default',
icon: 'default', icon: 'default',
name: 'someComponent', displayName: 'someComponent',
attributes: {}, attributes: {},
}, },
}, },
@ -121,7 +121,7 @@ describe('Content Type Builder - Components', () => {
expect(el).toMatchObject({ expect(el).toMatchObject({
uid: expect.any(String), uid: expect.any(String),
schema: expect.objectContaining({ schema: expect.objectContaining({
name: expect.any(String), displayName: expect.any(String),
description: expect.any(String), description: expect.any(String),
collectionName: expect.any(String), collectionName: expect.any(String),
attributes: expect.objectContaining({}), attributes: expect.objectContaining({}),
@ -157,7 +157,7 @@ describe('Content Type Builder - Components', () => {
category: 'default', category: 'default',
schema: { schema: {
icon: 'default', icon: 'default',
name: 'Some Component', displayName: 'Some Component',
description: '', description: '',
collectionName: 'components_default_some_components', collectionName: 'components_default_some_components',
pluginOptions: { pluginOptions: {
@ -215,7 +215,7 @@ describe('Content Type Builder - Components', () => {
error: { error: {
'component.category': ['category.required'], 'component.category': ['category.required'],
'component.icon': ['icon.required'], 'component.icon': ['icon.required'],
'component.name': ['name.required'], 'component.displayName': ['displayName.required'],
}, },
}); });
}); });
@ -228,7 +228,7 @@ describe('Content Type Builder - Components', () => {
component: { component: {
category: 'default', category: 'default',
icon: 'default', icon: 'default',
name: 'New Component', displayName: 'New Component',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',
@ -262,7 +262,7 @@ describe('Content Type Builder - Components', () => {
data: { data: {
uid: 'default.some-component', uid: 'default.some-component',
schema: { schema: {
name: 'New Component', displayName: 'New Component',
pluginOptions: { pluginOptions: {
pluginName: { pluginName: {
option: false, option: false,

View File

@ -127,7 +127,7 @@ program
// `$ strapi generate:template <directory>` // `$ strapi generate:template <directory>`
program program
.command('generate:template <directory>') .command('templates:generate <directory>')
.description('Generate template from Strapi project') .description('Generate template from Strapi project')
.action(getLocalScript('generate-template')); .action(getLocalScript('generate-template'));

View File

@ -30,23 +30,20 @@ describe('generate:template command', () => {
expect(fse.ensureDir).toHaveBeenCalledWith(templatePath); expect(fse.ensureDir).toHaveBeenCalledWith(templatePath);
}); });
it.each(['api', 'components', 'config/functions/bootstrap.js', 'data'])( it.each(['src', 'data'])('copies folder %s', async item => {
'copies folder %s', // Mock the empty directory arg
async item => { fse.pathExists.mockReturnValueOnce(false);
// Mock the empty directory arg // Mock the folder exists
fse.pathExists.mockReturnValueOnce(false); fse.pathExists.mockReturnValue(true);
// Mock the folder exists const directory = '../test-dir';
fse.pathExists.mockReturnValue(true); const rootPath = resolve(directory);
const directory = '../test-dir'; const templatePath = join(rootPath, 'template');
const rootPath = resolve(directory);
const templatePath = join(rootPath, 'template');
await exportTemplate(directory); await exportTemplate(directory);
expect(fse.pathExists).toHaveBeenCalledWith(join(process.cwd(), item)); expect(fse.pathExists).toHaveBeenCalledWith(join(process.cwd(), item));
expect(fse.copy).toHaveBeenCalledWith(join(process.cwd(), item), join(templatePath, item)); expect(fse.copy).toHaveBeenCalledWith(join(process.cwd(), item), join(templatePath, item));
} });
);
it('creates a json config file', async () => { it('creates a json config file', async () => {
fse.pathExists.mockReturnValue(false); fse.pathExists.mockReturnValue(false);

View File

@ -6,7 +6,7 @@ const chalk = require('chalk');
const inquirer = require('inquirer'); const inquirer = require('inquirer');
// All directories that a template could need // All directories that a template could need
const TEMPLATE_CONTENT = ['api', 'components', 'config/functions/bootstrap.js', 'data']; const TEMPLATE_CONTENT = ['src', 'data'];
/** /**
* *
@ -54,10 +54,9 @@ async function writeTemplateJson(rootPath) {
* @returns boolean * @returns boolean
*/ */
async function templateConfigExists(rootPath) { async function templateConfigExists(rootPath) {
const jsonConfig = await fse.pathExists(join(rootPath, 'template.json')); const configExists = await fse.pathExists(join(rootPath, 'template.json'));
const functionConfig = await fse.pathExists(join(rootPath, 'template.js')); console.log(`checking: ${join(rootPath, 'template.json')}. result ${configExists}`);
return configExists;
return jsonConfig || functionConfig;
} }
module.exports = async function generateTemplate(directory) { module.exports = async function generateTemplate(directory) {

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ let data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -14,7 +14,7 @@ const data = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -28,7 +28,7 @@ const product = {
}; };
const compo = { const compo = {
name: 'compo', displayName: 'compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -8,7 +8,7 @@ let strapi;
let rq; let rq;
const component = { const component = {
name: 'somecomponent', displayName: 'somecomponent',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',

View File

@ -38,7 +38,7 @@ const models = {
}, },
}, },
simpleCompo: { simpleCompo: {
name: 'simple-compo', displayName: 'simple-compo',
attributes: { attributes: {
name: { name: {
type: 'string', type: 'string',
@ -46,7 +46,7 @@ const models = {
}, },
}, },
otherCompo: { otherCompo: {
name: 'compo-with-other-compo', displayName: 'compo-with-other-compo',
attributes: { attributes: {
compo: { compo: {
type: 'component', type: 'component',

View File

@ -23,7 +23,7 @@ const uploadImg = () => {
const components = { const components = {
singleMedia: { singleMedia: {
name: 'one-media', displayName: 'one-media',
attributes: { attributes: {
media: { media: {
type: 'media', type: 'media',
@ -31,7 +31,7 @@ const components = {
}, },
}, },
multipleMedia: { multipleMedia: {
name: 'many-media', displayName: 'many-media',
attributes: { attributes: {
media: { media: {
type: 'media', type: 'media',
@ -40,7 +40,7 @@ const components = {
}, },
}, },
withNested: { withNested: {
name: 'with-nested', displayName: 'with-nested',
attributes: { attributes: {
singleMedia: { singleMedia: {
type: 'component', type: 'component',

View File

@ -97,7 +97,7 @@ const contentTypes = {
const components = { const components = {
comp: { comp: {
name: 'comp', displayName: 'comp',
attributes: { attributes: {
countries: { countries: {
type: 'relation', type: 'relation',

View File

@ -18,19 +18,7 @@ sentry.init({
dsn: 'https://841d2b2c9b4d4b43a4cde92794cb705a@sentry.io/1762059', dsn: 'https://841d2b2c9b4d4b43a4cde92794cb705a@sentry.io/1762059',
}); });
// TODO: to remove after the templates are updated for V4
const warnTemplatesAreDisabled = cliArguments => {
if (cliArguments.template) {
console.log(
'❌ Templates have been disabled for the Beta version of V4 and will be re-enabled soon!'
);
process.exit();
}
};
module.exports = (projectDirectory, cliArguments) => { module.exports = (projectDirectory, cliArguments) => {
warnTemplatesAreDisabled(cliArguments);
checkRequirements(); checkRequirements();
const rootPath = resolve(projectDirectory); const rootPath = resolve(projectDirectory);

View File

@ -1,94 +0,0 @@
'use strict';
const fetch = require('node-fetch');
const tar = require('tar');
const parseGitUrl = require('git-url-parse');
const chalk = require('chalk');
const stopProcess = require('./stop-process');
function parseShorthand(template) {
// Determine if it is comes from another owner
if (template.includes('/')) {
const [owner, partialName] = template.split('/');
const name = `strapi-template-${partialName}`;
return {
name,
fullName: `${owner}/${name}`,
};
}
const name = `strapi-template-${template}`;
return {
name,
fullName: `strapi/${name}`,
};
}
/**
* @param {string} repo The full name of the repository.
*/
async function getDefaultBranch(repo) {
const response = await fetch(`https://api.github.com/repos/${repo}`);
if (!response.ok) {
stopProcess(
`Could not find the information for ${chalk.yellow(
repo
)}. Make sure it is publicly accessible on github.`
);
}
const { default_branch } = await response.json();
return default_branch;
}
/**
* @param {string} template GitHub URL or shorthand to a template project.
*/
async function getRepoInfo(template) {
const { name, full_name: fullName, ref, filepath, protocols, source } = parseGitUrl(template);
if (protocols.length === 0) {
const repoInfo = parseShorthand(template);
return {
...repoInfo,
branch: await getDefaultBranch(repoInfo.fullName),
usedShorthand: true,
};
}
if (source !== 'github.com') {
stopProcess(`GitHub URL not found for: ${chalk.yellow(template)}.`);
}
let branch;
if (ref) {
// Append the filepath to the parsed ref since a branch name could contain '/'
// If so, the rest of the branch name will be considered 'filepath' by 'parseGitUrl'
branch = filepath ? `${ref}/${filepath}` : ref;
} else {
branch = await getDefaultBranch(fullName);
}
return { name, fullName, branch };
}
/**
* @param {string} repoInfo GitHub repository information (full name, branch...).
* @param {string} tmpDir Path to the destination temporary directory.
*/
async function downloadGitHubRepo(repoInfo, tmpDir) {
// Download from GitHub
const { fullName, branch } = repoInfo;
const codeload = `https://codeload.github.com/${fullName}/tar.gz/${branch}`;
const response = await fetch(codeload);
if (!response.ok) {
throw Error(`Could not download the ${chalk.yellow(fullName)} repository.`);
}
await new Promise(resolve => {
response.body.pipe(tar.extract({ strip: 1, cwd: tmpDir })).on('close', resolve);
});
}
module.exports = { getRepoInfo, downloadGitHubRepo };

View File

@ -0,0 +1,61 @@
'use strict';
const path = require('path');
const execa = require('execa');
const chalk = require('chalk');
/**
* Gets the package version on npm. Will fail if the package does not exist
* @param {string} packageName - Name to look up on npm, may include a specific version
* @returns {Object}
*/
async function getPackageInfo(packageName) {
const { stdout } = await execa.shell(`npm view ${packageName} name version --silent`);
// Use regex to parse name and version from CLI result
const [name, version] = stdout.match(/(?<=')(.*?)(?=')/gm);
return { name, version };
}
/**
* @param {string} template - The name of the template as provided by the user.
* @returns {Object} - The full name of the template package's name on npm
*/
async function getTemplatePackageInfo(template) {
// Check if template is a shorthand
try {
const longhand = `@strapi/template-${template}`;
const packageInfo = await getPackageInfo(longhand);
// Hasn't crashed so it is indeed a shorthand
return packageInfo;
} catch (error) {
// Ignore error, we now know it's not a shorthand
}
// Fetch version of the non-shorthand package
try {
return getPackageInfo(template);
} catch (error) {
throw new Error(`Could not find package ${chalk.green('template.json')} on npm`);
}
}
/**
* @param {Object} packageInfo - Template's npm package information
* @param {string} packageInfo.name
* @param {string} packageInfo.version
* @param {string} parentDir - Path inside of which we install the template.
*/
async function downloadNpmTemplate({ name, version }, parentDir) {
// Download from npm
await execa.shell(`npm install ${name}@${version} --no-save --silent`, {
cwd: parentDir,
});
// Return the path of the actual template
const exactTemplatePath = require.resolve(path.join('node_modules', name), {
paths: [parentDir],
});
// const exactTemplatePath = path.resolve(parentDir, 'node_modules', name);
return exactTemplatePath;
}
module.exports = { getTemplatePackageInfo, downloadNpmTemplate };

View File

@ -3,99 +3,83 @@
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const fse = require('fs-extra'); const fse = require('fs-extra');
const _ = require('lodash'); const _ = require('lodash/fp');
const chalk = require('chalk'); const chalk = require('chalk');
const { getTemplatePackageInfo, downloadNpmTemplate } = require('./fetch-npm-template');
const { getRepoInfo, downloadGitHubRepo } = require('./fetch-github');
// Specify all the files and directories a template can have // Specify all the files and directories a template can have
const allowChildren = '*'; const allowFile = Symbol();
const allowChildren = Symbol();
const allowedTemplateContents = { const allowedTemplateContents = {
'README.md': true, 'README.md': allowFile,
'.env.example': true, '.env.example': allowFile,
src: { 'package.json': allowFile,
api: allowChildren, src: allowChildren,
components: allowChildren,
plugins: allowChildren,
},
config: {
functions: allowChildren,
},
data: allowChildren, data: allowChildren,
database: allowChildren,
public: allowChildren, public: allowChildren,
scripts: allowChildren, scripts: allowChildren,
}; };
/** /**
* merge template with new project being created * Merge template with new project being created
* @param {string} scope project creation params * @param {string} scope project creation params
* @param {string} rootPath project path * @param {string} rootPath project path
*/ */
module.exports = async function mergeTemplate(scope, rootPath) { module.exports = async function mergeTemplate(scope, rootPath) {
// Parse template info let templatePath;
const repoInfo = await getRepoInfo(scope.template); let templateParentPath;
const { fullName } = repoInfo; let templatePackageInfo = {};
console.log(`Installing ${chalk.yellow(fullName)} template.`); const isLocalTemplate = ['./', '../', '/'].some(filePrefix =>
scope.template.startsWith(filePrefix)
);
// Download template repository to a temporary directory if (isLocalTemplate) {
const templatePath = await fse.mkdtemp(path.join(os.tmpdir(), 'strapi-')); // Template is a local directory
await downloadGitHubRepo(repoInfo, templatePath); console.log('Installing local template.');
templatePath = path.resolve(rootPath, '..', scope.template);
} else {
// Template should be an npm package. Fetch template info
templatePackageInfo = await getTemplatePackageInfo(scope.template);
console.log(`Installing ${chalk.yellow(templatePackageInfo.name)} template.`);
// Download template repository to a temporary directory
templateParentPath = await fse.mkdtemp(path.join(os.tmpdir(), 'strapi-'));
templatePath = await downloadNpmTemplate(templatePackageInfo, templateParentPath);
}
// Make sure the downloaded template matches the required format // Make sure the downloaded template matches the required format
const { templateConfig } = await checkTemplateRootStructure(templatePath, scope); const templateConfig = await checkTemplateRootStructure(templatePath, scope);
await checkTemplateContentsStructure(path.resolve(templatePath, 'template')); await checkTemplateContentsStructure(path.resolve(templatePath, 'template'));
// Merge contents of the template in the project // Merge contents of the template in the project
const fullTemplateUrl = `https://github.com/${fullName}`; await mergePackageJSON({ rootPath, templateConfig, templatePackageInfo });
await mergePackageJSON(rootPath, templateConfig, fullTemplateUrl);
await mergeFilesAndDirectories(rootPath, templatePath); await mergeFilesAndDirectories(rootPath, templatePath);
// Delete the downloaded template repo // Delete the template directory if it was downloaded
await fse.remove(templatePath); if (!isLocalTemplate) {
await fse.remove(templateParentPath);
}
}; };
// Make sure the template has the required top-level structure /**
async function checkTemplateRootStructure(templatePath, scope) { * Make sure the template has the required top-level structure
// Make sure the root of the repo has a template.json or a template.js file * @param {string} templatePath - Path of the locally downloaded template
* @returns {Object} - The template config object
*/
async function checkTemplateRootStructure(templatePath) {
// Make sure the root of the repo has a template.json file
const templateJsonPath = path.join(templatePath, 'template.json'); const templateJsonPath = path.join(templatePath, 'template.json');
const templateFunctionPath = path.join(templatePath, 'template.js'); const templateJsonExists = await fse.exists(templateJsonPath);
if (!templateJsonExists) {
// Store the template config, whether it comes from a JSON or a function throw new Error(`A template must have a ${chalk.green('template.json')} root file`);
let templateConfig = {}; }
const templateJsonStat = await fse.stat(templateJsonPath);
const hasJsonConfig = fse.existsSync(templateJsonPath); if (!templateJsonStat.isFile()) {
if (hasJsonConfig) { throw new Error(`A template's ${chalk.green('template.json')} must be a file`);
const jsonStat = await fse.stat(templateJsonPath);
if (!jsonStat.isFile()) {
throw new Error(`A template's ${chalk.green('template.json')} must be a file`);
}
templateConfig = require(templateJsonPath);
} }
const hasFunctionConfig = fse.existsSync(templateFunctionPath); const templateConfig = require(templateJsonPath);
if (hasFunctionConfig) {
const functionStat = await fse.stat(templateFunctionPath);
if (!functionStat.isFile()) {
throw new Error(`A template's ${chalk.green('template.js')} must be a file`);
}
// Get the config by passing the scope to the function
templateConfig = require(templateFunctionPath)(scope);
}
// Make sure there's exactly one template config file
if (!hasJsonConfig && !hasFunctionConfig) {
throw new Error(
`A template must have either a ${chalk.green('template.json')} or a ${chalk.green(
'template.js'
)} root file`
);
} else if (hasJsonConfig && hasFunctionConfig) {
throw new Error(
`A template cannot have both ${chalk.green('template.json')} and ${chalk.green(
'template.js'
)} root files`
);
}
// Make sure the root of the repo has a template folder // Make sure the root of the repo has a template folder
const templateDirPath = path.join(templatePath, 'template'); const templateDirPath = path.join(templatePath, 'template');
@ -112,21 +96,24 @@ async function checkTemplateRootStructure(templatePath, scope) {
throw error; throw error;
} }
return { templateConfig }; return templateConfig;
} }
// Traverse template tree to make sure each file and folder is allowed /**
* Traverse template tree to make sure each file and folder is allowed
* @param {string} templateContentsPath
*/
async function checkTemplateContentsStructure(templateContentsPath) { async function checkTemplateContentsStructure(templateContentsPath) {
// Recursively check if each item in a directory is allowed // Recursively check if each item in a directory is allowed
const checkPathContents = (pathToCheck, parents) => { const checkPathContents = async (pathToCheck, parents) => {
const contents = fse.readdirSync(pathToCheck); const contents = await fse.readdir(pathToCheck);
contents.forEach(item => { for (const item of contents) {
const nextParents = [...parents, item]; const nextParents = [...parents, item];
const matchingTreeValue = _.get(allowedTemplateContents, nextParents); const matchingTreeValue = _.get(nextParents, allowedTemplateContents);
// Treat files and directories separately // Treat files and directories separately
const itemPath = path.resolve(pathToCheck, item); const itemPath = path.resolve(pathToCheck, item);
const isDirectory = fse.statSync(itemPath).isDirectory(); const isDirectory = (await fse.stat(itemPath)).isDirectory();
if (matchingTreeValue === undefined) { if (matchingTreeValue === undefined) {
// Unknown paths are forbidden // Unknown paths are forbidden
@ -135,7 +122,7 @@ async function checkTemplateContentsStructure(templateContentsPath) {
); );
} }
if (matchingTreeValue === true) { if (matchingTreeValue === allowFile) {
if (!isDirectory) { if (!isDirectory) {
// All good, the file is allowed // All good, the file is allowed
return; return;
@ -153,20 +140,28 @@ async function checkTemplateContentsStructure(templateContentsPath) {
return; return;
} }
// Check if the contents of the directory are allowed // Check if the contents of the directory are allowed
checkPathContents(itemPath, nextParents); await checkPathContents(itemPath, nextParents);
} else { } else {
throw Error( throw Error(
`Illegal template structure, unknow file ${chalk.green(nextParents.join('/'))}` `Illegal template structure, unknow file ${chalk.green(nextParents.join('/'))}`
); );
} }
}); }
}; };
checkPathContents(templateContentsPath, []); await checkPathContents(templateContentsPath, []);
} }
// Merge the template's template.json into the Strapi project's package.json /**
async function mergePackageJSON(rootPath, templateConfig, templateUrl) { * Merge the template's template.json into the Strapi project's package.json
* @param {Object} config
* @param {string} config.rootPath
* @param {string} config.templateConfig
* @param {Object} config.templatePackageInfo - Info about the template's package on npm
* @param {Object} config.templatePackageInfo.name - The name of the template's package on npm
* @param {Object} config.templatePackageInfo.version - The name of the template's package on npm
*/
async function mergePackageJSON({ rootPath, templateConfig, templatePackageInfo }) {
// Import the package.json as an object // Import the package.json as an object
const packageJSON = require(path.resolve(rootPath, 'package.json')); const packageJSON = require(path.resolve(rootPath, 'package.json'));
@ -181,10 +176,12 @@ async function mergePackageJSON(rootPath, templateConfig, templateUrl) {
} }
// Use lodash to deeply merge them // Use lodash to deeply merge them
const mergedConfig = _.merge(packageJSON, templateConfig.package); const mergedConfig = _.merge(templateConfig.package, packageJSON);
// Add starter info to package.json // Add template info to package.json
_.set(mergedConfig, 'strapi.template', templateUrl); if (templatePackageInfo.name) {
_.set('strapi.template', templatePackageInfo.name, mergedConfig);
}
// Save the merged config as the new package.json // Save the merged config as the new package.json
const packageJSONPath = path.join(rootPath, 'package.json'); const packageJSONPath = path.join(rootPath, 'package.json');

View File

@ -17,7 +17,6 @@
"chalk": "^4.1.1", "chalk": "^4.1.1",
"execa": "^1.0.0", "execa": "^1.0.0",
"fs-extra": "^9.1.0", "fs-extra": "^9.1.0",
"git-url-parse": "^11.4.4",
"inquirer": "^6.3.1", "inquirer": "^6.3.1",
"lodash": "4.17.21", "lodash": "4.17.21",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",

View File

@ -4,7 +4,7 @@
* `{{ name }}` middleware. * `{{ name }}` middleware.
*/ */
module.exports = async (config, { strapi }) => { module.exports = (config, { strapi }) => {
// Add your own logic here. // Add your own logic here.
return async (ctx, next) => { return async (ctx, next) => {
strapi.log.info('In {{ name }} middleware.'); strapi.log.info('In {{ name }} middleware.');

View File

@ -29,7 +29,7 @@ const rgbColorComponent = {
type: 'integer', type: 'integer',
}, },
}, },
name: 'rgbColor', displayName: 'rgbColor',
}; };
const documentModel = { const documentModel = {

View File

@ -11883,7 +11883,7 @@ git-url-parse@11.4.4:
dependencies: dependencies:
git-up "^4.0.0" git-up "^4.0.0"
git-url-parse@^11.1.2, git-url-parse@^11.4.4: git-url-parse@^11.1.2:
version "11.6.0" version "11.6.0"
resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605" resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-11.6.0.tgz#c634b8de7faa66498a2b88932df31702c67df605"
integrity sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g== integrity sha512-WWUxvJs5HsyHL6L08wOusa/IXYtMuCAhrMmnTjQPpBU0TTHyDhnOATNH3xNQz7YOQUsqIIPTGr4xiVti1Hsk5g==