@verdaccio/store 9.0.0-next-9.31 → 9.0.0-next-9.32

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.
@@ -28,12 +28,12 @@ export declare function tagVersion(data: Manifest, version: string, tag: StringV
28
28
  */
29
29
  export declare function tagVersionNext(manifest: Manifest, version: string, tag: StringValue): Manifest;
30
30
  /**
31
- * Check if the version is newer than the older version.
32
- * @param newVersion
33
- * @param oldVersion
34
- * @returns
31
+ * Whether `newVersion` is strictly newer than `oldVersion` under loose semver.
32
+ * Equal versions in any spelling (`1.2.3`, `v1.2.3`, `01.2.3`, `1.2.3+build`) are
33
+ * not newer, so search merges keep the entry they saw first, which is the local one.
34
+ * Both arguments must already pass `pkgUtils.isValidVersion`.
35
35
  */
36
- export declare function isNewerVersion(newVersion: any, oldVersion: any): boolean;
36
+ export declare function isNewerVersion(newVersion: string, oldVersion: string): boolean;
37
37
  /**
38
38
  * Remove duplicates from search results.
39
39
  * @param {Array} objects
@@ -58,14 +58,13 @@ function tagVersionNext(manifest, version, tag) {
58
58
  return data;
59
59
  }
60
60
  /**
61
- * Check if the version is newer than the older version.
62
- * @param newVersion
63
- * @param oldVersion
64
- * @returns
61
+ * Whether `newVersion` is strictly newer than `oldVersion` under loose semver.
62
+ * Equal versions in any spelling (`1.2.3`, `v1.2.3`, `01.2.3`, `1.2.3+build`) are
63
+ * not newer, so search merges keep the entry they saw first, which is the local one.
64
+ * Both arguments must already pass `pkgUtils.isValidVersion`.
65
65
  */
66
66
  function isNewerVersion(newVersion, oldVersion) {
67
- const comparisonResult = semver.default.compare(newVersion, oldVersion);
68
- return comparisonResult === 1 || comparisonResult === 0;
67
+ return semver.default.compareLoose(newVersion, oldVersion) === 1;
69
68
  }
70
69
  /**
71
70
  * Remove duplicates from search results.
@@ -74,7 +73,13 @@ function isNewerVersion(newVersion, oldVersion) {
74
73
  */
75
74
  function removeLowerVersions(objects) {
76
75
  const versionMap = /* @__PURE__ */ new Map();
77
- objects.forEach((item) => {
76
+ const validObjects = objects.filter((item) => {
77
+ const version = item?.package?.version;
78
+ const valid = _verdaccio_core.pkgUtils.isValidVersion(version);
79
+ if (!valid) debug$1("ignoring invalid search version %o", version);
80
+ return valid;
81
+ });
82
+ validObjects.forEach((item) => {
78
83
  const { name, version } = item.package;
79
84
  const key = name;
80
85
  if (versionMap.has(name) === false || isNewerVersion(version, versionMap.get(name))) {
@@ -82,7 +87,7 @@ function removeLowerVersions(objects) {
82
87
  versionMap.set(key, version);
83
88
  }
84
89
  });
85
- return objects.reduce((acc, item) => {
90
+ return validObjects.reduce((acc, item) => {
86
91
  const { name, version } = item.package;
87
92
  if (versionMap.has(name) && versionMap.get(name) === version && acc.find((i) => i.package.name === name) === void 0) {
88
93
  debug$1("adding %o@%o", name, version);
@@ -1 +1 @@
1
- {"version":3,"file":"versions-utils.js","names":[],"sources":["../../src/lib/versions-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil } from 'lodash-es';\nimport type { SemVer } from 'semver';\nimport semver from 'semver';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { DIST_TAGS } from '@verdaccio/core';\nimport type { Manifest, StringValue, Version, Versions } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:utils');\n\n/**\n * Gets version from a package object taking into account semver weirdness.\n * @return {String} return the semantic version of a package\n */\nexport function getVersion(versions: Versions, version: string): Version | undefined {\n if (!versions) {\n return;\n }\n\n // this condition must allow cast\n if (isNil(versions[version]) === false) {\n return versions[version];\n }\n\n const versionSemver: SemVer | null = semver.parse(version, true);\n if (versionSemver === null) {\n return;\n }\n\n for (const versionItem in versions) {\n if (Object.prototype.hasOwnProperty.call(versions, versionItem)) {\n // @ts-ignore\n if (versionSemver.compare(semver.parse(versionItem, true)) === 0) {\n return versions[versionItem];\n }\n }\n }\n}\n\n/**\n * Function filters out bad semver versions and sorts the array.\n * @return {Array} sorted Array\n */\nexport function sortVersionsAndFilterInvalid(listVersions: string[] /* logger */): string[] {\n return listVersions\n .filter(function (version): boolean {\n if (!semver.parse(version, true)) {\n return false;\n }\n return true;\n })\n .sort(semver.compareLoose)\n .map(String);\n}\n\n/**\n * Create a tag for a package\n * @param {*} data\n * @param {*} version\n * @param {*} tag\n * @return {Boolean} whether a package has been tagged\n * @deprecated\n */\nexport function tagVersion(data: Manifest, version: string, tag: StringValue): boolean {\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n return true;\n }\n return false;\n}\n\n/**\n *\n * @param manifest\n * @param version\n * @param tag\n * @returns\n */\nexport function tagVersionNext(manifest: Manifest, version: string, tag: StringValue): Manifest {\n const data = { ...manifest };\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n }\n return data;\n}\n\n/**\n * Check if the version is newer than the older version.\n * @param newVersion\n * @param oldVersion\n * @returns\n */\nexport function isNewerVersion(newVersion, oldVersion) {\n const comparisonResult = semver.compare(newVersion, oldVersion);\n\n return comparisonResult === 1 || comparisonResult === 0;\n}\n\n/**\n * Remove duplicates from search results.\n * @param {Array} objects\n * @return {Array} filtered array\n */\nexport function removeLowerVersions(objects: searchUtils.SearchPackageItem[]) {\n const versionMap = new Map();\n\n // Iterate through the array and keep the highest version for each name\n objects.forEach((item) => {\n const { name, version } = item.package;\n const key = name;\n\n if (versionMap.has(name) === false || isNewerVersion(version, versionMap.get(name))) {\n debug('keeping %o@%o', name, version);\n versionMap.set(key, version);\n }\n });\n\n // Filter objects based on the version map\n return objects.reduce((acc, item) => {\n const { name, version } = item.package;\n if (\n versionMap.has(name) &&\n versionMap.get(name) === version &&\n acc.find((i) => i.package.name === name) === undefined\n ) {\n debug('adding %o@%o', name, version);\n acc.push(item);\n }\n return acc;\n }, [] as searchUtils.SearchPackageItem[]);\n}\n"],"mappings":";;;;;;;;AASA,IAAM,WAAA,GAAQ,MAAA,QAAA,CAAW,yBAAyB;;;;;AAMlD,SAAgB,WAAW,UAAoB,SAAsC;CACnF,IAAI,CAAC,UACH;CAIF,KAAA,GAAI,UAAA,MAAA,CAAM,SAAS,QAAQ,MAAM,OAC/B,OAAO,SAAS;CAGlB,MAAM,gBAA+B,OAAA,QAAO,MAAM,SAAS,IAAI;CAC/D,IAAI,kBAAkB,MACpB;CAGF,KAAK,MAAM,eAAe,UACxB,IAAI,OAAO,UAAU,eAAe,KAAK,UAAU,WAAW,GAExD;MAAA,cAAc,QAAQ,OAAA,QAAO,MAAM,aAAa,IAAI,CAAC,MAAM,GAC7D,OAAO,SAAS;CAAA;AAIxB;;;;;AAMA,SAAgB,6BAA6B,cAA+C;CAC1F,OAAO,aACJ,OAAO,SAAU,SAAkB;EAClC,IAAI,CAAC,OAAA,QAAO,MAAM,SAAS,IAAI,GAC7B,OAAO;EAET,OAAO;CACT,CAAC,CAAC,CACD,KAAK,OAAA,QAAO,YAAY,CAAC,CACzB,IAAI,MAAM;AACf;;;;;;;;;AAUA,SAAgB,WAAW,MAAgB,SAAiB,KAA2B;CACrF,IAAI,OAAO,KAAK,gBAAA,UAAU,CAAC,SAAS,WAAW,OAAA,QAAO,MAAM,SAAS,IAAI,GAAG;EAE1E,KAAK,gBAAA,UAAU,CAAC,OAAO;EACvB,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,UAAoB,SAAiB,KAA4B;CAC9F,MAAM,OAAO,EAAE,GAAG,SAAS;CAC3B,IAAI,OAAO,KAAK,gBAAA,UAAU,CAAC,SAAS,WAAW,OAAA,QAAO,MAAM,SAAS,IAAI,GAEvE,KAAK,gBAAA,UAAU,CAAC,OAAO;CAEzB,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,YAAY,YAAY;CACrD,MAAM,mBAAmB,OAAA,QAAO,QAAQ,YAAY,UAAU;CAE9D,OAAO,qBAAqB,KAAK,qBAAqB;AACxD;;;;;;AAOA,SAAgB,oBAAoB,SAA0C;CAC5E,MAAM,6BAAa,IAAI,IAAI;CAG3B,QAAQ,SAAS,SAAS;EACxB,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,MAAM,MAAM;EAEZ,IAAI,WAAW,IAAI,IAAI,MAAM,SAAS,eAAe,SAAS,WAAW,IAAI,IAAI,CAAC,GAAG;GACnF,QAAM,iBAAiB,MAAM,OAAO;GACpC,WAAW,IAAI,KAAK,OAAO;EAC7B;CACF,CAAC;CAGD,OAAO,QAAQ,QAAQ,KAAK,SAAS;EACnC,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,IACE,WAAW,IAAI,IAAI,KACnB,WAAW,IAAI,IAAI,MAAM,WACzB,IAAI,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,KAAA,GAC7C;GACA,QAAM,gBAAgB,MAAM,OAAO;GACnC,IAAI,KAAK,IAAI;EACf;EACA,OAAO;CACT,GAAG,CAAC,CAAoC;AAC1C"}
1
+ {"version":3,"file":"versions-utils.js","names":[],"sources":["../../src/lib/versions-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil } from 'lodash-es';\nimport type { SemVer } from 'semver';\nimport semver from 'semver';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { DIST_TAGS, pkgUtils } from '@verdaccio/core';\nimport type { Manifest, StringValue, Version, Versions } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:utils');\n\n/**\n * Gets version from a package object taking into account semver weirdness.\n * @return {String} return the semantic version of a package\n */\nexport function getVersion(versions: Versions, version: string): Version | undefined {\n if (!versions) {\n return;\n }\n\n // this condition must allow cast\n if (isNil(versions[version]) === false) {\n return versions[version];\n }\n\n const versionSemver: SemVer | null = semver.parse(version, true);\n if (versionSemver === null) {\n return;\n }\n\n for (const versionItem in versions) {\n if (Object.prototype.hasOwnProperty.call(versions, versionItem)) {\n // @ts-ignore\n if (versionSemver.compare(semver.parse(versionItem, true)) === 0) {\n return versions[versionItem];\n }\n }\n }\n}\n\n/**\n * Function filters out bad semver versions and sorts the array.\n * @return {Array} sorted Array\n */\nexport function sortVersionsAndFilterInvalid(listVersions: string[] /* logger */): string[] {\n return listVersions\n .filter(function (version): boolean {\n if (!semver.parse(version, true)) {\n return false;\n }\n return true;\n })\n .sort(semver.compareLoose)\n .map(String);\n}\n\n/**\n * Create a tag for a package\n * @param {*} data\n * @param {*} version\n * @param {*} tag\n * @return {Boolean} whether a package has been tagged\n * @deprecated\n */\nexport function tagVersion(data: Manifest, version: string, tag: StringValue): boolean {\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n return true;\n }\n return false;\n}\n\n/**\n *\n * @param manifest\n * @param version\n * @param tag\n * @returns\n */\nexport function tagVersionNext(manifest: Manifest, version: string, tag: StringValue): Manifest {\n const data = { ...manifest };\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n }\n return data;\n}\n\n/**\n * Whether `newVersion` is strictly newer than `oldVersion` under loose semver.\n * Equal versions in any spelling (`1.2.3`, `v1.2.3`, `01.2.3`, `1.2.3+build`) are\n * not newer, so search merges keep the entry they saw first, which is the local one.\n * Both arguments must already pass `pkgUtils.isValidVersion`.\n */\nexport function isNewerVersion(newVersion: string, oldVersion: string): boolean {\n return semver.compareLoose(newVersion, oldVersion) === 1;\n}\n\n/**\n * Remove duplicates from search results.\n * @param {Array} objects\n * @return {Array} filtered array\n */\nexport function removeLowerVersions(objects: searchUtils.SearchPackageItem[]) {\n const versionMap = new Map();\n const validObjects = objects.filter((item) => {\n const version = item?.package?.version;\n const valid = pkgUtils.isValidVersion(version);\n if (!valid) debug('ignoring invalid search version %o', version);\n return valid;\n });\n\n // Iterate through the array and keep the highest version for each name\n validObjects.forEach((item) => {\n const { name, version } = item.package;\n const key = name;\n\n if (versionMap.has(name) === false || isNewerVersion(version, versionMap.get(name))) {\n debug('keeping %o@%o', name, version);\n versionMap.set(key, version);\n }\n });\n\n // Filter objects based on the version map\n return validObjects.reduce((acc, item) => {\n const { name, version } = item.package;\n if (\n versionMap.has(name) &&\n versionMap.get(name) === version &&\n acc.find((i) => i.package.name === name) === undefined\n ) {\n debug('adding %o@%o', name, version);\n acc.push(item);\n }\n return acc;\n }, [] as searchUtils.SearchPackageItem[]);\n}\n"],"mappings":";;;;;;;;AASA,IAAM,WAAA,GAAQ,MAAA,QAAA,CAAW,yBAAyB;;;;;AAMlD,SAAgB,WAAW,UAAoB,SAAsC;CACnF,IAAI,CAAC,UACH;CAIF,KAAA,GAAI,UAAA,MAAA,CAAM,SAAS,QAAQ,MAAM,OAC/B,OAAO,SAAS;CAGlB,MAAM,gBAA+B,OAAA,QAAO,MAAM,SAAS,IAAI;CAC/D,IAAI,kBAAkB,MACpB;CAGF,KAAK,MAAM,eAAe,UACxB,IAAI,OAAO,UAAU,eAAe,KAAK,UAAU,WAAW,GAExD;MAAA,cAAc,QAAQ,OAAA,QAAO,MAAM,aAAa,IAAI,CAAC,MAAM,GAC7D,OAAO,SAAS;CAAA;AAIxB;;;;;AAMA,SAAgB,6BAA6B,cAA+C;CAC1F,OAAO,aACJ,OAAO,SAAU,SAAkB;EAClC,IAAI,CAAC,OAAA,QAAO,MAAM,SAAS,IAAI,GAC7B,OAAO;EAET,OAAO;CACT,CAAC,CAAC,CACD,KAAK,OAAA,QAAO,YAAY,CAAC,CACzB,IAAI,MAAM;AACf;;;;;;;;;AAUA,SAAgB,WAAW,MAAgB,SAAiB,KAA2B;CACrF,IAAI,OAAO,KAAK,gBAAA,UAAU,CAAC,SAAS,WAAW,OAAA,QAAO,MAAM,SAAS,IAAI,GAAG;EAE1E,KAAK,gBAAA,UAAU,CAAC,OAAO;EACvB,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,UAAoB,SAAiB,KAA4B;CAC9F,MAAM,OAAO,EAAE,GAAG,SAAS;CAC3B,IAAI,OAAO,KAAK,gBAAA,UAAU,CAAC,SAAS,WAAW,OAAA,QAAO,MAAM,SAAS,IAAI,GAEvE,KAAK,gBAAA,UAAU,CAAC,OAAO;CAEzB,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,YAAoB,YAA6B;CAC9E,OAAO,OAAA,QAAO,aAAa,YAAY,UAAU,MAAM;AACzD;;;;;;AAOA,SAAgB,oBAAoB,SAA0C;CAC5E,MAAM,6BAAa,IAAI,IAAI;CAC3B,MAAM,eAAe,QAAQ,QAAQ,SAAS;EAC5C,MAAM,UAAU,MAAM,SAAS;EAC/B,MAAM,QAAQ,gBAAA,SAAS,eAAe,OAAO;EAC7C,IAAI,CAAC,OAAO,QAAM,sCAAsC,OAAO;EAC/D,OAAO;CACT,CAAC;CAGD,aAAa,SAAS,SAAS;EAC7B,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,MAAM,MAAM;EAEZ,IAAI,WAAW,IAAI,IAAI,MAAM,SAAS,eAAe,SAAS,WAAW,IAAI,IAAI,CAAC,GAAG;GACnF,QAAM,iBAAiB,MAAM,OAAO;GACpC,WAAW,IAAI,KAAK,OAAO;EAC7B;CACF,CAAC;CAGD,OAAO,aAAa,QAAQ,KAAK,SAAS;EACxC,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,IACE,WAAW,IAAI,IAAI,KACnB,WAAW,IAAI,IAAI,MAAM,WACzB,IAAI,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,KAAA,GAC7C;GACA,QAAM,gBAAgB,MAAM,OAAO;GACnC,IAAI,KAAK,IAAI;EACf;EACA,OAAO;CACT,GAAG,CAAC,CAAoC;AAC1C"}
@@ -1,6 +1,6 @@
1
1
  import buildDebug from "debug";
2
2
  import { isNil } from "lodash-es";
3
- import { DIST_TAGS } from "@verdaccio/core";
3
+ import { DIST_TAGS, pkgUtils } from "@verdaccio/core";
4
4
  import semver from "semver";
5
5
  //#region src/lib/versions-utils.ts
6
6
  var debug = buildDebug("verdaccio:storage:utils");
@@ -55,14 +55,13 @@ function tagVersionNext(manifest, version, tag) {
55
55
  return data;
56
56
  }
57
57
  /**
58
- * Check if the version is newer than the older version.
59
- * @param newVersion
60
- * @param oldVersion
61
- * @returns
58
+ * Whether `newVersion` is strictly newer than `oldVersion` under loose semver.
59
+ * Equal versions in any spelling (`1.2.3`, `v1.2.3`, `01.2.3`, `1.2.3+build`) are
60
+ * not newer, so search merges keep the entry they saw first, which is the local one.
61
+ * Both arguments must already pass `pkgUtils.isValidVersion`.
62
62
  */
63
63
  function isNewerVersion(newVersion, oldVersion) {
64
- const comparisonResult = semver.compare(newVersion, oldVersion);
65
- return comparisonResult === 1 || comparisonResult === 0;
64
+ return semver.compareLoose(newVersion, oldVersion) === 1;
66
65
  }
67
66
  /**
68
67
  * Remove duplicates from search results.
@@ -71,7 +70,13 @@ function isNewerVersion(newVersion, oldVersion) {
71
70
  */
72
71
  function removeLowerVersions(objects) {
73
72
  const versionMap = /* @__PURE__ */ new Map();
74
- objects.forEach((item) => {
73
+ const validObjects = objects.filter((item) => {
74
+ const version = item?.package?.version;
75
+ const valid = pkgUtils.isValidVersion(version);
76
+ if (!valid) debug("ignoring invalid search version %o", version);
77
+ return valid;
78
+ });
79
+ validObjects.forEach((item) => {
75
80
  const { name, version } = item.package;
76
81
  const key = name;
77
82
  if (versionMap.has(name) === false || isNewerVersion(version, versionMap.get(name))) {
@@ -79,7 +84,7 @@ function removeLowerVersions(objects) {
79
84
  versionMap.set(key, version);
80
85
  }
81
86
  });
82
- return objects.reduce((acc, item) => {
87
+ return validObjects.reduce((acc, item) => {
83
88
  const { name, version } = item.package;
84
89
  if (versionMap.has(name) && versionMap.get(name) === version && acc.find((i) => i.package.name === name) === void 0) {
85
90
  debug("adding %o@%o", name, version);
@@ -1 +1 @@
1
- {"version":3,"file":"versions-utils.mjs","names":[],"sources":["../../src/lib/versions-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil } from 'lodash-es';\nimport type { SemVer } from 'semver';\nimport semver from 'semver';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { DIST_TAGS } from '@verdaccio/core';\nimport type { Manifest, StringValue, Version, Versions } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:utils');\n\n/**\n * Gets version from a package object taking into account semver weirdness.\n * @return {String} return the semantic version of a package\n */\nexport function getVersion(versions: Versions, version: string): Version | undefined {\n if (!versions) {\n return;\n }\n\n // this condition must allow cast\n if (isNil(versions[version]) === false) {\n return versions[version];\n }\n\n const versionSemver: SemVer | null = semver.parse(version, true);\n if (versionSemver === null) {\n return;\n }\n\n for (const versionItem in versions) {\n if (Object.prototype.hasOwnProperty.call(versions, versionItem)) {\n // @ts-ignore\n if (versionSemver.compare(semver.parse(versionItem, true)) === 0) {\n return versions[versionItem];\n }\n }\n }\n}\n\n/**\n * Function filters out bad semver versions and sorts the array.\n * @return {Array} sorted Array\n */\nexport function sortVersionsAndFilterInvalid(listVersions: string[] /* logger */): string[] {\n return listVersions\n .filter(function (version): boolean {\n if (!semver.parse(version, true)) {\n return false;\n }\n return true;\n })\n .sort(semver.compareLoose)\n .map(String);\n}\n\n/**\n * Create a tag for a package\n * @param {*} data\n * @param {*} version\n * @param {*} tag\n * @return {Boolean} whether a package has been tagged\n * @deprecated\n */\nexport function tagVersion(data: Manifest, version: string, tag: StringValue): boolean {\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n return true;\n }\n return false;\n}\n\n/**\n *\n * @param manifest\n * @param version\n * @param tag\n * @returns\n */\nexport function tagVersionNext(manifest: Manifest, version: string, tag: StringValue): Manifest {\n const data = { ...manifest };\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n }\n return data;\n}\n\n/**\n * Check if the version is newer than the older version.\n * @param newVersion\n * @param oldVersion\n * @returns\n */\nexport function isNewerVersion(newVersion, oldVersion) {\n const comparisonResult = semver.compare(newVersion, oldVersion);\n\n return comparisonResult === 1 || comparisonResult === 0;\n}\n\n/**\n * Remove duplicates from search results.\n * @param {Array} objects\n * @return {Array} filtered array\n */\nexport function removeLowerVersions(objects: searchUtils.SearchPackageItem[]) {\n const versionMap = new Map();\n\n // Iterate through the array and keep the highest version for each name\n objects.forEach((item) => {\n const { name, version } = item.package;\n const key = name;\n\n if (versionMap.has(name) === false || isNewerVersion(version, versionMap.get(name))) {\n debug('keeping %o@%o', name, version);\n versionMap.set(key, version);\n }\n });\n\n // Filter objects based on the version map\n return objects.reduce((acc, item) => {\n const { name, version } = item.package;\n if (\n versionMap.has(name) &&\n versionMap.get(name) === version &&\n acc.find((i) => i.package.name === name) === undefined\n ) {\n debug('adding %o@%o', name, version);\n acc.push(item);\n }\n return acc;\n }, [] as searchUtils.SearchPackageItem[]);\n}\n"],"mappings":";;;;;AASA,IAAM,QAAQ,WAAW,yBAAyB;;;;;AAMlD,SAAgB,WAAW,UAAoB,SAAsC;CACnF,IAAI,CAAC,UACH;CAIF,IAAI,MAAM,SAAS,QAAQ,MAAM,OAC/B,OAAO,SAAS;CAGlB,MAAM,gBAA+B,OAAO,MAAM,SAAS,IAAI;CAC/D,IAAI,kBAAkB,MACpB;CAGF,KAAK,MAAM,eAAe,UACxB,IAAI,OAAO,UAAU,eAAe,KAAK,UAAU,WAAW,GAExD;MAAA,cAAc,QAAQ,OAAO,MAAM,aAAa,IAAI,CAAC,MAAM,GAC7D,OAAO,SAAS;CAAA;AAIxB;;;;;AAMA,SAAgB,6BAA6B,cAA+C;CAC1F,OAAO,aACJ,OAAO,SAAU,SAAkB;EAClC,IAAI,CAAC,OAAO,MAAM,SAAS,IAAI,GAC7B,OAAO;EAET,OAAO;CACT,CAAC,CAAC,CACD,KAAK,OAAO,YAAY,CAAC,CACzB,IAAI,MAAM;AACf;;;;;;;;;AAUA,SAAgB,WAAW,MAAgB,SAAiB,KAA2B;CACrF,IAAI,OAAO,KAAK,UAAU,CAAC,SAAS,WAAW,OAAO,MAAM,SAAS,IAAI,GAAG;EAE1E,KAAK,UAAU,CAAC,OAAO;EACvB,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,UAAoB,SAAiB,KAA4B;CAC9F,MAAM,OAAO,EAAE,GAAG,SAAS;CAC3B,IAAI,OAAO,KAAK,UAAU,CAAC,SAAS,WAAW,OAAO,MAAM,SAAS,IAAI,GAEvE,KAAK,UAAU,CAAC,OAAO;CAEzB,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,YAAY,YAAY;CACrD,MAAM,mBAAmB,OAAO,QAAQ,YAAY,UAAU;CAE9D,OAAO,qBAAqB,KAAK,qBAAqB;AACxD;;;;;;AAOA,SAAgB,oBAAoB,SAA0C;CAC5E,MAAM,6BAAa,IAAI,IAAI;CAG3B,QAAQ,SAAS,SAAS;EACxB,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,MAAM,MAAM;EAEZ,IAAI,WAAW,IAAI,IAAI,MAAM,SAAS,eAAe,SAAS,WAAW,IAAI,IAAI,CAAC,GAAG;GACnF,MAAM,iBAAiB,MAAM,OAAO;GACpC,WAAW,IAAI,KAAK,OAAO;EAC7B;CACF,CAAC;CAGD,OAAO,QAAQ,QAAQ,KAAK,SAAS;EACnC,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,IACE,WAAW,IAAI,IAAI,KACnB,WAAW,IAAI,IAAI,MAAM,WACzB,IAAI,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,KAAA,GAC7C;GACA,MAAM,gBAAgB,MAAM,OAAO;GACnC,IAAI,KAAK,IAAI;EACf;EACA,OAAO;CACT,GAAG,CAAC,CAAoC;AAC1C"}
1
+ {"version":3,"file":"versions-utils.mjs","names":[],"sources":["../../src/lib/versions-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil } from 'lodash-es';\nimport type { SemVer } from 'semver';\nimport semver from 'semver';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { DIST_TAGS, pkgUtils } from '@verdaccio/core';\nimport type { Manifest, StringValue, Version, Versions } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:utils');\n\n/**\n * Gets version from a package object taking into account semver weirdness.\n * @return {String} return the semantic version of a package\n */\nexport function getVersion(versions: Versions, version: string): Version | undefined {\n if (!versions) {\n return;\n }\n\n // this condition must allow cast\n if (isNil(versions[version]) === false) {\n return versions[version];\n }\n\n const versionSemver: SemVer | null = semver.parse(version, true);\n if (versionSemver === null) {\n return;\n }\n\n for (const versionItem in versions) {\n if (Object.prototype.hasOwnProperty.call(versions, versionItem)) {\n // @ts-ignore\n if (versionSemver.compare(semver.parse(versionItem, true)) === 0) {\n return versions[versionItem];\n }\n }\n }\n}\n\n/**\n * Function filters out bad semver versions and sorts the array.\n * @return {Array} sorted Array\n */\nexport function sortVersionsAndFilterInvalid(listVersions: string[] /* logger */): string[] {\n return listVersions\n .filter(function (version): boolean {\n if (!semver.parse(version, true)) {\n return false;\n }\n return true;\n })\n .sort(semver.compareLoose)\n .map(String);\n}\n\n/**\n * Create a tag for a package\n * @param {*} data\n * @param {*} version\n * @param {*} tag\n * @return {Boolean} whether a package has been tagged\n * @deprecated\n */\nexport function tagVersion(data: Manifest, version: string, tag: StringValue): boolean {\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n return true;\n }\n return false;\n}\n\n/**\n *\n * @param manifest\n * @param version\n * @param tag\n * @returns\n */\nexport function tagVersionNext(manifest: Manifest, version: string, tag: StringValue): Manifest {\n const data = { ...manifest };\n if (tag && data[DIST_TAGS][tag] !== version && semver.parse(version, true)) {\n // valid version - store\n data[DIST_TAGS][tag] = version;\n }\n return data;\n}\n\n/**\n * Whether `newVersion` is strictly newer than `oldVersion` under loose semver.\n * Equal versions in any spelling (`1.2.3`, `v1.2.3`, `01.2.3`, `1.2.3+build`) are\n * not newer, so search merges keep the entry they saw first, which is the local one.\n * Both arguments must already pass `pkgUtils.isValidVersion`.\n */\nexport function isNewerVersion(newVersion: string, oldVersion: string): boolean {\n return semver.compareLoose(newVersion, oldVersion) === 1;\n}\n\n/**\n * Remove duplicates from search results.\n * @param {Array} objects\n * @return {Array} filtered array\n */\nexport function removeLowerVersions(objects: searchUtils.SearchPackageItem[]) {\n const versionMap = new Map();\n const validObjects = objects.filter((item) => {\n const version = item?.package?.version;\n const valid = pkgUtils.isValidVersion(version);\n if (!valid) debug('ignoring invalid search version %o', version);\n return valid;\n });\n\n // Iterate through the array and keep the highest version for each name\n validObjects.forEach((item) => {\n const { name, version } = item.package;\n const key = name;\n\n if (versionMap.has(name) === false || isNewerVersion(version, versionMap.get(name))) {\n debug('keeping %o@%o', name, version);\n versionMap.set(key, version);\n }\n });\n\n // Filter objects based on the version map\n return validObjects.reduce((acc, item) => {\n const { name, version } = item.package;\n if (\n versionMap.has(name) &&\n versionMap.get(name) === version &&\n acc.find((i) => i.package.name === name) === undefined\n ) {\n debug('adding %o@%o', name, version);\n acc.push(item);\n }\n return acc;\n }, [] as searchUtils.SearchPackageItem[]);\n}\n"],"mappings":";;;;;AASA,IAAM,QAAQ,WAAW,yBAAyB;;;;;AAMlD,SAAgB,WAAW,UAAoB,SAAsC;CACnF,IAAI,CAAC,UACH;CAIF,IAAI,MAAM,SAAS,QAAQ,MAAM,OAC/B,OAAO,SAAS;CAGlB,MAAM,gBAA+B,OAAO,MAAM,SAAS,IAAI;CAC/D,IAAI,kBAAkB,MACpB;CAGF,KAAK,MAAM,eAAe,UACxB,IAAI,OAAO,UAAU,eAAe,KAAK,UAAU,WAAW,GAExD;MAAA,cAAc,QAAQ,OAAO,MAAM,aAAa,IAAI,CAAC,MAAM,GAC7D,OAAO,SAAS;CAAA;AAIxB;;;;;AAMA,SAAgB,6BAA6B,cAA+C;CAC1F,OAAO,aACJ,OAAO,SAAU,SAAkB;EAClC,IAAI,CAAC,OAAO,MAAM,SAAS,IAAI,GAC7B,OAAO;EAET,OAAO;CACT,CAAC,CAAC,CACD,KAAK,OAAO,YAAY,CAAC,CACzB,IAAI,MAAM;AACf;;;;;;;;;AAUA,SAAgB,WAAW,MAAgB,SAAiB,KAA2B;CACrF,IAAI,OAAO,KAAK,UAAU,CAAC,SAAS,WAAW,OAAO,MAAM,SAAS,IAAI,GAAG;EAE1E,KAAK,UAAU,CAAC,OAAO;EACvB,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,UAAoB,SAAiB,KAA4B;CAC9F,MAAM,OAAO,EAAE,GAAG,SAAS;CAC3B,IAAI,OAAO,KAAK,UAAU,CAAC,SAAS,WAAW,OAAO,MAAM,SAAS,IAAI,GAEvE,KAAK,UAAU,CAAC,OAAO;CAEzB,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,YAAoB,YAA6B;CAC9E,OAAO,OAAO,aAAa,YAAY,UAAU,MAAM;AACzD;;;;;;AAOA,SAAgB,oBAAoB,SAA0C;CAC5E,MAAM,6BAAa,IAAI,IAAI;CAC3B,MAAM,eAAe,QAAQ,QAAQ,SAAS;EAC5C,MAAM,UAAU,MAAM,SAAS;EAC/B,MAAM,QAAQ,SAAS,eAAe,OAAO;EAC7C,IAAI,CAAC,OAAO,MAAM,sCAAsC,OAAO;EAC/D,OAAO;CACT,CAAC;CAGD,aAAa,SAAS,SAAS;EAC7B,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,MAAM,MAAM;EAEZ,IAAI,WAAW,IAAI,IAAI,MAAM,SAAS,eAAe,SAAS,WAAW,IAAI,IAAI,CAAC,GAAG;GACnF,MAAM,iBAAiB,MAAM,OAAO;GACpC,WAAW,IAAI,KAAK,OAAO;EAC7B;CACF,CAAC;CAGD,OAAO,aAAa,QAAQ,KAAK,SAAS;EACxC,MAAM,EAAE,MAAM,YAAY,KAAK;EAC/B,IACE,WAAW,IAAI,IAAI,KACnB,WAAW,IAAI,IAAI,MAAM,WACzB,IAAI,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,KAAA,GAC7C;GACA,MAAM,gBAAgB,MAAM,OAAO;GACnC,IAAI,KAAK,IAAI;EACf;EACA,OAAO;CACT,GAAG,CAAC,CAAoC;AAC1C"}
@@ -42,6 +42,8 @@ declare class Storage {
42
42
  * (privated and cached).
43
43
  */
44
44
  search(options: ProxySearchParams): Promise<searchUtils.SearchPackageItem[]>;
45
+ /** Cumulative, stable prefixes for Search v1; consumers stop once their page is full. */
46
+ searchPages(options: ProxySearchParams): AsyncGenerator<searchUtils.SearchPackageItem[], void, unknown>;
45
47
  private getTarballFromUpstream;
46
48
  private fetchTarballFromDistFile;
47
49
  /**
@@ -120,6 +122,16 @@ declare class Storage {
120
122
  * @param tags list of dist-tags
121
123
  */
122
124
  mergeTagsNext(name: string, tags: MergeTags): Promise<Manifest>;
125
+ /**
126
+ * Resolve the uplink to fetch a distfile url from, or null when the fetch
127
+ * must be refused.
128
+ *
129
+ * Off-uplink urls on a locally published package are not fetched: the
130
+ * client's `dist.tarball` is stored verbatim, and fetching it would be SSRF
131
+ * (and would attach uplink credentials when only one uplink is configured).
132
+ * Uplink-synced packages may still use an autogenerated proxy without
133
+ * credentials for CDN-hosted tarballs.
134
+ */
123
135
  private getUpLinkForDistFile;
124
136
  updateManifest(manifest: Manifest | OwnerManifestBody | UnPublishManifest, options: UpdateManifestOptions): Promise<string | undefined>;
125
137
  private deprecate;
package/build/storage.js CHANGED
@@ -159,6 +159,41 @@ var Storage = class {
159
159
  debug$1("unique results %o", uniqueResults.length);
160
160
  return uniqueResults;
161
161
  }
162
+ /** Cumulative, stable prefixes for Search v1; consumers stop once their page is full. */
163
+ async *searchPages(options) {
164
+ options.abort.signal.throwIfAborted();
165
+ const local = await this.getCachedPackages({
166
+ ...options.query,
167
+ from: 0,
168
+ size: _verdaccio_search.SEARCH_MAX_CANDIDATES
169
+ });
170
+ const merged = /* @__PURE__ */ new Map();
171
+ let candidates = 0;
172
+ const append = (items) => {
173
+ options.abort.signal.throwIfAborted();
174
+ candidates += items.length;
175
+ if (candidates > _verdaccio_search.SEARCH_MAX_CANDIDATES) throw _verdaccio_core.errorUtils.getServiceUnavailable("search pagination budget exhausted");
176
+ for (const item of items) {
177
+ if (!_verdaccio_core.pkgUtils.isValidVersion(item?.package?.version)) {
178
+ this.logger.warn({
179
+ name: item?.package?.name,
180
+ version: item?.package?.version
181
+ }, "ignoring invalid search version for @{name}: @{version}");
182
+ continue;
183
+ }
184
+ const previous = merged.get(item.package.name);
185
+ if (!previous || require_versions_utils.isNewerVersion(item.package.version, previous.package.version)) merged.set(item.package.name, item);
186
+ }
187
+ return [...merged.values()];
188
+ };
189
+ append(local);
190
+ let hadRound = false;
191
+ for await (const round of this.searchService.searchPages(options)) {
192
+ hadRound = true;
193
+ yield append(round);
194
+ }
195
+ if (!hadRound) yield [...merged.values()];
196
+ }
162
197
  async getTarballFromUpstream(name, filename, { signal }) {
163
198
  this.logger.info({
164
199
  name,
@@ -174,7 +209,7 @@ var Storage = class {
174
209
  const cachedDistFile = cachedManifest !== null ? require_storage_utils.lookupDistFile(cachedManifest, filename) : null;
175
210
  if (cachedDistFile !== null && typeof cachedDistFile.url === "string") {
176
211
  debug$1("dist file found, using it %o", cachedDistFile.url);
177
- return this.fetchTarballFromDistFile(name, filename, cachedDistFile, { signal });
212
+ return this.fetchTarballFromDistFile(name, filename, cachedDistFile, cachedManifest, { signal });
178
213
  } else {
179
214
  debug$1("dist file not found, proceed update upstream");
180
215
  const [updatedManifest] = await this.syncUplinksMetadata(name, cachedManifest, { uplinksLook: true });
@@ -183,15 +218,19 @@ var Storage = class {
183
218
  debug$1("remote tarball not found");
184
219
  throw _verdaccio_core.errorUtils.getNotFound(_verdaccio_core.API_ERROR.NO_SUCH_FILE);
185
220
  }
186
- return this.fetchTarballFromDistFile(name, filename, distFile, { signal });
221
+ return this.fetchTarballFromDistFile(name, filename, distFile, updatedManifest, { signal });
187
222
  }
188
223
  }
189
- fetchTarballFromDistFile(name, filename, distFile, { signal }) {
224
+ fetchTarballFromDistFile(name, filename, distFile, origin, { signal }) {
190
225
  let current_length = 0;
191
226
  let expected_length;
192
227
  let uplinkStatusError = null;
193
228
  const passThroughRemoteStream = new node_stream.PassThrough();
194
- const proxy = this.getUpLinkForDistFile(name, distFile);
229
+ const proxy = this.getUpLinkForDistFile(name, filename, distFile, origin);
230
+ if (proxy === null) {
231
+ setImmediate(() => passThroughRemoteStream.emit("error", _verdaccio_core.errorUtils.getNotFound(_verdaccio_core.API_ERROR.NO_SUCH_FILE)));
232
+ return passThroughRemoteStream;
233
+ }
195
234
  const remoteStream = proxy.fetchTarball(distFile.url, { throwHttpErrors: false });
196
235
  const readRemoteBody = () => new Promise((resolve) => {
197
236
  const chunks = [];
@@ -296,7 +335,14 @@ var Storage = class {
296
335
  const localStream = await this.getLocalTarball(name, filename, { signal });
297
336
  forwardContentLength(localStream);
298
337
  localStream.on("open", async () => {
299
- await (0, node_stream_promises.pipeline)(localStream, localTarballStream, { signal });
338
+ try {
339
+ await (0, node_stream_promises.pipeline)(localStream, localTarballStream, { signal });
340
+ } catch (err) {
341
+ this.logger.warn({
342
+ err,
343
+ filename
344
+ }, "error streaming local tarball @{filename}: @{err.message}");
345
+ }
300
346
  });
301
347
  localStream.on("error", (err) => {
302
348
  if (err.code === require_storage_utils.STORAGE.NO_SUCH_FILE_ERROR || err.code === _verdaccio_core.HTTP_STATUS.NOT_FOUND) this.getTarballFromUpstream(name, filename, { signal }).then((uplinkStream) => {
@@ -628,18 +674,45 @@ var Storage = class {
628
674
  return newData;
629
675
  });
630
676
  }
631
- getUpLinkForDistFile(pkgName, distFile) {
632
- if (distFile.registry && this.uplinks[distFile.registry]) {
633
- debug$1("tarball is served by the recorded uplink %o", distFile.registry);
634
- return this.uplinks[distFile.registry];
635
- }
677
+ /**
678
+ * Resolve the uplink to fetch a distfile url from, or null when the fetch
679
+ * must be refused.
680
+ *
681
+ * Off-uplink urls on a locally published package are not fetched: the
682
+ * client's `dist.tarball` is stored verbatim, and fetching it would be SSRF
683
+ * (and would attach uplink credentials when only one uplink is configured).
684
+ * Uplink-synced packages may still use an autogenerated proxy without
685
+ * credentials for CDN-hosted tarballs.
686
+ */
687
+ getUpLinkForDistFile(pkgName, filename, distFile, origin) {
636
688
  const candidates = [];
637
689
  for (const uplinkName in this.uplinks) if ((0, _verdaccio_config.hasProxyTo)(pkgName, uplinkName, this.config.packages)) candidates.push(this.uplinks[uplinkName]);
638
690
  let uplink = null;
639
- for (const candidate of candidates) if (require_storage_utils.uplinkServesUrl(candidate.url, distFile.url)) uplink = candidate;
640
- if (uplink === null && candidates.length === 1) uplink = candidates[0];
691
+ if (distFile.registry && this.uplinks[distFile.registry]) {
692
+ const recorded = this.uplinks[distFile.registry];
693
+ if (require_storage_utils.uplinkServesUrl(recorded.url, distFile.url)) {
694
+ debug$1("tarball is served by the recorded uplink %o", distFile.registry);
695
+ uplink = recorded;
696
+ }
697
+ }
698
+ if (uplink === null) {
699
+ for (const candidate of candidates) if (require_storage_utils.uplinkServesUrl(candidate.url, distFile.url)) uplink = candidate;
700
+ }
641
701
  if (uplink === null) {
642
- debug$1("upstream not found, creating one for %o", pkgName);
702
+ if (!Boolean(origin?._distfiles?.[filename])) {
703
+ debug$1("refusing off-uplink tarball fetch for locally published %o (%o)", pkgName, distFile.url);
704
+ this.logger.warn({
705
+ name: pkgName,
706
+ filename,
707
+ url: distFile.url
708
+ }, "refused off-uplink tarball fetch for @{name} (@{url})");
709
+ return null;
710
+ }
711
+ debug$1("upstream not found, creating one for %o without uplink credentials", pkgName);
712
+ this.logger.http({
713
+ name: pkgName,
714
+ url: distFile.url
715
+ }, "serving off-uplink tarball via autogenerated proxy for @{name} (@{url})");
643
716
  uplink = new _verdaccio_proxy.ProxyStorage(`verdaccio-${pkgName}`, {
644
717
  url: distFile.url,
645
718
  cache: true
@@ -887,10 +960,24 @@ var Storage = class {
887
960
  });
888
961
  else {
889
962
  const localStorageWriteStream = await storage.writeTarball(filename, { signal });
963
+ let uploadFailed = false;
890
964
  localStorageWriteStream.on("open", async () => {
891
- await (0, node_stream_promises.pipeline)(uploadStream, transformHash, localStorageWriteStream, { signal });
965
+ try {
966
+ await (0, node_stream_promises.pipeline)(uploadStream, transformHash, localStorageWriteStream, { signal });
967
+ } catch (err) {
968
+ uploadFailed = true;
969
+ this.logger.warn({
970
+ err,
971
+ filename,
972
+ pkgName
973
+ }, "error uploading tarball @{filename} for @{pkgName}: @{err.message}");
974
+ }
892
975
  });
893
976
  localStorageWriteStream.on("close", async () => {
977
+ if (uploadFailed) {
978
+ debug$1("skip metadata update for failed upload %o for %o", filename, pkgName);
979
+ return;
980
+ }
894
981
  try {
895
982
  debug$1("uploaded tarball %o for %o", filename, pkgName);
896
983
  await this.updatePackage(pkgName, async (data) => {
@@ -907,6 +994,7 @@ var Storage = class {
907
994
  }
908
995
  });
909
996
  localStorageWriteStream.on("error", async (err) => {
997
+ uploadFailed = true;
910
998
  uploadStream.emit("error", err);
911
999
  });
912
1000
  }