diff --git a/examples/getstarted/config/plugins.js b/examples/getstarted/config/plugins.js index a6bb88b407..1bac8ddda6 100644 --- a/examples/getstarted/config/plugins.js +++ b/examples/getstarted/config/plugins.js @@ -19,6 +19,9 @@ module.exports = () => ({ info: { version: '2.0.0', }, + 'x-strapi-config': { + plugins: ['upload'], + }, }, }, myplugin: { diff --git a/packages/plugins/documentation/package.json b/packages/plugins/documentation/package.json index bc54ccec4a..5539a3ccfe 100644 --- a/packages/plugins/documentation/package.json +++ b/packages/plugins/documentation/package.json @@ -33,6 +33,7 @@ "cheerio": "^1.0.0-rc.12", "formik": "2.2.9", "fs-extra": "10.0.0", + "immer": "^9.0.19", "koa-static": "^5.0.0", "lodash": "4.17.21", "path-to-regexp": "6.2.1", diff --git a/packages/plugins/documentation/server/config/default-plugin-config.js b/packages/plugins/documentation/server/config/default-plugin-config.js index ea982a50a8..2df51cde28 100644 --- a/packages/plugins/documentation/server/config/default-plugin-config.js +++ b/packages/plugins/documentation/server/config/default-plugin-config.js @@ -16,6 +16,7 @@ module.exports = { name: 'Apache 2.0', url: 'https://www.apache.org/licenses/LICENSE-2.0.html', }, + 'x-strapi-generation-date': new Date().toISOString(), }, 'x-strapi-config': { path: '/documentation', diff --git a/packages/plugins/documentation/server/services/__tests__/build-component-schema.test.js b/packages/plugins/documentation/server/services/__tests__/build-component-schema.test.js index 7fd1beba7f..9ac6ffd75b 100644 --- a/packages/plugins/documentation/server/services/__tests__/build-component-schema.test.js +++ b/packages/plugins/documentation/server/services/__tests__/build-component-schema.test.js @@ -1,6 +1,6 @@ 'use strict'; -const _ = require('lodash'); +const _ = require('lodash/fp'); const buildComponentSchema = require('../helpers/build-component-schema'); const strapi = { diff --git a/packages/plugins/documentation/server/services/__tests__/documentation.test.js b/packages/plugins/documentation/server/services/__tests__/documentation.test.js index d9f8c8980d..9701ba8980 100644 --- a/packages/plugins/documentation/server/services/__tests__/documentation.test.js +++ b/packages/plugins/documentation/server/services/__tests__/documentation.test.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash/fp'); const fse = require('fs-extra'); const SwaggerParser = require('@apidevtools/swagger-parser'); const { api, plugins, components, contentTypes } = require('../__mocks__/mock-strapi-data'); @@ -71,7 +72,8 @@ describe('Documentation service', () => { const lastMockCall = fse.writeJson.mock.calls[fse.writeJson.mock.calls.length - 1]; const mockFinalDoc = lastMockCall[1]; - const validatePromise = SwaggerParser.validate(mockFinalDoc); + // The final documenation is read only, clone deep for this test + const validatePromise = SwaggerParser.validate(_.cloneDeep(mockFinalDoc)); await expect(validatePromise).resolves.not.toThrow(); }); diff --git a/packages/plugins/documentation/server/services/documentation.js b/packages/plugins/documentation/server/services/documentation.js index be041b5963..08f6453ca0 100755 --- a/packages/plugins/documentation/server/services/documentation.js +++ b/packages/plugins/documentation/server/services/documentation.js @@ -2,9 +2,9 @@ const path = require('path'); const fs = require('fs-extra'); -const _ = require('lodash'); +const _ = require('lodash/fp'); +const { produce } = require('immer'); const { getAbsoluteServerUrl } = require('@strapi/utils'); - const { builApiEndpointPath, buildComponentSchema } = require('./helpers'); const defaultOpenApiComponents = require('./utils/default-openapi-components'); @@ -32,7 +32,7 @@ module.exports = ({ strapi }) => { }, getDocumentationVersion() { - return _.get(config, 'info.version'); + return _.get('info.version', config); }, getFullDocumentationPath() { @@ -58,7 +58,8 @@ module.exports = ({ strapi }) => { path.resolve(this.getFullDocumentationPath(), version, 'full_documentation.json') ) ); - const generatedDate = _.get(doc, ['info', 'x-generation-date'], null); + + const generatedDate = _.get(['info', 'x-generation-date'], doc); return { version, generatedDate, url: '' }; } catch (err) { @@ -142,6 +143,7 @@ module.exports = ({ strapi }) => { ); for (const api of apisThatNeedGeneratedDocumentation) { const apiName = api.name; + // TODO: check if this is necessary const apiDirPath = path.join(this.getApiDocumentationPath(api), version); // TODO: check if this is necessary @@ -176,47 +178,55 @@ module.exports = ({ strapi }) => { // Set config defaults const serverUrl = getAbsoluteServerUrl(strapi.config); const apiPath = strapi.config.get('api.rest.prefix'); - // When no servers found set the default - if (config.servers.length === 0) { - _.set(config, 'servers', [ - { - url: `${serverUrl}${apiPath}`, - description: 'Development server', - }, - ]); - } - _.set(config, ['info', 'x-generation-date'], new Date().toISOString()); - _.set(config, ['x-strapi-config', 'plugins'], pluginsThatNeedDocumentation); - // Get the documentation customizer - const documentationCustomizer = _.get(config, ['x-strapi-config', 'customizer']); - // Delete it from the config so it doesn't end up in the spec - _.unset(config, ['x-strapi-config', 'customizer']); - // Prepare the document to be written with default config and generated paths - const generatedDocumentation = { ...config, paths }; - // Add the default components to the document to be written - _.set(generatedDocumentation, 'components', defaultOpenApiComponents); - // Merge the generated component schemas with the defaults - _.merge(generatedDocumentation.components, { schemas }); - // Apply the the registered overrides - overrideService.registeredOverrides.forEach((doc) => { - // Merge override tags with the generated tags - generatedDocumentation.tags = generatedDocumentation.tags || []; - generatedDocumentation.tags.push(...(doc.tags || [])); - // Merge override paths with the generated paths - // The override will add a new path or replace the value of an existing path - _.assign(generatedDocumentation.paths, doc.paths); - // Add components - _.forEach(doc.components || {}, (val, key) => { - generatedDocumentation.components[key] = generatedDocumentation.components[key] || {}; - // Merge override components with the generated components, - // The override will add a new component or replace the value of an existing component - _.assign(generatedDocumentation.components[key], val); + const generatedDocumentation = produce(config, (draft) => { + // When no servers found set the default + if (draft.servers.length === 0) { + draft.servers = [ + { + url: `${serverUrl}${apiPath}`, + description: 'Development server', + }, + ]; + } + + draft['x-strapi-config'].plugins = pluginsThatNeedDocumentation; + // Delete it from the config so it doesn't end up in the spec + delete draft['x-strapi-config'].customizer; + + // Set the generated paths + draft.paths = paths; + // Merge the generated component schemas with the defaults + draft.components = _.merge(defaultOpenApiComponents, { schemas }); + + overrideService.registeredOverrides.forEach((doc) => { + if (doc.tags) { + // Merge override tags with the generated tags + draft.tags = draft.tags || []; + draft.tags.push(...doc.tags); + } + + if (doc.paths) { + // Merge override paths with the generated paths + // The override will add a new path or replace the value of an existing path + draft.paths = { ...draft.paths, ...doc.paths }; + } + + if (doc.components) { + Object.entries(doc.components).forEach(([key, val]) => { + draft.components[key] = draft.components[key] || {}; + // Merge override components with the generated components, + // The override will add a new component or replace the value of an existing component + draft.components[key] = { ...draft.components[key], ...val }; + }); + } }); }); + // Get the documentation customizer + const documentationCustomizer = config['x-strapi-config'].customizer; // Escape hatch, allow the user to provide a customizer function that can manipulate // the generated documentation before it is written to the file system const finalDocumentation = documentationCustomizer - ? documentationCustomizer(_.cloneDeep(generatedDocumentation)) + ? produce(generatedDocumentation, documentationCustomizer) : generatedDocumentation; await fs.ensureFile(fullDocJsonPath); diff --git a/yarn.lock b/yarn.lock index 8f5cb66e14..35af8a7dda 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7466,6 +7466,7 @@ __metadata: formik: 2.2.9 fs-extra: 10.0.0 history: ^4.9.0 + immer: ^9.0.19 koa-static: ^5.0.0 lodash: 4.17.21 msw: 1.0.1 @@ -18793,6 +18794,13 @@ __metadata: languageName: node linkType: hard +"immer@npm:^9.0.19": + version: 9.0.21 + resolution: "immer@npm:9.0.21" + checksum: 70e3c274165995352f6936695f0ef4723c52c92c92dd0e9afdfe008175af39fa28e76aafb3a2ca9d57d1fb8f796efc4dd1e1cc36f18d33fa5b74f3dfb0375432 + languageName: node + linkType: hard + "import-fresh@npm:^3.0.0, import-fresh@npm:^3.1.0, import-fresh@npm:^3.2.1": version: 3.3.0 resolution: "import-fresh@npm:3.3.0"