@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.
@@ -43,6 +43,8 @@ import {
43
43
 
44
44
  const debug = buildDebug('verdaccio:storage:local');
45
45
 
46
+ export const noSuchFile = 'ENOENT';
47
+ export const resourceNotAvailable = 'EAGAIN';
46
48
  export type IPluginStorage = pluginUtils.IPluginStorage<Config>;
47
49
 
48
50
  export function normalizeSearchPackage(
@@ -365,6 +367,144 @@ class LocalStorage {
365
367
  );
366
368
  }
367
369
 
370
+ public async addVersionNext(
371
+ name: string,
372
+ version: string,
373
+ metadata: Version,
374
+ tag: StringValue
375
+ ): Promise<void> {
376
+ debug(`add version %s package for %s`, version, name);
377
+ await this.updatePackageNext(name, async (data: Package): Promise<Package> => {
378
+ debug('%s package is being updated', name);
379
+ // keep only one readme per package
380
+ data.readme = metadata.readme;
381
+ debug('%s` readme mutated', name);
382
+ // TODO: lodash remove
383
+ metadata = cleanUpReadme(metadata);
384
+ metadata.contributors = normalizeContributors(metadata.contributors as Author[]);
385
+ debug('%s` contributors normalized', name);
386
+ const hasVersion = data.versions[version] != null;
387
+ if (hasVersion) {
388
+ debug('%s version %s already exists', name, version);
389
+ throw errorUtils.getConflict();
390
+ }
391
+
392
+ // if uploaded tarball has a different shasum, it's very likely that we
393
+ // have some kind of error
394
+ if (validatioUtils.isObject(metadata.dist) && _.isString(metadata.dist.tarball)) {
395
+ const tarball = metadata.dist.tarball.replace(/.*\//, '');
396
+
397
+ if (validatioUtils.isObject(data._attachments[tarball])) {
398
+ if (
399
+ _.isNil(data._attachments[tarball].shasum) === false &&
400
+ _.isNil(metadata.dist.shasum) === false
401
+ ) {
402
+ if (data._attachments[tarball].shasum != metadata.dist.shasum) {
403
+ const errorMessage =
404
+ `shasum error, ` +
405
+ `${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`;
406
+ throw errorUtils.getBadRequest(errorMessage);
407
+ }
408
+ }
409
+
410
+ const currentDate = new Date().toISOString();
411
+
412
+ // some old storage do not have this field #740
413
+ if (_.isNil(data.time)) {
414
+ data.time = {};
415
+ }
416
+
417
+ data.time['modified'] = currentDate;
418
+
419
+ if ('created' in data.time === false) {
420
+ data.time.created = currentDate;
421
+ }
422
+
423
+ data.time[version] = currentDate;
424
+ data._attachments[tarball].version = version;
425
+ }
426
+ }
427
+
428
+ data.versions[version] = metadata;
429
+ tagVersion(data, version, tag);
430
+
431
+ try {
432
+ debug('%s` add on database', name);
433
+ await this.storagePlugin.add(name);
434
+ } catch (err: any) {
435
+ throw errorUtils.getBadData(err.message);
436
+ }
437
+ return data;
438
+ });
439
+
440
+ // this._updatePackage(
441
+ // name,
442
+ // async (data, cb: Callback): Promise<void> => {
443
+ // debug('%s package is being updated', name);
444
+ // // keep only one readme per package
445
+ // data.readme = metadata.readme;
446
+ // debug('%s` readme mutated', name);
447
+ // // TODO: lodash remove
448
+ // metadata = cleanUpReadme(metadata);
449
+ // metadata.contributors = normalizeContributors(metadata.contributors as Author[]);
450
+ // debug('%s` contributors normalized', name);
451
+ // const hasVersion = data.versions[version] != null;
452
+ // if (hasVersion) {
453
+ // debug('%s version %s already exists', name, version);
454
+ // return cb(errorUtils.getConflict());
455
+ // }
456
+
457
+ // // if uploaded tarball has a different shasum, it's very likely that we
458
+ // // have some kind of error
459
+ // if (validatioUtils.isObject(metadata.dist) && _.isString(metadata.dist.tarball)) {
460
+ // const tarball = metadata.dist.tarball.replace(/.*\//, '');
461
+
462
+ // if (validatioUtils.isObject(data._attachments[tarball])) {
463
+ // if (
464
+ // _.isNil(data._attachments[tarball].shasum) === false &&
465
+ // _.isNil(metadata.dist.shasum) === false
466
+ // ) {
467
+ // if (data._attachments[tarball].shasum != metadata.dist.shasum) {
468
+ // const errorMessage =
469
+ // `shasum error, ` +
470
+ // `${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`;
471
+ // return cb(errorUtils.getBadRequest(errorMessage));
472
+ // }
473
+ // }
474
+
475
+ // const currentDate = new Date().toISOString();
476
+
477
+ // // some old storage do not have this field #740
478
+ // if (_.isNil(data.time)) {
479
+ // data.time = {};
480
+ // }
481
+
482
+ // data.time['modified'] = currentDate;
483
+
484
+ // if ('created' in data.time === false) {
485
+ // data.time.created = currentDate;
486
+ // }
487
+
488
+ // data.time[version] = currentDate;
489
+ // data._attachments[tarball].version = version;
490
+ // }
491
+ // }
492
+
493
+ // data.versions[version] = metadata;
494
+ // tagVersion(data, version, tag);
495
+
496
+ // try {
497
+ // debug('%s` add on database', name);
498
+ // await this.storagePlugin.add(name);
499
+ // cb();
500
+ // } catch (err: any) {
501
+ // cb(errorUtils.getBadData(err.message));
502
+ // }
503
+ // },
504
+ // callback
505
+ // );
506
+ }
507
+
368
508
  /**
369
509
  * Merge a new list of tags for a local packages with the existing one.
370
510
  * @param {*} pkgName
@@ -408,7 +548,7 @@ class LocalStorage {
408
548
  public changePackage(
409
549
  name: string,
410
550
  incomingPkg: Package,
411
- revision: string | void,
551
+ revision: string | undefined,
412
552
  callback: Callback
413
553
  ): void {
414
554
  debug(`change package tags for %o revision %s`, name, revision);
@@ -471,6 +611,70 @@ class LocalStorage {
471
611
  }
472
612
  );
473
613
  }
614
+
615
+ /**
616
+ * Update the package metadata, tags and attachments (tarballs).
617
+ * Note: Currently supports unpublishing and deprecation.
618
+ * @param {*} name
619
+ * @param {*} incomingPkg
620
+ * @param {*} revision
621
+ * @param {*} callback
622
+ * @return {Function}
623
+ */
624
+ public async changePackageNext(
625
+ name: string,
626
+ incomingPkg: Package,
627
+ revision: string | undefined
628
+ ): Promise<void> {
629
+ debug(`change package tags for %o revision %s`, name, revision);
630
+ if (
631
+ !validatioUtils.isObject(incomingPkg.versions) ||
632
+ !validatioUtils.isObject(incomingPkg[DIST_TAGS])
633
+ ) {
634
+ debug(`change package bad data for %o`, name);
635
+ throw errorUtils.getBadData();
636
+ }
637
+
638
+ debug(`change package udapting package for %o`, name);
639
+ await this.updatePackageNext(name, async (localData: Package): Promise<Package> => {
640
+ for (const version in localData.versions) {
641
+ const incomingVersion = incomingPkg.versions[version];
642
+ if (_.isNil(incomingVersion)) {
643
+ this.logger.info({ name: name, version: version }, 'unpublishing @{name}@@{version}');
644
+
645
+ // FIXME: I prefer return a new object rather mutate the metadata
646
+ delete localData.versions[version];
647
+ delete localData.time![version];
648
+
649
+ for (const file in localData._attachments) {
650
+ if (localData._attachments[file].version === version) {
651
+ delete localData._attachments[file].version;
652
+ }
653
+ }
654
+ } else if (Object.prototype.hasOwnProperty.call(incomingVersion, 'deprecated')) {
655
+ const incomingDeprecated = incomingVersion.deprecated;
656
+ if (incomingDeprecated != localData.versions[version].deprecated) {
657
+ if (!incomingDeprecated) {
658
+ this.logger.info(
659
+ { name: name, version: version },
660
+ 'undeprecating @{name}@@{version}'
661
+ );
662
+ delete localData.versions[version].deprecated;
663
+ } else {
664
+ this.logger.info({ name: name, version: version }, 'deprecating @{name}@@{version}');
665
+ localData.versions[version].deprecated = incomingDeprecated;
666
+ }
667
+ localData.time!.modified = new Date().toISOString();
668
+ }
669
+ }
670
+ }
671
+
672
+ localData[USERS] = incomingPkg[USERS];
673
+ localData[DIST_TAGS] = incomingPkg[DIST_TAGS];
674
+ return localData;
675
+ });
676
+ }
677
+
474
678
  /**
475
679
  * Remove a tarball.
476
680
  * @param {*} name
@@ -754,10 +958,16 @@ class LocalStorage {
754
958
  reject(err);
755
959
  }
756
960
 
961
+ if (_.isEmpty(pkg?.versions)) {
962
+ return resolve({});
963
+ }
964
+
757
965
  const searchPackage = normalizeSearchPackage(pkg, searchItem);
758
966
  const searchPackageItem: searchUtils.SearchPackageItem = {
759
967
  package: searchPackage,
760
968
  score: searchItem.score,
969
+ verdaccioPkgCached: searchItem.verdaccioPkgCached,
970
+ verdaccioPrivate: searchItem.verdaccioPrivate,
761
971
  flags: searchItem?.flags,
762
972
  // FUTURE: find a better way to calculate the score
763
973
  searchScore: 1,
@@ -890,12 +1100,12 @@ class LocalStorage {
890
1100
  private _updatePackage(
891
1101
  name: string,
892
1102
  updateHandler: StorageUpdateCallback,
893
- callback: Callback
1103
+ onEnd: Callback
894
1104
  ): void {
895
1105
  const storage: IPackageStorage = this._getLocalStorage(name);
896
1106
 
897
1107
  if (!storage) {
898
- return callback(errorUtils.getNotFound());
1108
+ return onEnd(errorUtils.getNotFound());
899
1109
  }
900
1110
 
901
1111
  storage.updatePackage(
@@ -903,10 +1113,42 @@ class LocalStorage {
903
1113
  updateHandler,
904
1114
  this._writePackage.bind(this),
905
1115
  normalizePackage,
906
- callback
1116
+ onEnd
907
1117
  );
908
1118
  }
909
1119
 
1120
+ /**
1121
+ * @param {*} name package name
1122
+ * @param {*} updateHandler function(package, cb) - update function
1123
+ * @param {*} callback callback that gets invoked after it's all updated
1124
+ * @return {Function}
1125
+ */
1126
+ private async updatePackageNext(
1127
+ name: string,
1128
+ updateHandler: (manifest: Package) => Promise<Package>
1129
+ ): Promise<void> {
1130
+ const storage: IPackageStorage = this._getLocalStorage(name);
1131
+
1132
+ if (!storage) {
1133
+ throw errorUtils.getNotFound();
1134
+ }
1135
+
1136
+ // we update the package on the local storage
1137
+ const updatedManifest: Package = await storage.updatePackageNext(name, updateHandler);
1138
+ // after correctly updated write to the storage
1139
+ try {
1140
+ await this.writePackageNext(name, normalizePackage(updatedManifest));
1141
+ } catch (err: any) {
1142
+ if (err.code === resourceNotAvailable) {
1143
+ throw errorUtils.getInternalError('resource temporarily unavailable');
1144
+ } else if (err.code === noSuchFile) {
1145
+ throw errorUtils.getNotFound();
1146
+ } else {
1147
+ throw err;
1148
+ }
1149
+ }
1150
+ }
1151
+
910
1152
  /**
911
1153
  * Update the revision (_rev) string for a package.
912
1154
  * @param {*} name
@@ -922,6 +1164,15 @@ class LocalStorage {
922
1164
  storage.savePackage(name, this._setDefaultRevision(json), callback);
923
1165
  }
924
1166
 
1167
+ private async writePackageNext(name: string, json: Package): Promise<void> {
1168
+ const storage: any = this._getLocalStorage(name);
1169
+ if (_.isNil(storage)) {
1170
+ // TODO: replace here 500 error
1171
+ throw errorUtils.getBadData();
1172
+ }
1173
+ await storage.savePackageNext(name, this._setDefaultRevision(json));
1174
+ }
1175
+
925
1176
  private _setDefaultRevision(json: Package): Package {
926
1177
  // calculate revision from couch db
927
1178
  if (_.isString(json._rev) === false) {
package/src/search.ts CHANGED
@@ -2,8 +2,6 @@
2
2
  // eslint-disable no-invalid-this
3
3
  import buildDebug from 'debug';
4
4
  import _ from 'lodash';
5
- import lunr from 'lunr';
6
- import lunrMutable from 'lunr-mutable-indexes';
7
5
  import { PassThrough, Transform, pipeline } from 'stream';
8
6
 
9
7
  import { VerdaccioError } from '@verdaccio/core';
@@ -20,8 +18,8 @@ export interface ISearchResult {
20
18
  ref: string;
21
19
  score: number;
22
20
  }
21
+ // @deprecated not longer used
23
22
  export interface IWebSearch {
24
- index: lunrMutable.index;
25
23
  storage: Storage;
26
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
25
  query(query: string): ISearchResult[];
@@ -33,7 +31,8 @@ export interface IWebSearch {
33
31
 
34
32
  export function removeDuplicates(results: searchUtils.SearchPackageItem[]) {
35
33
  const pkgNames: any[] = [];
36
- return results.filter((pkg) => {
34
+ const orderByResults = _.orderBy(results, ['verdaccioPrivate', 'asc']);
35
+ return orderByResults.filter((pkg) => {
37
36
  if (pkgNames.includes(pkg?.package?.name)) {
38
37
  return false;
39
38
  }
@@ -58,16 +57,18 @@ class TransFormResults extends Transform {
58
57
  */
59
58
  public _transform(chunk, _encoding, callback) {
60
59
  if (_.isArray(chunk)) {
60
+ // from remotes we should expect chunks as arrays
61
61
  (chunk as searchUtils.SearchItem[])
62
62
  .filter((pkgItem) => {
63
63
  debug(`streaming remote pkg name ${pkgItem?.package?.name}`);
64
64
  return true;
65
65
  })
66
66
  .forEach((pkgItem) => {
67
- this.push(pkgItem);
67
+ this.push({ ...pkgItem, verdaccioPkgCached: false, verdaccioPrivate: false });
68
68
  });
69
69
  return callback();
70
70
  } else {
71
+ // local we expect objects
71
72
  debug(`streaming local pkg name ${chunk?.package?.name}`);
72
73
  this.push(chunk);
73
74
  return callback();
@@ -105,30 +106,34 @@ export class SearchManager {
105
106
  if (!uplink) {
106
107
  // this should never tecnically happens
107
108
  logger.fatal({ uplinkId }, 'uplink @upLinkId not found');
108
- throw new Error(`uplink ${uplinkId} not found`);
109
109
  }
110
110
  return this.consumeSearchStream(uplinkId, uplink, options, streamPassThrough);
111
111
  });
112
112
 
113
113
  try {
114
114
  debug('search uplinks');
115
- await Promise.all([...searchUplinksStreams]);
115
+ // we only process those streams end successfully, if all fails
116
+ // we just include local storage
117
+ await Promise.allSettled([...searchUplinksStreams]);
116
118
  debug('search uplinks done');
117
- } catch (err) {
118
- logger.error({ err }, ' error on uplinks search @{err}');
119
+ } catch (err: any) {
120
+ logger.error({ err: err?.message }, ' error on uplinks search @{err}');
119
121
  streamPassThrough.emit('error', err);
120
- throw err;
121
122
  }
122
123
  debug('search local');
123
- await this.localStorage.search(streamPassThrough, options.query as searchUtils.SearchQuery);
124
-
124
+ try {
125
+ await this.localStorage.search(streamPassThrough, options.query as searchUtils.SearchQuery);
126
+ } catch (err: any) {
127
+ logger.error({ err: err?.message }, ' error on local search @{err}');
128
+ streamPassThrough.emit('error', err);
129
+ }
125
130
  const data: searchUtils.SearchPackageItem[] = [];
126
131
  const outPutStream = new PassThrough({ objectMode: true });
127
132
  pipeline(streamPassThrough, transformResults, outPutStream, (err) => {
128
133
  if (err) {
129
134
  throw errorUtils.getInternalError(err ? err.message : 'unknown error');
130
135
  } else {
131
- debug('Pipeline succeeded.');
136
+ debug('pipeline succeeded');
132
137
  }
133
138
  });
134
139
 
@@ -138,9 +143,9 @@ export class SearchManager {
138
143
 
139
144
  return new Promise((resolve) => {
140
145
  outPutStream.on('finish', async () => {
141
- const checkAccessPromises: searchUtils.SearchPackageItem[] = removeDuplicates(data);
142
- debug('stream finish event %s', checkAccessPromises.length);
143
- return resolve(checkAccessPromises);
146
+ const searchFinalResults: searchUtils.SearchPackageItem[] = removeDuplicates(data);
147
+ debug('search stream total results: %o', searchFinalResults.length);
148
+ return resolve(searchFinalResults);
144
149
  });
145
150
  debug('search done');
146
151
  });
@@ -168,111 +173,3 @@ export class SearchManager {
168
173
  });
169
174
  }
170
175
  }
171
-
172
- /**
173
- * Handle the search Indexer.
174
- */
175
- class Search implements IWebSearch {
176
- public readonly index: lunrMutable.index;
177
- // @ts-ignore
178
- public storage: Storage;
179
-
180
- /**
181
- * Constructor.
182
- */
183
- public constructor() {
184
- this.index = lunrMutable(function (): void {
185
- // FIXME: there is no types for this library
186
- /* eslint no-invalid-this:off */
187
- // @ts-ignore
188
- this.field('name', { boost: 10 });
189
- // @ts-ignore
190
- this.field('description', { boost: 4 });
191
- // @ts-ignore
192
- this.field('author', { boost: 6 });
193
- // @ts-ignore
194
- this.field('keywords', { boost: 7 });
195
- // @ts-ignore
196
- this.field('version');
197
- // @ts-ignore
198
- this.field('readme');
199
- });
200
-
201
- this.index.builder.pipeline.remove(lunr.stemmer);
202
- }
203
-
204
- public init() {
205
- return Promise.resolve();
206
- }
207
-
208
- /**
209
- * Performs a query to the indexer.
210
- * If the keyword is a * it returns all local elements
211
- * otherwise performs a search
212
- * @param {*} q the keyword
213
- * @return {Array} list of results.
214
- */
215
- public query(query: string): ISearchResult[] {
216
- const localStorage = this.storage.localStorage as LocalStorage;
217
-
218
- return query === '*'
219
- ? (localStorage.storagePlugin as any).get((items): any => {
220
- items.map(function (pkg): any {
221
- return { ref: pkg, score: 1 };
222
- });
223
- })
224
- : this.index.search(`*${query}*`);
225
- }
226
-
227
- /**
228
- * Add a new element to index
229
- * @param {*} pkg the package
230
- */
231
- public add(pkg: Version): void {
232
- this.index.add({
233
- id: pkg.name,
234
- name: pkg.name,
235
- description: pkg.description,
236
- version: `v${pkg.version}`,
237
- keywords: pkg.keywords,
238
- author: pkg._npmUser ? pkg._npmUser.name : '???',
239
- });
240
- }
241
-
242
- /**
243
- * Remove an element from the index.
244
- * @param {*} name the id element
245
- */
246
- public remove(name: string): void {
247
- this.index.remove({ id: name });
248
- }
249
-
250
- /**
251
- * Force a re-index.
252
- */
253
- public reindex(): void {
254
- this.storage.getLocalDatabase((error, packages): void => {
255
- if (error) {
256
- // that function shouldn't produce any
257
- throw error;
258
- }
259
- let i = packages.length;
260
- while (i--) {
261
- this.add(packages[i]);
262
- }
263
- });
264
- }
265
-
266
- /**
267
- * Set up the {Storage}
268
- * @param {*} storage An storage reference.
269
- */
270
- public configureStorage(storage: Storage): void {
271
- this.storage = storage;
272
- this.reindex();
273
- }
274
- }
275
-
276
- const SearchInstance = new Search();
277
-
278
- export { SearchInstance };
@@ -0,0 +1,38 @@
1
+ import _ from 'lodash';
2
+
3
+ import { Package } from '@verdaccio/types';
4
+
5
+ import { Users } from '.';
6
+
7
+ /**
8
+ * Check whether the package metadta has enough data to be published
9
+ * @param pkg metadata
10
+ */
11
+
12
+ export function isPublishablePackage(pkg: Package): boolean {
13
+ const keys: string[] = Object.keys(pkg);
14
+
15
+ return _.includes(keys, 'versions');
16
+ }
17
+
18
+ export function isRelatedToDeprecation(pkgInfo: Package): boolean {
19
+ const { versions } = pkgInfo;
20
+ for (const version in versions) {
21
+ if (Object.prototype.hasOwnProperty.call(versions[version], 'deprecated')) {
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+
28
+ export function validateInputs(localUsers: Users, username: string, isStar: boolean): boolean {
29
+ const isExistlocalUsers = _.isNil(localUsers[username]) === false;
30
+ if (isStar && isExistlocalUsers && localUsers[username]) {
31
+ return true;
32
+ } else if (!isStar && isExistlocalUsers) {
33
+ return false;
34
+ } else if (!isStar && !isExistlocalUsers) {
35
+ return true;
36
+ }
37
+ return false;
38
+ }
@@ -3,11 +3,10 @@ import semver from 'semver';
3
3
 
4
4
  import { errorUtils, pkgUtils, validatioUtils } from '@verdaccio/core';
5
5
  import { API_ERROR, DIST_TAGS, HTTP_STATUS, USERS } from '@verdaccio/core';
6
- import { Package, StringValue, Version } from '@verdaccio/types';
7
- import { generateRandomHexString, isNil, normalizeDistTags } from '@verdaccio/utils';
6
+ import { AttachMents, Package, StringValue, Version, Versions } from '@verdaccio/types';
7
+ import { generateRandomHexString, isNil, isObject, normalizeDistTags } from '@verdaccio/utils';
8
8
 
9
9
  import { LocalStorage } from './local-storage';
10
- import { SearchInstance } from './search';
11
10
 
12
11
  export const STORAGE = {
13
12
  PACKAGE_FILE_NAME: 'package.json',
@@ -148,11 +147,9 @@ export function publishPackage(
148
147
  localStorage: LocalStorage
149
148
  ): Promise<any> {
150
149
  return new Promise<void>((resolve, reject): void => {
151
- localStorage.addPackage(name, metadata, (err, latest): void => {
150
+ localStorage.addPackage(name, metadata, (err): void => {
152
151
  if (!_.isNull(err)) {
153
152
  return reject(err);
154
- } else if (!_.isUndefined(latest)) {
155
- SearchInstance.add(latest);
156
153
  }
157
154
  return resolve();
158
155
  });
@@ -246,3 +243,21 @@ export function tagVersion(data: Package, version: string, tag: StringValue): bo
246
243
  }
247
244
  return false;
248
245
  }
246
+
247
+ export function isDifferentThanOne(versions: Versions | AttachMents): boolean {
248
+ return Object.keys(versions).length !== 1;
249
+ }
250
+
251
+ export function hasInvalidPublishBody(manifest: Pick<Package, '_attachments' | 'versions'>) {
252
+ if (!manifest) {
253
+ return false;
254
+ }
255
+
256
+ const { _attachments, versions } = manifest;
257
+ const res =
258
+ isObject(_attachments) === false ||
259
+ isDifferentThanOne(_attachments) ||
260
+ isObject(versions) === false ||
261
+ isDifferentThanOne(versions);
262
+ return res;
263
+ }