@verdaccio/web 6.0.0-6-next.27 → 6.0.0-6-next.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/api/readme.ts CHANGED
@@ -6,13 +6,14 @@ import { HEADERS, HEADER_TYPE } from '@verdaccio/core';
6
6
  import { $NextFunctionVer, $RequestExtend, $ResponseExtend, allow } from '@verdaccio/middleware';
7
7
  import sanitizyReadme from '@verdaccio/readme';
8
8
  import { Storage } from '@verdaccio/store';
9
- import { Package } from '@verdaccio/types';
9
+ import { Manifest } from '@verdaccio/types';
10
10
 
11
11
  import { AuthorAvatar, addScope, parseReadme } from '../utils/web-utils';
12
12
 
13
13
  export { $RequestExtend, $ResponseExtend, $NextFunctionVer }; // Was required by other packages
14
14
 
15
- export type PackageExt = Package & { author: AuthorAvatar; dist?: { tarball: string } };
15
+ // TODO: review this type, should be on @verdacid/types
16
+ export type PackageExt = Manifest & { author: AuthorAvatar; dist?: { tarball: string } };
16
17
 
17
18
  const debug = buildDebug('verdaccio:web:api:readme');
18
19
 
@@ -26,31 +27,39 @@ function addReadmeWebApi(storage: Storage, auth: IAuth): Router {
26
27
  pkgRouter.get(
27
28
  '/package/readme/(@:scope/)?:package/:version?',
28
29
  can('access'),
29
- function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
30
+ async function (
31
+ req: $RequestExtend,
32
+ res: $ResponseExtend,
33
+ next: $NextFunctionVer
34
+ ): Promise<void> {
30
35
  debug('readme hit');
31
- const packageName = req.params.scope
36
+ const name = req.params.scope
32
37
  ? addScope(req.params.scope, req.params.package)
33
38
  : req.params.package;
34
- debug('readme name %o', packageName);
35
-
36
- // @ts-ignore
37
- storage.getPackage({
38
- name: packageName,
39
- uplinksLook: true,
40
- req,
41
- callback: function (err, info): void {
42
- debug('readme pkg %o', info?.name);
43
- res.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN_UTF8);
44
- if (err) {
45
- return next(err);
46
- }
47
- try {
48
- next(parseReadme(info.name, info.readme));
49
- } catch {
50
- next(sanitizyReadme(NOT_README_FOUND));
51
- }
52
- },
53
- });
39
+ debug('readme name %o', name);
40
+ const requestOptions = {
41
+ protocol: req.protocol,
42
+ headers: req.headers as any,
43
+ // FIXME: if we migrate to req.hostname, the port is not longer included.
44
+ host: req.host,
45
+ remoteAddress: req.socket.remoteAddress,
46
+ };
47
+ try {
48
+ const manifest = await storage.getPackageByOptions({
49
+ name,
50
+ uplinksLook: true,
51
+ requestOptions,
52
+ });
53
+ debug('readme pkg %o', manifest?.name);
54
+ res.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN_UTF8);
55
+ try {
56
+ next(parseReadme(manifest.name, manifest.readme as string));
57
+ } catch {
58
+ next(sanitizyReadme(NOT_README_FOUND));
59
+ }
60
+ } catch (err) {
61
+ next(err);
62
+ }
54
63
  }
55
64
  );
56
65
  return pkgRouter;
