264 lines
9.4 KiB
JavaScript
Raw Normal View History

'use strict';
const path = require('path');
2021-09-02 11:25:24 +02:00
const fs = require('fs-extra');
2023-03-22 15:18:51 +01:00
const { cloneDeep } = require('lodash/fp');
const { getAbsoluteServerUrl } = require('@strapi/utils');
2022-03-17 16:54:37 +01:00
const { builApiEndpointPath, buildComponentSchema } = require('./helpers');
const defaultOpenApiComponents = require('./utils/default-openapi-components');
const { getPluginsThatNeedDocumentation } = require('./utils/get-plugins-that-need-documentation');
2023-03-22 15:18:51 +01:00
/**
* @description
* Mutates the current state of the OpenAPI object and returns a new immutable object
*
* @param {object} currentState The immutable state of the OpenAPI object
* @param {function} mutateStateCallback A callback function that can mutate a copy of the immutable state
*
* @returns {object}
* The mutated copy as the new immutable state
*/
const mutateDocumentation = (currentState, mutateStateCallback) => {
// Create a copy of the current state that is mutable
2023-03-22 15:18:51 +01:00
const draftState = cloneDeep(currentState);
// Pass the draft to the callback for mutation
mutateStateCallback(draftState);
// Return the mutated state as a new immutable state
return Object.freeze(draftState);
};
module.exports = ({ strapi }) => {
const config = strapi.config.get('plugin.documentation');
const pluginsThatNeedDocumentation = getPluginsThatNeedDocumentation(config);
const overrideService = strapi.plugin('documentation').service('override');
2023-03-15 11:01:09 +01:00
2021-09-02 11:25:24 +02:00
return {
/**
2023-03-20 15:49:30 +01:00
*
* @deprecated
* registerDoc is deprecated it will be removed in the next major release,
* use strapi.plugin('documentation').service('override').registerOverride instead
* @param {object} doc - The openapi specifcation to override
* @param {object} options - The options to override the documentation
* @param {string} options.pluginOrigin - The name of the plugin that is overriding the documentation
* @param {string[]} options.excludeFromGeneration - The name of the plugin that is overriding the documentation
*/
2023-03-20 15:49:30 +01:00
registerDoc(doc, options) {
overrideService.registerOverride(doc, options);
2022-06-01 23:12:16 +02:00
},
2023-03-15 11:01:09 +01:00
2021-09-02 11:25:24 +02:00
getDocumentationVersion() {
2023-03-22 15:18:51 +01:00
return config.info.version;
2021-09-02 11:25:24 +02:00
},
2021-09-02 11:25:24 +02:00
getFullDocumentationPath() {
2022-03-14 17:54:35 +01:00
return path.join(strapi.dirs.app.extensions, 'documentation', 'documentation');
2021-09-02 11:25:24 +02:00
},
2023-03-15 11:01:09 +01:00
/**
*
* @deprecated
* This method will be removed in the next major release
*/
getCustomDocumentationPath() {
return path.join(strapi.dirs.app.extensions, 'documentation', 'config', 'settings.json');
},
2021-09-02 11:25:24 +02:00
getDocumentationVersions() {
return fs
.readdirSync(this.getFullDocumentationPath())
2022-08-08 23:33:39 +02:00
.map((version) => {
try {
2021-09-02 11:25:24 +02:00
const doc = JSON.parse(
fs.readFileSync(
path.resolve(this.getFullDocumentationPath(), version, 'full_documentation.json')
)
);
2023-03-22 15:18:51 +01:00
const generatedDate = doc.info['x-generation-date'];
2021-09-02 11:25:24 +02:00
return { version, generatedDate, url: '' };
} catch (err) {
2021-09-02 11:25:24 +02:00
return null;
}
2021-09-02 11:25:24 +02:00
})
2022-08-08 23:33:39 +02:00
.filter((x) => x);
2021-09-02 11:25:24 +02:00
},
/**
* Returns settings stored in core-store
*/
async getDocumentationAccess() {
const { restrictedAccess } = await strapi
2021-09-02 11:25:24 +02:00
.store({
environment: '',
type: 'plugin',
name: 'documentation',
key: 'config',
})
.get();
return { restrictedAccess };
2021-09-02 11:25:24 +02:00
},
/**
* @description - Gets the path for an api or plugin
*
* @param {object} api
* @property {string} api.name - Name of the api
* @property {string} api.getter - api | plugin
*
* @returns path to the api | plugin
*/
getApiDocumentationPath(api) {
if (api.getter === 'plugin') {
2022-03-14 17:54:35 +01:00
return path.join(strapi.dirs.app.extensions, api.name, 'documentation');
2021-09-02 11:25:24 +02:00
}
2022-03-14 17:54:35 +01:00
return path.join(strapi.dirs.app.api, api.name, 'documentation');
2021-09-02 11:25:24 +02:00
},
async deleteDocumentation(version) {
const apis = this.getPluginAndApiInfo();
for (const api of apis) {
await fs.remove(path.join(this.getApiDocumentationPath(api), version));
}
await fs.remove(path.join(this.getFullDocumentationPath(), version));
},
getPluginAndApiInfo() {
const pluginsToDocument = pluginsThatNeedDocumentation.map((plugin) => {
2021-09-02 11:25:24 +02:00
return {
name: plugin,
getter: 'plugin',
ctNames: Object.keys(strapi.plugin(plugin).contentTypes),
};
});
2022-08-08 23:33:39 +02:00
const apisToDocument = Object.keys(strapi.api).map((api) => {
2021-09-02 11:25:24 +02:00
return {
name: api,
getter: 'api',
ctNames: Object.keys(strapi.api[api].contentTypes),
};
});
2021-09-02 11:25:24 +02:00
return [...apisToDocument, ...pluginsToDocument];
},
/**
* @description - Creates the Swagger json files
*/
async generateFullDoc(version = this.getDocumentationVersion()) {
2021-09-02 11:25:24 +02:00
const apis = this.getPluginAndApiInfo();
2023-03-20 15:49:30 +01:00
const apisThatNeedGeneratedDocumentation = apis.filter(
({ name }) => !overrideService.excludedFromGeneration.includes(name)
2023-03-20 15:49:30 +01:00
);
2023-03-22 16:15:17 +01:00
// Initialize the generated documentation with defaults
let generatedDocumentation = mutateDocumentation(
{
...config,
components: defaultOpenApiComponents,
},
(draft) => {
if (draft.servers.length === 0) {
// When no servers found set the defaults
const serverUrl = getAbsoluteServerUrl(strapi.config);
const apiPath = strapi.config.get('api.rest.prefix');
draft.servers = [
{
url: `${serverUrl}${apiPath}`,
description: 'Development server',
},
];
}
// Set the generated date
draft.info['x-generation-date'] = new Date().toISOString();
// Set the plugins that need documentation
draft['x-strapi-config'].plugins = pluginsThatNeedDocumentation;
// Delete the mutateDocumentation key from the config so it doesn't end up in the spec
delete draft['x-strapi-config'].mutateDocumentation;
}
);
// Generate the documentation for each api and update the generatedDocumentation
2023-03-20 15:49:30 +01:00
for (const api of apisThatNeedGeneratedDocumentation) {
2021-09-02 11:25:24 +02:00
const apiName = api.name;
2023-03-22 16:15:17 +01:00
const newApiPath = builApiEndpointPath(api);
const generatedSchemas = buildComponentSchema(api);
// TODO: To be confirmed, do we still need to write these files...?
const apiDirPath = path.join(this.getApiDocumentationPath(api), version);
2021-09-02 11:25:24 +02:00
const apiDocPath = path.join(apiDirPath, `${apiName}.json`);
await fs.ensureFile(apiDocPath);
2023-03-22 16:15:17 +01:00
await fs.writeJson(apiDocPath, newApiPath, { spaces: 2 });
2022-03-17 16:54:37 +01:00
2023-03-22 16:15:17 +01:00
generatedDocumentation = mutateDocumentation(generatedDocumentation, (draft) => {
if (generatedSchemas) {
draft.components = {
schemas: { ...draft.components.schemas, ...generatedSchemas },
};
}
2022-03-17 16:54:37 +01:00
2023-03-22 16:15:17 +01:00
if (newApiPath) {
draft.paths = { ...draft.paths, ...newApiPath };
}
});
2021-09-02 11:25:24 +02:00
}
2021-09-02 11:25:24 +02:00
const fullDocJsonPath = path.join(
this.getFullDocumentationPath(),
version,
2021-09-02 11:25:24 +02:00
'full_documentation.json'
);
2023-03-22 16:15:17 +01:00
// When overrides are present update the generatedDocumentation
if (overrideService.registeredOverrides.length > 0) {
generatedDocumentation = mutateDocumentation(generatedDocumentation, (draft) => {
2023-03-22 15:18:51 +01:00
overrideService.registeredOverrides.forEach((override) => {
2023-03-22 11:30:48 +01:00
// Only run the overrrides when no override version is provided,
// or when the generated documentation version matches the override version
2023-03-22 15:18:51 +01:00
if (!override?.info?.version || override.info.version === version) {
if (override.tags) {
2023-03-22 11:30:48 +01:00
// Merge override tags with the generated tags
draft.tags = draft.tags || [];
2023-03-22 15:18:51 +01:00
draft.tags.push(...override.tags);
2023-03-22 11:30:48 +01:00
}
2023-03-22 15:18:51 +01:00
if (override.paths) {
2023-03-22 11:30:48 +01:00
// Merge override paths with the generated paths
// The override will add a new path or replace the value of an existing path
2023-03-22 15:18:51 +01:00
draft.paths = { ...draft.paths, ...override.paths };
2023-03-22 11:30:48 +01:00
}
2023-03-22 15:18:51 +01:00
if (override.components) {
Object.entries(override.components).forEach(([overrideKey, overrideValue]) => {
draft.components[overrideKey] = draft.components[overrideKey] || {};
2023-03-22 11:30:48 +01:00
// Merge override components with the generated components,
// The override will add a new component or replace the value of an existing component
2023-03-22 15:18:51 +01:00
draft.components[overrideKey] = {
...draft.components[overrideKey],
...overrideValue,
};
2023-03-22 11:30:48 +01:00
});
}
}
});
2023-03-22 16:15:17 +01:00
});
}
2023-03-22 11:30:48 +01:00
// Get the documentation mutateDocumentation
2023-03-22 15:18:51 +01:00
const userMutatesDocumentation = config['x-strapi-config'].mutateDocumentation;
2023-03-22 11:30:48 +01:00
// Escape hatch, allow the user to provide a mutateDocumentation function that can alter any part of
2023-03-20 15:49:30 +01:00
// the generated documentation before it is written to the file system
2023-03-22 15:18:51 +01:00
const finalDocumentation = userMutatesDocumentation
? mutateDocumentation(generatedDocumentation, userMutatesDocumentation)
2023-03-20 15:49:30 +01:00
: generatedDocumentation;
2022-06-01 23:12:16 +02:00
2021-09-02 11:25:24 +02:00
await fs.ensureFile(fullDocJsonPath);
2023-03-20 15:49:30 +01:00
await fs.writeJson(fullDocJsonPath, finalDocumentation, { spaces: 2 });
2021-09-02 11:25:24 +02:00
},
};
};