From 29bb57ad5f5d762e8f1f4e3267f5bd11f8caa272 Mon Sep 17 00:00:00 2001 From: ayusharma Date: Wed, 20 Jun 2018 00:15:25 +0200 Subject: [PATCH] feat: adds support for ascii-doc preview in readme (#464) --- flow-typed/npm/asciidoctor.js_vx.x.x.js | 67 +++++++++++++++++++++++++ package.json | 1 + src/api/web/endpoint/package.js | 5 +- src/lib/utils.js | 28 +++++++++++ test/unit/utils.spec.js | 35 ++++++++++++- yarn.lock | 19 ++++++- 6 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 flow-typed/npm/asciidoctor.js_vx.x.x.js 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 --- a/yarn.lock +++ b/yarn.lock @@ -631,6 +631,12 @@ asap@~2.0.3: version "2.0.6" resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" +asciidoctor.js@1.5.6: + version "1.5.6" + resolved "https://registry.npmjs.org/asciidoctor.js/-/asciidoctor.js-1.5.6.tgz#12f4810c62ea47867da145e819fc93b0674b5431" + dependencies: + opal-runtime "1.0.3" + asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -4109,7 +4115,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^6.0.1, glob@^6.0.4: +glob@6.0.4, glob@^6.0.1, glob@^6.0.4: version "6.0.4" resolved "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" dependencies: @@ -6686,6 +6692,13 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +opal-runtime@1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/opal-runtime/-/opal-runtime-1.0.3.tgz#e81e5c2a2568bbb0b05743b427d035dd901485b7" + dependencies: + glob "6.0.4" + xmlhttprequest "1.8.0" + opener@^1.4.3: version "1.4.3" resolved "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" @@ -9779,6 +9792,10 @@ xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" +xmlhttprequest@1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"