package/src/api/search.ts CHANGED
@@ -64,7 +64,7 @@ function addSearchWebApi(storage: Storage, auth: IAuth): Router {
64
64
  // @ts-ignore
65
65
  const urlParams = new URLSearchParams(query);
66
66
  debug('search web init');
67
- data = await storage.searchManager?.search({
67
+ data = await storage?.search({
68
68
  query,
69
69
  url: `/-/v1/search?${urlParams.toString()}`,
70
70
  abort,
@@ -1,22 +1,21 @@
1
1
  import buildDebug from 'debug';
2
2
  import { Router } from 'express';
3
- import _ from 'lodash';
4
3
 
5
4
  import { IAuth } from '@verdaccio/auth';
6
5
  import { DIST_TAGS, HTTP_STATUS } from '@verdaccio/core';
7
6
  import { $NextFunctionVer, $RequestExtend, $ResponseExtend, allow } from '@verdaccio/middleware';
8
7
  import { Storage } from '@verdaccio/store';
9
8
  import { convertDistRemoteToLocalTarballUrls } from '@verdaccio/tarball';
10
- import { Config, Package, Version } from '@verdaccio/types';
9
+ import { Config, Manifest, Version } from '@verdaccio/types';
11
10
  import { addGravatarSupport, formatAuthor, isVersionValid } from '@verdaccio/utils';
12
11
 
13
12
  import { AuthorAvatar, addScope, deleteProperties } from '../utils/web-utils';
14
13
 
15
14
  export { $RequestExtend, $ResponseExtend, $NextFunctionVer }; // Was required by other packages
16
15
 
17
- export type PackageExt = Package & { author: AuthorAvatar; dist?: { tarball: string } };
16
+ export type PackageExt = Manifest & { author: AuthorAvatar; dist?: { tarball: string } };
18
17
 
19
- export type $SidebarPackage = Package & { latest: Version };
18
+ export type $SidebarPackage = Manifest & { latest: Version };
20
19
  const debug = buildDebug('verdaccio:web:api:sidebar');
21
20
 
22
21
  function addSidebarWebApi(config: Config, storage: Storage, auth: IAuth): Router {
@@ -27,43 +26,56 @@ function addSidebarWebApi(config: Config, storage: Storage, auth: IAuth): Router
27
26
  router.get(
28
27
  '/sidebar/(@:scope/)?:package',
29
28
  can('access'),
30
- function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
31
- const packageName: string = req.params.scope
29
+ async function (
30
+ req: $RequestExtend,
31
+ res: $ResponseExtend,
32
+ next: $NextFunctionVer
33
+ ): Promise<void> {
34
+ const name: string = req.params.scope
32
35
  ? addScope(req.params.scope, req.params.package)
33
36
  : req.params.package;
34
-
35
- storage.getPackage({
36
- name: packageName,
37
- uplinksLook: true,
38
- keepUpLinkData: true,
39
- req,
40
- callback: function (err: Error, info: $SidebarPackage): void {
41
- if (_.isNil(err)) {
42
- const { v } = req.query;
43
- let sideBarInfo = _.clone(info);
44
- sideBarInfo.versions = convertDistRemoteToLocalTarballUrls(
45
- info,
46
- { protocol: req.protocol, headers: req.headers as any, host: req.hostname },
47
- config.url_prefix
48
- ).versions;
49
- if (typeof v === 'string' && isVersionValid(info, v)) {
50
- sideBarInfo.latest = sideBarInfo.versions[v];
51
- sideBarInfo.latest.author = formatAuthor(sideBarInfo.latest.author);
52
- } else {
53
- sideBarInfo.latest = sideBarInfo.versions[info[DIST_TAGS].latest];
54
- sideBarInfo.latest.author = formatAuthor(sideBarInfo.latest.author);
55
- }
56
- sideBarInfo = deleteProperties(['readme', '_attachments', '_rev', 'name'], sideBarInfo);
57
- const authorAvatar = config.web
58
- ? addGravatarSupport(sideBarInfo, config.web.gravatar)
59
- : addGravatarSupport(sideBarInfo);
60
- next(authorAvatar);
61
- } else {
62
- res.status(HTTP_STATUS.NOT_FOUND);
63
- res.end();
64
- }
65
- },
66
- });
37
+ const requestOptions = {
38
+ protocol: req.protocol,
39
+ headers: req.headers as any,
40
+ // FIXME: if we migrate to req.hostname, the port is not longer included.
41
+ host: req.host,
42
+ remoteAddress: req.socket.remoteAddress,
43
+ };
44
+ try {
45
+ const info = (await storage.getPackageByOptions({
46
+ name,
47
+ uplinksLook: true,
48
+ keepUpLinkData: true,
49
+ requestOptions,
50
+ })) as Manifest;
51
+ const { v } = req.query;
52
+ let sideBarInfo = { ...info };
53
+ sideBarInfo.versions = convertDistRemoteToLocalTarballUrls(
54
+ info,
55
+ { protocol: req.protocol, headers: req.headers as any, host: req.hostname },
56
+ config.url_prefix
57
+ ).versions;
58
+ // TODO: review this implementation
59
+ if (typeof v === 'string' && isVersionValid(info, v)) {
60
+ // @ts-ignore
61
+ sideBarInfo.latest = sideBarInfo.versions[v];
62
+ // @ts-ignore
63
+ sideBarInfo.latest.author = formatAuthor(sideBarInfo.latest.author);
64
+ } else {
65
+ // @ts-ignore
66
+ sideBarInfo.latest = sideBarInfo.versions[info[DIST_TAGS].latest];
67
+ // @ts-ignore
68
+ sideBarInfo.latest.author = formatAuthor(sideBarInfo.latest.author);
69
+ }
70
+ sideBarInfo = deleteProperties(['readme', '_attachments', '_rev', 'name'], sideBarInfo);
71
+ const authorAvatar = config.web
72
+ ? addGravatarSupport(sideBarInfo, config.web.gravatar)
73
+ : addGravatarSupport(sideBarInfo);
74
+ next(authorAvatar);
75
+ } catch (err) {
76
+ res.status(HTTP_STATUS.NOT_FOUND);
77
+ res.end();
78
+ }
67
79
  }
68
80
  );
69
81
 
package/src/renderHTML.ts CHANGED
@@ -26,6 +26,9 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
26
26
  const base = getPublicUrl(config?.url_prefix, req);
27
27
  const basename = new URL(base).pathname;
28
28
  const language = config?.i18n?.web ?? DEFAULT_LANGUAGE;
29
+ const needHtmlCache = [undefined, null].includes(config?.web?.html_cache)
30
+ ? true
31
+ : config.web.html_cache;
29
32
  const darkMode = config?.web?.darkMode ?? false;
30
33
  const title = config?.web?.title ?? WEB_TITLE;
31
34
  const login = hasLogin(config);
@@ -37,7 +40,17 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
37
40
  ...config.flags,
38
41
  };
39
42
  const primaryColor = validatePrimaryColor(config?.web?.primary_color) ?? '#4b5e40';
40
- const { scriptsBodyAfter, metaScripts, scriptsbodyBefore } = Object.assign(
43
+ const {
44
+ scriptsBodyAfter,
45
+ metaScripts,
46
+ scriptsbodyBefore,
47
+ showInfo,
48
+ showSettings,
49
+ showThemeSwitch,
50
+ showFooter,
51
+ showSearch,
52
+ showDownloadTarball,
53
+ } = Object.assign(
41
54
  {},
42
55
  {
43
56
  scriptsBodyAfter: [],
@@ -47,6 +60,12 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
47
60
  config?.web
48
61
  );
49
62
  const options: TemplateUIOptions = {
63
+ showInfo,
64
+ showSettings,
65
+ showThemeSwitch,
66
+ showFooter,
67
+ showSearch,
68
+ showDownloadTarball,
50
69
  darkMode,
51
70
  url_prefix,
52
71
  basename,
@@ -66,10 +85,7 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
66
85
 
67
86
  try {
68
87
  webPage = cache.get('template');
69
-
70
88
  if (!webPage) {
71
- debug('web options %o', options);
72
- debug('web manifestFiles %o', manifestFiles);
73
89
  webPage = renderTemplate(
74
90
  {
75
91
  manifest: manifestFiles ?? defaultManifestFiles,
@@ -80,9 +96,10 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
80
96
  },
81
97
  manifest
82
98
  );
83
- debug('template :: %o', webPage);
84
- cache.set('template', webPage);
85
- debug('set template cache');
99
+ if (needHtmlCache) {
100
+ cache.set('template', webPage);
101
+ debug('set template cache');
102
+ }
86
103
  } else {
87
104
  debug('reuse template cache');
88
105
  }
@@ -91,5 +108,5 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
91
108
  }
92
109
  res.setHeader('Content-Type', HEADERS.TEXT_HTML);
93
110
  res.send(webPage);
94
- debug('render web');
111
+ debug('web rendered');
95
112
  }
@@ -10,7 +10,7 @@ export type AuthorAvatar = Author & { avatar?: string };
10
10
  const debug = buildDebug('verdaccio:web:utils');
11
11
 
12
12
  export function validatePrimaryColor(primaryColor) {
13
- const isHex = /^#+([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/i.test(primaryColor);
13
+ const isHex = /^#([0-9A-F]{3}){1,2}$/i.test(primaryColor);
14
14
  if (!isHex) {
15
15
  debug('invalid primary color %o', primaryColor);
16
16
  return;
@@ -38,7 +38,7 @@ describe('readme api', () => {
38
38
  .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN_UTF8)
39
39
  .expect(HTTP_STATUS.OK);
40
40
  expect(response.text).toMatch('my readme scoped');
41
- });
41
+ }, 70000);
42
42
 
43
43
  test('should fetch readme scoped package with not found message', async () => {
44
44
  const app = await initializeServer('default-test.yaml');
@@ -0,0 +1,46 @@
1
+ auth:
2
+ auth-memory:
3
+ users:
4
+ test:
5
+ name: test
6
+ password: test
7
+
8
+ web:
9
+ title: verdaccio web
10
+ login: true
11
+ scope: '@scope'
12
+ pkgManagers:
13
+ - pnpm
14
+ - yarn
15
+ showInfo: true
16
+ showSettings: true
17
+ showSearch: true
18
+ showFooter: true
19
+ showThemeSwitch: true
20
+ showDownloadTarball: true
21
+ showRaw: true
22
+ primary_color: '#ffffff'
23
+ logoURI: 'http://logo.org/logo.png'
24
+ flags:
25
+ - something: false
26
+
27
+ url_prefix: /prefix
28
+
29
+ publish:
30
+ allow_offline: false
31
+
32
+ uplinks:
33
+
34
+ log: { type: stdout, format: pretty, level: trace }
35
+
36
+ packages:
37
+ '@*/*':
38
+ access: $anonymous
39
+ publish: $anonymous
40
+ '**':
41
+ access: $anonymous
42
+ publish: $anonymous
43
+ _debug: true
44
+
45
+ flags:
46
+ changePassword: true
@@ -1,3 +1,4 @@
1
+ import { JSDOM } from 'jsdom';
1
2
  import path from 'path';
2
3
  import supertest from 'supertest';
3
4
 
@@ -28,34 +29,80 @@ describe('test web server', () => {
28
29
  });
29
30
 
30
31
  describe('render', () => {
31
- test('should return the root', async () => {
32
- return supertest(await initializeServer('default-test.yaml'))
33
- .get('/')
34
- .set('Accept', HEADERS.TEXT_HTML)
35
- .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_HTML_UTF8)
36
- .expect(HTTP_STATUS.OK);
37
- });
32
+ describe('output', () => {
33
+ const render = async (config = 'default-test.yaml') => {
34
+ const response = await supertest(await initializeServer(config))
35
+ .get('/')
36
+ .set('Accept', HEADERS.TEXT_HTML)
37
+ .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_HTML_UTF8)
38
+ .expect(HTTP_STATUS.OK);
39
+ return new JSDOM(response.text, { runScripts: 'dangerously' });
40
+ };
38
41
 
39
- test('should return the body for a package detail page', async () => {
40
- return supertest(await initializeServer('default-test.yaml'))
41
- .get('/-/web/section/some-package')
42
- .set('Accept', HEADERS.TEXT_HTML)
43
- .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_HTML_UTF8)
44
- .expect(HTTP_STATUS.OK);
45
- });
42
+ test('should match render set ui properties', async () => {
43
+ const {
44
+ window: { __VERDACCIO_BASENAME_UI_OPTIONS },
45
+ } = await render('web.yaml');
46
+ expect(__VERDACCIO_BASENAME_UI_OPTIONS).toEqual(
47
+ expect.objectContaining({
48
+ showInfo: true,
49
+ showSettings: true,
50
+ showThemeSwitch: true,
51
+ showFooter: true,
52
+ showSearch: true,
53
+ showDownloadTarball: true,
54
+ darkMode: false,
55
+ url_prefix: '/prefix',
56
+ basename: '/prefix/',
57
+ primaryColor: '#ffffff',
58
+ // FIXME: mock these values, avoid random
59
+ // base: 'http://127.0.0.1:60864/prefix/',
60
+ // version: '6.0.0-6-next.28',
61
+ logoURI: '',
62
+ flags: { searchRemote: true },
63
+ login: true,
64
+ pkgManagers: ['pnpm', 'yarn'],
65
+ title: 'verdaccio web',
66
+ scope: '@scope',
67
+ language: 'es-US',
68
+ })
69
+ );
70
+ });
46
71
 
47
- test.skip('should static file not found', async () => {
48
- return supertest(await initializeServer('default-test.yaml'))
49
- .get('/-/static/not-found.js')
50
- .set('Accept', HEADERS.TEXT_HTML)
51
- .expect(HTTP_STATUS.NOT_FOUND);
72
+ test.todo('should default title');
73
+ test.todo('should need html cache');
52
74
  });
53
75
 
54
- test('should static file found', async () => {
55
- return supertest(await initializeServer('default-test.yaml'))
56
- .get('/-/static/main.js')
57
- .set('Accept', HEADERS.TEXT_HTML)
58
- .expect(HTTP_STATUS.OK);
76
+ describe('status', () => {
77
+ test('should return the http status 200 for root', async () => {
78
+ return supertest(await initializeServer('default-test.yaml'))
79
+ .get('/')
80
+ .set('Accept', HEADERS.TEXT_HTML)
81
+ .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_HTML_UTF8)
82
+ .expect(HTTP_STATUS.OK);
83
+ });
84
+
85
+ test('should return the body for a package detail page', async () => {
86
+ return supertest(await initializeServer('default-test.yaml'))
87
+ .get('/-/web/section/some-package')
88
+ .set('Accept', HEADERS.TEXT_HTML)
89
+ .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_HTML_UTF8)
90
+ .expect(HTTP_STATUS.OK);
91
+ });
92
+
93
+ test('should static file not found', async () => {
94
+ return supertest(await initializeServer('default-test.yaml'))
95
+ .get('/-/static/not-found.js')
96
+ .set('Accept', HEADERS.TEXT_HTML)
97
+ .expect(HTTP_STATUS.NOT_FOUND);
98
+ });
99
+
100
+ test('should static file found', async () => {
101
+ return supertest(await initializeServer('default-test.yaml'))
102
+ .get('/-/static/main.js')
103
+ .set('Accept', HEADERS.TEXT_HTML)
104
+ .expect(HTTP_STATUS.OK);
105
+ });
59
106
  });
60
107
  });
61
108
  });
@@ -1,13 +1,22 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
 
4
- import { parseReadme } from '../src/utils/web-utils';
4
+ import { parseReadme, validatePrimaryColor } from '../src/utils/web-utils';
5
5
 
6
6
  const readmeFile = (fileName = 'markdown.md') => {
7
7
  return fs.readFileSync(path.join(__dirname, `./partials/readme/${fileName}`));
8
8
  };
9
9
 
10
10
  describe('Utilities', () => {
11
+ describe('validatePrimaryColor', () => {
12
+ test('is valid', () => {
13
+ expect(validatePrimaryColor('#222222')).toEqual('#222222');
14
+ expect(validatePrimaryColor('#222fff')).toEqual('#222fff');
15
+ });
16
+ test('is invalid', () => {
17
+ expect(validatePrimaryColor('fff')).toBeUndefined();
18
+ });
19
+ });
11
20
  describe('parseReadme', () => {
12
21
  test('should parse makrdown text to html template', () => {
13
22
  const markdown = '# markdown';
package/tsconfig.json CHANGED
@@ -13,9 +13,6 @@
13
13
  {
14
14
  "path": "../config"
15
15
  },
16
- {
17
- "path": "../core/commons-api"
18
- },
19
16
  {
20
17
  "path": "../core/readme"
21
18
  },