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

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/search.ts CHANGED
@@ -1,55 +1,39 @@
1
1
  import buildDebug from 'debug';
2
2
  import { Router } from 'express';
3
+ import _ from 'lodash';
4
+ import { URLSearchParams } from 'url';
3
5
 
4
6
  import { IAuth } from '@verdaccio/auth';
5
- import { DIST_TAGS } from '@verdaccio/core';
6
- import { SearchInstance } from '@verdaccio/store';
7
+ import { errorUtils, searchUtils } from '@verdaccio/core';
8
+ import { SearchQuery } from '@verdaccio/core/src/search-utils';
7
9
  import { Storage } from '@verdaccio/store';
8
- import { Package } from '@verdaccio/types';
10
+ import { Manifest } from '@verdaccio/types';
9
11
 
10
12
  import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from './package';
11
13
 
12
14
  const debug = buildDebug('verdaccio:web:api:search');
13
15
 
14
- function addSearchWebApi(storage: Storage, auth: IAuth): Router {
15
- const router = Router(); /* eslint new-cap: 0 */
16
- const getPackageInfo = async function (name, remoteUser): Promise<any> {
17
- return new Promise((resolve, reject) => {
18
- debug('searching for %o', name);
19
- try {
20
- // @ts-ignore
21
- storage.getPackage({
22
- name,
23
- uplinksLook: false,
24
- callback: (err, pkg: Package): void => {
25
- debug('callback get package err %o', err?.message);
26
- if (!err && pkg) {
27
- debug('valid package %o', pkg?.name);
28
- auth.allow_access(
29
- { packageName: pkg.name },
30
- remoteUser,
31
- function (err, allowed): void {
32
- debug('is allowed %o', allowed);
33
- if (err || !allowed) {
34
- debug('deny access');
35
- reject(err);
36
- return;
37
- }
38
- debug('access succeed');
39
- resolve(pkg.versions[pkg[DIST_TAGS].latest]);
40
- }
41
- );
42
- } else {
43
- reject(err);
44
- }
45
- },
46
- });
47
- } catch (err: any) {
48
- reject(err);
16
+ function checkAccess(pkg: any, auth: any, remoteUser): Promise<Manifest | null> {
17
+ return new Promise((resolve, reject) => {
18
+ auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function (err, allowed) {
19
+ if (err) {
20
+ if (err.status && String(err.status).match(/^4\d\d$/)) {
21
+ // auth plugin returns 4xx user error,
22
+ // that's equivalent of !allowed basically
23
+ allowed = false;
24
+ return resolve(null);
25
+ } else {
26
+ reject(err);
27
+ }
28
+ } else {
29
+ return resolve(allowed ? pkg : null);
49
30
  }
50
31
  });
51
- };
32
+ });
33
+ }
52
34
 
35
+ function addSearchWebApi(storage: Storage, auth: IAuth): Router {
36
+ const router = Router(); /* eslint new-cap: 0 */
53
37
  router.get(
54
38
  '/search/:anything',
55
39
  async function (
@@ -57,22 +41,47 @@ function addSearchWebApi(storage: Storage, auth: IAuth): Router {
57
41
  res: $ResponseExtend,
58
42
  next: $NextFunctionVer
59
43
  ): Promise<void> {
60
- const results = SearchInstance.query(req.params.anything);
61
- debug('search results %o', results);
62
- if (results.length > 0) {
63
- let packages: Package[] = [];
64
- for (let result of results) {
65
- try {
66
- const pkg = await getPackageInfo(result.ref, req.remote_user);
67
- debug('package found %o', result.ref);
68
- packages.push(pkg);
69
- } catch (err: any) {
70
- debug('search for %o failed err %o', result.ref, err?.message);
71
- }
72
- }
73
- next(packages);
74
- } else {
75
- next([]);
44
+ try {
45
+ let data;
46
+ const abort = new AbortController();
47
+ req.on('aborted', () => {
48
+ debug('search web aborted');
49
+ abort.abort();
50
+ });
51
+ const text: string = (req.params.anything as string) ?? '';
52
+ // These values are declared as optimal by npm cli
53
+ // FUTURE: could be overwritten by ui settings.
54
+ const size = 20;
55
+ const from = 0;
56
+ const query: SearchQuery = {
57
+ from: 0,
58
+ maintenance: 0.5,
59
+ popularity: 0.98,
60
+ quality: 0.65,
61
+ size: 20,
62
+ text,
63
+ };
64
+ // @ts-ignore
65
+ const urlParams = new URLSearchParams(query);
66
+ debug('search web init');
67
+ data = await storage.searchManager?.search({
68
+ query,
69
+ url: `/-/v1/search?${urlParams.toString()}`,
70
+ abort,
71
+ });
72
+ const checkAccessPromises: searchUtils.SearchItemPkg[] = await Promise.all(
73
+ data.map((pkgItem) => {
74
+ return checkAccess(pkgItem, auth, req.remote_user);
75
+ })
76
+ );
77
+
78
+ const final: searchUtils.SearchItemPkg[] = checkAccessPromises
79
+ .filter((i) => !_.isNull(i))
80
+ .slice(from, size);
81
+
82
+ next(final);
83
+ } catch (err: any) {
84
+ next(errorUtils.getInternalError(err.message));
76
85
  }
77
86
  }
78
87
  );
@@ -5,7 +5,6 @@ import path from 'path';
5
5
 
6
6
  import { HTTP_STATUS } from '@verdaccio/core';
7
7
  import { loadPlugin } from '@verdaccio/loaders';
8
- import { SearchInstance } from '@verdaccio/store';
9
8
  import { isURLhasValidProtocol } from '@verdaccio/url';
10
9
 
11
10
  import renderHTML from '../renderHTML';
@@ -40,10 +39,9 @@ const sendFileCallback = (next) => (err) => {
40
39
  }
41
40
  };
42
41
 
43
- export function renderWebMiddleware(config, auth, storage): any {
42
+ export function renderWebMiddleware(config, auth): any {
44
43
  const { staticPath, manifest, manifestFiles } = require('@verdaccio/ui-theme')();
45
44
  debug('static path %o', staticPath);
46
- SearchInstance.configureStorage(storage);
47
45
 
48
46
  /* eslint new-cap:off */
49
47
  const router = express.Router();
@@ -3,7 +3,6 @@ import { Router } from 'express';
3
3
 
4
4
  import { IAuth } from '@verdaccio/auth';
5
5
  import { match, validateName, validatePackage } from '@verdaccio/middleware';
6
- import { SearchInstance } from '@verdaccio/store';
7
6
  import { Storage } from '@verdaccio/store';
8
7
  import { Config } from '@verdaccio/types';
9
8
 
@@ -13,7 +12,6 @@ import { setSecurityWebHeaders } from './security';
13
12
  export function webAPI(config: Config, auth: IAuth, storage: Storage): Router {
14
13
  // eslint-disable-next-line new-cap
15
14
  const route = Router();
16
- SearchInstance.configureStorage(storage);
17
15
  // validate all of these params as a package name
18
16
  // this might be too harsh, so ask if it causes trouble=
19
17
  route.param('package', validatePackage);
package/src/renderHTML.ts CHANGED
@@ -4,6 +4,7 @@ import { URL } from 'url';
4
4
 
5
5
  import { WEB_TITLE } from '@verdaccio/config';
6
6
  import { HEADERS } from '@verdaccio/core';
7
+ import { TemplateUIOptions } from '@verdaccio/types';
7
8
  import { getPublicUrl } from '@verdaccio/url';
8
9
 
9
10
  import renderTemplate from './template';
@@ -32,6 +33,9 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
32
33
  const logoURI = config?.web?.logo ?? '';
33
34
  const pkgManagers = config?.web?.pkgManagers ?? ['yarn', 'pnpm', 'npm'];
34
35
  const version = pkgJSON.version;
36
+ const flags = {
37
+ ...config.flags,
38
+ };
35
39
  const primaryColor = validatePrimaryColor(config?.web?.primary_color) ?? '#4b5e40';
36
40
  const { scriptsBodyAfter, metaScripts, scriptsbodyBefore } = Object.assign(
37
41
  {},
@@ -42,7 +46,7 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
42
46
  },
43
47
  config?.web
44
48
  );
45
- const options = {
49
+ const options: TemplateUIOptions = {
46
50
  darkMode,
47
51
  url_prefix,
48
52
  basename,
@@ -50,6 +54,7 @@ export default function renderHTML(config, manifest, manifestFiles, req, res) {
50
54
  primaryColor,
51
55
  version,
52
56
  logoURI,
57
+ flags,
53
58
  login,
54
59
  pkgManagers,
55
60
  title,
@@ -7,7 +7,7 @@ export default (config, auth, storage) => {
7
7
  // eslint-disable-next-line new-cap
8
8
  const app = express.Router();
9
9
  // load application
10
- app.use('/', renderWebMiddleware(config, auth, storage));
10
+ app.use('/', renderWebMiddleware(config, auth));
11
11
  // web endpoints, search, packages, etc
12
12
  app.use('/-/verdaccio/', webAPI(config, auth, storage));
13
13
  return app;
@@ -3,7 +3,7 @@ import supertest from 'supertest';
3
3
 
4
4
  import { HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
5
5
  import { setup } from '@verdaccio/logger';
6
- import { IGetPackageOptions } from '@verdaccio/store';
6
+ import { publishVersion } from '@verdaccio/test-helper';
7
7
 
8
8
  import { NOT_README_FOUND } from '../src/api/readme';
9
9
  import { initializeServer } from './helper';
@@ -13,30 +13,6 @@ setup([]);
13
13
  const mockManifest = jest.fn();
14
14
  jest.mock('@verdaccio/ui-theme', () => mockManifest());
15
15
 
16
- jest.mock('@verdaccio/store', () => ({
17
- Storage: class {
18
- public init() {
19
- return Promise.resolve();
20
- }
21
- public getPackage({ name, callback }: IGetPackageOptions) {
22
- callback(null, {
23
- name,
24
- ['dist-tags']: {
25
- latest: '1.0.0',
26
- },
27
- versions: {
28
- ['1.0.0']: {
29
- name,
30
- },
31
- },
32
- });
33
- }
34
- },
35
- SearchInstance: {
36
- configureStorage: () => {},
37
- },
38
- }));
39
-
40
16
  describe('readme api', () => {
41
17
  beforeAll(() => {
42
18
  mockManifest.mockReturnValue(() => ({
@@ -54,7 +30,20 @@ describe('readme api', () => {
54
30
  });
55
31
 
56
32
  test('should fetch readme scoped package', async () => {
57
- const response = await supertest(await initializeServer('default-test.yaml'))
33
+ const app = await initializeServer('default-test.yaml');
34
+ await publishVersion(app, '@scope/pk1-test', '1.0.0', { readme: 'my readme scoped' });
35
+ const response = await supertest(app)
36
+ .get('/-/verdaccio/data/package/readme/@scope/pk1-test')
37
+ .set('Accept', HEADERS.TEXT_PLAIN)
38
+ .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN_UTF8)
39
+ .expect(HTTP_STATUS.OK);
40
+ expect(response.text).toMatch('my readme scoped');
41
+ });
42
+
43
+ test('should fetch readme scoped package with not found message', async () => {
44
+ const app = await initializeServer('default-test.yaml');
45
+ await publishVersion(app, '@scope/pk1-test', '1.0.0', { readme: null });
46
+ const response = await supertest(app)
58
47
  .get('/-/verdaccio/data/package/readme/@scope/pk1-test')
59
48
  .set('Accept', HEADERS.TEXT_PLAIN)
60
49
  .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN_UTF8)
@@ -63,7 +52,20 @@ describe('readme api', () => {
63
52
  });
64
53
 
65
54
  test('should fetch readme a package', async () => {
66
- const response = await supertest(await initializeServer('default-test.yaml'))
55
+ const app = await initializeServer('default-test.yaml');
56
+ await publishVersion(app, 'pk1-test', '1.0.0', { readme: 'my readme' });
57
+ const response = await supertest(app)
58
+ .get('/-/verdaccio/data/package/readme/pk1-test')
59
+ .set('Accept', HEADERS.TEXT_PLAIN)
60
+ .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN_UTF8)
61
+ .expect(HTTP_STATUS.OK);
62
+ expect(response.text).toMatch('my readme');
63
+ });
64
+
65
+ test('should fetch readme a package with not found message', async () => {
66
+ const app = await initializeServer('default-test.yaml');
67
+ await publishVersion(app, 'pk1-test', '1.0.0', { readme: null });
68
+ const response = await supertest(app)
67
69
  .get('/-/verdaccio/data/package/readme/pk1-test')
68
70
  .set('Accept', HEADERS.TEXT_PLAIN)
69
71
  .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.TEXT_PLAIN_UTF8)
@@ -3,44 +3,15 @@ import supertest from 'supertest';
3
3
 
4
4
  import { HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
5
5
  import { setup } from '@verdaccio/logger';
6
- import { IGetPackageOptions } from '@verdaccio/store';
6
+ import { publishVersion } from '@verdaccio/test-helper';
7
7
 
8
8
  import { initializeServer } from './helper';
9
9
 
10
10
  setup([]);
11
11
 
12
12
  const mockManifest = jest.fn();
13
- const mockQuery = jest.fn(() => [
14
- { ref: 'pkg1', score: 1 },
15
- { ref: 'pk2', score: 0.9 },
16
- ]);
17
13
  jest.mock('@verdaccio/ui-theme', () => mockManifest());
18
14
 
19
- jest.mock('@verdaccio/store', () => ({
20
- Storage: class {
21
- public init() {
22
- return Promise.resolve();
23
- }
24
- public getPackage({ name, callback }: IGetPackageOptions) {
25
- callback(null, {
26
- name,
27
- ['dist-tags']: {
28
- latest: '1.0.0',
29
- },
30
- versions: {
31
- ['1.0.0']: {
32
- name,
33
- },
34
- },
35
- });
36
- }
37
- },
38
- SearchInstance: {
39
- configureStorage: () => {},
40
- query: () => mockQuery(),
41
- },
42
- }));
43
-
44
15
  describe('test web server', () => {
45
16
  beforeAll(() => {
46
17
  mockManifest.mockReturnValue(() => ({
@@ -53,21 +24,24 @@ describe('test web server', () => {
53
24
  });
54
25
 
55
26
  afterEach(() => {
27
+ Date.now = jest.fn(() => new Date(Date.UTC(2017, 1, 14)).valueOf());
56
28
  jest.clearAllMocks();
57
29
  mockManifest.mockClear();
58
30
  });
59
31
 
60
- test('should OK to search api', async () => {
61
- const response = await supertest(await initializeServer('default-test.yaml'))
62
- .get('/-/verdaccio/data/search/keyword')
32
+ test('should find results to search api', async () => {
33
+ const app = await initializeServer('default-test.yaml');
34
+ await publishVersion(app, 'foo', '1.0.0');
35
+ const response = await supertest(app)
36
+ .get('/-/verdaccio/data/search/foo')
63
37
  .set('Accept', HEADERS.JSON_CHARSET)
64
38
  .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
65
39
  .expect(HTTP_STATUS.OK);
66
- expect(response.body).toHaveLength(2);
40
+ expect(response.body).toHaveLength(1);
41
+ // FUTURE: we can improve here matching the right outcome
67
42
  });
68
43
 
69
- test('should 404 to search api', async () => {
70
- mockQuery.mockReturnValue([]);
44
+ test('should found no results to search', async () => {
71
45
  const response = await supertest(await initializeServer('default-test.yaml'))
72
46
  .get('/-/verdaccio/data/search/notFound')
73
47
  .set('Accept', HEADERS.JSON_CHARSET)
@@ -76,19 +50,18 @@ describe('test web server', () => {
76
50
  expect(response.body).toHaveLength(0);
77
51
  });
78
52
 
79
- test('should fail search api', async () => {
80
- mockQuery.mockImplementation(() => {
81
- return [
82
- { ref: 'aa', score: 1 },
83
- { ref: 'bb', score: 0.8 },
84
- { ref: 'cc', score: 0.6 },
85
- ];
86
- });
53
+ // TODO: need a way to make this fail
54
+ test.skip('should fail search api', async () => {
87
55
  const response = await supertest(await initializeServer('default-test.yaml'))
88
- .get('/-/verdaccio/data/search/notFound')
56
+ .get('/-/verdaccio/data/search/thisWillFail')
89
57
  .set('Accept', HEADERS.JSON_CHARSET)
90
58
  .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
91
59
  .expect(HTTP_STATUS.OK);
92
60
  expect(response.body).toHaveLength(3);
93
61
  });
62
+
63
+ test.todo('search abort request');
64
+ // maybe these could be done in storage package to avoid have specifics on this level
65
+ test.todo('search allow request permissions');
66
+ test.todo('search query params, pagination etc');
94
67
  });
@@ -3,9 +3,8 @@ import supertest from 'supertest';
3
3
 
4
4
  import { HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
5
5
  import { setup } from '@verdaccio/logger';
6
- import { IGetPackageOptions } from '@verdaccio/store';
6
+ import { publishVersion } from '@verdaccio/test-helper';
7
7
 
8
- import { NOT_README_FOUND } from '../src/api/readme';
9
8
  import { initializeServer } from './helper';
10
9
 
11
10
  setup([]);
@@ -13,36 +12,15 @@ setup([]);
13
12
  const mockManifest = jest.fn();
14
13
  jest.mock('@verdaccio/ui-theme', () => mockManifest());
15
14
 
16
- jest.mock('@verdaccio/store', () => ({
17
- Storage: class {
18
- public init() {
19
- return Promise.resolve();
20
- }
21
- public getPackage({ name, callback }: IGetPackageOptions) {
22
- callback(null, {
23
- name,
24
- ['dist-tags']: {
25
- latest: '1.0.0',
26
- },
27
- versions: {
28
- ['1.0.0']: {
29
- name,
30
- },
31
- },
32
- });
33
- }
34
- },
35
- SearchInstance: {
36
- configureStorage: () => {},
37
- },
38
- }));
39
-
40
- describe.skip('sidebar api', () => {
15
+ describe('sidebar api', () => {
41
16
  beforeAll(() => {
42
- mockManifest.mockReturnValue({
17
+ mockManifest.mockReturnValue(() => ({
43
18
  staticPath: path.join(__dirname, 'static'),
19
+ manifestFiles: {
20
+ js: ['runtime.js', 'vendors.js', 'main.js'],
21
+ },
44
22
  manifest: require('./partials/manifest/manifest.json'),
45
- });
23
+ }));
46
24
  });
47
25
 
48
26
  afterEach(() => {
@@ -50,15 +28,23 @@ describe.skip('sidebar api', () => {
50
28
  mockManifest.mockClear();
51
29
  });
52
30
 
53
- test('should display sidebar info', async () => {
54
- mockManifest.mockReturnValue({
55
- manifest: require('./partials/manifest/manifest.json'),
56
- });
57
- const response = await supertest(await initializeServer('default-test.yaml'))
31
+ test('should display sidebar info scoped package', async () => {
32
+ const app = await initializeServer('default-test.yaml');
33
+ await publishVersion(app, '@scope/pk1-test', '1.0.0', { readme: 'my readme scoped' });
34
+ const response = await supertest(app)
58
35
  .get('/-/verdaccio/data/sidebar/@scope/pk1-test')
59
- .set('Accept', HEADERS.TEXT_PLAIN)
60
36
  .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
61
37
  .expect(HTTP_STATUS.OK);
62
- expect(response.text).toMatch(NOT_README_FOUND);
38
+ expect(response.text).toMatch('@scope/pk1-test');
39
+ });
40
+
41
+ test('should display sidebar info package', async () => {
42
+ const app = await initializeServer('default-test.yaml');
43
+ await publishVersion(app, 'pk2-test', '1.0.0', { readme: 'my readme scoped' });
44
+ const response = await supertest(app)
45
+ .get('/-/verdaccio/data/sidebar/pk2-test')
46
+ .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
47
+ .expect(HTTP_STATUS.OK);
48
+ expect(response.text).toMatch('pk2-test');
63
49
  });
64
50
  });
@@ -64,7 +64,7 @@ describe('test web server', () => {
64
64
  });
65
65
  });
66
66
 
67
- test('log in should be disabled', async () => {
67
+ test.skip('log in should be disabled', async () => {
68
68
  return supertest(await initializeServer('login-disabled.yaml'))
69
69
  .post('/-/verdaccio/sec/login')
70
70
  .send(
@@ -1,7 +1,3 @@
1
- store:
2
- memory:
3
- limit: 1000
4
-
5
1
  auth:
6
2
  auth-memory:
7
3
  users:
@@ -17,7 +13,7 @@ publish:
17
13
 
18
14
  uplinks:
19
15
 
20
- logs: { type: stdout, format: pretty, level: trace }
16
+ log: { type: stdout, format: pretty, level: trace }
21
17
 
22
18
  packages:
23
19
  '@*/*':
@@ -1,7 +1,3 @@
1
- store:
2
- memory:
3
- limit: 1000
4
-
5
1
  auth:
6
2
  auth-memory:
7
3
  users:
@@ -18,7 +14,7 @@ publish:
18
14
 
19
15
  uplinks:
20
16
 
21
- logs: { type: stdout, format: pretty, level: trace }
17
+ log: { type: stdout, format: pretty, level: trace }
22
18
 
23
19
  packages:
24
20
  '@*/*':
package/test/helper.ts CHANGED
@@ -1,38 +1,22 @@
1
- import bodyParser from 'body-parser';
2
- import express from 'express';
3
1
  import { Application } from 'express';
4
2
  import path from 'path';
5
3
 
6
- import { Auth, IAuth } from '@verdaccio/auth';
7
- import { Config, parseConfigFile } from '@verdaccio/config';
4
+ import apiMiddleware from '@verdaccio/api';
5
+ import { parseConfigFile } from '@verdaccio/config';
8
6
  import { setup } from '@verdaccio/logger';
9
- import { errorReportingMiddleware, final, handleError } from '@verdaccio/middleware';
10
7
  import { Storage } from '@verdaccio/store';
8
+ import { initializeServer as initializeServerHelper } from '@verdaccio/test-helper';
11
9
 
12
10
  import routes from '../src';
13
11
 
14
12
  setup([]);
15
13
 
16
- const getConf = (configName: string) => {
14
+ export const getConf = (configName: string) => {
17
15
  const configPath = path.join(__dirname, 'config', configName);
18
16
  return parseConfigFile(configPath);
19
17
  };
20
18
 
21
- export async function initializeServer(configName: string): Promise<Application> {
22
- const app = express();
23
- const config = new Config(getConf(configName));
24
- config.checkSecretKey('12345');
25
- const storage = new Storage(config);
26
- await storage.init(config, []);
27
- const auth: IAuth = new Auth(config);
28
- // for parsing the body (login api)
29
- app.use(bodyParser.json({ strict: false, limit: '10mb' }));
30
- // @ts-ignore
31
- app.use(errorReportingMiddleware);
32
- app.use(routes(config, auth, storage));
33
- // @ts-ignore
34
- app.use(handleError);
35
- // @ts-ignore
36
- app.use(final);
37
- return app;
19
+ // @deprecated
20
+ export async function initializeServer(configName): Promise<Application> {
21
+ return initializeServerHelper(getConf(configName), [apiMiddleware, routes], Storage);
38
22
  }