diff --git a/flow-typed/npm/asciidoctor.js_vx.x.x.js b/flow-typed/npm/asciidoctor.js_vx.x.x.js new file mode 100644 index 000000000..2060be46e --- /dev/null +++ b/flow-typed/npm/asciidoctor.js_vx.x.x.js @@ -0,0 +1,67 @@ +// flow-typed signature: b7109b7e394ff03ed211118d5af4cff8 +// flow-typed version: <>/asciidoctor.js_v1.5.6/flow_v0.69.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'asciidoctor.js' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'asciidoctor.js' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'asciidoctor.js/dist/asciidoctor' { + declare module.exports: any; +} + +declare module 'asciidoctor.js/dist/asciidoctor.min' { + declare module.exports: any; +} + +declare module 'asciidoctor.js/dist/browser/asciidoctor' { + declare module.exports: any; +} + +declare module 'asciidoctor.js/dist/nashorn/asciidoctor' { + declare module.exports: any; +} + +declare module 'asciidoctor.js/dist/node/asciidoctor' { + declare module.exports: any; +} + +declare module 'asciidoctor.js/dist/umd/asciidoctor' { + declare module.exports: any; +} + +// Filename aliases +declare module 'asciidoctor.js/dist/asciidoctor.js' { + declare module.exports: $Exports<'asciidoctor.js/dist/asciidoctor'>; +} +declare module 'asciidoctor.js/dist/asciidoctor.min.js' { + declare module.exports: $Exports<'asciidoctor.js/dist/asciidoctor.min'>; +} +declare module 'asciidoctor.js/dist/browser/asciidoctor.js' { + declare module.exports: $Exports<'asciidoctor.js/dist/browser/asciidoctor'>; +} +declare module 'asciidoctor.js/dist/nashorn/asciidoctor.js' { + declare module.exports: $Exports<'asciidoctor.js/dist/nashorn/asciidoctor'>; +} +declare module 'asciidoctor.js/dist/node/asciidoctor.js' { + declare module.exports: $Exports<'asciidoctor.js/dist/node/asciidoctor'>; +} +declare module 'asciidoctor.js/dist/umd/asciidoctor.js' { + declare module.exports: $Exports<'asciidoctor.js/dist/umd/asciidoctor'>; +} diff --git a/package.json b/package.json index 1867d53dd..2e14d0575 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@verdaccio/local-storage": "1.1.2", "@verdaccio/streams": "1.0.0", "JSONStream": "1.3.2", + "asciidoctor.js": "1.5.6", "async": "2.6.0", "body-parser": "1.18.2", "bunyan": "1.8.12", diff --git a/src/api/web/endpoint/package.js b/src/api/web/endpoint/package.js index e2f6a48af..5ab515eb8 100644 --- a/src/api/web/endpoint/package.js +++ b/src/api/web/endpoint/package.js @@ -1,9 +1,8 @@ // @flow import _ from 'lodash'; -import {addScope, addGravatarSupport, deleteProperties, sortByName, DIST_TAGS} from '../../../lib/utils'; +import {addScope, addGravatarSupport, deleteProperties, sortByName, DIST_TAGS, parseReadme} from '../../../lib/utils'; import {allow} from '../../middleware'; -import marked from 'marked'; import type {Router} from 'express'; import type { IAuth, @@ -71,7 +70,7 @@ function addPackageWebApi(route: Router, storage: IStorageHandler, auth: IAuth) } res.set('Content-Type', 'text/plain'); - next(marked(info.readme || 'ERROR: No README data found!')); + next(parseReadme(info.name, info.readme)); }, }); }); diff --git a/src/lib/utils.js b/src/lib/utils.js index 0285bee8a..3cbb890fb 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -10,6 +10,8 @@ import _ from 'lodash'; import createError from 'http-errors'; import type {Package} from '@verdaccio/types'; import type {$Request} from 'express'; +import marked from 'marked'; +import asciidoctor from 'asciidoctor.js'; import type {StringValue} from '../../types'; const Logger = require('./logger'); @@ -437,6 +439,31 @@ function addGravatarSupport(pkgInfo: any) { return pkgInfo; } +/** + * parse package readme - markdown/ascii + * @param {String} packageName name of package + * @param {String} readme package readme + * @return {String} converted html template + */ +function parseReadme(packageName: string, readme: string): string { + const ascii = asciidoctor(); + const docTypeIdentifier = new RegExp(/^=+ \w/, 'g'); + + // asciidoc + if (docTypeIdentifier.test(readme)) { + return ascii.convert(readme, {safe: 'safe', attributes: {showtitle: true, icons: 'font'}}); + } + + if (readme) { + return marked(readme); + } + + // logs readme not found error + Logger.logger.error({packageName}, '@{packageName}: No readme found'); + + return marked('ERROR: No README data found!'); +} + export { addGravatarSupport, deleteProperties, @@ -458,4 +485,5 @@ export { getLatestVersion, ErrorCode, parseConfigFile, + parseReadme, }; diff --git a/test/unit/utils.spec.js b/test/unit/utils.spec.js index c66696773..0f277e5c8 100644 --- a/test/unit/utils.spec.js +++ b/test/unit/utils.spec.js @@ -1,9 +1,12 @@ // @flow import assert from 'assert'; -import {validateName as validate, convertDistRemoteToLocalTarballUrls} from '../../src/lib/utils'; +import {validateName as validate, convertDistRemoteToLocalTarballUrls, parseReadme} from '../../src/lib/utils'; import {generateGravatarUrl, GRAVATAR_DEFAULT} from '../../src/utils/user'; import {spliceURL} from '../../src/utils/string'; import Package from "../../src/webui/src/components/Package"; +import Logger, {setup} from '../../src/lib/logger'; + +setup([]); describe('Utilities', () => { @@ -110,4 +113,34 @@ describe('Utilities', () => { expect(convertDist.versions['1.0.1'].dist.tarball).toEqual(buildURI(host, '1.0.1')); }); }); + + describe('parseReadme', () => { + test('should pass for ascii/makrdown text to html template', () => { + const markdown = '# markdown'; + const ascii = "= AsciiDoc"; + expect(parseReadme('testPackage', markdown)).toEqual('

markdown

\n'); + expect(parseReadme('testPackage', ascii)).toEqual('

AsciiDoc

\n'); + }); + + test('should pass for conversion of non-ascii to markdown text', () => { + const simpleText = 'simple text'; + const randomText = '%%%%%**##=='; + const randomTextNonAscii = 'simple text \n = ascii'; + const randomTextMarkdown = 'simple text \n # markdown'; + expect(parseReadme('testPackage', randomText)).toEqual('

%%%%%**##==

\n'); + expect(parseReadme('testPackage', simpleText)).toEqual('

simple text

\n'); + expect(parseReadme('testPackage', randomTextNonAscii)) + .toEqual('

simple text \n = ascii

\n'); + expect(parseReadme('testPackage', randomTextMarkdown)) + .toEqual('

simple text

\n

markdown

\n'); + }); + + test('should show error for no readme data', () => { + const noData = ''; + const spy = jest.spyOn(Logger.logger, 'error') + expect(parseReadme('testPackage', noData)) + .toEqual('

ERROR: No README data found!

\n'); + expect(spy).toHaveBeenCalledWith({'packageName': 'testPackage'}, '@{packageName}: No readme found'); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index eb4f30aba..cd2a2818c 100644 Binary files a/yarn.lock and b/yarn.lock differ