diff --git a/docs/3.x.x/en/concepts/concepts.md b/docs/3.x.x/en/concepts/concepts.md index 966482fdf4..abc38254ad 100644 --- a/docs/3.x.x/en/concepts/concepts.md +++ b/docs/3.x.x/en/concepts/concepts.md @@ -92,7 +92,7 @@ The controllers are defined in each `./api/**/controllers/` folders. Every JavaS ## Filters -Filters are a handy way to request data according to generic parameters. It makes filtering, sorting and paginating easy and reusable (eg. `GET /user?_limit=30&name=John`). +Filters are a handy way to request data according to generic parameters. It makes filtering, sorting and paginating easy and reusable (eg. `GET /users?_limit=30&name=John`). > Please refer to the [filters' guide](../guides/filters.md) for more informations. diff --git a/docs/3.x.x/en/getting-started/quick-start.md b/docs/3.x.x/en/getting-started/quick-start.md index 1a26062328..3a1340c9bc 100644 --- a/docs/3.x.x/en/getting-started/quick-start.md +++ b/docs/3.x.x/en/getting-started/quick-start.md @@ -137,16 +137,16 @@ In order to do so, you'll need to allow access to other users (identified as 'Gu ### List entries (GET) -To retrieve the list of products, use the `GET /your-content-type` route. +To retrieve the list of products, use the `GET /products` route. -Generated APIs provide a handy way to filter and order queries. In that way, ordering products by price is as easy as `GET http://localhost:1337/product?_sort=price:asc`. For more informations, read the [filters documentation](../guides/filters.md) +Generated APIs provide a handy way to filter and order queries. In that way, ordering products by price is as easy as `GET http://localhost:1337/products?_sort=price:asc`. For more informations, read the [filters documentation](../guides/filters.md) Here is an example using jQuery. ```js $.ajax({ type: 'GET', - url: 'http://localhost:1337/product?_sort=price:asc', // Order by price. + url: 'http://localhost:1337/products?_sort=price:asc', // Order by price. done: function(products) { console.log('Well done, here is the list of products: ', products); }, @@ -163,7 +163,7 @@ If you want to get a specific entry, add the `id` of the wanted product at the e ```js $.ajax({ type: 'GET', - url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product. + url: 'http://localhost:1337/products/123', // Where `123` is the `id` of the product. done: function(product) { console.log('Well done, here is the product having the `id` 123: ', product); }, @@ -182,7 +182,7 @@ jQuery example: ```js $.ajax({ type: 'POST', - url: 'http://localhost:1337/product', + url: 'http://localhost:1337/products', data: { name: 'Cheese cake', description: 'Chocolate cheese cake with ice cream', @@ -206,7 +206,7 @@ jQuery example: ```js $.ajax({ type: 'PUT', - url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product. + url: 'http://localhost:1337/products/123', // Where `123` is the `id` of the product. data: { description: 'This is the new description' }, @@ -228,7 +228,7 @@ jQuery example: ```js $.ajax({ type: 'DELETE', - url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product. + url: 'http://localhost:1337/products/123', // Where `123` is the `id` of the product. done: function(product) { console.log('Congrats, your product has been successfully deleted: ', product); }, diff --git a/docs/3.x.x/en/guides/authentication.md b/docs/3.x.x/en/guides/authentication.md index 1e74f59b0f..b5340ca3e8 100644 --- a/docs/3.x.x/en/guides/authentication.md +++ b/docs/3.x.x/en/guides/authentication.md @@ -97,7 +97,7 @@ By default, each API request is identified as `guest` role (see permissions of ` ```js $.ajax({ type: 'GET', - url: 'http://localhost:1337/article', + url: 'http://localhost:1337/articles', headers: { Authorization: `Bearer ${token}` }, @@ -117,7 +117,8 @@ This action sends an email to a user with the link of you reset password page. T #### Usage - `email` is your user email. -- `url` is the url link that user will receive. +- `url` is the url link that user will receive. After the user triggers a new password reset, +it is used to redirect the user to the new-password form. ```js $.ajax({ @@ -125,7 +126,7 @@ $.ajax({ url: 'http://localhost:1337/auth/forgot-password', data: { email: 'user@strapi.io', - url: 'http://mon-site.com/rest-password' + url: 'http:/localhost:1337/admin/plugins/users-permissions/auth/reset-password' }, done: function() { console.log('Your user received an email'); @@ -136,8 +137,6 @@ $.ajax({ }); ``` -> Received link url format http://mon-site.com/rest-password?code=privateCode - ## Reset user password. This action will reset the user password. @@ -165,7 +164,7 @@ $.ajax({ ``` ## User Object In Strapi Context -The User object is available to successfully authenticated requests. +The `user` object is available to successfully authenticated requests. #### Usage - The authenticated `user` object is a property of `ctx.state`. @@ -189,10 +188,9 @@ The User object is available to successfully authenticated requests. ``` - ## Add a new provider -To add a new provider on strapi, you will need to perform changes onto the following files: +To add a new provider on Strapi, you will need to perform changes onto the following files: ``` packages/strapi-plugin-users-permissions/services/Providers.js @@ -203,7 +201,7 @@ packages/strapi-plugin-users-permissions/admin/src/translations/en.json We will go step by step. -### Configure your Provider request +### Configure your Provider Request First, we need to configure our new provider onto `Provider.js` file. Jump onto the `getProfile` function, you will see the list of currently available providers in the form of a `switch...case`. @@ -212,9 +210,10 @@ As you can see, `getProfile` take three params: 1. provider :: The name of the used provider as a string. 2. query :: The query is the result of the provider callback. -3. callback :: The callback function who will continue the internal strapi login logic. +3. callback :: The callback function who will continue the internal Strapi login logic. -Let's take the `discord` one as an example since it's not the easier, it should cover most of the case you may encounter trying to implement your own provider. +Let's take the `discord` one as an example since it's not the easier, it should cover most of the case you +may encounter trying to implement your own provider. #### Configure your oauth generic information @@ -239,9 +238,11 @@ Let's take the `discord` one as an example since it's not the easier, it should } } }); + } ``` -So here, you can see that we use a module called `Purest`. This module gives us with a generic way to interact with the REST API. +So here, you can see that we use a module called `Purest`. This module gives us with a generic way to interact +with the REST API. To understand each value usage, and the templating syntax, I invite you to read the [Official Purest Documentation](https://github.com/simov/purest/tree/2.x) @@ -265,17 +266,21 @@ You may also want to take a look onto the numerous already made configurations [ } ``` -Here is the next part of our switch. Now that we have properly configured our provider, we want to use it to retrieve user information. +Here is the next part of our switch. Now that we have properly configured our provider, we want to use it to retrieve +user information. -Here you see the real power of `purest`, you can simply make a get request on the desired URL, using the `access_token` from the `query` parameter to authenticate. +Here you see the real power of `purest`, you can simply make a get request on the desired URL, using the `access_token` +from the `query` parameter to authenticate. That way, you should be able to retrieve the user info you need. -Now, you can simply call the `callback` function with the username and email of your user. That way, strapi will be able to retrieve your user from the database and log you in. +Now, you can simply call the `callback` function with the username and email of your user. That way, strapi will be able +to retrieve your user from the database and log you in. #### Configure the new provider model onto database -Now, we need to configure our 'model' for our new provider. That way, our settings can be stored in the database, and managed from the admin panel. +Now, we need to configure our 'model' for our new provider. That way, our settings can be stored in the database, and +managed from the admin panel. Into: `packages/strapi-plugin-users-permissions/config/functions/bootstrap.js` @@ -296,9 +301,8 @@ For our discord provider it will look like: }, ``` - -You have already done the hard part, now, we simply need to make our new provider available from the front side of our application. So let's do it! - +You have already done the hard part, now, we simply need to make our new provider available from the front +side of our application. So let's do it! @@ -323,7 +327,6 @@ These two change will set up the popup message who appear on the UI when we will That's it, now you should be able to use your new provider. - ## Email templates [See the documentation on GitHub](https://github.com/strapi/strapi/blob/master/packages/strapi-plugin-users-permissions/docs/email-templates.md) diff --git a/docs/3.x.x/en/guides/filters.md b/docs/3.x.x/en/guides/filters.md index 351a6f5a5b..e1f2119855 100644 --- a/docs/3.x.x/en/guides/filters.md +++ b/docs/3.x.x/en/guides/filters.md @@ -29,11 +29,11 @@ Easily filter results according to fields values. Find users having `John` as first name. -`GET /user?firstName=John` +`GET /users?firstName=John` Find products having a price equal or greater than `3`. -`GET /product?price_gte=3` +`GET /products?price_gte=3` ### Sort @@ -43,8 +43,8 @@ Sort according to a specific field. Sort users by email. - - ASC: `GET /user?_sort=email:asc` - - DESC: `GET /user?_sort=email:desc` + - ASC: `GET /users?_sort=email:asc` + - DESC: `GET /users?_sort=email:desc` ### Limit @@ -54,7 +54,7 @@ Limit the size of the returned results. Limit the result length to 30. -`GET /user?_limit=30` +`GET /users?_limit=30` ### Start @@ -64,7 +64,7 @@ Skip a specific number of entries (especially useful for pagination). Get the second page of results. -`GET /user?_start=10&_limit=10` +`GET /users?_start=10&_limit=10` ## Programmatic usage diff --git a/docs/3.x.x/en/guides/policies.md b/docs/3.x.x/en/guides/policies.md index e0553f1659..caf25ddfac 100644 --- a/docs/3.x.x/en/guides/policies.md +++ b/docs/3.x.x/en/guides/policies.md @@ -40,7 +40,7 @@ The global policies can be associated to any routes in your project. "routes": [ { "method": "GET", - "path": "/car", + "path": "/cars", "handler": "Car.find", "config": { "policies": [ @@ -66,7 +66,7 @@ Plugins can add and expose policies into your app. For example, the plugin `Auth "routes": [ { "method": "GET", - "path": "/car", + "path": "/cars", "handler": "Car.find", "config": { "policies": [ @@ -102,7 +102,7 @@ module.exports = async (ctx, next) => { "routes": [ { "method": "GET", - "path": "/car", + "path": "/cars", "handler": "Car.find", "config": { "policies": [ diff --git a/docs/3.x.x/en/guides/routing.md b/docs/3.x.x/en/guides/routing.md index 8f97c04489..5fc6228233 100644 --- a/docs/3.x.x/en/guides/routing.md +++ b/docs/3.x.x/en/guides/routing.md @@ -12,17 +12,17 @@ You have to edit the `routes.json` file in one of your APIs folders (`./api/**/c "routes": [ { "method": "GET", - "path": "/product", + "path": "/products", "handler": "Product.find", }, { "method": ["POST", "PUT"], - "path": "/product/:id", + "path": "/products/:id", "handler": "Product.createOrUpdate", }, { "method": "POST", - "path": "/product/:id/buy", + "path": "/products/:id/buy", "handler": "Product.buy", "config": { "policies": ["isAuthenticated", "hasCreditCard"] @@ -33,7 +33,7 @@ You have to edit the `routes.json` file in one of your APIs folders (`./api/**/c ``` - `method` (string): Method or array of methods to hit the route (ex: `GET`, `POST`, `PUT`, `HEAD`, `DELETE`, `PATCH`) -- `path` (string): URL starting with `/` (ex: `/product`) +- `path` (string): URL starting with `/` (ex: `/products`) - `handler` (string): Action to executed when the route is hit following this syntax `.` - `config` - `policies` (array): Array of policies names or path ([see more](../guides/policies.md)) @@ -48,12 +48,12 @@ The router used by Strapi allows you to create dynamic routes where you can use "routes": [ { "method": "GET", - "path": "/product/:category/:id", + "path": "/products/:category/:id", "handler": "Product.findOneByCategory", }, { "method": "GET", - "path": "/product/:region(\\d{2}|\\d{3})/:id", // Only match when the first parameter contains 2 or 3 digits. + "path": "/products/:region(\\d{2}|\\d{3})/:id", // Only match when the first parameter contains 2 or 3 digits. "handler": "Product.findOneByRegion", } ] diff --git a/packages/strapi-generate-api/json/routes.json.js b/packages/strapi-generate-api/json/routes.json.js index 96c1489f71..7f65faf4af 100755 --- a/packages/strapi-generate-api/json/routes.json.js +++ b/packages/strapi-generate-api/json/routes.json.js @@ -21,42 +21,42 @@ module.exports = scope => { const routes = { routes: [{ method: 'GET', - path: '/' + scope.humanizeId, + path: '/' + scope.idPluralized, handler: scope.globalID + '.find', config: { policies: [] } }, { method: 'GET', - path: '/' + scope.humanizeId + '/count', + path: '/' + scope.idPluralized + '/count', handler: scope.globalID + '.count', config: { policies: [] } }, { method: 'GET', - path: '/' + scope.humanizeId + '/:' + tokenID, + path: '/' + scope.idPluralized + '/:' + tokenID, handler: scope.globalID + '.findOne', config: { policies: [] } }, { method: 'POST', - path: '/' + scope.humanizeId, + path: '/' + scope.idPluralized, handler: scope.globalID + '.create', config: { policies: [] } }, { method: 'PUT', - path: '/' + scope.humanizeId + '/:' + tokenID, + path: '/' + scope.idPluralized + '/:' + tokenID, handler: scope.globalID + '.update', config: { policies: [] } }, { method: 'DELETE', - path: '/' + scope.humanizeId + '/:' + tokenID, + path: '/' + scope.idPluralized + '/:' + tokenID, handler: scope.globalID + '.destroy', config: { policies: [] @@ -67,21 +67,21 @@ module.exports = scope => { if (scope.args.tpl && scope.args.tpl !== 'mongoose') { routes.routes.push({ method: 'POST', - path: '/' + scope.humanizeId + '/:' + tokenID + '/relationships/:relation', + path: '/' + scope.idPluralized + '/:' + tokenID + '/relationships/:relation', handler: scope.globalID + '.createRelation', config: { policies: [] } }, { method: 'PUT', - path: '/' + scope.humanizeId + '/:' + tokenID + '/relationships/:relation', + path: '/' + scope.idPluralized + '/:' + tokenID + '/relationships/:relation', handler: scope.globalID + '.updateRelation', config: { policies: [] } }, { method: 'DELETE', - path: '/' + scope.humanizeId + '/:' + tokenID + '/relationships/:relation', + path: '/' + scope.idPluralized + '/:' + tokenID + '/relationships/:relation', handler: scope.globalID + '.destroyRelation', config: { policies: [] diff --git a/packages/strapi-generate-new/files/config/environments/development/server.json b/packages/strapi-generate-new/files/config/environments/development/server.json index 46db2086bb..81302a2134 100755 --- a/packages/strapi-generate-new/files/config/environments/development/server.json +++ b/packages/strapi-generate-new/files/config/environments/development/server.json @@ -9,5 +9,8 @@ }, "cron": { "enabled": false + }, + "admin": { + "autoOpen": true } } diff --git a/packages/strapi-generate-new/files/config/environments/production/server.json b/packages/strapi-generate-new/files/config/environments/production/server.json index bc0294e31f..4e5d9d1b5e 100755 --- a/packages/strapi-generate-new/files/config/environments/production/server.json +++ b/packages/strapi-generate-new/files/config/environments/production/server.json @@ -9,5 +9,8 @@ }, "cron": { "enabled": false + }, + "admin": { + "autoOpen": false } } diff --git a/packages/strapi-generate-new/files/config/environments/staging/server.json b/packages/strapi-generate-new/files/config/environments/staging/server.json index bc0294e31f..4e5d9d1b5e 100755 --- a/packages/strapi-generate-new/files/config/environments/staging/server.json +++ b/packages/strapi-generate-new/files/config/environments/staging/server.json @@ -9,5 +9,8 @@ }, "cron": { "enabled": false + }, + "admin": { + "autoOpen": false } } diff --git a/packages/strapi-hook-bookshelf/lib/index.js b/packages/strapi-hook-bookshelf/lib/index.js index 5d4ddbc11c..a5d651de30 100755 --- a/packages/strapi-hook-bookshelf/lib/index.js +++ b/packages/strapi-hook-bookshelf/lib/index.js @@ -461,6 +461,11 @@ module.exports = function(strapi) { const connection = strapi.config.connections[definition.connection]; let columns = Object.keys(attributes).filter(attribute => ['string', 'text'].includes(attributes[attribute].type)); + if (!columns) { + // No text columns founds, exit from creating Fulltext Index + return; + } + switch (connection.settings.client) { case 'pg': { // Enable extension to allow GIN indexes. diff --git a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js index c27c7daafe..11ebcc5421 100644 --- a/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js +++ b/packages/strapi-plugin-content-type-builder/admin/src/containers/Form/index.js @@ -143,7 +143,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- } // Check if user is adding a relation with the same content type - + if (includes(this.props.hash, 'attributerelation') && this.props.modifiedDataAttribute.params.target === this.props.modelName && get(this.props.modifiedDataAttribute, ['params', 'nature'], '') !== 'oneWay') { // Insert two attributes this.props.addAttributeRelationToContentType(this.props.modifiedDataAttribute); @@ -418,7 +418,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- if (includes(this.props.hash, 'choose')) { const { nodeToFocus } = this.state; let toAdd = 0; - + switch(e.keyCode) { case 37: // Left arrow case 39: // Right arrow @@ -446,7 +446,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- toAdd = 0; break; } - + this.setState(prevState => ({ nodeToFocus: prevState.nodeToFocus + toAdd })); } } @@ -456,7 +456,6 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- const hashArray = split(this.props.hash, ('::')); const valueToReplace = includes(this.props.hash, '#create') ? '#create' : '#edit'; const contentTypeName = replace(hashArray[0], valueToReplace, ''); - let cbSuccess; let dataSucces = null; let cbFail; @@ -466,7 +465,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- // Check if the user is editing the attribute const isAttribute = includes(hashArray[1], 'attribute'); cbSuccess = isAttribute ? () => this.editTempContentTypeAttribute(redirectToChoose) : this.createContentType; - dataSucces = isAttribute ? null : this.props.modifiedDataEdit; + dataSucces = isAttribute ? null : this.getModelWithCamelCaseName(this.props.modifiedDataEdit); cbFail = isAttribute ? () => this.editContentTypeAttribute(redirectToChoose) : this.contentTypeEdit; return this.testContentType(contentTypeName, cbSuccess, dataSucces, cbFail); } @@ -476,11 +475,24 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- return this.testContentType(contentTypeName, cbSuccess, dataSucces, cbFail); } default: { - return this.createContentType(this.props.modifiedData); + return this.createContentType( + this.getModelWithCamelCaseName(this.props.modifiedData) + ); } } } + getModelWithCamelCaseName = (model = {}) => { + if (isEmpty(model) || isEmpty(model.name)) { + return; + } + + return { + ...model, + name: camelCase(model.name), + }; + } + initComponent = (props, condition) => { if (!isEmpty(props.hash)) { this.setState({ showModal: true }); @@ -514,7 +526,7 @@ export class Form extends React.Component { // eslint-disable-line react/prefer- } renderModalBodyChooseAttributes = () => { - const attributesDisplay = has(this.context.plugins.toJS(), 'upload') + const attributesDisplay = has(this.context.plugins.toJS(), 'upload') ? forms.attributesDisplay.items : forms.attributesDisplay.items.filter(obj => obj.type !== 'media'); // Don't display the media field if the upload plugin isn't installed diff --git a/packages/strapi-plugin-email/controllers/Email.js b/packages/strapi-plugin-email/controllers/Email.js index 95cb9073be..baf3e588fb 100644 --- a/packages/strapi-plugin-email/controllers/Email.js +++ b/packages/strapi-plugin-email/controllers/Email.js @@ -28,9 +28,9 @@ module.exports = { return; } - let options = ctx.request.body; + let options = ctx.request.body; - await strapi.plugins.email.services.send(options, config); + await strapi.plugins.email.services.email.send(options, config); // Send 200 `ok` ctx.send({}); @@ -48,7 +48,7 @@ module.exports = { }, getSettings: async (ctx) => { - let config = await strapi.plugins.email.services.email.getProviderConfig(ctx.params.environment); + let config = await strapi.plugins.email.services.email.getProviderConfig(ctx.params.environment); ctx.send({ providers: strapi.plugins.email.config.providers, diff --git a/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss b/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss index 5307203cc9..a2796a0c04 100755 --- a/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss +++ b/packages/strapi-plugin-settings-manager/admin/src/components/List/styles.scss @@ -113,14 +113,16 @@ button { margin-left: 1rem; } -.flexed { +.flexed, +.label { display: flex; } .label { + align-items: center; + line-height: 1; width: 15rem; height: 5.2rem; - line-height: 5.2rem; margin-left: 5rem; color: #333740; font-weight: 600; diff --git a/packages/strapi-plugin-users-permissions/config/routes.json b/packages/strapi-plugin-users-permissions/config/routes.json index b9881d1e9d..0cb559e3c5 100644 --- a/packages/strapi-plugin-users-permissions/config/routes.json +++ b/packages/strapi-plugin-users-permissions/config/routes.json @@ -211,10 +211,9 @@ "prefix": "" } }, - { "method": "GET", - "path": "/user", + "path": "/users", "handler": "User.find", "config": { "policies": [], @@ -223,7 +222,7 @@ }, { "method": "GET", - "path": "/user/me", + "path": "/users/me", "handler": "User.me", "config": { "policies": [], @@ -232,7 +231,7 @@ }, { "method": "GET", - "path": "/user/:_id", + "path": "/users/:_id", "handler": "User.findOne", "config": { "policies": [], @@ -241,7 +240,7 @@ }, { "method": "POST", - "path": "/user", + "path": "/users", "handler": "User.create", "config": { "policies": [], @@ -250,7 +249,7 @@ }, { "method": "PUT", - "path": "/user/:_id", + "path": "/users/:_id", "handler": "User.update", "config": { "policies": [], @@ -259,7 +258,7 @@ }, { "method": "DELETE", - "path": "/user/:_id", + "path": "/users/:_id", "handler": "User.destroy", "config": { "policies": [], diff --git a/packages/strapi-utils/lib/models.js b/packages/strapi-utils/lib/models.js index 0cfebd0248..f690a032b1 100644 --- a/packages/strapi-utils/lib/models.js +++ b/packages/strapi-utils/lib/models.js @@ -12,7 +12,7 @@ const _ = require('lodash'); // Following this discussion https://stackoverflow.com/questions/18082/validate-decimal-numbers-in-javascript-isnumeric this function is the best implem to determine if a value is a valid number candidate const isNumeric = (value) => { - return !isNaN(parseFloat(value)) && isFinite(value); + return !_.isObject(value) && !isNaN(parseFloat(value)) && isFinite(value); }; /* eslint-disable prefer-template */ @@ -466,7 +466,7 @@ module.exports = { // Remove the filter keyword at the end let splitKey = key.split('_').slice(0,-1); splitKey = splitKey.join('_'); - + if (modelAttributes[splitKey]) { fieldType = modelAttributes[splitKey]['type']; } diff --git a/packages/strapi/lib/core/configurations.js b/packages/strapi/lib/core/configurations.js index 0e046de1bc..98bb8a5ef1 100755 --- a/packages/strapi/lib/core/configurations.js +++ b/packages/strapi/lib/core/configurations.js @@ -382,6 +382,31 @@ const enableHookNestedDependencies = function (name, flattenHooksConfig, force = } }; +/** + * Allow dynamic config values through + * the native ES6 template string function. + */ +const regex = /\$\{[^()]*\}/g; +const excludeConfigPaths = ['info.scripts']; +const templateConfigurations = function (obj, configPath = '') { + // Allow values which looks like such as + // an ES6 literal string without parenthesis inside (aka function call). + // Exclude config with conflicting syntax (e.g. npm scripts). + return Object.keys(obj).reduce((acc, key) => { + if (isPlainObject(obj[key]) && !isString(obj[key])) { + acc[key] = templateConfigurations(obj[key], `${configPath}.${key}`); + } else if (isString(obj[key]) + && !excludeConfigPaths.includes(configPath.substr(1)) + && obj[key].match(regex) !== null) { + acc[key] = eval('`' + obj[key] + '`'); // eslint-disable-line prefer-template + } else { + acc[key] = obj[key]; + } + + return acc; + }, {}); +}; + const isAdminInDevMode = function () { try { fs.accessSync(path.resolve(this.config.appPath, 'admin', 'admin', 'build', 'index.html'), fs.constants.R_OK | fs.constants.W_OK);