mirror of
https://github.com/strapi/strapi.git
synced 2025-12-25 22:23:10 +00:00
Merge branch 'master' of github.com:strapi/strapi into content-manager-filters
This commit is contained in:
commit
8302d32d71
@ -10,7 +10,8 @@
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
"mocha": true,
|
||||
"jest": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
|
||||
@ -6,7 +6,7 @@ Installation is very easy and only takes a few seconds.
|
||||
|
||||
Please make sure your computer/server meets the following requirements:
|
||||
- [Node.js](https://nodejs.org) >= 9: Node.js is a server platform which runs JavaScript. Installation guide [here](https://nodejs.org/en/download/).
|
||||
- [MongoDB](https://www.mongodb.com/): MongoDB is a powerful document store. Installation guide [here](https://www.mongodb.com/download-center?j#community).
|
||||
- [MongoDB](https://www.mongodb.com/) >= 2.6: MongoDB is a powerful document store. Installation guide [here](https://www.mongodb.com/download-center?j#community).
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"version": "3.0.0-alpha.12.2",
|
||||
"devDependencies": {
|
||||
"assert": "~1.3.0",
|
||||
"axios": "^0.18.0",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"chalk": "^2.4.1",
|
||||
"eslint": "^3.12.2",
|
||||
@ -11,11 +12,14 @@
|
||||
"eslint-plugin-redux-saga": "^0.3.0",
|
||||
"gitbook-cli": "^2.3.2",
|
||||
"istanbul": "~0.4.2",
|
||||
"jest": "^22.4.3",
|
||||
"jest-cli": "^22.4.4",
|
||||
"lerna": "^2.0.0",
|
||||
"lodash": "^4.17.5",
|
||||
"mocha": "~2.4.5",
|
||||
"pre-commit": "~1.1.2",
|
||||
"redux-saga": "^0.14.3",
|
||||
"request": "^2.87.0",
|
||||
"shelljs": "^0.7.7",
|
||||
"strapi-lint": "file:packages/strapi-lint"
|
||||
},
|
||||
@ -29,7 +33,7 @@
|
||||
"setup:build": "npm run setup --build",
|
||||
"setup": "npm run clean:all && npm install ./packages/strapi-lint --save-dev && npm install && node ./scripts/setup.js && npm run clean",
|
||||
"lint": "node ./scripts/lint.js",
|
||||
"test": "echo \"Error: no test specified yet...\"",
|
||||
"test": "node ./test/start.js",
|
||||
"prettier": "node ./packages/strapi-lint/lib/internals/prettier/index.js"
|
||||
},
|
||||
"author": {
|
||||
|
||||
@ -19,10 +19,7 @@ import { get, includes, isFunction, map, omit } from 'lodash';
|
||||
import { compose } from 'redux';
|
||||
|
||||
// Actions required for disabling and enabling the OverlayBlocker
|
||||
import {
|
||||
disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker,
|
||||
} from 'actions/overlayBlocker';
|
||||
import { disableGlobalOverlayBlocker, enableGlobalOverlayBlocker } from 'actions/overlayBlocker';
|
||||
|
||||
import { pluginLoaded, updatePlugin } from 'containers/App/actions';
|
||||
import {
|
||||
@ -63,17 +60,16 @@ import styles from './styles.scss';
|
||||
|
||||
const PLUGINS_TO_BLOCK_PRODUCTION = ['content-type-builder', 'settings-manager'];
|
||||
|
||||
export class AdminPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
export class AdminPage extends React.Component {
|
||||
// eslint-disable-line react/prefer-stateless-function
|
||||
state = { hasAlreadyRegistereOtherPlugins: false };
|
||||
|
||||
getChildContext = () => (
|
||||
{
|
||||
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
|
||||
plugins: this.props.plugins,
|
||||
updatePlugin: this.props.updatePlugin,
|
||||
}
|
||||
);
|
||||
getChildContext = () => ({
|
||||
disableGlobalOverlayBlocker: this.props.disableGlobalOverlayBlocker,
|
||||
enableGlobalOverlayBlocker: this.props.enableGlobalOverlayBlocker,
|
||||
plugins: this.props.plugins,
|
||||
updatePlugin: this.props.updatePlugin,
|
||||
});
|
||||
|
||||
componentDidMount() {
|
||||
this.checkLogin(this.props);
|
||||
@ -91,7 +87,10 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
|
||||
}
|
||||
}
|
||||
|
||||
if (get(nextProps.plugins.toJS(), ['users-permissions', 'hasAdminUser']) !== get(this.props.plugins.toJS(), ['users-permissions', 'hasAdminUser'])) {
|
||||
if (
|
||||
get(nextProps.plugins.toJS(), ['users-permissions', 'hasAdminUser']) !==
|
||||
get(this.props.plugins.toJS(), ['users-permissions', 'hasAdminUser'])
|
||||
) {
|
||||
this.checkLogin(nextProps, true);
|
||||
}
|
||||
|
||||
@ -106,23 +105,34 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
|
||||
return;
|
||||
}
|
||||
|
||||
const endPoint = this.hasAdminUser(props) ? 'login': 'register';
|
||||
const endPoint = this.hasAdminUser(props) ? 'login' : 'register';
|
||||
this.props.history.push(`/plugins/users-permissions/auth/${endPoint}`);
|
||||
}
|
||||
|
||||
if (!this.isUrlProtected(props) && includes(props.location.pathname, 'auth/register') && this.hasAdminUser(props) && !skipAction) {
|
||||
if (
|
||||
!this.isUrlProtected(props) &&
|
||||
includes(props.location.pathname, 'auth/register') &&
|
||||
this.hasAdminUser(props) &&
|
||||
!skipAction
|
||||
) {
|
||||
this.props.history.push('/plugins/users-permissions/auth/login');
|
||||
}
|
||||
|
||||
if (props.hasUserPlugin && !this.isUrlProtected(props) && !includes(props.location.pathname, 'auth/register') && !this.hasAdminUser(props)) {
|
||||
if (
|
||||
props.hasUserPlugin &&
|
||||
!this.isUrlProtected(props) &&
|
||||
!includes(props.location.pathname, 'auth/register') &&
|
||||
!this.hasAdminUser(props)
|
||||
) {
|
||||
this.props.history.push('/plugins/users-permissions/auth/register');
|
||||
}
|
||||
|
||||
if (!props.hasUserPlugin || auth.getToken() && !this.state.hasAlreadyRegistereOtherPlugins) {
|
||||
if (!props.hasUserPlugin || (auth.getToken() && !this.state.hasAlreadyRegistereOtherPlugins)) {
|
||||
map(omit(this.props.plugins.toJS(), ['users-permissions', 'email']), plugin => {
|
||||
switch (true) {
|
||||
case isFunction(plugin.bootstrap) && isFunction(plugin.pluginRequirements):
|
||||
plugin.pluginRequirements(plugin)
|
||||
plugin
|
||||
.pluginRequirements(plugin)
|
||||
.then(plugin => {
|
||||
return plugin.bootstrap(plugin);
|
||||
})
|
||||
@ -140,31 +150,35 @@ export class AdminPage extends React.Component { // eslint-disable-line react/pr
|
||||
|
||||
this.setState({ hasAlreadyRegistereOtherPlugins: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
hasUserPluginLoaded = (props) => typeof get(props.plugins.toJS(), ['users-permissions', 'hasAdminUser']) !== 'undefined';
|
||||
hasUserPluginLoaded = props =>
|
||||
typeof get(props.plugins.toJS(), ['users-permissions', 'hasAdminUser']) !== 'undefined';
|
||||
|
||||
hasAdminUser = (props) => get(props.plugins.toJS(), ['users-permissions', 'hasAdminUser']);
|
||||
hasAdminUser = props => get(props.plugins.toJS(), ['users-permissions', 'hasAdminUser']);
|
||||
|
||||
isUrlProtected = (props) => !includes(props.location.pathname, get(props.plugins.toJS(), ['users-permissions', 'nonProtectedUrl']));
|
||||
isUrlProtected = props =>
|
||||
!includes(props.location.pathname, get(props.plugins.toJS(), ['users-permissions', 'nonProtectedUrl']));
|
||||
|
||||
shouldDisplayLogout = () => auth.getToken() && this.props.hasUserPlugin && this.isUrlProtected(this.props);
|
||||
|
||||
showLeftMenu = () => !includes(this.props.location.pathname, 'users-permissions/auth/');
|
||||
|
||||
retrievePlugins = () => {
|
||||
const { adminPage: { currentEnvironment }, plugins } = this.props;
|
||||
const {
|
||||
adminPage: { currentEnvironment },
|
||||
plugins,
|
||||
} = this.props;
|
||||
|
||||
if (currentEnvironment === 'production') {
|
||||
let pluginsToDisplay = plugins;
|
||||
PLUGINS_TO_BLOCK_PRODUCTION.map(plugin =>
|
||||
pluginsToDisplay = pluginsToDisplay.delete(plugin));
|
||||
PLUGINS_TO_BLOCK_PRODUCTION.map(plugin => (pluginsToDisplay = pluginsToDisplay.delete(plugin)));
|
||||
|
||||
return pluginsToDisplay;
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { adminPage } = this.props;
|
||||
@ -247,13 +261,27 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
disableGlobalOverlayBlocker: () => { dispatch(disableGlobalOverlayBlocker()); },
|
||||
enableGlobalOverlayBlocker: () => { dispatch(enableGlobalOverlayBlocker()); },
|
||||
getGaStatus: () => { dispatch(getGaStatus()); },
|
||||
getLayout: () => { dispatch(getLayout()); },
|
||||
onHideNotification: (id) => { dispatch(hideNotification(id)); },
|
||||
pluginLoaded: (plugin) => { dispatch(pluginLoaded(plugin)); },
|
||||
updatePlugin: (pluginId, updatedKey, updatedValue) => { dispatch(updatePlugin(pluginId, updatedKey, updatedValue)); },
|
||||
disableGlobalOverlayBlocker: () => {
|
||||
dispatch(disableGlobalOverlayBlocker());
|
||||
},
|
||||
enableGlobalOverlayBlocker: () => {
|
||||
dispatch(enableGlobalOverlayBlocker());
|
||||
},
|
||||
getGaStatus: () => {
|
||||
dispatch(getGaStatus());
|
||||
},
|
||||
getLayout: () => {
|
||||
dispatch(getLayout());
|
||||
},
|
||||
onHideNotification: id => {
|
||||
dispatch(hideNotification(id));
|
||||
},
|
||||
pluginLoaded: plugin => {
|
||||
dispatch(pluginLoaded(plugin));
|
||||
},
|
||||
updatePlugin: (pluginId, updatedKey, updatedValue) => {
|
||||
dispatch(updatePlugin(pluginId, updatedKey, updatedValue));
|
||||
},
|
||||
dispatch,
|
||||
};
|
||||
}
|
||||
@ -262,10 +290,6 @@ const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
||||
const withReducer = injectReducer({ key: 'adminPage', reducer });
|
||||
const withSaga = injectSaga({ key: 'adminPage', saga });
|
||||
|
||||
export default compose(
|
||||
withReducer,
|
||||
withSaga,
|
||||
withConnect,
|
||||
)(AdminPage);
|
||||
export default compose(withReducer, withSaga, withConnect)(AdminPage);
|
||||
|
||||
// export default connect(mapStateToProps, mapDispatchToProps)(AdminPage);
|
||||
|
||||
@ -361,7 +361,7 @@ module.exports = function(strapi) {
|
||||
type = definition.client === 'pg' ? 'double precision' : 'double';
|
||||
break;
|
||||
case 'decimal':
|
||||
type = 'decimal';
|
||||
type = 'decimal(10,2)';
|
||||
break;
|
||||
case 'date':
|
||||
case 'time':
|
||||
@ -431,9 +431,9 @@ module.exports = function(strapi) {
|
||||
const queries = columns.reduce((acc, attribute) => {
|
||||
acc.push(`ALTER TABLE ${quote}${table}${quote} ADD ${attribute};`);
|
||||
return acc;
|
||||
}, []).join('\n\r');
|
||||
}, []);
|
||||
|
||||
await ORM.knex.raw(queries);
|
||||
await Promise.all(queries.map(query => ORM.knex.raw(query)));
|
||||
}
|
||||
|
||||
// Execute query to update column type
|
||||
|
||||
@ -179,7 +179,11 @@ module.exports = {
|
||||
);
|
||||
});
|
||||
} else if (_.get(this._attributes, `${current}.isVirtual`) !== true) {
|
||||
acc[current] = params.values[current];
|
||||
if (typeof params.values[current] === 'object') {
|
||||
acc[current] = _.get(params.values[current], this.primaryKey);
|
||||
} else {
|
||||
acc[current] = params.values[current];
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@ -22,7 +22,7 @@ const appPath = (() => {
|
||||
return isAdmin ? path.resolve(process.env.PWD, '..') : path.resolve(process.env.PWD, '..', '..');
|
||||
})();
|
||||
// const isSetup = path.resolve(process.env.PWD, '..', '..') === path.resolve(process.env.INIT_CWD);
|
||||
const isSetup = process.env.IS_MONOREPO;
|
||||
const isSetup = process.env.IS_MONOREPO;
|
||||
|
||||
const adminPath = (() => {
|
||||
if (isAdmin && isSetup) {
|
||||
@ -42,7 +42,13 @@ const URLs = {
|
||||
|
||||
if (isAdmin && !isSetup) {
|
||||
// Load server configuration.
|
||||
const serverConfig = path.resolve(appPath, 'config', 'environments', _.lowerCase(process.env.NODE_ENV), 'server.json');
|
||||
const serverConfig = path.resolve(
|
||||
appPath,
|
||||
'config',
|
||||
'environments',
|
||||
_.lowerCase(process.env.NODE_ENV),
|
||||
'server.json',
|
||||
);
|
||||
|
||||
try {
|
||||
const server = require(serverConfig);
|
||||
@ -88,21 +94,32 @@ if (process.env.npm_lifecycle_event === 'start') {
|
||||
// Read `plugins` directory and check if the plugin comes with an UI (it has an App container).
|
||||
// If we don't do this check webpack expects the plugin to have a containers/App/reducer.js to create
|
||||
// the plugin's store (redux).
|
||||
plugins.src = isAdmin && !plugins.exist ? fs.readdirSync(path.resolve(appPath, 'plugins')).filter(x => {
|
||||
let hasAdminFolder;
|
||||
plugins.src =
|
||||
isAdmin && !plugins.exist
|
||||
? fs.readdirSync(path.resolve(appPath, 'plugins')).filter(x => {
|
||||
let hasAdminFolder;
|
||||
|
||||
try {
|
||||
fs.accessSync(path.resolve(appPath, 'plugins', x, 'admin', 'src', 'containers', 'App'));
|
||||
hasAdminFolder = true;
|
||||
} catch(err) {
|
||||
hasAdminFolder = false;
|
||||
}
|
||||
return x[0] !== '.' && hasAdminFolder;
|
||||
}) : [];
|
||||
try {
|
||||
fs.accessSync(path.resolve(appPath, 'plugins', x, 'admin', 'src', 'containers', 'App'));
|
||||
hasAdminFolder = true;
|
||||
} catch (err) {
|
||||
hasAdminFolder = false;
|
||||
}
|
||||
return x[0] !== '.' && hasAdminFolder;
|
||||
})
|
||||
: [];
|
||||
|
||||
// Construct object of plugin' paths.
|
||||
plugins.folders = plugins.src.reduce((acc, current) => {
|
||||
acc[current] = path.resolve(appPath, 'plugins', current, 'node_modules', 'strapi-helper-plugin', 'lib', 'src');
|
||||
acc[current] = path.resolve(
|
||||
appPath,
|
||||
'plugins',
|
||||
current,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'lib',
|
||||
'src',
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
@ -110,14 +127,16 @@ if (process.env.npm_lifecycle_event === 'start') {
|
||||
|
||||
// Tell webpack to use a loader only for those files
|
||||
const foldersToInclude = [path.join(adminPath, 'admin', 'src')]
|
||||
.concat(plugins.src.reduce((acc, current) => {
|
||||
acc.push(path.resolve(appPath, 'plugins', current, 'admin', 'src'), plugins.folders[current]);
|
||||
.concat(
|
||||
plugins.src.reduce((acc, current) => {
|
||||
acc.push(path.resolve(appPath, 'plugins', current, 'admin', 'src'), plugins.folders[current]);
|
||||
|
||||
return acc;
|
||||
}, []))
|
||||
return acc;
|
||||
}, []),
|
||||
)
|
||||
.concat([path.join(adminPath, 'node_modules', 'strapi-helper-plugin', 'lib', 'src')]);
|
||||
|
||||
module.exports = (options) => {
|
||||
module.exports = options => {
|
||||
// The disable option is only for production
|
||||
// Config from https://github.com/facebook/create-react-app/blob/next/packages/react-scripts/config/webpack.config.prod.js
|
||||
const extractSass = new ExtractTextPlugin({
|
||||
@ -127,11 +146,16 @@ module.exports = (options) => {
|
||||
|
||||
return {
|
||||
entry: options.entry,
|
||||
output: Object.assign({ // Compile into js/build.js
|
||||
path: path.join(adminPath, 'admin', 'build'),
|
||||
}, options.output), // Merge with env dependent settings
|
||||
output: Object.assign(
|
||||
{
|
||||
// Compile into js/build.js
|
||||
path: path.join(adminPath, 'admin', 'build'),
|
||||
},
|
||||
options.output,
|
||||
), // Merge with env dependent settings
|
||||
module: {
|
||||
rules: [ // TODO: add eslint formatter
|
||||
rules: [
|
||||
// TODO: add eslint formatter
|
||||
{
|
||||
// "oneOf" will traverse all following loaders until one will
|
||||
// match the requirements. When no loader matches it will fall
|
||||
@ -145,9 +169,7 @@ module.exports = (options) => {
|
||||
presets: options.babelPresets,
|
||||
env: {
|
||||
production: {
|
||||
only: [
|
||||
'src',
|
||||
],
|
||||
only: ['src'],
|
||||
plugins: [
|
||||
require.resolve('babel-plugin-transform-react-remove-prop-types'),
|
||||
require.resolve('babel-plugin-transform-react-constant-elements'),
|
||||
@ -159,9 +181,7 @@ module.exports = (options) => {
|
||||
],
|
||||
},
|
||||
test: {
|
||||
plugins: [
|
||||
'istanbul',
|
||||
],
|
||||
plugins: ['istanbul'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -305,16 +325,8 @@ module.exports = (options) => {
|
||||
],
|
||||
alias: options.alias,
|
||||
symlinks: false,
|
||||
extensions: [
|
||||
'.js',
|
||||
'.jsx',
|
||||
'.react.js',
|
||||
],
|
||||
mainFields: [
|
||||
'browser',
|
||||
'jsnext:main',
|
||||
'main',
|
||||
],
|
||||
extensions: ['.js', '.jsx', '.react.js'],
|
||||
mainFields: ['browser', 'jsnext:main', 'main'],
|
||||
},
|
||||
externals: options.externals,
|
||||
resolveLoader: {
|
||||
|
||||
@ -26,7 +26,9 @@ const appPath = (() => {
|
||||
|
||||
const rootAdminpath = (() => {
|
||||
if (isSetup) {
|
||||
return isAdmin ? path.resolve(appPath, 'strapi-admin') : path.resolve(appPath, 'packages', 'strapi-admin');
|
||||
return isAdmin
|
||||
? path.resolve(appPath, 'strapi-admin')
|
||||
: path.resolve(appPath, 'packages', 'strapi-admin');
|
||||
}
|
||||
return path.resolve(appPath, 'admin');
|
||||
})();
|
||||
@ -46,42 +48,54 @@ if (process.env.npm_lifecycle_event === 'start') {
|
||||
plugins.exist = true;
|
||||
}
|
||||
|
||||
plugins.src = process.env.IS_ADMIN === 'true' && !plugins.exist ? fs.readdirSync(path.resolve(appPath, 'plugins')).filter(x => {
|
||||
let hasAdminFolder;
|
||||
plugins.src =
|
||||
process.env.IS_ADMIN === 'true' && !plugins.exist
|
||||
? fs.readdirSync(path.resolve(appPath, 'plugins')).filter(x => {
|
||||
let hasAdminFolder;
|
||||
|
||||
// Don't inject the plugins that don't have an admin into the app
|
||||
try {
|
||||
fs.accessSync(path.resolve(appPath, 'plugins', x, 'admin', 'src', 'containers', 'App'));
|
||||
hasAdminFolder = true;
|
||||
} catch(err) {
|
||||
hasAdminFolder = false;
|
||||
}
|
||||
// Don't inject the plugins that don't have an admin into the app
|
||||
try {
|
||||
fs.accessSync(path.resolve(appPath, 'plugins', x, 'admin', 'src', 'containers', 'App'));
|
||||
hasAdminFolder = true;
|
||||
} catch (err) {
|
||||
hasAdminFolder = false;
|
||||
}
|
||||
|
||||
return x[0] !== '.' && hasAdminFolder;
|
||||
}) : [];
|
||||
return x[0] !== '.' && hasAdminFolder;
|
||||
})
|
||||
: [];
|
||||
|
||||
plugins.folders = plugins.src.reduce((acc, current) => {
|
||||
acc[current] = path.resolve(appPath, 'plugins', current, 'node_modules', 'strapi-helper-plugin', 'lib', 'src');
|
||||
acc[current] = path.resolve(
|
||||
appPath,
|
||||
'plugins',
|
||||
current,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'lib',
|
||||
'src',
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
|
||||
const port = argv.port || process.env.PORT || 3000;
|
||||
|
||||
module.exports = require('./webpack.base.babel')({
|
||||
// Add hot reloading in development
|
||||
entry: Object.assign({
|
||||
main: [
|
||||
`webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
|
||||
path.join(appPath, 'admin', 'admin', 'src', 'app.js'),
|
||||
],
|
||||
}, plugins.src.reduce((acc, current) => {
|
||||
acc[current] = path.resolve(plugins.folders[current], 'app.js');
|
||||
entry: Object.assign(
|
||||
{
|
||||
main: [
|
||||
`webpack-hot-middleware/client?path=http://localhost:${port}/__webpack_hmr`,
|
||||
path.join(appPath, 'admin', 'admin', 'src', 'app.js'),
|
||||
],
|
||||
},
|
||||
plugins.src.reduce((acc, current) => {
|
||||
acc[current] = path.resolve(plugins.folders[current], 'app.js');
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
return acc;
|
||||
}, {}),
|
||||
),
|
||||
|
||||
// Don't use hashes in dev mode for better performance
|
||||
@ -112,10 +126,12 @@ module.exports = require('./webpack.base.babel')({
|
||||
// Process the CSS with PostCSS
|
||||
postcssPlugins: [
|
||||
postcssFocus(), // Add a :focus to every :hover
|
||||
cssnext({ // Allow future CSS features to be used, also auto-prefixes the CSS...
|
||||
cssnext({
|
||||
// Allow future CSS features to be used, also auto-prefixes the CSS...
|
||||
browsers: ['last 2 versions', 'IE > 10'], // ...based on this browser list
|
||||
}),
|
||||
postcssReporter({ // Posts messages from plugins to the terminal
|
||||
postcssReporter({
|
||||
// Posts messages from plugins to the terminal
|
||||
clearMessages: true,
|
||||
}),
|
||||
],
|
||||
@ -136,30 +152,69 @@ module.exports = require('./webpack.base.babel')({
|
||||
],
|
||||
alias: {
|
||||
moment: 'moment/moment.js',
|
||||
'babel-polyfill': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'babel-polyfill'),
|
||||
'lodash': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'lodash'),
|
||||
'immutable': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'immutable'),
|
||||
'react-intl': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-intl'),
|
||||
'react': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react'),
|
||||
'react-dom': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-dom'),
|
||||
'react-transition-group': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-transition-group'),
|
||||
'reactstrap': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'reactstrap'),
|
||||
'styled-components': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'styled-components')
|
||||
'babel-polyfill': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'babel-polyfill',
|
||||
),
|
||||
lodash: path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'lodash'),
|
||||
immutable: path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'immutable',
|
||||
),
|
||||
'react-intl': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'react-intl',
|
||||
),
|
||||
react: path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react'),
|
||||
'react-dom': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'react-dom',
|
||||
),
|
||||
'react-transition-group': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'react-transition-group',
|
||||
),
|
||||
reactstrap: path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'reactstrap',
|
||||
),
|
||||
'styled-components': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'styled-components',
|
||||
),
|
||||
},
|
||||
|
||||
// Emit a source map for easier debugging
|
||||
devtool: 'cheap-module-source-map',
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* We dynamically generate the HTML content in development so that the different
|
||||
* DLL Javascript files are loaded in script tags and available to our application.
|
||||
*/
|
||||
function templateContent() {
|
||||
const html = fs.readFileSync(
|
||||
path.resolve(appPath, 'admin', 'admin', 'src', 'index.html')
|
||||
).toString();
|
||||
const html = fs.readFileSync(path.resolve(appPath, 'admin', 'admin', 'src', 'index.html')).toString();
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@ -33,7 +33,9 @@ const adminPath = (() => {
|
||||
|
||||
const rootAdminpath = (() => {
|
||||
if (isSetup) {
|
||||
return isAdmin ? path.resolve(appPath, 'strapi-admin') : path.resolve(appPath, 'packages', 'strapi-admin');
|
||||
return isAdmin
|
||||
? path.resolve(appPath, 'strapi-admin')
|
||||
: path.resolve(appPath, 'packages', 'strapi-admin');
|
||||
}
|
||||
return path.resolve(appPath, 'admin');
|
||||
})();
|
||||
@ -64,7 +66,13 @@ let publicPath;
|
||||
|
||||
if (isAdmin && !isSetup) {
|
||||
// Load server configuration.
|
||||
const serverConfig = path.resolve(appPath, 'config', 'environments', _.lowerCase(process.env.NODE_ENV), 'server.json');
|
||||
const serverConfig = path.resolve(
|
||||
appPath,
|
||||
'config',
|
||||
'environments',
|
||||
_.lowerCase(process.env.NODE_ENV),
|
||||
'server.json',
|
||||
);
|
||||
|
||||
try {
|
||||
const server = require(serverConfig);
|
||||
@ -83,32 +91,40 @@ if (isAdmin && !isSetup) {
|
||||
|
||||
// Build the `index.html file`
|
||||
if (isAdmin) {
|
||||
plugins.push(new HtmlWebpackPlugin({
|
||||
template: 'admin/src/index.html',
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
chunksSortMode: 'manual',
|
||||
chunks: ['main'],
|
||||
inject: true,
|
||||
}));
|
||||
plugins.push(new AddAssetHtmlPlugin({
|
||||
filepath: path.resolve(__dirname, 'dist/*.dll.js'),
|
||||
}));
|
||||
plugins.push(new CopyWebpackPlugin([{
|
||||
from: 'config/plugins.json',
|
||||
context: path.resolve(adminPath, 'admin', 'src'),
|
||||
to: 'config/plugins.json',
|
||||
}]));
|
||||
plugins.push(
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'admin/src/index.html',
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeRedundantAttributes: true,
|
||||
useShortDoctype: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
keepClosingSlash: true,
|
||||
minifyJS: true,
|
||||
minifyCSS: true,
|
||||
minifyURLs: true,
|
||||
},
|
||||
chunksSortMode: 'manual',
|
||||
chunks: ['main'],
|
||||
inject: true,
|
||||
}),
|
||||
);
|
||||
plugins.push(
|
||||
new AddAssetHtmlPlugin({
|
||||
filepath: path.resolve(__dirname, 'dist/*.dll.js'),
|
||||
}),
|
||||
);
|
||||
plugins.push(
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: 'config/plugins.json',
|
||||
context: path.resolve(adminPath, 'admin', 'src'),
|
||||
to: 'config/plugins.json',
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
const main = (() => {
|
||||
@ -128,11 +144,14 @@ module.exports = base({
|
||||
},
|
||||
|
||||
// Utilize long-term caching by adding content hashes (not compilation hashes) to compiled assets
|
||||
output: _.omitBy({
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].[chunkhash].chunk.js',
|
||||
publicPath,
|
||||
}, _.isUndefined),
|
||||
output: _.omitBy(
|
||||
{
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].[chunkhash].chunk.js',
|
||||
publicPath,
|
||||
},
|
||||
_.isUndefined,
|
||||
),
|
||||
|
||||
// In production, we minify our CSS with cssnano
|
||||
postcssPlugins: [
|
||||
@ -164,15 +183,57 @@ module.exports = base({
|
||||
|
||||
alias: {
|
||||
moment: 'moment/moment.js',
|
||||
'babel-polyfill': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'babel-polyfill'),
|
||||
'lodash': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'lodash'),
|
||||
'immutable': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'immutable'),
|
||||
'react-intl': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-intl'),
|
||||
'react': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react'),
|
||||
'react-dom': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-dom'),
|
||||
'react-transition-group': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react-transition-group'),
|
||||
'reactstrap': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'reactstrap'),
|
||||
'styled-components': path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'styled-components'),
|
||||
'babel-polyfill': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'babel-polyfill',
|
||||
),
|
||||
lodash: path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'lodash'),
|
||||
immutable: path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'immutable',
|
||||
),
|
||||
'react-intl': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'react-intl',
|
||||
),
|
||||
react: path.resolve(rootAdminpath, 'node_modules', 'strapi-helper-plugin', 'node_modules', 'react'),
|
||||
'react-dom': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'react-dom',
|
||||
),
|
||||
'react-transition-group': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'react-transition-group',
|
||||
),
|
||||
reactstrap: path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'reactstrap',
|
||||
),
|
||||
'styled-components': path.resolve(
|
||||
rootAdminpath,
|
||||
'node_modules',
|
||||
'strapi-helper-plugin',
|
||||
'node_modules',
|
||||
'styled-components',
|
||||
),
|
||||
},
|
||||
|
||||
devtool: 'cheap-module-source-map',
|
||||
|
||||
@ -78,8 +78,9 @@ module.exports = strapi => {
|
||||
|
||||
// Make sure the client is installed in the application
|
||||
// `node_modules` directory.
|
||||
let client;
|
||||
try {
|
||||
require(path.resolve(strapi.config.appPath, 'node_modules', connection.settings.client));
|
||||
client = require(path.resolve(strapi.config.appPath, 'node_modules', connection.settings.client));
|
||||
} catch (err) {
|
||||
strapi.log.error('The client `' + connection.settings.client + '` is not installed.');
|
||||
strapi.log.error('You can install it with `$ npm install ' + connection.settings.client + ' --save`.');
|
||||
@ -105,18 +106,22 @@ module.exports = strapi => {
|
||||
migrations: _.get(connection.options, 'migrations')
|
||||
}, strapi.config.hook.settings.knex);
|
||||
|
||||
if (options.client === 'pg' && _.isString(_.get(options.connection, 'schema'))) {
|
||||
options.pool = {
|
||||
min: _.get(connection.options, 'pool.min') || 0,
|
||||
max: _.get(connection.options, 'pool.max') || 10,
|
||||
afterCreate: (conn, cb) => {
|
||||
conn.query(`SET SESSION SCHEMA '${options.connection.schema}';`, (err) => {
|
||||
cb(err, conn);
|
||||
});
|
||||
}
|
||||
};
|
||||
} else {
|
||||
delete options.connection.schema;
|
||||
if (options.client === 'pg') {
|
||||
client.types.setTypeParser(1700, 'text', parseFloat);
|
||||
|
||||
if (_.isString(_.get(options.connection, 'schema'))) {
|
||||
options.pool = {
|
||||
min: _.get(connection.options, 'pool.min') || 0,
|
||||
max: _.get(connection.options, 'pool.max') || 10,
|
||||
afterCreate: (conn, cb) => {
|
||||
conn.query(`SET SESSION SCHEMA '${options.connection.schema}';`, (err) => {
|
||||
cb(err, conn);
|
||||
});
|
||||
}
|
||||
};
|
||||
} else {
|
||||
delete options.connection.schema;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.client === 'mysql') {
|
||||
|
||||
@ -10,6 +10,19 @@ export default function setParallelAttribute(newAttribute) {
|
||||
parallelAttribute.params.columnName = newAttribute.params.targetColumnName;
|
||||
parallelAttribute.params.targetColumnName = newAttribute.params.columnName;
|
||||
parallelAttribute.params.dominant = false;
|
||||
|
||||
if (newAttribute.params.nature) {
|
||||
switch (newAttribute.params.nature) {
|
||||
case 'manyToOne':
|
||||
parallelAttribute.params.nature = 'oneToMany';
|
||||
break;
|
||||
case 'oneToMany':
|
||||
parallelAttribute.params.nature = 'manyToOne';
|
||||
break;
|
||||
default:
|
||||
//
|
||||
}
|
||||
}
|
||||
return parallelAttribute;
|
||||
}
|
||||
return;
|
||||
|
||||
@ -177,6 +177,18 @@ function setParallelAttribute(data) {
|
||||
parallelAttribute.params.columnName = data.params.targetColumnName;
|
||||
parallelAttribute.params.targetColumnName = data.params.columnName;
|
||||
parallelAttribute.params.dominant = false;
|
||||
|
||||
switch (data.params.nature) {
|
||||
case 'manyToOne':
|
||||
parallelAttribute.params.nature = 'oneToMany';
|
||||
break;
|
||||
case 'oneToMany':
|
||||
parallelAttribute.params.nature = 'manyToOne';
|
||||
break;
|
||||
default:
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
return parallelAttribute;
|
||||
}
|
||||
|
||||
@ -624,6 +624,7 @@ module.exports = {
|
||||
break;
|
||||
// falls through
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Where.
|
||||
queryOpts.query = strapi.utils.models.convertParams(name, {
|
||||
|
||||
@ -40,4 +40,4 @@
|
||||
"configurable": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,16 +24,18 @@ const { cli, logger } = require('strapi-utils');
|
||||
* (fire up the application in our working directory).
|
||||
*/
|
||||
|
||||
module.exports = function() {
|
||||
module.exports = function(appPath = '') {
|
||||
// Check that we're in a valid Strapi project.
|
||||
if (!cli.isStrapiApp()) {
|
||||
return logger.error('This command can only be used inside a Strapi project.');
|
||||
}
|
||||
|
||||
appPath = path.join(process.cwd(), appPath);
|
||||
|
||||
try {
|
||||
const strapi = function () {
|
||||
try {
|
||||
return require(path.resolve(process.cwd(), 'node_modules', 'strapi'));
|
||||
return require(path.resolve(appPath, 'node_modules', 'strapi'));
|
||||
} catch (e) {
|
||||
return require('strapi'); // eslint-disable-line import/no-unresolved
|
||||
}
|
||||
@ -46,7 +48,7 @@ module.exports = function() {
|
||||
|
||||
// Require server configurations
|
||||
const server = require(path.resolve(
|
||||
process.cwd(),
|
||||
appPath,
|
||||
'config',
|
||||
'environments',
|
||||
'development',
|
||||
@ -81,7 +83,7 @@ module.exports = function() {
|
||||
});
|
||||
};
|
||||
|
||||
setFilesToWatch(process.cwd());
|
||||
setFilesToWatch(appPath);
|
||||
|
||||
|
||||
|
||||
@ -124,7 +126,9 @@ module.exports = function() {
|
||||
}
|
||||
});
|
||||
|
||||
return strapi.start(afterwards);
|
||||
return strapi.start({
|
||||
appPath
|
||||
}, afterwards);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@ -133,7 +137,9 @@ module.exports = function() {
|
||||
// Otherwise, if no workable local `strapi` module exists,
|
||||
// run the application using the currently running version
|
||||
// of `strapi`. This is probably always the global install.
|
||||
strapi.start(afterwards);
|
||||
strapi.start({
|
||||
appPath
|
||||
}, afterwards);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
process.exit(0);
|
||||
|
||||
@ -64,9 +64,11 @@ program
|
||||
|
||||
// `$ strapi start`
|
||||
program
|
||||
.command('start')
|
||||
.command('start [appPath]')
|
||||
.description('start your Strapi application')
|
||||
.action(require('./strapi-start'));
|
||||
.action((appPath) => {
|
||||
require('./strapi-start')(appPath);
|
||||
});
|
||||
|
||||
// `$ strapi generate:api`
|
||||
program
|
||||
|
||||
@ -10,7 +10,16 @@ const { includes, get, assign, toLower } = require('lodash');
|
||||
const { logger, models } = require('strapi-utils');
|
||||
const stackTrace = require('stack-trace');
|
||||
const utils = require('./utils');
|
||||
const { nestedConfigurations, appConfigurations, apis, middlewares, hooks, plugins, admin, store } = require('./core');
|
||||
const {
|
||||
nestedConfigurations,
|
||||
appConfigurations,
|
||||
apis,
|
||||
middlewares,
|
||||
hooks,
|
||||
plugins,
|
||||
admin,
|
||||
store,
|
||||
} = require('./core');
|
||||
const initializeMiddlewares = require('./middlewares');
|
||||
const initializeHooks = require('./hooks');
|
||||
|
||||
@ -41,7 +50,7 @@ class Strapi extends EventEmitter {
|
||||
|
||||
// Utils.
|
||||
this.utils = {
|
||||
models
|
||||
models,
|
||||
};
|
||||
|
||||
// Exclude EventEmitter, Koa and HTTP server to be freezed.
|
||||
@ -73,12 +82,12 @@ class Strapi extends EventEmitter {
|
||||
services: 'services',
|
||||
static: 'public',
|
||||
validators: 'validators',
|
||||
views: 'views'
|
||||
views: 'views',
|
||||
},
|
||||
middleware: {},
|
||||
hook: {},
|
||||
functions: {},
|
||||
routes: {}
|
||||
routes: {},
|
||||
};
|
||||
|
||||
// Bind context functions.
|
||||
@ -92,8 +101,6 @@ class Strapi extends EventEmitter {
|
||||
// Emit starting event.
|
||||
this.emit('server:starting');
|
||||
|
||||
// Enhance app.
|
||||
await this.enhancer();
|
||||
// Load the app.
|
||||
await this.load();
|
||||
// Run bootstrap function.
|
||||
@ -169,7 +176,11 @@ class Strapi extends EventEmitter {
|
||||
// Destroy server and available connections.
|
||||
this.server.destroy();
|
||||
|
||||
if (cluster.isWorker && this.config.environment === 'development' && get(this.config, 'currentEnvironment.server.autoReload.enabled', true) === true) {
|
||||
if (
|
||||
cluster.isWorker &&
|
||||
this.config.environment === 'development' &&
|
||||
get(this.config, 'currentEnvironment.server.autoReload.enabled', true) === true
|
||||
) {
|
||||
process.send('stop');
|
||||
}
|
||||
|
||||
@ -194,7 +205,7 @@ class Strapi extends EventEmitter {
|
||||
nestedConfigurations.call(this),
|
||||
apis.call(this),
|
||||
middlewares.call(this),
|
||||
hooks.call(this)
|
||||
hooks.call(this),
|
||||
]);
|
||||
|
||||
// Populate AST with configurations.
|
||||
@ -207,10 +218,7 @@ class Strapi extends EventEmitter {
|
||||
await store.call(this);
|
||||
|
||||
// Initialize hooks and middlewares.
|
||||
await Promise.all([
|
||||
initializeMiddlewares.call(this),
|
||||
initializeHooks.call(this)
|
||||
]);
|
||||
await Promise.all([initializeMiddlewares.call(this), initializeHooks.call(this)]);
|
||||
|
||||
// Harmonize plugins configuration.
|
||||
await plugins.call(this);
|
||||
@ -218,15 +226,19 @@ class Strapi extends EventEmitter {
|
||||
|
||||
reload() {
|
||||
const state = {
|
||||
shouldReload: true
|
||||
shouldReload: true,
|
||||
};
|
||||
|
||||
const reload = function () {
|
||||
const reload = function() {
|
||||
if (state.shouldReload === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cluster.isWorker && this.config.environment === 'development' && get(this.config, 'currentEnvironment.server.autoReload.enabled', true) === true) {
|
||||
if (
|
||||
cluster.isWorker &&
|
||||
this.config.environment === 'development' &&
|
||||
get(this.config, 'currentEnvironment.server.autoReload.enabled', true) === true
|
||||
) {
|
||||
process.send('reload');
|
||||
}
|
||||
};
|
||||
@ -234,14 +246,14 @@ class Strapi extends EventEmitter {
|
||||
Object.defineProperty(reload, 'isWatching', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
set: (value) => {
|
||||
set: value => {
|
||||
// Special state when the reloader is disabled temporarly (see GraphQL plugin example).
|
||||
state.shouldReload = !(state.isWatching === false && value === true);
|
||||
state.isWatching = value;
|
||||
},
|
||||
get: () => {
|
||||
return state.isWatching;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
reload.isReloading = false;
|
||||
@ -251,45 +263,49 @@ class Strapi extends EventEmitter {
|
||||
}
|
||||
|
||||
async bootstrap() {
|
||||
const execBootstrap = (fn) => !fn ? Promise.resolve() : new Promise((resolve, reject) => {
|
||||
const timeoutMs = this.config.bootstrapTimeout || 3500;
|
||||
const timer = setTimeout(() => {
|
||||
this.log.warn(`Bootstrap is taking unusually long to execute its callback ${timeoutMs} miliseconds).`);
|
||||
this.log.warn('Perhaps you forgot to call it?');
|
||||
}, timeoutMs);
|
||||
const execBootstrap = fn =>
|
||||
!fn
|
||||
? Promise.resolve()
|
||||
: new Promise((resolve, reject) => {
|
||||
const timeoutMs = this.config.bootstrapTimeout || 3500;
|
||||
const timer = setTimeout(() => {
|
||||
this.log.warn(
|
||||
`Bootstrap is taking unusually long to execute its callback ${timeoutMs} miliseconds).`,
|
||||
);
|
||||
this.log.warn('Perhaps you forgot to call it?');
|
||||
}, timeoutMs);
|
||||
|
||||
let ranBootstrapFn = false;
|
||||
let ranBootstrapFn = false;
|
||||
|
||||
try {
|
||||
fn(err => {
|
||||
if (ranBootstrapFn) {
|
||||
this.log.error('You called the callback in `strapi.config.boostrap` more than once!');
|
||||
try {
|
||||
fn(err => {
|
||||
if (ranBootstrapFn) {
|
||||
this.log.error('You called the callback in `strapi.config.boostrap` more than once!');
|
||||
|
||||
return reject();
|
||||
return reject();
|
||||
}
|
||||
|
||||
ranBootstrapFn = true;
|
||||
clearTimeout(timer);
|
||||
|
||||
return resolve(err);
|
||||
});
|
||||
} catch (e) {
|
||||
if (ranBootstrapFn) {
|
||||
this.log.error('The bootstrap function threw an error after its callback was called.');
|
||||
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
ranBootstrapFn = true;
|
||||
clearTimeout(timer);
|
||||
|
||||
return resolve(e);
|
||||
}
|
||||
|
||||
ranBootstrapFn = true;
|
||||
clearTimeout(timer);
|
||||
|
||||
return resolve(err);
|
||||
});
|
||||
} catch (e) {
|
||||
if (ranBootstrapFn) {
|
||||
this.log.error('The bootstrap function threw an error after its callback was called.');
|
||||
|
||||
return reject(e);
|
||||
}
|
||||
|
||||
ranBootstrapFn = true;
|
||||
clearTimeout(timer);
|
||||
|
||||
return resolve(e);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(
|
||||
Object.values(this.plugins)
|
||||
.map(x => execBootstrap(get(x, 'config.functions.bootstrap')))
|
||||
Object.values(this.plugins).map(x => execBootstrap(get(x, 'config.functions.bootstrap'))),
|
||||
).then(() => execBootstrap(this.config.functions.bootstrap));
|
||||
}
|
||||
|
||||
@ -299,19 +315,24 @@ class Strapi extends EventEmitter {
|
||||
// Remove object from tree.
|
||||
delete this.propertiesToNotFreeze;
|
||||
|
||||
return Object.keys(this).filter(x => !includes(propertiesToNotFreeze, x)).forEach(key => {
|
||||
Object.freeze(this[key]);
|
||||
});
|
||||
return Object.keys(this)
|
||||
.filter(x => !includes(propertiesToNotFreeze, x))
|
||||
.forEach(key => {
|
||||
Object.freeze(this[key]);
|
||||
});
|
||||
}
|
||||
|
||||
query(entity, plugin) {
|
||||
if (!entity) {
|
||||
return this.log.error(`You can't call the query method without passing the model's name as a first argument.`);
|
||||
return this.log.error(
|
||||
`You can't call the query method without passing the model's name as a first argument.`,
|
||||
);
|
||||
}
|
||||
|
||||
const model = entity.toLowerCase();
|
||||
|
||||
const Model = get(strapi.plugins, [plugin, 'models', model]) || get(strapi, ['models', model]) || undefined;
|
||||
const Model =
|
||||
get(strapi.plugins, [plugin, 'models', model]) || get(strapi, ['models', model]) || undefined;
|
||||
|
||||
if (!Model) {
|
||||
return this.log.error(`The model ${model} can't be found.`);
|
||||
@ -355,12 +376,15 @@ class Strapi extends EventEmitter {
|
||||
}
|
||||
|
||||
// Bind queries with the current model to allow the use of `this`.
|
||||
const bindQueries = Object.keys(queries).reduce((acc, current) => {
|
||||
return acc[current] = queries[current].bind(Model), acc;
|
||||
}, {
|
||||
orm: connector,
|
||||
primaryKey: Model.primaryKey
|
||||
});
|
||||
const bindQueries = Object.keys(queries).reduce(
|
||||
(acc, current) => {
|
||||
return (acc[current] = queries[current].bind(Model)), acc;
|
||||
},
|
||||
{
|
||||
orm: connector,
|
||||
primaryKey: Model.primaryKey,
|
||||
},
|
||||
);
|
||||
|
||||
return bindQueries;
|
||||
}
|
||||
|
||||
@ -25,7 +25,8 @@ module.exports = function() {
|
||||
'./node_modules/strapi-middleware-*',
|
||||
'./node_modules/strapi-upload-*',
|
||||
'./node_modules/strapi-lint'
|
||||
]
|
||||
],
|
||||
cwd: this.config.appPath
|
||||
}, (err, files) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
|
||||
@ -8,13 +8,13 @@ const _ = require('lodash');
|
||||
module.exports = function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const folder = ((url = _.get(strapi.config.currentEnvironment.server, 'admin.path', 'admin')) =>
|
||||
url[0] === '/' ? url.substring(1) : url
|
||||
)().replace(/\/$/, '') ;
|
||||
url[0] === '/' ? url.substring(1) : url)().replace(/\/$/, '');
|
||||
|
||||
const configuratePlugin = (acc, current, source, name) => {
|
||||
switch (source) {
|
||||
case 'host': {
|
||||
const host = _.get(this.config.environments[current].server, 'admin.build.host').replace(/\/$/, '') || '/';
|
||||
const host =
|
||||
_.get(this.config.environments[current].server, 'admin.build.host').replace(/\/$/, '') || '/';
|
||||
|
||||
if (!host) {
|
||||
throw new Error(`You can't use \`remote\` as a source without set the \`host\` configuration.`);
|
||||
@ -32,12 +32,20 @@ module.exports = function() {
|
||||
}
|
||||
case 'custom':
|
||||
if (!_.isEmpty(_.get(this.plugins[name].config, `sources.${current}`, {}))) {
|
||||
return acc[current] = this.plugins[name].config.sources[current];
|
||||
return (acc[current] = this.plugins[name].config.sources[current]);
|
||||
}
|
||||
|
||||
throw new Error(`You have to define the source URL for each environment in \`./plugins/**/config/sources.json\``);
|
||||
throw new Error(
|
||||
`You have to define the source URL for each environment in \`./plugins/**/config/sources.json\``,
|
||||
);
|
||||
case 'backend': {
|
||||
const backend = _.get(this.config.environments[current], 'server.admin.build.backend', `http://${this.config.environments[current].server.host}:${this.config.environments[current].server.port}`).replace(/\/$/, '');
|
||||
const backend = _.get(
|
||||
this.config.environments[current],
|
||||
'server.admin.build.backend',
|
||||
`http://${this.config.environments[current].server.host}:${
|
||||
this.config.environments[current].server.port
|
||||
}`,
|
||||
).replace(/\/$/, '');
|
||||
|
||||
return `${backend}/${folder.replace(/\/$/, '')}/${name}/main.js`;
|
||||
}
|
||||
@ -46,12 +54,30 @@ module.exports = function() {
|
||||
}
|
||||
};
|
||||
|
||||
const sourcePath = process.env.NODE_ENV !== 'test' ?
|
||||
path.resolve(this.config.appPath, 'admin', 'admin', 'src', 'config', 'plugins.json'):
|
||||
path.resolve(this.config.appPath, 'packages', 'strapi-admin', 'admin', 'src', 'config', 'plugins.json');
|
||||
const buildPath = process.env.NODE_ENV !== 'test' ?
|
||||
path.resolve(this.config.appPath, 'admin', 'admin', 'build', 'config', 'plugins.json'):
|
||||
path.resolve(this.config.appPath, 'packages', 'strapi-admin', 'admin', 'build', 'config', 'plugins.json');
|
||||
const sourcePath =
|
||||
this.config.environment !== 'test'
|
||||
? path.resolve(this.config.appPath, 'admin', 'admin', 'src', 'config', 'plugins.json')
|
||||
: path.resolve(
|
||||
this.config.appPath,
|
||||
'packages',
|
||||
'strapi-admin',
|
||||
'admin',
|
||||
'src',
|
||||
'config',
|
||||
'plugins.json',
|
||||
);
|
||||
const buildPath =
|
||||
this.config.environment !== 'test'
|
||||
? path.resolve(this.config.appPath, 'admin', 'admin', 'build', 'config', 'plugins.json')
|
||||
: path.resolve(
|
||||
this.config.appPath,
|
||||
'packages',
|
||||
'strapi-admin',
|
||||
'admin',
|
||||
'build',
|
||||
'config',
|
||||
'plugins.json',
|
||||
);
|
||||
|
||||
try {
|
||||
fs.access(path.resolve(this.config.appPath, 'admin', 'admin'), err => {
|
||||
@ -115,14 +141,16 @@ module.exports = function() {
|
||||
|
||||
// Create `plugins.json` file.
|
||||
// Don't inject the plugins without an Admin
|
||||
const data = Object.keys(this.plugins)
|
||||
const data = Object.keys(this.plugins)
|
||||
.filter(plugin => {
|
||||
let hasAdminFolder;
|
||||
|
||||
try {
|
||||
fs.accessSync(path.resolve(this.config.appPath, 'plugins', plugin, 'admin', 'src', 'containers', 'App'));
|
||||
fs.accessSync(
|
||||
path.resolve(this.config.appPath, 'plugins', plugin, 'admin', 'src', 'containers', 'App'),
|
||||
);
|
||||
hasAdminFolder = true;
|
||||
} catch(err) {
|
||||
} catch (err) {
|
||||
hasAdminFolder = false;
|
||||
}
|
||||
return hasAdminFolder;
|
||||
@ -130,7 +158,11 @@ module.exports = function() {
|
||||
.map(name => ({
|
||||
id: name,
|
||||
source: Object.keys(this.config.environments).reduce((acc, current) => {
|
||||
const source = _.get(this.config.environments[current].server, 'admin.build.plugins.source', 'default');
|
||||
const source = _.get(
|
||||
this.config.environments[current].server,
|
||||
'admin.build.plugins.source',
|
||||
'default',
|
||||
);
|
||||
|
||||
if (_.isString(source)) {
|
||||
acc[current] = configuratePlugin(acc, current, source, name);
|
||||
@ -139,7 +171,7 @@ module.exports = function() {
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
}, {}),
|
||||
}));
|
||||
|
||||
fs.writeFileSync(sourcePath, JSON.stringify(data, null, 2), 'utf8');
|
||||
|
||||
@ -6,7 +6,7 @@ const { after, includes, indexOf, drop, dropRight, uniq, defaultsDeep, get, set,
|
||||
module.exports = async function() {
|
||||
// Set if is admin destination for middleware application.
|
||||
this.app.use(async (ctx, next) => {
|
||||
if (ctx.request.header['origin'] === 'http://localhost:4000' || ctx.request.method === 'OPTIONS') {
|
||||
if (ctx.request.header['origin'] === 'http://localhost:4000') {
|
||||
ctx.request.header['x-forwarded-host'] = 'strapi';
|
||||
}
|
||||
|
||||
|
||||
129
test/helpers/generators.json
Normal file
129
test/helpers/generators.json
Normal file
@ -0,0 +1,129 @@
|
||||
{
|
||||
"article": {
|
||||
"attributes":[
|
||||
{
|
||||
"name":"title",
|
||||
"params":{
|
||||
"appearance":{
|
||||
"WYSIWYG":false
|
||||
},
|
||||
"multiple":false,
|
||||
"type":"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"content",
|
||||
"params":{
|
||||
"appearance":{
|
||||
"WYSIWYG":true
|
||||
},
|
||||
"multiple":false,
|
||||
"type":"text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"published",
|
||||
"params":{
|
||||
"appearance":{
|
||||
"WYSIWYG":false
|
||||
},
|
||||
"multiple":false,
|
||||
"type":"boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"author",
|
||||
"params":{
|
||||
"nature":"manyToOne",
|
||||
"target":"user",
|
||||
"pluginValue":"users-permissions",
|
||||
"key":"articles",
|
||||
"plugin":true
|
||||
}
|
||||
}
|
||||
],
|
||||
"connection":"default",
|
||||
"name":"article",
|
||||
"description":"",
|
||||
"collectionName":""
|
||||
},
|
||||
"tag": {
|
||||
"attributes":[
|
||||
{
|
||||
"name":"name",
|
||||
"params":{
|
||||
"appearance":{
|
||||
"WYSIWYG":false
|
||||
},
|
||||
"multiple":false,
|
||||
"type":"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"articles",
|
||||
"params":{
|
||||
"dominant":true,
|
||||
"nature":"manyToMany",
|
||||
"target":"article",
|
||||
"key":"tags"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connection":"default",
|
||||
"name":"tag",
|
||||
"description":"",
|
||||
"collectionName":""
|
||||
},
|
||||
"category": {
|
||||
"attributes":[
|
||||
{
|
||||
"name":"name",
|
||||
"params":{
|
||||
"appearance":{
|
||||
"WYSIWYG":false
|
||||
},
|
||||
"multiple":false,
|
||||
"type":"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"articles",
|
||||
"params":{
|
||||
"nature":"oneToMany",
|
||||
"target":"article",
|
||||
"key":"category"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connection":"default",
|
||||
"name":"category",
|
||||
"description":"",
|
||||
"collectionName":""
|
||||
},
|
||||
"reference": {
|
||||
"attributes":[
|
||||
{
|
||||
"name":"name",
|
||||
"params":{
|
||||
"appearance":{
|
||||
"WYSIWYG":false
|
||||
},
|
||||
"multiple":false,
|
||||
"type":"string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name":"article",
|
||||
"params":{
|
||||
"target":"article",
|
||||
"key":"reference",
|
||||
"nature":"oneToOne"
|
||||
}
|
||||
}
|
||||
],
|
||||
"connection":"default",
|
||||
"name":"reference",
|
||||
"description":"",
|
||||
"collectionName":""
|
||||
}
|
||||
}
|
||||
32
test/helpers/restart.js
Normal file
32
test/helpers/restart.js
Normal file
@ -0,0 +1,32 @@
|
||||
module.exports = function (rq) {
|
||||
return new Promise(async (resolve) => {
|
||||
const ping = async () => {
|
||||
try {
|
||||
await rq({
|
||||
url: '/_health',
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
json: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Keep-Alive': false,
|
||||
}
|
||||
});
|
||||
|
||||
return resolve();
|
||||
} catch (err) {
|
||||
if (err.statusCode) {
|
||||
return resolve();
|
||||
} else {
|
||||
return setTimeout(() => {
|
||||
ping();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
ping();
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
774
test/index.test.js
Normal file
774
test/index.test.js
Normal file
@ -0,0 +1,774 @@
|
||||
let request = require('request');
|
||||
|
||||
const form = require('./helpers/generators');
|
||||
const restart = require('./helpers/restart');
|
||||
|
||||
request = request.defaults({
|
||||
baseUrl: 'http://localhost:1337'
|
||||
});
|
||||
|
||||
const rq = (options) => {
|
||||
const params = JSON.parse(JSON.stringify(options));
|
||||
|
||||
for (let key in params.formData) {
|
||||
if (typeof params.formData[key] === 'object') {
|
||||
params.formData[key] = JSON.stringify(params.formData[key]);
|
||||
}
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
request(params, (err, res, body) => {
|
||||
if (err || res.statusCode < 200 || res.statusCode >= 300) {
|
||||
return reject(err || body);
|
||||
}
|
||||
|
||||
return resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const cleanDate = (entry) => {
|
||||
delete entry.updatedAt;
|
||||
delete entry.createdAt;
|
||||
delete entry.created_at;
|
||||
delete entry.updated_at;
|
||||
};
|
||||
|
||||
let data;
|
||||
|
||||
describe('App setup auth', () => {
|
||||
test(
|
||||
'Register admin user',
|
||||
async () => {
|
||||
const body = await rq({
|
||||
url: `/auth/local/register`,
|
||||
method: 'POST',
|
||||
body: {
|
||||
username: 'admin',
|
||||
email: 'admin@strapi.io',
|
||||
password: 'pcw123'
|
||||
},
|
||||
json: true
|
||||
});
|
||||
|
||||
request = request.defaults({
|
||||
headers: {
|
||||
'Authorization': `Bearer ${body.jwt}`
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Generate test APIs', () => {
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create new article API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.article,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create new tag API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.tag,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create new category API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.category,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create new reference API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models`,
|
||||
method: 'POST',
|
||||
body: form.reference,
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Test manyToMany relation (article - tag) with Content Manager', () => {
|
||||
beforeAll(() => {
|
||||
data = {
|
||||
articles: [],
|
||||
tags: []
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create tag1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/tag/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: {
|
||||
name: 'tag1'
|
||||
}
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.tags.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('tag1');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create tag2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/tag/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: {
|
||||
name: 'tag2'
|
||||
}
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.tags.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('tag2');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create tag3',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/tag/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: {
|
||||
name: 'tag3'
|
||||
}
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.tags.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('tag3');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article1 without relation',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 1',
|
||||
content: 'My super content 1'
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(0);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article2 with tag1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 2',
|
||||
content: 'Content 2',
|
||||
tags: [data.tags[0]]
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(1);
|
||||
expect(body.tags[0].id).toBe(data.tags[0].id);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 add tag2',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
tags: [data.tags[1]]
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(1);
|
||||
expect(body.tags[0].id).toBe(data.tags[1].id);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 add tag1 and tag3',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0]);
|
||||
entry.tags.push(data.tags[0]);
|
||||
entry.tags.push(data.tags[2]);
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(3);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 remove one tag',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0]);
|
||||
entry.tags = entry.tags.slice(1);
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(2);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 remove all tag',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
tags: []
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
expect(body.tags.length).toBe(0);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Test oneToMany - manyToOne relation (article - category) with Content Manager', () => {
|
||||
beforeAll(() => {
|
||||
data = {
|
||||
articles: [],
|
||||
categories: []
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create cat1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/category/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: {
|
||||
name: 'cat1'
|
||||
}
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.categories.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('cat1');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create cat2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/category/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: {
|
||||
name: 'cat2'
|
||||
}
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.categories.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.name).toBe('cat2');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article1 with cat1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 1',
|
||||
content: 'Content 1',
|
||||
category: data.categories[0]
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.category.name).toBe(entry.category.name);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 with cat2',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
category: data.categories[1]
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.category.name).toBe(entry.category.name);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article2',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 2',
|
||||
content: 'Content 2'
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article2 with cat2',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[1], {
|
||||
category: data.categories[1]
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles[1] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.category.name).toBe(entry.category.name);
|
||||
expect(Array.isArray(body.tags)).toBeTruthy();
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update cat1 with article1',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.categories[0]);
|
||||
entry.articles.push(data.articles[0]);
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/category/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.categories[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.name).toBe(entry.name);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create cat3 with article1',
|
||||
async () => {
|
||||
const entry = {
|
||||
name: 'cat3',
|
||||
articles: [data.articles[0]]
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/category/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.categories.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(Array.isArray(body.articles)).toBeTruthy();
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.name).toBe(entry.name);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get article1 with cat3',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${data.articles[0].id}?source=content-manager`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.category.id).toBe(data.categories[2].id)
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get article2 with cat2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${data.articles[1].id}?source=content-manager`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.category.id).toBe(data.categories[1].id)
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get cat1 without relations',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/category/${data.categories[0].id}?source=content-manager`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.articles.length).toBe(0);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get cat2 with article2',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/category/${data.categories[1].id}?source=content-manager`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.articles[0].id).toBe(data.articles[1].id);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get cat3 with article1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/category/${data.categories[2].id}?source=content-manager`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.articles.length).toBe(1);
|
||||
expect(body.articles[0].id).toBe(data.articles[0].id);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Test oneToOne relation (article - reference) with Content Manager', () => {
|
||||
beforeAll(() => {
|
||||
data = {
|
||||
articles: [],
|
||||
references: []
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Create ref1',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/reference/?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: {
|
||||
name: 'ref1'
|
||||
}
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.references.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.name).toBe('ref1');
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 1',
|
||||
content: 'Content 1'
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Update article1 with ref1',
|
||||
async () => {
|
||||
const entry = Object.assign({}, data.articles[0], {
|
||||
reference: data.references[0].id
|
||||
});
|
||||
|
||||
cleanDate(entry);
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${entry.id}?source=content-manager`,
|
||||
method: 'PUT',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles[0] = body;
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.reference.id).toBe(entry.reference);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Create article2 with ref1',
|
||||
async () => {
|
||||
const entry = {
|
||||
title: 'Article 2',
|
||||
content: 'Content 2',
|
||||
reference: data.references[0].id
|
||||
};
|
||||
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article?source=content-manager`,
|
||||
method: 'POST',
|
||||
formData: entry
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
data.articles.push(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.title).toBe(entry.title);
|
||||
expect(body.content).toBe(entry.content);
|
||||
expect(body.reference.id).toBe(entry.reference);
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Get article1 without relations',
|
||||
async () => {
|
||||
let body = await rq({
|
||||
url: `/content-manager/explorer/article/${data.articles[0].id}?source=content-manager`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
body = JSON.parse(body);
|
||||
|
||||
expect(body.id);
|
||||
expect(body.reference).toBe(null);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('Delete test APIs', () => {
|
||||
beforeEach(async () => {
|
||||
await restart(rq);
|
||||
}, 60000);
|
||||
|
||||
test(
|
||||
'Delete article API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models/article`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Delete tag API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models/tag`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
test(
|
||||
'Delete category API',
|
||||
async () => {
|
||||
await rq({
|
||||
url: `/content-type-builder/models/category`,
|
||||
method: 'DELETE',
|
||||
json: true
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
96
test/start.js
Normal file
96
test/start.js
Normal file
@ -0,0 +1,96 @@
|
||||
const exec = require('child_process').exec;
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
const strapiBin = path.resolve('./packages/strapi/bin/strapi.js');
|
||||
const appName = 'testApp';
|
||||
let appStart;
|
||||
|
||||
const databases = {
|
||||
mongo: `--dbclient=mongo --dbhost=127.0.0.1 --dbport=27017 --dbname=strapi-test-${new Date().getTime()} --dbusername="" --dbpassword=""`,
|
||||
postgres: `--dbclient=postgres --dbhost=127.0.0.1 --dbport=5432 --dbname=strapi-test --dbusername="" --dbpassword=""`,
|
||||
mysql: `--dbclient=mysql --dbhost=127.0.0.1 --dbport=3306 --dbname=strapi-test --dbusername="root" --dbpassword="root"`
|
||||
};
|
||||
|
||||
const {runCLI: jest} = require('jest-cli/build/cli');
|
||||
|
||||
const main = async () => {
|
||||
const clean = () => {
|
||||
return new Promise((resolve) => {
|
||||
fs.exists(appName, exists => {
|
||||
if (exists) {
|
||||
fs.removeSync(appName);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const generate = (database) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const appCreation = exec(
|
||||
`node ${strapiBin} new ${appName} --dev ${database}`,
|
||||
);
|
||||
|
||||
appCreation.stdout.on('data', data => {
|
||||
console.log(data.toString());
|
||||
|
||||
if (data.includes('is ready at')) {
|
||||
appCreation.kill();
|
||||
return resolve();
|
||||
}
|
||||
|
||||
if (data.includes('Database connection has failed')) {
|
||||
appCreation.kill();
|
||||
return reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const start = () => {
|
||||
return new Promise((resolve) => {
|
||||
appStart = exec(
|
||||
`node ${strapiBin} start ${appName}`,
|
||||
);
|
||||
|
||||
appStart.stdout.on('data', data => {
|
||||
console.log(data.toString());
|
||||
|
||||
if (data.includes('To shut down your server')) {
|
||||
return resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const test = () => {
|
||||
console.log('Launch test suits');
|
||||
return new Promise(async (resolve) => {
|
||||
const options = {
|
||||
projects: [process.cwd()],
|
||||
silent: false
|
||||
};
|
||||
|
||||
await jest(options, options.projects);
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const testProcess = async (database) => {
|
||||
await clean();
|
||||
await generate(database);
|
||||
await start();
|
||||
await test();
|
||||
|
||||
appStart.kill();
|
||||
};
|
||||
|
||||
await testProcess(databases.mongo);
|
||||
await testProcess(databases.postgres);
|
||||
await testProcess(databases.mysql);
|
||||
};
|
||||
|
||||
main();
|
||||
Loading…
x
Reference in New Issue
Block a user