@verdaccio/store 6.0.0-6-next.18 → 6.0.0-6-next.21

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/storage.ts CHANGED
@@ -2,16 +2,24 @@ import assert from 'assert';
2
2
  import async, { AsyncResultArrayCallback } from 'async';
3
3
  import buildDebug from 'debug';
4
4
  import _ from 'lodash';
5
- import semver from 'semver';
6
5
 
7
6
  import { hasProxyTo } from '@verdaccio/config';
8
- import { API_ERROR, DIST_TAGS, HTTP_STATUS, errorUtils } from '@verdaccio/core';
9
- import { pkgUtils, validatioUtils } from '@verdaccio/core';
7
+ import {
8
+ API_ERROR,
9
+ DIST_TAGS,
10
+ HTTP_STATUS,
11
+ errorUtils,
12
+ pkgUtils,
13
+ validatioUtils,
14
+ } from '@verdaccio/core';
10
15
  import { logger } from '@verdaccio/logger';
11
16
  import { ProxyStorage } from '@verdaccio/proxy';
12
17
  import { IProxy, ProxyList } from '@verdaccio/proxy';
13
18
  import { ReadTarball } from '@verdaccio/streams';
14
- import { convertDistRemoteToLocalTarballUrls } from '@verdaccio/tarball';
19
+ import {
20
+ convertDistRemoteToLocalTarballUrls,
21
+ convertDistVersionToLocalTarballsUrl,
22
+ } from '@verdaccio/tarball';
15
23
  import {
16
24
  Callback,
17
25
  CallbackAction,
@@ -32,7 +40,8 @@ import {
32
40
  import { getVersion, normalizeDistTags } from '@verdaccio/utils';
33
41
 
34
42
  import { LocalStorage } from './local-storage';
35
- import { SearchInstance, SearchManager } from './search';
43
+ import { SearchManager } from './search';
44
+ // import { isPublishablePackage, validateInputs } from './star-utils';
36
45
  import {
37
46
  checkPackageLocal,
38
47
  checkPackageRemote,
@@ -42,12 +51,9 @@ import {
42
51
  publishPackage,
43
52
  } from './storage-utils';
44
53
  import { IGetPackageOptions, IGetPackageOptionsNext, IPluginFilters, ISyncUplinks } from './type';
54
+ // import { StarBody, Users } from './type';
45
55
  import { setupUpLinks, updateVersionsHiddenUpLink } from './uplink-util';
46
56
 
47
- if (semver.lte(process.version, 'v15.0.0')) {
48
- global.AbortController = require('abortcontroller-polyfill/dist/cjs-ponyfill').AbortController;
49
- }
50
-
51
57
  const debug = buildDebug('verdaccio:storage');
52
58
  class Storage {
53
59
  public localStorage: LocalStorage;
@@ -110,6 +116,33 @@ class Storage {
110
116
  }
111
117
  }
112
118
 
119
+ /**
120
+ * Add a {name} package to a system
121
+ Function checks if package with the same name is available from uplinks.
122
+ If it isn't, we create package locally
123
+ Used storages: local (write) && uplinks
124
+ */
125
+ public async addPackageNext(name: string, metadata: Package): Promise<Package> {
126
+ try {
127
+ debug('add package for %o', name);
128
+ await checkPackageLocal(name, this.localStorage);
129
+ debug('look up remote for %o', name);
130
+ await checkPackageRemote(
131
+ name,
132
+ this._isAllowPublishOffline(),
133
+ this._syncUplinksMetadata.bind(this)
134
+ );
135
+ debug('publishing a package for %o', name);
136
+ // FIXME: publishPackage should return fresh metadata from backend
137
+ // instead return metadata
138
+ await publishPackage(name, metadata, this.localStorage as LocalStorage);
139
+ return metadata;
140
+ } catch (err: any) {
141
+ debug('error on add a package for %o with error %o', name, err);
142
+ throw err;
143
+ }
144
+ }
145
+
113
146
  private _isAllowPublishOffline(): boolean {
114
147
  return (
115
148
  typeof this.config.publish !== 'undefined' &&
@@ -145,6 +178,16 @@ class Storage {
145
178
  this.localStorage.addVersion(name, version, metadata, tag, callback);
146
179
  }
147
180
 
181
+ public async addVersionNext(
182
+ name: string,
183
+ version: string,
184
+ metadata: Version,
185
+ tag: StringValue
186
+ ): Promise<void> {
187
+ debug('add the version %o for package %o', version, name);
188
+ return this.localStorage.addVersionNext(name, version, metadata, tag);
189
+ }
190
+
148
191
  /**
149
192
  * Tags a package version with a provided tag
150
193
  Used storages: local (write)
@@ -169,6 +212,16 @@ class Storage {
169
212
  this.localStorage.changePackage(name, metadata, revision, callback);
170
213
  }
171
214
 
215
+ /**
216
+ * Change an existing package (i.e. unpublish one version)
217
+ Function changes a package info from local storage and all uplinks with write access./
218
+ Used storages: local (write)
219
+ */
220
+ public async changePackageNext(name: string, metadata: Package, revision: string): Promise<void> {
221
+ debug('change existing package for package %o revision %o', name, revision);
222
+ this.localStorage.changePackageNext(name, metadata, revision);
223
+ }
224
+
172
225
  /**
173
226
  * Remove a package from a system
174
227
  Function removes a package from local storage
@@ -177,8 +230,6 @@ class Storage {
177
230
  public async removePackage(name: string): Promise<void> {
178
231
  debug('remove packagefor package %o', name);
179
232
  await this.localStorage.removePackage(name);
180
- // update the indexer
181
- SearchInstance.remove(name);
182
233
  }
183
234
 
184
235
  /**
@@ -346,7 +397,130 @@ class Storage {
346
397
  }
347
398
  }
348
399
 
349
- public async getPackageNext(options: IGetPackageOptionsNext): Promise<Package | Version> {
400
+ // public async starPackage(body: StarBody, options: IGetPackageOptionsNext): Promise<void> {
401
+ // debug('star package');
402
+ // const manifest = await this.getPackageNext(options);
403
+ // const newStarUser = body[constants.USERS];
404
+ // const remoteUsername: string = options.remoteUser.name as string;
405
+ // const localStarUsers = manifest[constants.USERS];
406
+ // // Check is star or unstar
407
+ // const isStar = Object.keys(newStarUser).includes(remoteUsername);
408
+ // debug('is start? %o', isStar);
409
+ // if (
410
+ // _.isNil(localStarUsers) === false &&
411
+ // validateInputs(localStarUsers, remoteUsername, isStar)
412
+ // ) {
413
+ // // return afterChangePackage();
414
+ // }
415
+ // const users: Users = isStar
416
+ // ? {
417
+ // ...localStarUsers,
418
+ // [remoteUsername]: true,
419
+ // }
420
+ // : _.reduce(
421
+ // localStarUsers,
422
+ // (users, value, key) => {
423
+ // if (key !== remoteUsername) {
424
+ // users[key] = value;
425
+ // }
426
+ // return users;
427
+ // },
428
+ // {}
429
+ // );
430
+ // debug('update package for %o', name);
431
+ // }
432
+
433
+ // public async publish(body: any, options: IGetPackageOptionsNext): Promise<any> {
434
+ // const { name } = options;
435
+ // debug('publishing or updating a new version for %o', name);
436
+ // // we check if the request is npm star
437
+ // if (!isPublishablePackage(body) && isObject(body.users)) {
438
+ // debug('starting star a package');
439
+ // await this.starPackage(body as StarBody, options);
440
+ // }
441
+
442
+ // return { ok: API_MESSAGE.PKG_CHANGED, success: true };
443
+ // }
444
+
445
+ public async getPackageByVersion(options: IGetPackageOptionsNext): Promise<Version> {
446
+ const queryVersion = options.version as string;
447
+ if (_.isNil(queryVersion)) {
448
+ throw errorUtils.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`);
449
+ }
450
+
451
+ // we have version, so we need to return specific version
452
+ const [convertedManifest] = await this.getPackageNext(options);
453
+
454
+ const version: Version | undefined = getVersion(convertedManifest.versions, queryVersion);
455
+
456
+ debug('query by latest version %o and result %o', queryVersion, version);
457
+ if (typeof version !== 'undefined') {
458
+ debug('latest version found %o', version);
459
+ return convertDistVersionToLocalTarballsUrl(
460
+ convertedManifest.name,
461
+ version,
462
+ options.requestOptions,
463
+ this.config.url_prefix
464
+ );
465
+ }
466
+
467
+ // the version could be a dist-tag eg: beta, alpha, so we find the matched version
468
+ // on disg-tag list
469
+ if (_.isNil(convertedManifest[DIST_TAGS]) === false) {
470
+ if (_.isNil(convertedManifest[DIST_TAGS][queryVersion]) === false) {
471
+ // the version found as a distag
472
+ const matchedDisTagVersion: string = convertedManifest[DIST_TAGS][queryVersion];
473
+ debug('dist-tag version found %o', matchedDisTagVersion);
474
+ const disTagVersion: Version | undefined = getVersion(
475
+ convertedManifest.versions,
476
+ matchedDisTagVersion
477
+ );
478
+ if (typeof disTagVersion !== 'undefined') {
479
+ debug('dist-tag found %o', disTagVersion);
480
+ return convertDistVersionToLocalTarballsUrl(
481
+ convertedManifest.name,
482
+ disTagVersion,
483
+ options.requestOptions,
484
+ this.config.url_prefix
485
+ );
486
+ }
487
+ }
488
+ } else {
489
+ debug('dist tag not detected');
490
+ }
491
+
492
+ // we didn't find the version, not found error
493
+ debug('package version not found %o', queryVersion);
494
+ throw errorUtils.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`);
495
+ }
496
+
497
+ public async getPackageManifest(options: IGetPackageOptionsNext): Promise<Package> {
498
+ // convert dist remotes to local bars
499
+ const [manifest] = await this.getPackageNext(options);
500
+ const convertedManifest = convertDistRemoteToLocalTarballUrls(
501
+ manifest,
502
+ options.requestOptions,
503
+ this.config.url_prefix
504
+ );
505
+
506
+ return convertedManifest;
507
+ }
508
+
509
+ /**
510
+ * Return a manifest or version based on the options.
511
+ * @param options {Object}
512
+ * @returns A package manifest or specific version
513
+ */
514
+ public async getPackageByOptions(options: IGetPackageOptionsNext): Promise<Package | Version> {
515
+ // if no version we return the whole manifest
516
+ if (_.isNil(options.version) === false) {
517
+ return this.getPackageByVersion(options);
518
+ } else {
519
+ return this.getPackageManifest(options);
520
+ }
521
+ }
522
+
523
+ public async getPackageNext(options: IGetPackageOptionsNext): Promise<[Package, any[]]> {
350
524
  const { name } = options;
351
525
  debug('get package for %o', name);
352
526
  try {
@@ -363,59 +537,11 @@ class Storage {
363
537
  // time to sync with uplinks if we have any
364
538
  debug('sync uplinks for %o', name);
365
539
  // @ts-expect-error
366
- const [manifest, errors] = await this._syncUplinksMetadataNext(name, data, {
540
+ return this._syncUplinksMetadataNext(name, data, {
367
541
  req: options.req,
368
542
  uplinksLook: options.uplinksLook,
369
543
  keepUpLinkData: options.keepUpLinkData,
370
544
  });
371
- debug('no. sync uplinks errors %o', errors?.length);
372
-
373
- // TODO: we could improve performance here, if a specific version is requested
374
- // we just convert that version and return it otherwise we convert
375
- // the whole manifest, bonus points for contribution :)
376
- // convert dist remotes to local bars
377
- const convertedManifest = convertDistRemoteToLocalTarballUrls(
378
- manifest,
379
- options.requestOptions,
380
- this.config.url_prefix
381
- );
382
- // if no version we return the whole manifest
383
- if (_.isNil(options.version)) {
384
- return convertedManifest;
385
- }
386
-
387
- // we have version, so we need to return specific version
388
- const queryVersion = options.version as string;
389
- const version: Version | undefined = getVersion(convertedManifest.versions, options.version);
390
- debug('query by latest version %o and result %o', options.version, version);
391
- if (typeof version !== 'undefined') {
392
- debug('latest version found %o', version);
393
- return version;
394
- }
395
-
396
- // the version could be a dist-tag eg: beta, alpha, so we find the matched version
397
- // on disg-tag list
398
- if (_.isNil(convertedManifest[DIST_TAGS]) === false) {
399
- if (_.isNil(convertedManifest[DIST_TAGS][queryVersion]) === false) {
400
- // the version found as a distag
401
- const matchedDisTagVersion: string = convertedManifest[DIST_TAGS][queryVersion];
402
- debug('dist-tag version found %o', matchedDisTagVersion);
403
- const disTagVersion: Version | undefined = getVersion(
404
- convertedManifest.versions,
405
- matchedDisTagVersion
406
- );
407
- if (typeof disTagVersion !== 'undefined') {
408
- debug('dist-tag found %o', disTagVersion);
409
- return disTagVersion;
410
- }
411
- }
412
- } else {
413
- debug('dist tag not detected');
414
- }
415
-
416
- // we didn't find the version, not found error
417
- debug('package version not found %o', queryVersion);
418
- throw errorUtils.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`);
419
545
  } catch (err: any) {
420
546
  this.logger.error(
421
547
  { name, err: err.message },
@@ -446,7 +572,7 @@ class Storage {
446
572
  _attachments: {},
447
573
  });
448
574
 
449
- debug('no. sync uplinks errors %o', uplinkErrors?.length);
575
+ debug('no. sync uplinks errors %o for %s', uplinkErrors?.length, name);
450
576
  resolve([normalizedPkg, uplinkErrors]);
451
577
  }
452
578
  );
package/src/type.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Callback, Config, IPluginStorageFilter } from '@verdaccio/types';
1
+ import { Callback, Config, IPluginStorageFilter, RemoteUser } from '@verdaccio/types';
2
2
 
3
3
  // @deprecated
4
4
  export interface IGetPackageOptions {
@@ -15,6 +15,7 @@ export type IGetPackageOptionsNext = {
15
15
  name: string;
16
16
  version?: string;
17
17
  keepUpLinkData?: boolean;
18
+ remoteUser?: RemoteUser;
18
19
  uplinksLook: boolean;
19
20
  requestOptions: {
20
21
  // RequestOptions from url package
@@ -30,4 +31,13 @@ export interface ISyncUplinks {
30
31
  req?: Request;
31
32
  }
32
33
 
34
+ export type Users = {
35
+ [key: string]: string;
36
+ };
37
+ export interface StarBody {
38
+ _id: string;
39
+ _rev: string;
40
+ users: Users;
41
+ }
42
+
33
43
  export type IPluginFilters = IPluginStorageFilter<Config>[];
@@ -1,10 +1,11 @@
1
+ import { setGlobalDispatcher } from 'undici';
2
+
1
3
  import { Config } from '@verdaccio/config';
2
4
  import { searchUtils } from '@verdaccio/core';
3
5
  import { setup } from '@verdaccio/logger';
4
6
  import { configExample } from '@verdaccio/mock';
5
7
 
6
8
  import { Storage, removeDuplicates } from '../src';
7
- import { SearchInstance } from '../src/search';
8
9
 
9
10
  setup([]);
10
11
 
@@ -30,7 +31,6 @@ describe('search', () => {
30
31
  test('search items', async () => {
31
32
  const { MockAgent } = require('undici');
32
33
  // FIXME: fetch is already part of undici
33
- const { setGlobalDispatcher } = require('undici-fetch');
34
34
  const domain = 'http://localhost:4873';
35
35
  const url = '/-/v1/search?maintenance=1&popularity=1&quality=1&size=10&text=verdaccio';
36
36
  const response = require('./fixtures/search.json');
@@ -52,68 +52,4 @@ describe('search', () => {
52
52
  expect(results).toHaveLength(4);
53
53
  });
54
54
  });
55
-
56
- describe('search index', () => {
57
- const packages = [
58
- {
59
- name: 'test1',
60
- description: 'description',
61
- _npmUser: {
62
- name: 'test_user',
63
- },
64
- },
65
- {
66
- name: 'test2',
67
- description: 'description',
68
- _npmUser: {
69
- name: 'test_user',
70
- },
71
- },
72
- {
73
- name: 'test3',
74
- description: 'description',
75
- _npmUser: {
76
- name: 'test_user',
77
- },
78
- },
79
- ];
80
-
81
- test('search query item', async () => {
82
- const config = new Config(configExample());
83
- const storage = new Storage(config);
84
- await storage.init(config);
85
- SearchInstance.configureStorage(storage);
86
- packages.map(function (item) {
87
- // @ts-ignore
88
- SearchInstance.add(item);
89
- });
90
- const result = SearchInstance.query('t');
91
- expect(result).toHaveLength(3);
92
- });
93
-
94
- test('search remove item', async () => {
95
- const config = new Config(configExample());
96
- const storage = new Storage(config);
97
- await storage.init(config);
98
- SearchInstance.configureStorage(storage);
99
- packages.map(function (item) {
100
- // @ts-ignore
101
- SearchInstance.add(item);
102
- });
103
- const item = {
104
- name: 'test6',
105
- description: 'description',
106
- _npmUser: {
107
- name: 'test_user',
108
- },
109
- };
110
- // @ts-ignore
111
- SearchInstance.add(item);
112
- let result = SearchInstance.query('test6');
113
- expect(result).toHaveLength(1);
114
- SearchInstance.remove(item.name);
115
- result = SearchInstance.query('test6');
116
- expect(result).toHaveLength(0);
117
- });
118
- });
119
55
  });
@@ -3,7 +3,13 @@ import assert from 'assert';
3
3
  import { DIST_TAGS } from '@verdaccio/core';
4
4
  import { Package } from '@verdaccio/types';
5
5
 
6
- import { STORAGE, mergeUplinkTimeIntoLocal, normalizePackage } from '../src/storage-utils';
6
+ import {
7
+ STORAGE,
8
+ hasInvalidPublishBody,
9
+ isDifferentThanOne,
10
+ mergeUplinkTimeIntoLocal,
11
+ normalizePackage,
12
+ } from '../src/storage-utils';
7
13
  import { tagVersion } from '../src/storage-utils';
8
14
  import { readFile } from './fixtures/test.utils';
9
15
 
@@ -172,4 +178,92 @@ describe('Storage Utils', () => {
172
178
  });
173
179
  });
174
180
  });
181
+
182
+ describe('isDifferentThanOne', () => {
183
+ test('isDifferentThanOne is true', () => {
184
+ expect(isDifferentThanOne({})).toBeTruthy();
185
+ });
186
+ test('isDifferentThanOne is false', () => {
187
+ expect(
188
+ isDifferentThanOne({
189
+ foo: 'bar',
190
+ })
191
+ ).toBeFalsy();
192
+ });
193
+ test('isDifferentThanOne with two items is true', () => {
194
+ expect(
195
+ isDifferentThanOne({
196
+ foo: 'bar',
197
+ foo1: 'bar',
198
+ })
199
+ ).toBeTruthy();
200
+ });
201
+ });
202
+
203
+ describe('hasInvalidPublishBody', () => {
204
+ test('should be valid', () => {
205
+ expect(
206
+ hasInvalidPublishBody({
207
+ _attachments: {
208
+ 'forbidden-place-1.0.6.tgz': {
209
+ content_type: 'application/octet-stream',
210
+ data: 'foo',
211
+ length: 512,
212
+ },
213
+ },
214
+ versions: {
215
+ // @ts-expect-error
216
+ '1.0.0': {},
217
+ },
218
+ })
219
+ ).toBeFalsy();
220
+ });
221
+
222
+ test('should be invalid due missing versions', () => {
223
+ expect(
224
+ hasInvalidPublishBody({
225
+ _attachments: {},
226
+ versions: {},
227
+ })
228
+ ).toBeTruthy();
229
+ });
230
+
231
+ test('should be invalid due missing _attachments', () => {
232
+ expect(
233
+ hasInvalidPublishBody({
234
+ _attachments: {},
235
+ versions: {},
236
+ })
237
+ ).toBeTruthy();
238
+ });
239
+
240
+ test('should be invalid due invalid empty versions object', () => {
241
+ expect(
242
+ hasInvalidPublishBody({
243
+ _attachments: {
244
+ 'forbidden-place-1.0.6.tgz': {
245
+ content_type: 'application/octet-stream',
246
+ data: 'foo',
247
+ length: 512,
248
+ },
249
+ },
250
+ versions: {},
251
+ })
252
+ ).toBeTruthy();
253
+ });
254
+
255
+ test('should be invalid due empty _attachments object', () => {
256
+ expect(
257
+ hasInvalidPublishBody({
258
+ _attachments: {},
259
+ versions: {
260
+ // @ts-expect-error
261
+ '1.0.0': {},
262
+ // @ts-expect-error
263
+ '1.0.1': {},
264
+ },
265
+ })
266
+ ).toBeTruthy();
267
+ });
268
+ });
175
269
  });
@@ -3,9 +3,9 @@ import * as httpMocks from 'node-mocks-http';
3
3
 
4
4
  import { Config } from '@verdaccio/config';
5
5
  import { HEADERS, errorUtils } from '@verdaccio/core';
6
- import { generatePackageMetadata } from '@verdaccio/helper';
7
6
  import { setup } from '@verdaccio/logger';
8
7
  import { configExample, generateRamdonStorage } from '@verdaccio/mock';
8
+ import { generatePackageMetadata } from '@verdaccio/test-helper';
9
9
 
10
10
  import { Storage } from '../src';
11
11
 
@@ -60,7 +60,7 @@ describe('storage', () => {
60
60
  const storage = new Storage(config);
61
61
  await storage.init(config);
62
62
  await expect(
63
- storage.getPackageNext({
63
+ storage.getPackageByOptions({
64
64
  name: 'foo',
65
65
  uplinksLook: true,
66
66
  req,
@@ -92,7 +92,7 @@ describe('storage', () => {
92
92
  const storage = new Storage(config);
93
93
  await storage.init(config);
94
94
  await expect(
95
- storage.getPackageNext({
95
+ storage.getPackageByOptions({
96
96
  name: 'foo',
97
97
  version: '1.0.0',
98
98
  uplinksLook: true,
@@ -125,7 +125,7 @@ describe('storage', () => {
125
125
  const storage = new Storage(config);
126
126
  await storage.init(config);
127
127
  await expect(
128
- storage.getPackageNext({
128
+ storage.getPackageByOptions({
129
129
  name: 'foo',
130
130
  version: 'latest',
131
131
  uplinksLook: true,
@@ -158,7 +158,7 @@ describe('storage', () => {
158
158
  const storage = new Storage(config);
159
159
  await storage.init(config);
160
160
  await expect(
161
- storage.getPackageNext({
161
+ storage.getPackageByOptions({
162
162
  name: 'foo',
163
163
  version: '1.0.0-does-not-exist',
164
164
  uplinksLook: true,
@@ -193,7 +193,7 @@ describe('storage', () => {
193
193
  const storage = new Storage(config);
194
194
  await storage.init(config);
195
195
  await expect(
196
- storage.getPackageNext({
196
+ storage.getPackageByOptions({
197
197
  name: 'foo2',
198
198
  uplinksLook: true,
199
199
  req,
@@ -228,7 +228,7 @@ describe('storage', () => {
228
228
  const storage = new Storage(config);
229
229
  await storage.init(config);
230
230
  await expect(
231
- storage.getPackageNext({
231
+ storage.getPackageByOptions({
232
232
  name: 'foo2',
233
233
  uplinksLook: true,
234
234
  req,