@verdaccio/store 9.0.0-next-9.35 → 9.0.0-next-9.37
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/build/filter-pipeline.js.map +1 -1
- package/build/filter-pipeline.mjs.map +1 -1
- package/build/lib/storage-utils.js.map +1 -1
- package/build/lib/versions-utils.js.map +1 -1
- package/build/local-storage.js.map +1 -1
- package/build/local-storage.mjs.map +1 -1
- package/build/stage-storage.d.ts +3 -1
- package/build/stage-storage.js +4 -1
- package/build/stage-storage.js.map +1 -1
- package/build/stage-storage.mjs +4 -1
- package/build/stage-storage.mjs.map +1 -1
- package/build/storage.d.ts +2 -0
- package/build/storage.js +38 -14
- package/build/storage.js.map +1 -1
- package/build/storage.mjs +38 -14
- package/build/storage.mjs.map +1 -1
- package/build/type.d.ts +1 -0
- package/package.json +16 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-pipeline.js","names":[],"sources":["../src/filter-pipeline.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { PLUGIN_CATEGORY, PLUGIN_PREFIX, pluginUtils as pluginSanity } from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport type { Config, Logger, Manifest } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:filter-pipeline');\n\nexport type Filters = pluginUtils.ManifestFilter<Config>[];\n\n/**\n * Apply all loaded filter plugins to a manifest in sequence.\n * Each filter receives the output of the previous one.\n */\nexport async function applyManifestFilters(\n manifest: Manifest,\n filters: pluginUtils.ManifestFilter<unknown>[],\n logger: Logger\n): Promise<[Manifest, any[]]> {\n if (!filters || filters.length === 0) {\n return [manifest, []];\n }\n\n const filterPluginErrors: any[] = [];\n let filteredManifest = { ...manifest };\n for (const filter of filters) {\n try {\n filteredManifest = await filter.filter_metadata(filteredManifest);\n } catch (err: any) {\n logger.error({ err }, 'filter has failed: @{err.message}');\n filterPluginErrors.push(err);\n }\n }\n return [filteredManifest, filterPluginErrors];\n}\n\n/**\n * Load filter plugins using the same loader Verdaccio uses at startup.\n */\nexport async function loadFilterPlugins(config: Config, logger: Logger): Promise<Filters> {\n const filters = await asyncLoadPlugin<pluginUtils.ManifestFilter<unknown>>(\n config.filters,\n {\n config,\n logger,\n },\n pluginSanity.filterSanityCheck,\n false,\n config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.FILTER\n );\n debug('filters available %o', filters.length);\n return filters;\n}\n"],"mappings":";;;;;;AAOA,
|
|
1
|
+
{"version":3,"file":"filter-pipeline.js","names":["debug","buildDebug","asyncLoadPlugin","pluginSanity","PLUGIN_PREFIX","PLUGIN_CATEGORY"],"sources":["../src/filter-pipeline.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { PLUGIN_CATEGORY, PLUGIN_PREFIX, pluginUtils as pluginSanity } from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport type { Config, Logger, Manifest } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:filter-pipeline');\n\nexport type Filters = pluginUtils.ManifestFilter<Config>[];\n\n/**\n * Apply all loaded filter plugins to a manifest in sequence.\n * Each filter receives the output of the previous one.\n */\nexport async function applyManifestFilters(\n manifest: Manifest,\n filters: pluginUtils.ManifestFilter<unknown>[],\n logger: Logger\n): Promise<[Manifest, any[]]> {\n if (!filters || filters.length === 0) {\n return [manifest, []];\n }\n\n const filterPluginErrors: any[] = [];\n let filteredManifest = { ...manifest };\n for (const filter of filters) {\n try {\n filteredManifest = await filter.filter_metadata(filteredManifest);\n } catch (err: any) {\n logger.error({ err }, 'filter has failed: @{err.message}');\n filterPluginErrors.push(err);\n }\n }\n return [filteredManifest, filterPluginErrors];\n}\n\n/**\n * Load filter plugins using the same loader Verdaccio uses at startup.\n */\nexport async function loadFilterPlugins(config: Config, logger: Logger): Promise<Filters> {\n const filters = await asyncLoadPlugin<pluginUtils.ManifestFilter<unknown>>(\n config.filters,\n {\n config,\n logger,\n },\n pluginSanity.filterSanityCheck,\n false,\n config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.FILTER\n );\n debug('filters available %o', filters.length);\n return filters;\n}\n"],"mappings":";;;;;;AAOA,IAAMA,WAAAA,GAAQC,MAAAA,QAAAA,CAAW,mCAAmC;;;;;AAQ5D,eAAsB,qBACpB,UACA,SACA,QAC4B;CAC5B,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC,OAAO,CAAC,UAAU,CAAC,CAAC;CAGtB,MAAM,qBAA4B,CAAC;CACnC,IAAI,mBAAmB,EAAE,GAAG,SAAS;CACrC,KAAK,MAAM,UAAU,SACnB,IAAI;EACF,mBAAmB,MAAM,OAAO,gBAAgB,gBAAgB;CAClE,SAAS,KAAU;EACjB,OAAO,MAAM,EAAE,IAAI,GAAG,mCAAmC;EACzD,mBAAmB,KAAK,GAAG;CAC7B;CAEF,OAAO,CAAC,kBAAkB,kBAAkB;AAC9C;;;;AAKA,eAAsB,kBAAkB,QAAgB,QAAkC;CACxF,MAAM,UAAU,OAAA,GAAMC,mBAAAA,gBAAAA,CACpB,OAAO,SACP;EACE;EACA;CACF,GACAC,gBAAAA,YAAa,mBACb,OACA,OAAO,QAAQ,gBAAgBC,gBAAAA,eAC/BC,gBAAAA,gBAAgB,MAClB;CACA,QAAM,wBAAwB,QAAQ,MAAM;CAC5C,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-pipeline.mjs","names":[],"sources":["../src/filter-pipeline.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { PLUGIN_CATEGORY, PLUGIN_PREFIX, pluginUtils as pluginSanity } from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport type { Config, Logger, Manifest } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:filter-pipeline');\n\nexport type Filters = pluginUtils.ManifestFilter<Config>[];\n\n/**\n * Apply all loaded filter plugins to a manifest in sequence.\n * Each filter receives the output of the previous one.\n */\nexport async function applyManifestFilters(\n manifest: Manifest,\n filters: pluginUtils.ManifestFilter<unknown>[],\n logger: Logger\n): Promise<[Manifest, any[]]> {\n if (!filters || filters.length === 0) {\n return [manifest, []];\n }\n\n const filterPluginErrors: any[] = [];\n let filteredManifest = { ...manifest };\n for (const filter of filters) {\n try {\n filteredManifest = await filter.filter_metadata(filteredManifest);\n } catch (err: any) {\n logger.error({ err }, 'filter has failed: @{err.message}');\n filterPluginErrors.push(err);\n }\n }\n return [filteredManifest, filterPluginErrors];\n}\n\n/**\n * Load filter plugins using the same loader Verdaccio uses at startup.\n */\nexport async function loadFilterPlugins(config: Config, logger: Logger): Promise<Filters> {\n const filters = await asyncLoadPlugin<pluginUtils.ManifestFilter<unknown>>(\n config.filters,\n {\n config,\n logger,\n },\n pluginSanity.filterSanityCheck,\n false,\n config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.FILTER\n );\n debug('filters available %o', filters.length);\n return filters;\n}\n"],"mappings":";;;;AAOA,IAAM,QAAQ,WAAW,mCAAmC;;;;;AAQ5D,eAAsB,qBACpB,UACA,SACA,QAC4B;CAC5B,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC,OAAO,CAAC,UAAU,CAAC,CAAC;CAGtB,MAAM,qBAA4B,CAAC;CACnC,IAAI,mBAAmB,EAAE,GAAG,SAAS;CACrC,KAAK,MAAM,UAAU,SACnB,IAAI;EACF,mBAAmB,MAAM,OAAO,gBAAgB,gBAAgB;CAClE,SAAS,KAAU;EACjB,OAAO,MAAM,EAAE,IAAI,GAAG,mCAAmC;EACzD,mBAAmB,KAAK,GAAG;CAC7B;CAEF,OAAO,CAAC,kBAAkB,kBAAkB;AAC9C;;;;AAKA,eAAsB,kBAAkB,QAAgB,QAAkC;CACxF,MAAM,UAAU,MAAM,gBACpB,OAAO,SACP;EACE;EACA;CACF,
|
|
1
|
+
{"version":3,"file":"filter-pipeline.mjs","names":["pluginSanity"],"sources":["../src/filter-pipeline.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { PLUGIN_CATEGORY, PLUGIN_PREFIX, pluginUtils as pluginSanity } from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport type { Config, Logger, Manifest } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:filter-pipeline');\n\nexport type Filters = pluginUtils.ManifestFilter<Config>[];\n\n/**\n * Apply all loaded filter plugins to a manifest in sequence.\n * Each filter receives the output of the previous one.\n */\nexport async function applyManifestFilters(\n manifest: Manifest,\n filters: pluginUtils.ManifestFilter<unknown>[],\n logger: Logger\n): Promise<[Manifest, any[]]> {\n if (!filters || filters.length === 0) {\n return [manifest, []];\n }\n\n const filterPluginErrors: any[] = [];\n let filteredManifest = { ...manifest };\n for (const filter of filters) {\n try {\n filteredManifest = await filter.filter_metadata(filteredManifest);\n } catch (err: any) {\n logger.error({ err }, 'filter has failed: @{err.message}');\n filterPluginErrors.push(err);\n }\n }\n return [filteredManifest, filterPluginErrors];\n}\n\n/**\n * Load filter plugins using the same loader Verdaccio uses at startup.\n */\nexport async function loadFilterPlugins(config: Config, logger: Logger): Promise<Filters> {\n const filters = await asyncLoadPlugin<pluginUtils.ManifestFilter<unknown>>(\n config.filters,\n {\n config,\n logger,\n },\n pluginSanity.filterSanityCheck,\n false,\n config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.FILTER\n );\n debug('filters available %o', filters.length);\n return filters;\n}\n"],"mappings":";;;;AAOA,IAAM,QAAQ,WAAW,mCAAmC;;;;;AAQ5D,eAAsB,qBACpB,UACA,SACA,QAC4B;CAC5B,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC,OAAO,CAAC,UAAU,CAAC,CAAC;CAGtB,MAAM,qBAA4B,CAAC;CACnC,IAAI,mBAAmB,EAAE,GAAG,SAAS;CACrC,KAAK,MAAM,UAAU,SACnB,IAAI;EACF,mBAAmB,MAAM,OAAO,gBAAgB,gBAAgB;CAClE,SAAS,KAAU;EACjB,OAAO,MAAM,EAAE,IAAI,GAAG,mCAAmC;EACzD,mBAAmB,KAAK,GAAG;CAC7B;CAEF,OAAO,CAAC,kBAAkB,kBAAkB;AAC9C;;;;AAKA,eAAsB,kBAAkB,QAAgB,QAAkC;CACxF,MAAM,UAAU,MAAM,gBACpB,OAAO,SACP;EACE;EACA;CACF,GACAA,YAAa,mBACb,OACA,OAAO,QAAQ,gBAAgB,eAC/B,gBAAgB,MAClB;CACA,MAAM,wBAAwB,QAAQ,MAAM;CAC5C,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-utils.js","names":[],"sources":["../../src/lib/storage-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil, trim } from 'lodash-es';\nimport semver from 'semver';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport {\n ANONYMOUS_USER,\n API_ERROR,\n DIST_TAGS,\n HTTP_STATUS,\n MAINTAINERS,\n USERS,\n cryptoUtils,\n errorUtils,\n pkgUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport type {\n Author,\n DistFile,\n GenericBody,\n Manifest,\n Person,\n ReadmeOptions,\n Version,\n} from '@verdaccio/types';\n\nimport { sortVersionsAndFilterInvalid } from './versions-utils';\n\nconst debug = buildDebug('verdaccio:storage:utils');\n\nexport const STORAGE = {\n PACKAGE_FILE_NAME: 'package.json',\n FILE_EXIST_ERROR: 'EEXISTS',\n NO_SUCH_FILE_ERROR: 'ENOENT',\n DEFAULT_REVISION: '0-0000000000000000',\n};\n\n/**\n * Create a new package in the storage, return a boilerplate package\n * @param name package name\n * @returns {Manifest}\n */\nexport function generatePackageTemplate(name: string): Manifest {\n return {\n // standard things\n name,\n versions: {},\n time: {},\n [USERS]: {},\n [DIST_TAGS]: {},\n [MAINTAINERS]: [],\n _uplinks: {},\n _distfiles: {},\n _attachments: {},\n _rev: '',\n };\n}\n\n/**\n * Normalize package properties, tags, revision id.\n * @param {Object} pkg package reference.\n */\nexport function normalizePackage(pkg: Manifest): Manifest {\n const pkgProperties = ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time'];\n\n pkgProperties.forEach((key): void => {\n const pkgProp = pkg[key];\n\n if (isNil(pkgProp) || validationUtils.isObject(pkgProp) === false) {\n pkg[key] = {};\n }\n });\n\n if (typeof pkg?._rev !== 'string') {\n pkg._rev = STORAGE.DEFAULT_REVISION;\n }\n\n if (typeof pkg?._id !== 'string') {\n pkg._id = pkg.name;\n }\n\n // normalize dist-tags\n return normalizeDistTags(pkg);\n}\n\nexport function generateRevision(rev: string): string {\n const _rev = rev.split('-');\n\n return (+_rev[0] || 0) + 1 + '-' + cryptoUtils.generateRandomHexString();\n}\n\nexport function getLatestReadme(pkg: Manifest): string {\n const versions = pkg['versions'] || {};\n const distTags = pkg[DIST_TAGS] || {};\n // FIXME: here is a bit tricky add the types\n const latestVersion: Version | any = distTags['latest'] ? versions[distTags['latest']] || {} : {};\n let readme = trim(pkg.readme || latestVersion.readme || '');\n if (readme) {\n return readme;\n }\n\n // In case of empty readme - trying to get ANY readme in the following order:\n // 'next','beta','alpha','test','dev','canary'\n const readmeDistTagsPriority = ['next', 'beta', 'alpha', 'test', 'dev', 'canary'];\n readmeDistTagsPriority.forEach(function (tag): string | void {\n if (readme) {\n return readme;\n }\n const version: Version | any = distTags[tag] ? versions[distTags[tag]] || {} : {};\n readme = trim(version.readme || readme);\n });\n return readme;\n}\n\n/**\n * Cleanup readme from package version\n *\n * By default, we don't keep readmes for package versions, only one readme per package.\n * Using publish.keep_readmes you can override this behavior and keep all readmes\n * or only readmes for tagged versions.\n */\nexport function cleanUpReadme(\n version: Version,\n distTags?: GenericBody,\n keepReadmes?: ReadmeOptions\n): Version {\n if (keepReadmes === 'all') {\n return version;\n } else if (keepReadmes === 'tagged') {\n if (distTags && Object.values(distTags).includes(version.version)) {\n return version;\n }\n }\n\n if (isNil(version) === false) {\n version.readme = '';\n }\n\n return version;\n}\n\nexport const WHITELIST = [\n '_rev',\n 'name',\n 'versions',\n 'dist-tags',\n 'readme',\n 'time',\n '_id',\n 'users',\n 'maintainers',\n];\n\nexport function cleanUpLinksRef(result: Manifest, keepUpLinkData?: boolean): Manifest {\n const propertyToKeep = [...WHITELIST];\n if (keepUpLinkData === true) {\n propertyToKeep.push('_uplinks');\n }\n\n for (const i in result) {\n if (propertyToKeep.indexOf(i) === -1) {\n // Remove sections like '_uplinks' from response\n delete result[i];\n }\n }\n\n return result;\n}\n\n// TODO: move to abstract storage\n// @deprecated use abstract.storage.ts:checkPackageRemote\nexport function checkPackageRemote(\n name: string,\n isAllowPublishOffline: boolean,\n syncMetadata: Function\n): Promise<any> {\n return new Promise<void>((resolve, reject): void => {\n syncMetadata(name, null, {}, (err, packageJsonLocal, upLinksErrors): void => {\n // something weird\n if (err && err.status !== HTTP_STATUS.NOT_FOUND) {\n return reject(err);\n }\n\n // checking package exist already\n if (isNil(packageJsonLocal) === false) {\n return reject(errorUtils.getConflict(API_ERROR.PACKAGE_EXIST));\n }\n\n for (let errorItem = 0; errorItem < upLinksErrors.length; errorItem++) {\n // checking error\n // if uplink fails with a status other than 404, we report failure\n if (isNil(upLinksErrors[errorItem][0]) === false) {\n if (upLinksErrors[errorItem][0].status !== HTTP_STATUS.NOT_FOUND) {\n if (isAllowPublishOffline) {\n return resolve();\n }\n\n return reject(errorUtils.getServiceUnavailable(API_ERROR.UPLINK_OFFLINE_PUBLISH));\n }\n }\n }\n\n return resolve();\n });\n });\n}\n\n// @deprecated use export function mergeUplinkTimeIntoLocalNext\nexport function mergeUplinkTimeIntoLocal(localMetadata: Manifest, remoteMetadata: Manifest): any {\n if ('time' in remoteMetadata) {\n return Object.assign({}, localMetadata.time, remoteMetadata.time);\n }\n\n return localMetadata.time;\n}\n\nexport function mergeUplinkTimeIntoLocalNext(\n cacheManifest: Manifest,\n remoteManifest: Manifest\n): Manifest {\n if ('time' in remoteManifest) {\n // remote override cache time conflicts\n return { ...cacheManifest, time: { ...cacheManifest.time, ...remoteManifest.time } };\n }\n\n return cacheManifest;\n}\n\nexport function updateUpLinkMetadata(uplinkName: string, manifest: Manifest, etag: string) {\n const _uplinks = {\n ...manifest._uplinks,\n [uplinkName]: {\n etag,\n fetched: Date.now(),\n },\n };\n return {\n ...manifest,\n _uplinks,\n };\n}\n\nexport function prepareSearchPackage(data: Manifest): any {\n const latest = pkgUtils.getLatest(data);\n\n if (latest && data.versions[latest]) {\n const version: Version = data.versions[latest];\n const versions: any = { [latest]: 'latest' };\n const pkg: any = {\n name: version.name,\n description: version.description,\n [DIST_TAGS]: { latest },\n maintainers: version.maintainers || [version.author].filter(Boolean),\n author: version.author,\n repository: version.repository,\n readmeFilename: version.readmeFilename || '',\n homepage: version.homepage,\n keywords: version.keywords,\n bugs: version.bugs,\n license: version.license,\n // time: {\n // modified: time,\n // },\n versions,\n };\n\n return pkg;\n }\n}\n\n/** Maintainer names copied from an uplink. Never a local account. */\nexport const UPSTREAM_MAINTAINER_PREFIX = '$uplink:';\n\nexport function scopeUpstreamMaintainers(maintainers: Person[], uplinkName: string): Person[] {\n const origin = uplinkName.length > 0 ? uplinkName : 'remote';\n return maintainers.map((maintainer) => {\n if (typeof maintainer === 'string') {\n return scopeUpstreamMaintainerName(origin, maintainer);\n }\n const name = typeof maintainer?.name === 'string' ? maintainer.name : '';\n return {\n ...maintainer,\n name: scopeUpstreamMaintainerName(origin, name),\n };\n });\n}\n\nfunction scopeUpstreamMaintainerName(uplinkName: string, name: string): string {\n if (name.startsWith(UPSTREAM_MAINTAINER_PREFIX)) {\n return name;\n }\n return `${UPSTREAM_MAINTAINER_PREFIX}${uplinkName}:${name}`;\n}\n\nexport function maintainerAllowsLocalUser(\n maintainer: Person,\n username: string | undefined\n): boolean {\n const name = typeof maintainer === 'string' ? maintainer : maintainer?.name;\n // reserved uplink identities must not authorize the local user who registered that string\n if (typeof name === 'string' && name.startsWith(UPSTREAM_MAINTAINER_PREFIX)) {\n return false;\n }\n return name === username || name === ANONYMOUS_USER;\n}\n\n/**\n * Function gets a local info and an info from uplinks and tries to merge it\n exported for unit tests only.\n * @param {*} cacheManifest\n * @param {*} remoteManifest\n */\nexport function mergeVersions(cacheManifest: Manifest, remoteManifest: Manifest): Manifest {\n let _cacheManifest = { ...cacheManifest };\n const { versions, time } = remoteManifest;\n // copy new versions to a cache\n // NOTE: if a certain version was updated, we can't refresh it reliably\n // however, we can refresh the deprecated status\n for (const i in versions) {\n if (typeof _cacheManifest.versions[i] === 'undefined') {\n _cacheManifest.versions[i] = versions[i];\n } else if (versions[i].deprecated) {\n _cacheManifest.versions[i].deprecated = versions[i].deprecated;\n }\n }\n\n for (const i in time) {\n if (typeof _cacheManifest.time[i] === 'undefined') {\n _cacheManifest.time[i] = time[i];\n }\n }\n\n for (const distTag in remoteManifest[DIST_TAGS]) {\n if (_cacheManifest[DIST_TAGS][distTag] !== remoteManifest[DIST_TAGS][distTag]) {\n if (\n !_cacheManifest[DIST_TAGS][distTag] ||\n semver.lte(_cacheManifest[DIST_TAGS][distTag], remoteManifest[DIST_TAGS][distTag])\n ) {\n _cacheManifest[DIST_TAGS][distTag] = remoteManifest[DIST_TAGS][distTag];\n }\n if (\n distTag === 'latest' &&\n _cacheManifest[DIST_TAGS][distTag] === remoteManifest[DIST_TAGS][distTag]\n ) {\n // NOTE: this override the latest publish readme from local cache with\n // the remote one\n _cacheManifest = { ..._cacheManifest, readme: remoteManifest.readme };\n }\n }\n }\n return _cacheManifest;\n}\n\n/**\n * Normalize dist-tags.\n *\n * There is a legacy behaviour where the dist-tags could be an array, in such\n * case, the array is orderded and the highest version becames the\n * latest.\n *\n * The dist-tag tags must be plain strigs, if the latest is empty (for whatever reason) is\n * normalized to be the highest version available.\n *\n * This function cleans up every invalid version on dist-tags, but does not remove\n * invalid versions from the manifest.\n *\n * @param {*} data\n */\nexport function normalizeDistTags(manifest: Manifest): Manifest {\n let sorted;\n // handle missing latest dist-tag\n if (!manifest[DIST_TAGS].latest) {\n // if there is no latest tag, set the highest known version based on semver sort\n sorted = sortVersionsAndFilterInvalid(Object.keys(manifest.versions));\n if (sorted?.length) {\n // get the highest published version\n manifest[DIST_TAGS].latest = sorted.pop();\n }\n }\n\n for (const tag in manifest[DIST_TAGS]) {\n // deprecated (will be removed un future majors)\n // this should not happen, tags should be plain strings, legacy fallback\n if (Array.isArray(manifest[DIST_TAGS][tag])) {\n if (manifest[DIST_TAGS][tag].length) {\n // sort array\n // FIXME: this is clearly wrong, we need to research why this is like this.\n // @ts-ignore\n sorted = sortVersionsAndFilterInvalid(manifest[DIST_TAGS][tag]);\n if (sorted.length) {\n // use highest version based on semver sort\n manifest[DIST_TAGS][tag] = sorted.pop();\n }\n } else {\n delete manifest[DIST_TAGS][tag];\n }\n } else if (typeof manifest[DIST_TAGS][tag] === 'string') {\n if (!semver.parse(manifest[DIST_TAGS][tag], true)) {\n // if the version is invalid, delete the dist-tag entry\n delete manifest[DIST_TAGS][tag];\n }\n }\n }\n\n return manifest;\n}\n\nexport function hasDeprecatedVersions(pkgInfo: Manifest): boolean {\n const { versions } = pkgInfo;\n for (const version in versions) {\n if (Object.prototype.hasOwnProperty.call(versions[version], 'deprecated')) {\n return true;\n }\n }\n return false;\n}\n\nexport function isDeprecatedManifest(manifest: Manifest): boolean {\n return (\n hasDeprecatedVersions(manifest) &&\n (typeof manifest._attachments === 'undefined' ||\n Object.keys(manifest._attachments).length === 0)\n );\n}\n\nexport function mapManifestToSearchPackageBody(\n pkg: Manifest,\n _searchItem: searchUtils.SearchItem\n): searchUtils.SearchPackageBody {\n const latest = pkgUtils.getLatest(pkg);\n const version: Version = pkg.versions[latest];\n // packument maintainers carry `name`, but the npm search API contract uses\n // `username` and the npm CLI crashes on entries without it — emit both\n const maintainers = Array.isArray(version.maintainers)\n ? version.maintainers.map((maintainer: any) =>\n typeof maintainer === 'string'\n ? { username: maintainer, email: '' }\n : {\n ...maintainer,\n username: maintainer?.username ?? maintainer?.name ?? '',\n email: maintainer?.email ?? '',\n }\n )\n : [];\n // npmjs fills `publisher` with the user who published the version; use\n // `_npmUser` when the client provided it and fall back to the first\n // maintainer (verdaccio sets it to the publishing user), otherwise the\n // npm CLI renders \"published by ???\"\n const publisher = version._npmUser?.name\n ? { username: version._npmUser.name, email: version._npmUser.email ?? '' }\n : (maintainers[0] ?? {});\n const links: NonNullable<searchUtils.SearchPackageBody['links']> = {};\n const repository =\n typeof version.repository === 'string' ? version.repository : version.repository?.url;\n const bugs = typeof version.bugs === 'string' ? version.bugs : version.bugs?.url;\n if (version.homepage) {\n links.homepage = version.homepage;\n }\n if (repository) {\n links.repository = repository;\n }\n if (bugs) {\n links.bugs = bugs;\n }\n const result: searchUtils.SearchPackageBody = {\n name: version.name,\n description: version.description,\n version: latest,\n license: version.license,\n keywords: version.keywords,\n date: pkg.time[latest],\n // FIXME: type\n author: version.author as any,\n publisher,\n maintainers,\n };\n\n if (Object.keys(links).length > 0) {\n result.links = links;\n }\n\n return result;\n}\n\nexport function normalizeContributors(contributors: Author[]): Author[] {\n if (isNil(contributors)) {\n return [];\n } else if (contributors && !Array.isArray(contributors)) {\n return [contributors];\n } else if (typeof contributors === 'string') {\n return [\n {\n name: contributors,\n },\n ];\n }\n\n return contributors;\n}\n\n/**\n * Whether the tarball url path ends with the given file name (any query\n * string or fragment is ignored, like the path parsing in\n * `tarballUtils.extractTarballFromUrl`, which creates the `_distfiles`\n * keys — if that parsing ever changes, this must change with it).\n */\nexport function tarballMatchesFilename(tarball: string, filename: string): boolean {\n return tarball.split(/[?#]/)[0].endsWith(`/${filename}`);\n}\n\n/**\n * Build a distfile record from a version's dist metadata when its tarball\n * url matches the given file name.\n */\nexport function distFileFromVersion(\n version: Version | undefined,\n filename: string\n): DistFile | null {\n const dist = version?.dist;\n\n if (dist?.tarball && tarballMatchesFilename(dist.tarball, filename)) {\n return { url: dist.tarball, sha: dist.shasum } as DistFile;\n }\n\n return null;\n}\n\n/**\n * Resolve the distfile record for a tarball filename. Prefers the\n * `_distfiles` bookkeeping, but falls back to the version metadata when the\n * record is missing (storages written by other verdaccio versions cache\n * versions without their distfile records, which made those tarballs\n * permanently unavailable).\n *\n * Note: no extension pre-filter before the scan on purpose — GitHub\n * Packages advertises tarball urls ending in a bare sha-256 digest.\n * @param manifest cached manifest\n * @param filename tarball file name, eg. pkg-1.0.0.tgz\n */\nexport function lookupDistFile(manifest: Manifest, filename: string): DistFile | null {\n const distFile = manifest?._distfiles?.[filename];\n if (isNil(distFile) === false) {\n return distFile;\n }\n\n const versions = manifest?.versions ?? {};\n\n // tarballs are conventionally named <basename>-<version>.tgz, so the\n // version can usually be derived from the filename without scanning\n // manifests that carry thousands of versions\n const packageBasename = manifest?.name?.replace(/^.*\\//, '');\n if (packageBasename && filename.startsWith(`${packageBasename}-`) && filename.endsWith('.tgz')) {\n const versionId = filename.slice(packageBasename.length + 1, -'.tgz'.length);\n const distFromVersion = distFileFromVersion(versions[versionId], filename);\n\n if (distFromVersion) {\n debug('distfile record missing for %o, using version %o dist', filename, versionId);\n return distFromVersion;\n }\n }\n\n // unconventional tarball names: fall back to a scan\n for (const versionId in versions) {\n const distFromVersion = distFileFromVersion(versions[versionId], filename);\n\n if (distFromVersion) {\n debug('distfile record missing for %o, using version %o dist', filename, versionId);\n return distFromVersion;\n }\n }\n\n debug('no distfile record or version dist found for %o', filename);\n return null;\n}\n\n/**\n * Whether an uplink's configured url serves the given tarball url: same\n * protocol, same host and the uplink path is a prefix of the tarball path.\n * WHATWG URL normalizes default ports (https://h:443 === https://h).\n */\nexport function uplinkServesUrl(uplinkUrl: URL, tarballUrl: string): boolean {\n try {\n const parsed = new URL(tarballUrl);\n // Compare on a path segment boundary: an uplink at /registry must not\n // match tarballs under /registry2/... (wrong-uplink selection would send\n // the uplink's credentials to a sibling path).\n const basePath = uplinkUrl.pathname.endsWith('/')\n ? uplinkUrl.pathname\n : `${uplinkUrl.pathname}/`;\n return (\n parsed.protocol === uplinkUrl.protocol &&\n parsed.host === uplinkUrl.host &&\n parsed.pathname.startsWith(basePath)\n );\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage-utils.js","names":["debug","buildDebug","USERS","DIST_TAGS","MAINTAINERS","isNil","validationUtils","cryptoUtils","trim","HTTP_STATUS","errorUtils","API_ERROR","pkgUtils","ANONYMOUS_USER","sortVersionsAndFilterInvalid"],"sources":["../../src/lib/storage-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { isNil, trim } from 'lodash-es';\nimport semver from 'semver';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport {\n ANONYMOUS_USER,\n API_ERROR,\n DIST_TAGS,\n HTTP_STATUS,\n MAINTAINERS,\n USERS,\n cryptoUtils,\n errorUtils,\n pkgUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport type {\n Author,\n DistFile,\n GenericBody,\n Manifest,\n Person,\n ReadmeOptions,\n Version,\n} from '@verdaccio/types';\n\nimport { sortVersionsAndFilterInvalid } from './versions-utils';\n\nconst debug = buildDebug('verdaccio:storage:utils');\n\nexport const STORAGE = {\n PACKAGE_FILE_NAME: 'package.json',\n FILE_EXIST_ERROR: 'EEXISTS',\n NO_SUCH_FILE_ERROR: 'ENOENT',\n DEFAULT_REVISION: '0-0000000000000000',\n};\n\n/**\n * Create a new package in the storage, return a boilerplate package\n * @param name package name\n * @returns {Manifest}\n */\nexport function generatePackageTemplate(name: string): Manifest {\n return {\n // standard things\n name,\n versions: {},\n time: {},\n [USERS]: {},\n [DIST_TAGS]: {},\n [MAINTAINERS]: [],\n _uplinks: {},\n _distfiles: {},\n _attachments: {},\n _rev: '',\n };\n}\n\n/**\n * Normalize package properties, tags, revision id.\n * @param {Object} pkg package reference.\n */\nexport function normalizePackage(pkg: Manifest): Manifest {\n const pkgProperties = ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time'];\n\n pkgProperties.forEach((key): void => {\n const pkgProp = pkg[key];\n\n if (isNil(pkgProp) || validationUtils.isObject(pkgProp) === false) {\n pkg[key] = {};\n }\n });\n\n if (typeof pkg?._rev !== 'string') {\n pkg._rev = STORAGE.DEFAULT_REVISION;\n }\n\n if (typeof pkg?._id !== 'string') {\n pkg._id = pkg.name;\n }\n\n // normalize dist-tags\n return normalizeDistTags(pkg);\n}\n\nexport function generateRevision(rev: string): string {\n const _rev = rev.split('-');\n\n return (+_rev[0] || 0) + 1 + '-' + cryptoUtils.generateRandomHexString();\n}\n\nexport function getLatestReadme(pkg: Manifest): string {\n const versions = pkg['versions'] || {};\n const distTags = pkg[DIST_TAGS] || {};\n // FIXME: here is a bit tricky add the types\n const latestVersion: Version | any = distTags['latest'] ? versions[distTags['latest']] || {} : {};\n let readme = trim(pkg.readme || latestVersion.readme || '');\n if (readme) {\n return readme;\n }\n\n // In case of empty readme - trying to get ANY readme in the following order:\n // 'next','beta','alpha','test','dev','canary'\n const readmeDistTagsPriority = ['next', 'beta', 'alpha', 'test', 'dev', 'canary'];\n readmeDistTagsPriority.forEach(function (tag): string | void {\n if (readme) {\n return readme;\n }\n const version: Version | any = distTags[tag] ? versions[distTags[tag]] || {} : {};\n readme = trim(version.readme || readme);\n });\n return readme;\n}\n\n/**\n * Cleanup readme from package version\n *\n * By default, we don't keep readmes for package versions, only one readme per package.\n * Using publish.keep_readmes you can override this behavior and keep all readmes\n * or only readmes for tagged versions.\n */\nexport function cleanUpReadme(\n version: Version,\n distTags?: GenericBody,\n keepReadmes?: ReadmeOptions\n): Version {\n if (keepReadmes === 'all') {\n return version;\n } else if (keepReadmes === 'tagged') {\n if (distTags && Object.values(distTags).includes(version.version)) {\n return version;\n }\n }\n\n if (isNil(version) === false) {\n version.readme = '';\n }\n\n return version;\n}\n\nexport const WHITELIST = [\n '_rev',\n 'name',\n 'versions',\n 'dist-tags',\n 'readme',\n 'time',\n '_id',\n 'users',\n 'maintainers',\n];\n\nexport function cleanUpLinksRef(result: Manifest, keepUpLinkData?: boolean): Manifest {\n const propertyToKeep = [...WHITELIST];\n if (keepUpLinkData === true) {\n propertyToKeep.push('_uplinks');\n }\n\n for (const i in result) {\n if (propertyToKeep.indexOf(i) === -1) {\n // Remove sections like '_uplinks' from response\n delete result[i];\n }\n }\n\n return result;\n}\n\n// TODO: move to abstract storage\n// @deprecated use abstract.storage.ts:checkPackageRemote\nexport function checkPackageRemote(\n name: string,\n isAllowPublishOffline: boolean,\n syncMetadata: Function\n): Promise<any> {\n return new Promise<void>((resolve, reject): void => {\n syncMetadata(name, null, {}, (err, packageJsonLocal, upLinksErrors): void => {\n // something weird\n if (err && err.status !== HTTP_STATUS.NOT_FOUND) {\n return reject(err);\n }\n\n // checking package exist already\n if (isNil(packageJsonLocal) === false) {\n return reject(errorUtils.getConflict(API_ERROR.PACKAGE_EXIST));\n }\n\n for (let errorItem = 0; errorItem < upLinksErrors.length; errorItem++) {\n // checking error\n // if uplink fails with a status other than 404, we report failure\n if (isNil(upLinksErrors[errorItem][0]) === false) {\n if (upLinksErrors[errorItem][0].status !== HTTP_STATUS.NOT_FOUND) {\n if (isAllowPublishOffline) {\n return resolve();\n }\n\n return reject(errorUtils.getServiceUnavailable(API_ERROR.UPLINK_OFFLINE_PUBLISH));\n }\n }\n }\n\n return resolve();\n });\n });\n}\n\n// @deprecated use export function mergeUplinkTimeIntoLocalNext\nexport function mergeUplinkTimeIntoLocal(localMetadata: Manifest, remoteMetadata: Manifest): any {\n if ('time' in remoteMetadata) {\n return Object.assign({}, localMetadata.time, remoteMetadata.time);\n }\n\n return localMetadata.time;\n}\n\nexport function mergeUplinkTimeIntoLocalNext(\n cacheManifest: Manifest,\n remoteManifest: Manifest\n): Manifest {\n if ('time' in remoteManifest) {\n // remote override cache time conflicts\n return { ...cacheManifest, time: { ...cacheManifest.time, ...remoteManifest.time } };\n }\n\n return cacheManifest;\n}\n\nexport function updateUpLinkMetadata(uplinkName: string, manifest: Manifest, etag: string) {\n const _uplinks = {\n ...manifest._uplinks,\n [uplinkName]: {\n etag,\n fetched: Date.now(),\n },\n };\n return {\n ...manifest,\n _uplinks,\n };\n}\n\nexport function prepareSearchPackage(data: Manifest): any {\n const latest = pkgUtils.getLatest(data);\n\n if (latest && data.versions[latest]) {\n const version: Version = data.versions[latest];\n const versions: any = { [latest]: 'latest' };\n const pkg: any = {\n name: version.name,\n description: version.description,\n [DIST_TAGS]: { latest },\n maintainers: version.maintainers || [version.author].filter(Boolean),\n author: version.author,\n repository: version.repository,\n readmeFilename: version.readmeFilename || '',\n homepage: version.homepage,\n keywords: version.keywords,\n bugs: version.bugs,\n license: version.license,\n // time: {\n // modified: time,\n // },\n versions,\n };\n\n return pkg;\n }\n}\n\n/** Maintainer names copied from an uplink. Never a local account. */\nexport const UPSTREAM_MAINTAINER_PREFIX = '$uplink:';\n\nexport function scopeUpstreamMaintainers(maintainers: Person[], uplinkName: string): Person[] {\n const origin = uplinkName.length > 0 ? uplinkName : 'remote';\n return maintainers.map((maintainer) => {\n if (typeof maintainer === 'string') {\n return scopeUpstreamMaintainerName(origin, maintainer);\n }\n const name = typeof maintainer?.name === 'string' ? maintainer.name : '';\n return {\n ...maintainer,\n name: scopeUpstreamMaintainerName(origin, name),\n };\n });\n}\n\nfunction scopeUpstreamMaintainerName(uplinkName: string, name: string): string {\n if (name.startsWith(UPSTREAM_MAINTAINER_PREFIX)) {\n return name;\n }\n return `${UPSTREAM_MAINTAINER_PREFIX}${uplinkName}:${name}`;\n}\n\nexport function maintainerAllowsLocalUser(\n maintainer: Person,\n username: string | undefined\n): boolean {\n const name = typeof maintainer === 'string' ? maintainer : maintainer?.name;\n // reserved uplink identities must not authorize the local user who registered that string\n if (typeof name === 'string' && name.startsWith(UPSTREAM_MAINTAINER_PREFIX)) {\n return false;\n }\n return name === username || name === ANONYMOUS_USER;\n}\n\n/**\n * Function gets a local info and an info from uplinks and tries to merge it\n exported for unit tests only.\n * @param {*} cacheManifest\n * @param {*} remoteManifest\n */\nexport function mergeVersions(cacheManifest: Manifest, remoteManifest: Manifest): Manifest {\n let _cacheManifest = { ...cacheManifest };\n const { versions, time } = remoteManifest;\n // copy new versions to a cache\n // NOTE: if a certain version was updated, we can't refresh it reliably\n // however, we can refresh the deprecated status\n for (const i in versions) {\n if (typeof _cacheManifest.versions[i] === 'undefined') {\n _cacheManifest.versions[i] = versions[i];\n } else if (versions[i].deprecated) {\n _cacheManifest.versions[i].deprecated = versions[i].deprecated;\n }\n }\n\n for (const i in time) {\n if (typeof _cacheManifest.time[i] === 'undefined') {\n _cacheManifest.time[i] = time[i];\n }\n }\n\n for (const distTag in remoteManifest[DIST_TAGS]) {\n if (_cacheManifest[DIST_TAGS][distTag] !== remoteManifest[DIST_TAGS][distTag]) {\n if (\n !_cacheManifest[DIST_TAGS][distTag] ||\n semver.lte(_cacheManifest[DIST_TAGS][distTag], remoteManifest[DIST_TAGS][distTag])\n ) {\n _cacheManifest[DIST_TAGS][distTag] = remoteManifest[DIST_TAGS][distTag];\n }\n if (\n distTag === 'latest' &&\n _cacheManifest[DIST_TAGS][distTag] === remoteManifest[DIST_TAGS][distTag]\n ) {\n // NOTE: this override the latest publish readme from local cache with\n // the remote one\n _cacheManifest = { ..._cacheManifest, readme: remoteManifest.readme };\n }\n }\n }\n return _cacheManifest;\n}\n\n/**\n * Normalize dist-tags.\n *\n * There is a legacy behaviour where the dist-tags could be an array, in such\n * case, the array is orderded and the highest version becames the\n * latest.\n *\n * The dist-tag tags must be plain strigs, if the latest is empty (for whatever reason) is\n * normalized to be the highest version available.\n *\n * This function cleans up every invalid version on dist-tags, but does not remove\n * invalid versions from the manifest.\n *\n * @param {*} data\n */\nexport function normalizeDistTags(manifest: Manifest): Manifest {\n let sorted;\n // handle missing latest dist-tag\n if (!manifest[DIST_TAGS].latest) {\n // if there is no latest tag, set the highest known version based on semver sort\n sorted = sortVersionsAndFilterInvalid(Object.keys(manifest.versions));\n if (sorted?.length) {\n // get the highest published version\n manifest[DIST_TAGS].latest = sorted.pop();\n }\n }\n\n for (const tag in manifest[DIST_TAGS]) {\n // deprecated (will be removed un future majors)\n // this should not happen, tags should be plain strings, legacy fallback\n if (Array.isArray(manifest[DIST_TAGS][tag])) {\n if (manifest[DIST_TAGS][tag].length) {\n // sort array\n // FIXME: this is clearly wrong, we need to research why this is like this.\n // @ts-ignore\n sorted = sortVersionsAndFilterInvalid(manifest[DIST_TAGS][tag]);\n if (sorted.length) {\n // use highest version based on semver sort\n manifest[DIST_TAGS][tag] = sorted.pop();\n }\n } else {\n delete manifest[DIST_TAGS][tag];\n }\n } else if (typeof manifest[DIST_TAGS][tag] === 'string') {\n if (!semver.parse(manifest[DIST_TAGS][tag], true)) {\n // if the version is invalid, delete the dist-tag entry\n delete manifest[DIST_TAGS][tag];\n }\n }\n }\n\n return manifest;\n}\n\nexport function hasDeprecatedVersions(pkgInfo: Manifest): boolean {\n const { versions } = pkgInfo;\n for (const version in versions) {\n if (Object.prototype.hasOwnProperty.call(versions[version], 'deprecated')) {\n return true;\n }\n }\n return false;\n}\n\nexport function isDeprecatedManifest(manifest: Manifest): boolean {\n return (\n hasDeprecatedVersions(manifest) &&\n (typeof manifest._attachments === 'undefined' ||\n Object.keys(manifest._attachments).length === 0)\n );\n}\n\nexport function mapManifestToSearchPackageBody(\n pkg: Manifest,\n _searchItem: searchUtils.SearchItem\n): searchUtils.SearchPackageBody {\n const latest = pkgUtils.getLatest(pkg);\n const version: Version = pkg.versions[latest];\n // packument maintainers carry `name`, but the npm search API contract uses\n // `username` and the npm CLI crashes on entries without it — emit both\n const maintainers = Array.isArray(version.maintainers)\n ? version.maintainers.map((maintainer: any) =>\n typeof maintainer === 'string'\n ? { username: maintainer, email: '' }\n : {\n ...maintainer,\n username: maintainer?.username ?? maintainer?.name ?? '',\n email: maintainer?.email ?? '',\n }\n )\n : [];\n // npmjs fills `publisher` with the user who published the version; use\n // `_npmUser` when the client provided it and fall back to the first\n // maintainer (verdaccio sets it to the publishing user), otherwise the\n // npm CLI renders \"published by ???\"\n const publisher = version._npmUser?.name\n ? { username: version._npmUser.name, email: version._npmUser.email ?? '' }\n : (maintainers[0] ?? {});\n const links: NonNullable<searchUtils.SearchPackageBody['links']> = {};\n const repository =\n typeof version.repository === 'string' ? version.repository : version.repository?.url;\n const bugs = typeof version.bugs === 'string' ? version.bugs : version.bugs?.url;\n if (version.homepage) {\n links.homepage = version.homepage;\n }\n if (repository) {\n links.repository = repository;\n }\n if (bugs) {\n links.bugs = bugs;\n }\n const result: searchUtils.SearchPackageBody = {\n name: version.name,\n description: version.description,\n version: latest,\n license: version.license,\n keywords: version.keywords,\n date: pkg.time[latest],\n // FIXME: type\n author: version.author as any,\n publisher,\n maintainers,\n };\n\n if (Object.keys(links).length > 0) {\n result.links = links;\n }\n\n return result;\n}\n\nexport function normalizeContributors(contributors: Author[]): Author[] {\n if (isNil(contributors)) {\n return [];\n } else if (contributors && !Array.isArray(contributors)) {\n return [contributors];\n } else if (typeof contributors === 'string') {\n return [\n {\n name: contributors,\n },\n ];\n }\n\n return contributors;\n}\n\n/**\n * Whether the tarball url path ends with the given file name (any query\n * string or fragment is ignored, like the path parsing in\n * `tarballUtils.extractTarballFromUrl`, which creates the `_distfiles`\n * keys — if that parsing ever changes, this must change with it).\n */\nexport function tarballMatchesFilename(tarball: string, filename: string): boolean {\n return tarball.split(/[?#]/)[0].endsWith(`/${filename}`);\n}\n\n/**\n * Build a distfile record from a version's dist metadata when its tarball\n * url matches the given file name.\n */\nexport function distFileFromVersion(\n version: Version | undefined,\n filename: string\n): DistFile | null {\n const dist = version?.dist;\n\n if (dist?.tarball && tarballMatchesFilename(dist.tarball, filename)) {\n return { url: dist.tarball, sha: dist.shasum } as DistFile;\n }\n\n return null;\n}\n\n/**\n * Resolve the distfile record for a tarball filename. Prefers the\n * `_distfiles` bookkeeping, but falls back to the version metadata when the\n * record is missing (storages written by other verdaccio versions cache\n * versions without their distfile records, which made those tarballs\n * permanently unavailable).\n *\n * Note: no extension pre-filter before the scan on purpose — GitHub\n * Packages advertises tarball urls ending in a bare sha-256 digest.\n * @param manifest cached manifest\n * @param filename tarball file name, eg. pkg-1.0.0.tgz\n */\nexport function lookupDistFile(manifest: Manifest, filename: string): DistFile | null {\n const distFile = manifest?._distfiles?.[filename];\n if (isNil(distFile) === false) {\n return distFile;\n }\n\n const versions = manifest?.versions ?? {};\n\n // tarballs are conventionally named <basename>-<version>.tgz, so the\n // version can usually be derived from the filename without scanning\n // manifests that carry thousands of versions\n const packageBasename = manifest?.name?.replace(/^.*\\//, '');\n if (packageBasename && filename.startsWith(`${packageBasename}-`) && filename.endsWith('.tgz')) {\n const versionId = filename.slice(packageBasename.length + 1, -'.tgz'.length);\n const distFromVersion = distFileFromVersion(versions[versionId], filename);\n\n if (distFromVersion) {\n debug('distfile record missing for %o, using version %o dist', filename, versionId);\n return distFromVersion;\n }\n }\n\n // unconventional tarball names: fall back to a scan\n for (const versionId in versions) {\n const distFromVersion = distFileFromVersion(versions[versionId], filename);\n\n if (distFromVersion) {\n debug('distfile record missing for %o, using version %o dist', filename, versionId);\n return distFromVersion;\n }\n }\n\n debug('no distfile record or version dist found for %o', filename);\n return null;\n}\n\n/**\n * Whether an uplink's configured url serves the given tarball url: same\n * protocol, same host and the uplink path is a prefix of the tarball path.\n * WHATWG URL normalizes default ports (https://h:443 === https://h).\n */\nexport function uplinkServesUrl(uplinkUrl: URL, tarballUrl: string): boolean {\n try {\n const parsed = new URL(tarballUrl);\n // Compare on a path segment boundary: an uplink at /registry must not\n // match tarballs under /registry2/... (wrong-uplink selection would send\n // the uplink's credentials to a sibling path).\n const basePath = uplinkUrl.pathname.endsWith('/')\n ? uplinkUrl.pathname\n : `${uplinkUrl.pathname}/`;\n return (\n parsed.protocol === uplinkUrl.protocol &&\n parsed.host === uplinkUrl.host &&\n parsed.pathname.startsWith(basePath)\n );\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -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, 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 (Object.hasOwn(versions, version) && 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,
|
|
1
|
+
{"version":3,"file":"versions-utils.js","names":["debug","buildDebug","isNil","DIST_TAGS","pkgUtils"],"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 (Object.hasOwn(versions, version) && 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,IAAMA,WAAAA,GAAQC,MAAAA,QAAAA,CAAW,yBAAyB;;;;;AAMlD,SAAgB,WAAW,UAAoB,SAAsC;CACnF,IAAI,CAAC,UACH;CAIF,IAAI,OAAO,OAAO,UAAU,OAAO,MAAA,GAAKC,UAAAA,MAAAA,CAAM,SAAS,QAAQ,MAAM,OACnE,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,KAAKC,gBAAAA,UAAU,CAAC,SAAS,WAAW,OAAA,QAAO,MAAM,SAAS,IAAI,GAAG;EAE1E,KAAKA,gBAAAA,UAAU,CAAC,OAAO;EACvB,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,UAAoB,SAAiB,KAA4B;CAC9F,MAAM,OAAO,EAAE,GAAG,SAAS;CAC3B,IAAI,OAAO,KAAKA,gBAAAA,UAAU,CAAC,SAAS,WAAW,OAAA,QAAO,MAAM,SAAS,IAAI,GAEvE,KAAKA,gBAAAA,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,QAAQC,gBAAAA,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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-storage.js","names":[],"sources":["../src/local-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { head, isNil } from 'lodash-es';\nimport assert from 'node:assert';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport {\n PLUGIN_CATEGORY,\n PLUGIN_PREFIX,\n errorUtils,\n pluginUtils as pluginSanity,\n} from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport LocalDatabase from '@verdaccio/local-storage';\nimport type { Config, Logger } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:local');\n\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport type PluginStorage = pluginUtils.Storage<Config>;\n\n/**\n * Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).\n */\nclass LocalStorage {\n public config: Config;\n public storagePlugin: PluginStorage;\n public logger: Logger;\n\n public constructor(config: Config, logger: Logger) {\n debug('local storage created');\n this.logger = logger.child({ sub: 'fs' });\n this.config = config;\n // @ts-expect-error\n this.storagePlugin = null;\n }\n\n public async init() {\n if (this.storagePlugin === null) {\n this.storagePlugin = await this.loadStorage(this.config, this.logger);\n debug('storage plugin init');\n await this.storagePlugin.init();\n debug('storage plugin initialized');\n } else {\n this.logger.warn('storage plugin has been already initialized');\n }\n return;\n }\n\n public getStoragePlugin(): PluginStorage {\n if (this.storagePlugin === null) {\n throw errorUtils.getInternalError('storage plugin is not initialized');\n }\n\n return this.storagePlugin;\n }\n\n public async getSecret(config: Config): Promise<void> {\n const secretKey = await this.storagePlugin.getSecret();\n\n return this.storagePlugin.setSecret(config.checkSecretKey(secretKey));\n }\n\n private async loadStorage(config: Config, logger: Logger): Promise<PluginStorage> {\n const Storage = await this.loadStorePlugin();\n if (isNil(Storage)) {\n assert(this.config.storage, 'CONFIG: storage path not defined');\n debug('no custom storage found, loading default storage @verdaccio/local-storage');\n const localStorage = new LocalDatabase(config, logger);\n logger.info(\n { name: '@verdaccio/local-storage', pluginCategory: PLUGIN_CATEGORY.STORAGE },\n 'plugin @{name} successfully loaded (@{pluginCategory})'\n );\n return localStorage;\n }\n return Storage as PluginStorage;\n }\n\n private async loadStorePlugin(): Promise<PluginStorage | undefined> {\n const plugins: PluginStorage[] = await asyncLoadPlugin<pluginUtils.Storage<unknown>>(\n this.config.store,\n {\n config: this.config,\n logger: this.logger,\n },\n pluginSanity.storageSanityCheck,\n false,\n this.config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.STORAGE\n );\n\n if (plugins.length > 1) {\n this.logger.warn(\n 'more than one storage plugins has been detected, multiple storage are not supported, one will be selected automatically'\n );\n }\n\n return head(plugins);\n }\n}\n\nexport { LocalStorage };\n"],"mappings":";;;;;;;;;;;AAeA,
|
|
1
|
+
{"version":3,"file":"local-storage.js","names":["debug","buildDebug","errorUtils","isNil","LocalDatabase","PLUGIN_CATEGORY","asyncLoadPlugin","pluginSanity","PLUGIN_PREFIX","head"],"sources":["../src/local-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { head, isNil } from 'lodash-es';\nimport assert from 'node:assert';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport {\n PLUGIN_CATEGORY,\n PLUGIN_PREFIX,\n errorUtils,\n pluginUtils as pluginSanity,\n} from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport LocalDatabase from '@verdaccio/local-storage';\nimport type { Config, Logger } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:local');\n\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport type PluginStorage = pluginUtils.Storage<Config>;\n\n/**\n * Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).\n */\nclass LocalStorage {\n public config: Config;\n public storagePlugin: PluginStorage;\n public logger: Logger;\n\n public constructor(config: Config, logger: Logger) {\n debug('local storage created');\n this.logger = logger.child({ sub: 'fs' });\n this.config = config;\n // @ts-expect-error\n this.storagePlugin = null;\n }\n\n public async init() {\n if (this.storagePlugin === null) {\n this.storagePlugin = await this.loadStorage(this.config, this.logger);\n debug('storage plugin init');\n await this.storagePlugin.init();\n debug('storage plugin initialized');\n } else {\n this.logger.warn('storage plugin has been already initialized');\n }\n return;\n }\n\n public getStoragePlugin(): PluginStorage {\n if (this.storagePlugin === null) {\n throw errorUtils.getInternalError('storage plugin is not initialized');\n }\n\n return this.storagePlugin;\n }\n\n public async getSecret(config: Config): Promise<void> {\n const secretKey = await this.storagePlugin.getSecret();\n\n return this.storagePlugin.setSecret(config.checkSecretKey(secretKey));\n }\n\n private async loadStorage(config: Config, logger: Logger): Promise<PluginStorage> {\n const Storage = await this.loadStorePlugin();\n if (isNil(Storage)) {\n assert(this.config.storage, 'CONFIG: storage path not defined');\n debug('no custom storage found, loading default storage @verdaccio/local-storage');\n const localStorage = new LocalDatabase(config, logger);\n logger.info(\n { name: '@verdaccio/local-storage', pluginCategory: PLUGIN_CATEGORY.STORAGE },\n 'plugin @{name} successfully loaded (@{pluginCategory})'\n );\n return localStorage;\n }\n return Storage as PluginStorage;\n }\n\n private async loadStorePlugin(): Promise<PluginStorage | undefined> {\n const plugins: PluginStorage[] = await asyncLoadPlugin<pluginUtils.Storage<unknown>>(\n this.config.store,\n {\n config: this.config,\n logger: this.logger,\n },\n pluginSanity.storageSanityCheck,\n false,\n this.config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.STORAGE\n );\n\n if (plugins.length > 1) {\n this.logger.warn(\n 'more than one storage plugins has been detected, multiple storage are not supported, one will be selected automatically'\n );\n }\n\n return head(plugins);\n }\n}\n\nexport { LocalStorage };\n"],"mappings":";;;;;;;;;;;AAeA,IAAMA,WAAAA,GAAQC,MAAAA,QAAAA,CAAW,yBAAyB;;;;AASlD,IAAM,eAAN,MAAmB;CACjB;CACA;CACA;CAEA,YAAmB,QAAgB,QAAgB;EACjD,QAAM,uBAAuB;EAC7B,KAAK,SAAS,OAAO,MAAM,EAAE,KAAK,KAAK,CAAC;EACxC,KAAK,SAAS;EAEd,KAAK,gBAAgB;CACvB;CAEA,MAAa,OAAO;EAClB,IAAI,KAAK,kBAAkB,MAAM;GAC/B,KAAK,gBAAgB,MAAM,KAAK,YAAY,KAAK,QAAQ,KAAK,MAAM;GACpE,QAAM,qBAAqB;GAC3B,MAAM,KAAK,cAAc,KAAK;GAC9B,QAAM,4BAA4B;EACpC,OACE,KAAK,OAAO,KAAK,6CAA6C;CAGlE;CAEA,mBAAyC;EACvC,IAAI,KAAK,kBAAkB,MACzB,MAAMC,gBAAAA,WAAW,iBAAiB,mCAAmC;EAGvE,OAAO,KAAK;CACd;CAEA,MAAa,UAAU,QAA+B;EACpD,MAAM,YAAY,MAAM,KAAK,cAAc,UAAU;EAErD,OAAO,KAAK,cAAc,UAAU,OAAO,eAAe,SAAS,CAAC;CACtE;CAEA,MAAc,YAAY,QAAgB,QAAwC;EAChF,MAAM,UAAU,MAAM,KAAK,gBAAgB;EAC3C,KAAA,GAAIC,UAAAA,MAAAA,CAAM,OAAO,GAAG;GAClB,CAAA,GAAA,YAAA,QAAA,CAAO,KAAK,OAAO,SAAS,kCAAkC;GAC9D,QAAM,2EAA2E;GACjF,MAAM,eAAe,IAAIC,yBAAAA,QAAc,QAAQ,MAAM;GACrD,OAAO,KACL;IAAE,MAAM;IAA4B,gBAAgBC,gBAAAA,gBAAgB;GAAQ,GAC5E,wDACF;GACA,OAAO;EACT;EACA,OAAO;CACT;CAEA,MAAc,kBAAsD;EAClE,MAAM,UAA2B,OAAA,GAAMC,mBAAAA,gBAAAA,CACrC,KAAK,OAAO,OACZ;GACE,QAAQ,KAAK;GACb,QAAQ,KAAK;EACf,GACAC,gBAAAA,YAAa,oBACb,OACA,KAAK,OAAO,QAAQ,gBAAgBC,gBAAAA,eACpCH,gBAAAA,gBAAgB,OAClB;EAEA,IAAI,QAAQ,SAAS,GACnB,KAAK,OAAO,KACV,yHACF;EAGF,QAAA,GAAOI,UAAAA,KAAAA,CAAK,OAAO;CACrB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-storage.mjs","names":[],"sources":["../src/local-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { head, isNil } from 'lodash-es';\nimport assert from 'node:assert';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport {\n PLUGIN_CATEGORY,\n PLUGIN_PREFIX,\n errorUtils,\n pluginUtils as pluginSanity,\n} from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport LocalDatabase from '@verdaccio/local-storage';\nimport type { Config, Logger } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:local');\n\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport type PluginStorage = pluginUtils.Storage<Config>;\n\n/**\n * Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).\n */\nclass LocalStorage {\n public config: Config;\n public storagePlugin: PluginStorage;\n public logger: Logger;\n\n public constructor(config: Config, logger: Logger) {\n debug('local storage created');\n this.logger = logger.child({ sub: 'fs' });\n this.config = config;\n // @ts-expect-error\n this.storagePlugin = null;\n }\n\n public async init() {\n if (this.storagePlugin === null) {\n this.storagePlugin = await this.loadStorage(this.config, this.logger);\n debug('storage plugin init');\n await this.storagePlugin.init();\n debug('storage plugin initialized');\n } else {\n this.logger.warn('storage plugin has been already initialized');\n }\n return;\n }\n\n public getStoragePlugin(): PluginStorage {\n if (this.storagePlugin === null) {\n throw errorUtils.getInternalError('storage plugin is not initialized');\n }\n\n return this.storagePlugin;\n }\n\n public async getSecret(config: Config): Promise<void> {\n const secretKey = await this.storagePlugin.getSecret();\n\n return this.storagePlugin.setSecret(config.checkSecretKey(secretKey));\n }\n\n private async loadStorage(config: Config, logger: Logger): Promise<PluginStorage> {\n const Storage = await this.loadStorePlugin();\n if (isNil(Storage)) {\n assert(this.config.storage, 'CONFIG: storage path not defined');\n debug('no custom storage found, loading default storage @verdaccio/local-storage');\n const localStorage = new LocalDatabase(config, logger);\n logger.info(\n { name: '@verdaccio/local-storage', pluginCategory: PLUGIN_CATEGORY.STORAGE },\n 'plugin @{name} successfully loaded (@{pluginCategory})'\n );\n return localStorage;\n }\n return Storage as PluginStorage;\n }\n\n private async loadStorePlugin(): Promise<PluginStorage | undefined> {\n const plugins: PluginStorage[] = await asyncLoadPlugin<pluginUtils.Storage<unknown>>(\n this.config.store,\n {\n config: this.config,\n logger: this.logger,\n },\n pluginSanity.storageSanityCheck,\n false,\n this.config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.STORAGE\n );\n\n if (plugins.length > 1) {\n this.logger.warn(\n 'more than one storage plugins has been detected, multiple storage are not supported, one will be selected automatically'\n );\n }\n\n return head(plugins);\n }\n}\n\nexport { LocalStorage };\n"],"mappings":";;;;;;;AAeA,IAAM,QAAQ,WAAW,yBAAyB;;;;AASlD,IAAM,eAAN,MAAmB;CACjB;CACA;CACA;CAEA,YAAmB,QAAgB,QAAgB;EACjD,MAAM,uBAAuB;EAC7B,KAAK,SAAS,OAAO,MAAM,EAAE,KAAK,KAAK,CAAC;EACxC,KAAK,SAAS;EAEd,KAAK,gBAAgB;CACvB;CAEA,MAAa,OAAO;EAClB,IAAI,KAAK,kBAAkB,MAAM;GAC/B,KAAK,gBAAgB,MAAM,KAAK,YAAY,KAAK,QAAQ,KAAK,MAAM;GACpE,MAAM,qBAAqB;GAC3B,MAAM,KAAK,cAAc,KAAK;GAC9B,MAAM,4BAA4B;EACpC,OACE,KAAK,OAAO,KAAK,6CAA6C;CAGlE;CAEA,mBAAyC;EACvC,IAAI,KAAK,kBAAkB,MACzB,MAAM,WAAW,iBAAiB,mCAAmC;EAGvE,OAAO,KAAK;CACd;CAEA,MAAa,UAAU,QAA+B;EACpD,MAAM,YAAY,MAAM,KAAK,cAAc,UAAU;EAErD,OAAO,KAAK,cAAc,UAAU,OAAO,eAAe,SAAS,CAAC;CACtE;CAEA,MAAc,YAAY,QAAgB,QAAwC;EAChF,MAAM,UAAU,MAAM,KAAK,gBAAgB;EAC3C,IAAI,MAAM,OAAO,GAAG;GAClB,OAAO,KAAK,OAAO,SAAS,kCAAkC;GAC9D,MAAM,2EAA2E;GACjF,MAAM,eAAe,IAAI,cAAc,QAAQ,MAAM;GACrD,OAAO,KACL;IAAE,MAAM;IAA4B,gBAAgB,gBAAgB;GAAQ,GAC5E,wDACF;GACA,OAAO;EACT;EACA,OAAO;CACT;CAEA,MAAc,kBAAsD;EAClE,MAAM,UAA2B,MAAM,gBACrC,KAAK,OAAO,OACZ;GACE,QAAQ,KAAK;GACb,QAAQ,KAAK;EACf,
|
|
1
|
+
{"version":3,"file":"local-storage.mjs","names":["pluginSanity"],"sources":["../src/local-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { head, isNil } from 'lodash-es';\nimport assert from 'node:assert';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport {\n PLUGIN_CATEGORY,\n PLUGIN_PREFIX,\n errorUtils,\n pluginUtils as pluginSanity,\n} from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport LocalDatabase from '@verdaccio/local-storage';\nimport type { Config, Logger } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:storage:local');\n\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport type PluginStorage = pluginUtils.Storage<Config>;\n\n/**\n * Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).\n */\nclass LocalStorage {\n public config: Config;\n public storagePlugin: PluginStorage;\n public logger: Logger;\n\n public constructor(config: Config, logger: Logger) {\n debug('local storage created');\n this.logger = logger.child({ sub: 'fs' });\n this.config = config;\n // @ts-expect-error\n this.storagePlugin = null;\n }\n\n public async init() {\n if (this.storagePlugin === null) {\n this.storagePlugin = await this.loadStorage(this.config, this.logger);\n debug('storage plugin init');\n await this.storagePlugin.init();\n debug('storage plugin initialized');\n } else {\n this.logger.warn('storage plugin has been already initialized');\n }\n return;\n }\n\n public getStoragePlugin(): PluginStorage {\n if (this.storagePlugin === null) {\n throw errorUtils.getInternalError('storage plugin is not initialized');\n }\n\n return this.storagePlugin;\n }\n\n public async getSecret(config: Config): Promise<void> {\n const secretKey = await this.storagePlugin.getSecret();\n\n return this.storagePlugin.setSecret(config.checkSecretKey(secretKey));\n }\n\n private async loadStorage(config: Config, logger: Logger): Promise<PluginStorage> {\n const Storage = await this.loadStorePlugin();\n if (isNil(Storage)) {\n assert(this.config.storage, 'CONFIG: storage path not defined');\n debug('no custom storage found, loading default storage @verdaccio/local-storage');\n const localStorage = new LocalDatabase(config, logger);\n logger.info(\n { name: '@verdaccio/local-storage', pluginCategory: PLUGIN_CATEGORY.STORAGE },\n 'plugin @{name} successfully loaded (@{pluginCategory})'\n );\n return localStorage;\n }\n return Storage as PluginStorage;\n }\n\n private async loadStorePlugin(): Promise<PluginStorage | undefined> {\n const plugins: PluginStorage[] = await asyncLoadPlugin<pluginUtils.Storage<unknown>>(\n this.config.store,\n {\n config: this.config,\n logger: this.logger,\n },\n pluginSanity.storageSanityCheck,\n false,\n this.config.server?.pluginPrefix ?? PLUGIN_PREFIX,\n PLUGIN_CATEGORY.STORAGE\n );\n\n if (plugins.length > 1) {\n this.logger.warn(\n 'more than one storage plugins has been detected, multiple storage are not supported, one will be selected automatically'\n );\n }\n\n return head(plugins);\n }\n}\n\nexport { LocalStorage };\n"],"mappings":";;;;;;;AAeA,IAAM,QAAQ,WAAW,yBAAyB;;;;AASlD,IAAM,eAAN,MAAmB;CACjB;CACA;CACA;CAEA,YAAmB,QAAgB,QAAgB;EACjD,MAAM,uBAAuB;EAC7B,KAAK,SAAS,OAAO,MAAM,EAAE,KAAK,KAAK,CAAC;EACxC,KAAK,SAAS;EAEd,KAAK,gBAAgB;CACvB;CAEA,MAAa,OAAO;EAClB,IAAI,KAAK,kBAAkB,MAAM;GAC/B,KAAK,gBAAgB,MAAM,KAAK,YAAY,KAAK,QAAQ,KAAK,MAAM;GACpE,MAAM,qBAAqB;GAC3B,MAAM,KAAK,cAAc,KAAK;GAC9B,MAAM,4BAA4B;EACpC,OACE,KAAK,OAAO,KAAK,6CAA6C;CAGlE;CAEA,mBAAyC;EACvC,IAAI,KAAK,kBAAkB,MACzB,MAAM,WAAW,iBAAiB,mCAAmC;EAGvE,OAAO,KAAK;CACd;CAEA,MAAa,UAAU,QAA+B;EACpD,MAAM,YAAY,MAAM,KAAK,cAAc,UAAU;EAErD,OAAO,KAAK,cAAc,UAAU,OAAO,eAAe,SAAS,CAAC;CACtE;CAEA,MAAc,YAAY,QAAgB,QAAwC;EAChF,MAAM,UAAU,MAAM,KAAK,gBAAgB;EAC3C,IAAI,MAAM,OAAO,GAAG;GAClB,OAAO,KAAK,OAAO,SAAS,kCAAkC;GAC9D,MAAM,2EAA2E;GACjF,MAAM,eAAe,IAAI,cAAc,QAAQ,MAAM;GACrD,OAAO,KACL;IAAE,MAAM;IAA4B,gBAAgB,gBAAgB;GAAQ,GAC5E,wDACF;GACA,OAAO;EACT;EACA,OAAO;CACT;CAEA,MAAc,kBAAsD;EAClE,MAAM,UAA2B,MAAM,gBACrC,KAAK,OAAO,OACZ;GACE,QAAQ,KAAK;GACb,QAAQ,KAAK;EACf,GACAA,YAAa,oBACb,OACA,KAAK,OAAO,QAAQ,gBAAgB,eACpC,gBAAgB,OAClB;EAEA,IAAI,QAAQ,SAAS,GACnB,KAAK,OAAO,KACV,yHACF;EAGF,OAAO,KAAK,OAAO;CACrB;AACF"}
|
package/build/stage-storage.d.ts
CHANGED
|
@@ -36,7 +36,9 @@ export interface StageRecord {
|
|
|
36
36
|
tarballFilename: string;
|
|
37
37
|
}
|
|
38
38
|
/** Shape served to clients: {@link StageRecord} without the internal fields. */
|
|
39
|
-
export type StagePackageVersion = Omit<StageRecord, 'tarballFilename'
|
|
39
|
+
export type StagePackageVersion = Omit<StageRecord, 'tarballFilename'> & {
|
|
40
|
+
status: 'staged';
|
|
41
|
+
};
|
|
40
42
|
/**
|
|
41
43
|
* The publish payload kept for a staged item, minus `_attachments` (the tarball
|
|
42
44
|
* is stored separately as a real tarball, not as base64 inside this document).
|
package/build/stage-storage.js
CHANGED
|
@@ -26,7 +26,10 @@ var STAGE_INDEX = "stage-index";
|
|
|
26
26
|
/** Strip the internal fields before the record leaves the registry. */
|
|
27
27
|
function toStagePackageVersion(record) {
|
|
28
28
|
const { tarballFilename: _tarballFilename, ...rest } = record;
|
|
29
|
-
return
|
|
29
|
+
return {
|
|
30
|
+
...rest,
|
|
31
|
+
status: "staged"
|
|
32
|
+
};
|
|
30
33
|
}
|
|
31
34
|
function isNotFound(err) {
|
|
32
35
|
return err?.code === "ENOENT" || err?.code === 404 || err?.status === 404;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stage-storage.js","names":[],"sources":["../src/stage-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { randomUUID } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils, validationUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { STORAGE } from './lib/storage-utils';\n\nconst debug = buildDebug('verdaccio:storage:stage');\n\n/**\n * Storage namespace holding every staged item.\n *\n * It is deliberately NOT registered through `storagePlugin.add()`: doing so\n * would list it in the plugin database and leak it into search results and the\n * web UI package list. The staged items are enumerated through {@link STAGE_INDEX}\n * instead.\n */\nexport const STAGE_NAMESPACE = '.stage';\n\n/**\n * Logical name used when reading/writing the index document. Storage handlers\n * are scoped to a folder and ignore this argument when resolving the path, but\n * it is what shows up in plugin logs, so keep it descriptive.\n */\nconst STAGE_INDEX = 'stage-index';\n\n/**\n * Metadata of a package version awaiting review.\n *\n * Mirrors the `StagePackageVersion` schema documented by npmjs, plus\n * {@link StageRecord.tarballFilename}, which is internal and must be stripped\n * before serving the record over the API (see {@link toStagePackageVersion}).\n */\nexport interface StageRecord {\n /** Unique identifier. Must be a UUID: the npm CLI validates the format client side. */\n id: string;\n packageName: string;\n version: string;\n /** dist-tag the version will be published under once approved. */\n tag: string;\n /** ISO timestamp of when the version was staged. */\n createdAt: string;\n /** Username of whoever staged the version. */\n actor: string;\n actorType: 'user';\n access: 'public' | 'private';\n /** sha1 of the tarball, as reported by `npm stage list`. */\n shasum: string;\n /** Internal: name of the tarball inside the staged item folder. */\n tarballFilename: string;\n}\n\n/** Shape served to clients: {@link StageRecord} without the internal fields. */\nexport type StagePackageVersion = Omit<StageRecord, 'tarballFilename'>;\n\n/**\n * The publish payload kept for a staged item, minus `_attachments` (the tarball\n * is stored separately as a real tarball, not as base64 inside this document).\n */\nexport type StagedPackument = Omit<Manifest, '_attachments'>;\n\n/** Persisted document of a single staged item. */\ninterface StageDocument {\n record: StageRecord;\n packument: StagedPackument;\n}\n\n/** Persisted index of every staged item, newest first. */\ninterface StageIndex {\n items: StageRecord[];\n}\n\nexport interface AddStageInput {\n packageName: string;\n version: string;\n tag: string;\n actor: string;\n access: 'public' | 'private';\n shasum: string;\n tarballFilename: string;\n packument: StagedPackument;\n}\n\nexport interface ListStageFilter {\n /** Restrict the result to a single package name. */\n packageName?: string;\n}\n\n/** Strip the internal fields before the record leaves the registry. */\nexport function toStagePackageVersion(record: StageRecord): StagePackageVersion {\n const { tarballFilename: _tarballFilename, ...rest } = record;\n return rest;\n}\n\nfunction isNotFound(err: any): boolean {\n return err?.code === 'ENOENT' || err?.code === 404 || err?.status === 404;\n}\n\n/**\n * Persistence for the staged publish workflow (`npm stage`).\n *\n * Everything goes through {@link pluginUtils.StorageHandler}, so it works with\n * any storage plugin (local-storage, memory, S3, GCS) without the plugin having\n * to know that staging exists. On disk it looks like:\n *\n * ```\n * <storage>/.stage/package.json index of every staged item\n * <storage>/.stage/<uuid>/package.json the staged item (record + packument)\n * <storage>/.stage/<uuid>/<name>-<v>.tgz the staged tarball\n * ```\n */\nexport class StageStorage {\n private readonly plugin: pluginUtils.Storage<any>;\n private readonly logger: Logger;\n /**\n * Serializes index mutations within this process.\n *\n * `StorageHandler.updatePackage()` takes a lock in local-storage but releases\n * it before we get to save, and other plugins do not lock at all, so a\n * read-modify-write on the index would race. This keeps a single Verdaccio\n * process consistent; several processes sharing one storage backend are NOT\n * supported.\n */\n private indexQueue: Promise<unknown> = Promise.resolve();\n\n public constructor(plugin: pluginUtils.Storage<any>, logger: Logger) {\n this.plugin = plugin;\n this.logger = logger;\n }\n\n /** Stage a new version. Returns the persisted record, including its generated id. */\n public async add(\n input: AddStageInput,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<StageRecord> {\n // the filename reaches us from the `_attachments` key of the request, so it\n // is attacker controlled. The regular publish path asserts the same thing\n // before writing; without it a name like `..` escapes the item folder and\n // the write fails inside an unawaited stream handler, taking the process\n // down rather than the request.\n if (validationUtils.validateName(input.tarballFilename) === false) {\n throw errorUtils.getBadRequest(`invalid tarball name ${input.tarballFilename}`);\n }\n\n const record: StageRecord = {\n id: randomUUID(),\n packageName: input.packageName,\n version: input.version,\n tag: input.tag,\n createdAt: new Date().toISOString(),\n actor: input.actor,\n actorType: 'user',\n access: input.access,\n shasum: input.shasum,\n tarballFilename: input.tarballFilename,\n };\n debug('staging %o@%o as %o', record.packageName, record.version, record.id);\n\n return this.enqueueIndexMutation(async () => {\n const items = await this.readIndex();\n const alreadyStaged = items.find(\n (item) => item.packageName === record.packageName && item.version === record.version\n );\n if (alreadyStaged) {\n throw errorUtils.getConflict(`${record.packageName}@${record.version} is already staged`);\n }\n\n const handler = this.itemStorage(record.id);\n // the tarball goes first: if it fails there is no record pointing at a\n // tarball that never landed.\n await this.writeTarball(handler, record.tarballFilename, tarball, { signal });\n const document: StageDocument = { record, packument: input.packument };\n await handler.savePackage(record.id, document as unknown as Manifest);\n await this.writeIndex([record, ...items]);\n\n this.logger.info(\n { packageName: record.packageName, version: record.version, stageId: record.id },\n 'staged @{packageName}@@{version} as @{stageId}'\n );\n return record;\n });\n }\n\n /** Every staged item, newest first, optionally narrowed to one package. */\n public async list(filter: ListStageFilter = {}): Promise<StageRecord[]> {\n const items = await this.readIndex();\n if (typeof filter.packageName === 'string') {\n return items.filter((item) => item.packageName === filter.packageName);\n }\n return items;\n }\n\n /** A single staged item, or `undefined` when the id is unknown. */\n public async get(id: string): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.id === id);\n }\n\n /** Whether a version of a package is already staged (used to answer 409). */\n public async findByVersion(\n packageName: string,\n version: string\n ): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.packageName === packageName && item.version === version);\n }\n\n /** The publish payload kept for a staged item, needed to approve it. */\n public async getPackument(id: string): Promise<StagedPackument | undefined> {\n const document = await this.readDocument(id);\n return document?.packument;\n }\n\n /** Read the staged tarball as a stream. */\n public async readTarball(id: string, { signal }: { signal: AbortSignal }): Promise<Readable> {\n const record = await this.get(id);\n if (!record) {\n throw errorUtils.getNotFound('no such staged package version');\n }\n return this.itemStorage(id).readTarball(record.tarballFilename, { signal });\n }\n\n /**\n * Drop a staged item and its tarball.\n *\n * @returns `false` when the id was unknown, `true` when something was removed.\n */\n public async remove(id: string): Promise<boolean> {\n const record = await this.get(id);\n if (!record) {\n return false;\n }\n const handler = this.itemStorage(id);\n // the index entry goes first: a leftover folder is invisible, whereas a\n // leftover index entry would surface a staged item that cannot be read.\n await this.updateIndex((items) => items.filter((item) => item.id !== id));\n // the folder can only be dropped once it is empty, so both files go first\n for (const file of [record.tarballFilename, STORAGE.PACKAGE_FILE_NAME]) {\n try {\n await handler.deletePackage(file);\n } catch (err: any) {\n debug('could not delete staged file %o: %s', file, err?.message);\n }\n }\n try {\n await handler.removePackage(id);\n } catch (err: any) {\n debug('could not delete staged folder %o: %s', id, err?.message);\n }\n debug('removed staged item %o', id);\n return true;\n }\n\n private itemStorage(id: string): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(`${STAGE_NAMESPACE}/${id}`);\n }\n\n private indexStorage(): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(STAGE_NAMESPACE);\n }\n\n private async readDocument(id: string): Promise<StageDocument | undefined> {\n try {\n const document = (await this.itemStorage(id).readPackage(id)) as unknown as StageDocument;\n return document;\n } catch (err: any) {\n if (isNotFound(err)) {\n return undefined;\n }\n throw err;\n }\n }\n\n private async readIndex(): Promise<StageRecord[]> {\n try {\n const index = (await this.indexStorage().readPackage(STAGE_INDEX)) as unknown as StageIndex;\n return Array.isArray(index?.items) ? index.items : [];\n } catch (err: any) {\n if (isNotFound(err)) {\n debug('no stage index yet');\n return [];\n }\n throw err;\n }\n }\n\n /** Read-modify-write the index, serialized against other callers (see {@link indexQueue}). */\n private async updateIndex(\n mutate: (items: StageRecord[]) => StageRecord[]\n ): Promise<StageRecord[]> {\n return this.enqueueIndexMutation(async (): Promise<StageRecord[]> => {\n const items = mutate(await this.readIndex());\n await this.writeIndex(items);\n return items;\n });\n }\n\n private async enqueueIndexMutation<T>(run: () => Promise<T>): Promise<T> {\n const queued = this.indexQueue.then(run, run);\n // keep the chain alive even when a caller rejects\n this.indexQueue = queued.catch(() => undefined);\n return queued;\n }\n\n private async writeIndex(items: StageRecord[]): Promise<void> {\n const index: StageIndex = { items };\n await this.indexStorage().savePackage(STAGE_INDEX, index as unknown as Manifest);\n }\n\n private async writeTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<void> {\n const writeStream = await handler.writeTarball(filename, { signal });\n\n // `StorageHandler.writeTarball` only promises a Writable. The two bundled\n // plugins happen to emit 'open' before accepting writes, but nothing in the\n // interface says a plugin must, and waiting for it would hang staging\n // forever on one that does not. Piping straight away is correct either way:\n // a stream that is not ready yet buffers the writes.\n await pipeline(Readable.from(tarball), writeStream);\n\n // Both plugins rename the temporary file inside an async 'close' handler\n // that nobody awaits, so 'close' does not guarantee the tarball is in its\n // final place yet. Give the rename a bounded chance to land.\n await this.waitForTarball(handler, filename);\n }\n\n private async waitForTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n attempts = 20\n ): Promise<void> {\n for (let attempt = 0; attempt < attempts; attempt++) {\n if (await handler.hasTarball(filename)) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw errorUtils.getInternalError(`staged tarball ${filename} was never persisted`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stage-storage.js","names":["debug","buildDebug","validationUtils","errorUtils","randomUUID","STORAGE","pipeline","Readable"],"sources":["../src/stage-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { randomUUID } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils, validationUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { STORAGE } from './lib/storage-utils';\n\nconst debug = buildDebug('verdaccio:storage:stage');\n\n/**\n * Storage namespace holding every staged item.\n *\n * It is deliberately NOT registered through `storagePlugin.add()`: doing so\n * would list it in the plugin database and leak it into search results and the\n * web UI package list. The staged items are enumerated through {@link STAGE_INDEX}\n * instead.\n */\nexport const STAGE_NAMESPACE = '.stage';\n\n/**\n * Logical name used when reading/writing the index document. Storage handlers\n * are scoped to a folder and ignore this argument when resolving the path, but\n * it is what shows up in plugin logs, so keep it descriptive.\n */\nconst STAGE_INDEX = 'stage-index';\n\n/**\n * Metadata of a package version awaiting review.\n *\n * Mirrors the `StagePackageVersion` schema documented by npmjs, plus\n * {@link StageRecord.tarballFilename}, which is internal and must be stripped\n * before serving the record over the API (see {@link toStagePackageVersion}).\n */\nexport interface StageRecord {\n /** Unique identifier. Must be a UUID: the npm CLI validates the format client side. */\n id: string;\n packageName: string;\n version: string;\n /** dist-tag the version will be published under once approved. */\n tag: string;\n /** ISO timestamp of when the version was staged. */\n createdAt: string;\n /** Username of whoever staged the version. */\n actor: string;\n actorType: 'user';\n access: 'public' | 'private';\n /** sha1 of the tarball, as reported by `npm stage list`. */\n shasum: string;\n /** Internal: name of the tarball inside the staged item folder. */\n tarballFilename: string;\n}\n\n/** Shape served to clients: {@link StageRecord} without the internal fields. */\nexport type StagePackageVersion = Omit<StageRecord, 'tarballFilename'> & {\n status: 'staged';\n};\n\n/**\n * The publish payload kept for a staged item, minus `_attachments` (the tarball\n * is stored separately as a real tarball, not as base64 inside this document).\n */\nexport type StagedPackument = Omit<Manifest, '_attachments'>;\n\n/** Persisted document of a single staged item. */\ninterface StageDocument {\n record: StageRecord;\n packument: StagedPackument;\n}\n\n/** Persisted index of every staged item, newest first. */\ninterface StageIndex {\n items: StageRecord[];\n}\n\nexport interface AddStageInput {\n packageName: string;\n version: string;\n tag: string;\n actor: string;\n access: 'public' | 'private';\n shasum: string;\n tarballFilename: string;\n packument: StagedPackument;\n}\n\nexport interface ListStageFilter {\n /** Restrict the result to a single package name. */\n packageName?: string;\n}\n\n/** Strip the internal fields before the record leaves the registry. */\nexport function toStagePackageVersion(record: StageRecord): StagePackageVersion {\n const { tarballFilename: _tarballFilename, ...rest } = record;\n return { ...rest, status: 'staged' };\n}\n\nfunction isNotFound(err: any): boolean {\n return err?.code === 'ENOENT' || err?.code === 404 || err?.status === 404;\n}\n\n/**\n * Persistence for the staged publish workflow (`npm stage`).\n *\n * Everything goes through {@link pluginUtils.StorageHandler}, so it works with\n * any storage plugin (local-storage, memory, S3, GCS) without the plugin having\n * to know that staging exists. On disk it looks like:\n *\n * ```\n * <storage>/.stage/package.json index of every staged item\n * <storage>/.stage/<uuid>/package.json the staged item (record + packument)\n * <storage>/.stage/<uuid>/<name>-<v>.tgz the staged tarball\n * ```\n */\nexport class StageStorage {\n private readonly plugin: pluginUtils.Storage<any>;\n private readonly logger: Logger;\n /**\n * Serializes index mutations within this process.\n *\n * `StorageHandler.updatePackage()` takes a lock in local-storage but releases\n * it before we get to save, and other plugins do not lock at all, so a\n * read-modify-write on the index would race. This keeps a single Verdaccio\n * process consistent; several processes sharing one storage backend are NOT\n * supported.\n */\n private indexQueue: Promise<unknown> = Promise.resolve();\n\n public constructor(plugin: pluginUtils.Storage<any>, logger: Logger) {\n this.plugin = plugin;\n this.logger = logger;\n }\n\n /** Stage a new version. Returns the persisted record, including its generated id. */\n public async add(\n input: AddStageInput,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<StageRecord> {\n // the filename reaches us from the `_attachments` key of the request, so it\n // is attacker controlled. The regular publish path asserts the same thing\n // before writing; without it a name like `..` escapes the item folder and\n // the write fails inside an unawaited stream handler, taking the process\n // down rather than the request.\n if (validationUtils.validateName(input.tarballFilename) === false) {\n throw errorUtils.getBadRequest(`invalid tarball name ${input.tarballFilename}`);\n }\n\n const record: StageRecord = {\n id: randomUUID(),\n packageName: input.packageName,\n version: input.version,\n tag: input.tag,\n createdAt: new Date().toISOString(),\n actor: input.actor,\n actorType: 'user',\n access: input.access,\n shasum: input.shasum,\n tarballFilename: input.tarballFilename,\n };\n debug('staging %o@%o as %o', record.packageName, record.version, record.id);\n\n return this.enqueueIndexMutation(async () => {\n const items = await this.readIndex();\n const alreadyStaged = items.find(\n (item) => item.packageName === record.packageName && item.version === record.version\n );\n if (alreadyStaged) {\n throw errorUtils.getConflict(`${record.packageName}@${record.version} is already staged`);\n }\n\n const handler = this.itemStorage(record.id);\n // the tarball goes first: if it fails there is no record pointing at a\n // tarball that never landed.\n await this.writeTarball(handler, record.tarballFilename, tarball, { signal });\n const document: StageDocument = { record, packument: input.packument };\n await handler.savePackage(record.id, document as unknown as Manifest);\n await this.writeIndex([record, ...items]);\n\n this.logger.info(\n { packageName: record.packageName, version: record.version, stageId: record.id },\n 'staged @{packageName}@@{version} as @{stageId}'\n );\n return record;\n });\n }\n\n /** Every staged item, newest first, optionally narrowed to one package. */\n public async list(filter: ListStageFilter = {}): Promise<StageRecord[]> {\n const items = await this.readIndex();\n if (typeof filter.packageName === 'string') {\n return items.filter((item) => item.packageName === filter.packageName);\n }\n return items;\n }\n\n /** A single staged item, or `undefined` when the id is unknown. */\n public async get(id: string): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.id === id);\n }\n\n /** Whether a version of a package is already staged (used to answer 409). */\n public async findByVersion(\n packageName: string,\n version: string\n ): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.packageName === packageName && item.version === version);\n }\n\n /** The publish payload kept for a staged item, needed to approve it. */\n public async getPackument(id: string): Promise<StagedPackument | undefined> {\n const document = await this.readDocument(id);\n return document?.packument;\n }\n\n /** Read the staged tarball as a stream. */\n public async readTarball(id: string, { signal }: { signal: AbortSignal }): Promise<Readable> {\n const record = await this.get(id);\n if (!record) {\n throw errorUtils.getNotFound('no such staged package version');\n }\n return this.itemStorage(id).readTarball(record.tarballFilename, { signal });\n }\n\n /**\n * Drop a staged item and its tarball.\n *\n * @returns `false` when the id was unknown, `true` when something was removed.\n */\n public async remove(id: string): Promise<boolean> {\n const record = await this.get(id);\n if (!record) {\n return false;\n }\n const handler = this.itemStorage(id);\n // the index entry goes first: a leftover folder is invisible, whereas a\n // leftover index entry would surface a staged item that cannot be read.\n await this.updateIndex((items) => items.filter((item) => item.id !== id));\n // the folder can only be dropped once it is empty, so both files go first\n for (const file of [record.tarballFilename, STORAGE.PACKAGE_FILE_NAME]) {\n try {\n await handler.deletePackage(file);\n } catch (err: any) {\n debug('could not delete staged file %o: %s', file, err?.message);\n }\n }\n try {\n await handler.removePackage(id);\n } catch (err: any) {\n debug('could not delete staged folder %o: %s', id, err?.message);\n }\n debug('removed staged item %o', id);\n return true;\n }\n\n private itemStorage(id: string): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(`${STAGE_NAMESPACE}/${id}`);\n }\n\n private indexStorage(): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(STAGE_NAMESPACE);\n }\n\n private async readDocument(id: string): Promise<StageDocument | undefined> {\n try {\n const document = (await this.itemStorage(id).readPackage(id)) as unknown as StageDocument;\n return document;\n } catch (err: any) {\n if (isNotFound(err)) {\n return undefined;\n }\n throw err;\n }\n }\n\n private async readIndex(): Promise<StageRecord[]> {\n try {\n const index = (await this.indexStorage().readPackage(STAGE_INDEX)) as unknown as StageIndex;\n return Array.isArray(index?.items) ? index.items : [];\n } catch (err: any) {\n if (isNotFound(err)) {\n debug('no stage index yet');\n return [];\n }\n throw err;\n }\n }\n\n /** Read-modify-write the index, serialized against other callers (see {@link indexQueue}). */\n private async updateIndex(\n mutate: (items: StageRecord[]) => StageRecord[]\n ): Promise<StageRecord[]> {\n return this.enqueueIndexMutation(async (): Promise<StageRecord[]> => {\n const items = mutate(await this.readIndex());\n await this.writeIndex(items);\n return items;\n });\n }\n\n private async enqueueIndexMutation<T>(run: () => Promise<T>): Promise<T> {\n const queued = this.indexQueue.then(run, run);\n // keep the chain alive even when a caller rejects\n this.indexQueue = queued.catch(() => undefined);\n return queued;\n }\n\n private async writeIndex(items: StageRecord[]): Promise<void> {\n const index: StageIndex = { items };\n await this.indexStorage().savePackage(STAGE_INDEX, index as unknown as Manifest);\n }\n\n private async writeTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<void> {\n const writeStream = await handler.writeTarball(filename, { signal });\n\n // `StorageHandler.writeTarball` only promises a Writable. The two bundled\n // plugins happen to emit 'open' before accepting writes, but nothing in the\n // interface says a plugin must, and waiting for it would hang staging\n // forever on one that does not. Piping straight away is correct either way:\n // a stream that is not ready yet buffers the writes.\n await pipeline(Readable.from(tarball), writeStream);\n\n // Both plugins rename the temporary file inside an async 'close' handler\n // that nobody awaits, so 'close' does not guarantee the tarball is in its\n // final place yet. Give the rename a bounded chance to land.\n await this.waitForTarball(handler, filename);\n }\n\n private async waitForTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n attempts = 20\n ): Promise<void> {\n for (let attempt = 0; attempt < attempts; attempt++) {\n if (await handler.hasTarball(filename)) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw errorUtils.getInternalError(`staged tarball ${filename} was never persisted`);\n }\n}\n"],"mappings":"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"}
|
package/build/stage-storage.mjs
CHANGED
|
@@ -24,7 +24,10 @@ var STAGE_INDEX = "stage-index";
|
|
|
24
24
|
/** Strip the internal fields before the record leaves the registry. */
|
|
25
25
|
function toStagePackageVersion(record) {
|
|
26
26
|
const { tarballFilename: _tarballFilename, ...rest } = record;
|
|
27
|
-
return
|
|
27
|
+
return {
|
|
28
|
+
...rest,
|
|
29
|
+
status: "staged"
|
|
30
|
+
};
|
|
28
31
|
}
|
|
29
32
|
function isNotFound(err) {
|
|
30
33
|
return err?.code === "ENOENT" || err?.code === 404 || err?.status === 404;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stage-storage.mjs","names":[],"sources":["../src/stage-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { randomUUID } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils, validationUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { STORAGE } from './lib/storage-utils';\n\nconst debug = buildDebug('verdaccio:storage:stage');\n\n/**\n * Storage namespace holding every staged item.\n *\n * It is deliberately NOT registered through `storagePlugin.add()`: doing so\n * would list it in the plugin database and leak it into search results and the\n * web UI package list. The staged items are enumerated through {@link STAGE_INDEX}\n * instead.\n */\nexport const STAGE_NAMESPACE = '.stage';\n\n/**\n * Logical name used when reading/writing the index document. Storage handlers\n * are scoped to a folder and ignore this argument when resolving the path, but\n * it is what shows up in plugin logs, so keep it descriptive.\n */\nconst STAGE_INDEX = 'stage-index';\n\n/**\n * Metadata of a package version awaiting review.\n *\n * Mirrors the `StagePackageVersion` schema documented by npmjs, plus\n * {@link StageRecord.tarballFilename}, which is internal and must be stripped\n * before serving the record over the API (see {@link toStagePackageVersion}).\n */\nexport interface StageRecord {\n /** Unique identifier. Must be a UUID: the npm CLI validates the format client side. */\n id: string;\n packageName: string;\n version: string;\n /** dist-tag the version will be published under once approved. */\n tag: string;\n /** ISO timestamp of when the version was staged. */\n createdAt: string;\n /** Username of whoever staged the version. */\n actor: string;\n actorType: 'user';\n access: 'public' | 'private';\n /** sha1 of the tarball, as reported by `npm stage list`. */\n shasum: string;\n /** Internal: name of the tarball inside the staged item folder. */\n tarballFilename: string;\n}\n\n/** Shape served to clients: {@link StageRecord} without the internal fields. */\nexport type StagePackageVersion = Omit<StageRecord, 'tarballFilename'>;\n\n/**\n * The publish payload kept for a staged item, minus `_attachments` (the tarball\n * is stored separately as a real tarball, not as base64 inside this document).\n */\nexport type StagedPackument = Omit<Manifest, '_attachments'>;\n\n/** Persisted document of a single staged item. */\ninterface StageDocument {\n record: StageRecord;\n packument: StagedPackument;\n}\n\n/** Persisted index of every staged item, newest first. */\ninterface StageIndex {\n items: StageRecord[];\n}\n\nexport interface AddStageInput {\n packageName: string;\n version: string;\n tag: string;\n actor: string;\n access: 'public' | 'private';\n shasum: string;\n tarballFilename: string;\n packument: StagedPackument;\n}\n\nexport interface ListStageFilter {\n /** Restrict the result to a single package name. */\n packageName?: string;\n}\n\n/** Strip the internal fields before the record leaves the registry. */\nexport function toStagePackageVersion(record: StageRecord): StagePackageVersion {\n const { tarballFilename: _tarballFilename, ...rest } = record;\n return rest;\n}\n\nfunction isNotFound(err: any): boolean {\n return err?.code === 'ENOENT' || err?.code === 404 || err?.status === 404;\n}\n\n/**\n * Persistence for the staged publish workflow (`npm stage`).\n *\n * Everything goes through {@link pluginUtils.StorageHandler}, so it works with\n * any storage plugin (local-storage, memory, S3, GCS) without the plugin having\n * to know that staging exists. On disk it looks like:\n *\n * ```\n * <storage>/.stage/package.json index of every staged item\n * <storage>/.stage/<uuid>/package.json the staged item (record + packument)\n * <storage>/.stage/<uuid>/<name>-<v>.tgz the staged tarball\n * ```\n */\nexport class StageStorage {\n private readonly plugin: pluginUtils.Storage<any>;\n private readonly logger: Logger;\n /**\n * Serializes index mutations within this process.\n *\n * `StorageHandler.updatePackage()` takes a lock in local-storage but releases\n * it before we get to save, and other plugins do not lock at all, so a\n * read-modify-write on the index would race. This keeps a single Verdaccio\n * process consistent; several processes sharing one storage backend are NOT\n * supported.\n */\n private indexQueue: Promise<unknown> = Promise.resolve();\n\n public constructor(plugin: pluginUtils.Storage<any>, logger: Logger) {\n this.plugin = plugin;\n this.logger = logger;\n }\n\n /** Stage a new version. Returns the persisted record, including its generated id. */\n public async add(\n input: AddStageInput,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<StageRecord> {\n // the filename reaches us from the `_attachments` key of the request, so it\n // is attacker controlled. The regular publish path asserts the same thing\n // before writing; without it a name like `..` escapes the item folder and\n // the write fails inside an unawaited stream handler, taking the process\n // down rather than the request.\n if (validationUtils.validateName(input.tarballFilename) === false) {\n throw errorUtils.getBadRequest(`invalid tarball name ${input.tarballFilename}`);\n }\n\n const record: StageRecord = {\n id: randomUUID(),\n packageName: input.packageName,\n version: input.version,\n tag: input.tag,\n createdAt: new Date().toISOString(),\n actor: input.actor,\n actorType: 'user',\n access: input.access,\n shasum: input.shasum,\n tarballFilename: input.tarballFilename,\n };\n debug('staging %o@%o as %o', record.packageName, record.version, record.id);\n\n return this.enqueueIndexMutation(async () => {\n const items = await this.readIndex();\n const alreadyStaged = items.find(\n (item) => item.packageName === record.packageName && item.version === record.version\n );\n if (alreadyStaged) {\n throw errorUtils.getConflict(`${record.packageName}@${record.version} is already staged`);\n }\n\n const handler = this.itemStorage(record.id);\n // the tarball goes first: if it fails there is no record pointing at a\n // tarball that never landed.\n await this.writeTarball(handler, record.tarballFilename, tarball, { signal });\n const document: StageDocument = { record, packument: input.packument };\n await handler.savePackage(record.id, document as unknown as Manifest);\n await this.writeIndex([record, ...items]);\n\n this.logger.info(\n { packageName: record.packageName, version: record.version, stageId: record.id },\n 'staged @{packageName}@@{version} as @{stageId}'\n );\n return record;\n });\n }\n\n /** Every staged item, newest first, optionally narrowed to one package. */\n public async list(filter: ListStageFilter = {}): Promise<StageRecord[]> {\n const items = await this.readIndex();\n if (typeof filter.packageName === 'string') {\n return items.filter((item) => item.packageName === filter.packageName);\n }\n return items;\n }\n\n /** A single staged item, or `undefined` when the id is unknown. */\n public async get(id: string): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.id === id);\n }\n\n /** Whether a version of a package is already staged (used to answer 409). */\n public async findByVersion(\n packageName: string,\n version: string\n ): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.packageName === packageName && item.version === version);\n }\n\n /** The publish payload kept for a staged item, needed to approve it. */\n public async getPackument(id: string): Promise<StagedPackument | undefined> {\n const document = await this.readDocument(id);\n return document?.packument;\n }\n\n /** Read the staged tarball as a stream. */\n public async readTarball(id: string, { signal }: { signal: AbortSignal }): Promise<Readable> {\n const record = await this.get(id);\n if (!record) {\n throw errorUtils.getNotFound('no such staged package version');\n }\n return this.itemStorage(id).readTarball(record.tarballFilename, { signal });\n }\n\n /**\n * Drop a staged item and its tarball.\n *\n * @returns `false` when the id was unknown, `true` when something was removed.\n */\n public async remove(id: string): Promise<boolean> {\n const record = await this.get(id);\n if (!record) {\n return false;\n }\n const handler = this.itemStorage(id);\n // the index entry goes first: a leftover folder is invisible, whereas a\n // leftover index entry would surface a staged item that cannot be read.\n await this.updateIndex((items) => items.filter((item) => item.id !== id));\n // the folder can only be dropped once it is empty, so both files go first\n for (const file of [record.tarballFilename, STORAGE.PACKAGE_FILE_NAME]) {\n try {\n await handler.deletePackage(file);\n } catch (err: any) {\n debug('could not delete staged file %o: %s', file, err?.message);\n }\n }\n try {\n await handler.removePackage(id);\n } catch (err: any) {\n debug('could not delete staged folder %o: %s', id, err?.message);\n }\n debug('removed staged item %o', id);\n return true;\n }\n\n private itemStorage(id: string): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(`${STAGE_NAMESPACE}/${id}`);\n }\n\n private indexStorage(): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(STAGE_NAMESPACE);\n }\n\n private async readDocument(id: string): Promise<StageDocument | undefined> {\n try {\n const document = (await this.itemStorage(id).readPackage(id)) as unknown as StageDocument;\n return document;\n } catch (err: any) {\n if (isNotFound(err)) {\n return undefined;\n }\n throw err;\n }\n }\n\n private async readIndex(): Promise<StageRecord[]> {\n try {\n const index = (await this.indexStorage().readPackage(STAGE_INDEX)) as unknown as StageIndex;\n return Array.isArray(index?.items) ? index.items : [];\n } catch (err: any) {\n if (isNotFound(err)) {\n debug('no stage index yet');\n return [];\n }\n throw err;\n }\n }\n\n /** Read-modify-write the index, serialized against other callers (see {@link indexQueue}). */\n private async updateIndex(\n mutate: (items: StageRecord[]) => StageRecord[]\n ): Promise<StageRecord[]> {\n return this.enqueueIndexMutation(async (): Promise<StageRecord[]> => {\n const items = mutate(await this.readIndex());\n await this.writeIndex(items);\n return items;\n });\n }\n\n private async enqueueIndexMutation<T>(run: () => Promise<T>): Promise<T> {\n const queued = this.indexQueue.then(run, run);\n // keep the chain alive even when a caller rejects\n this.indexQueue = queued.catch(() => undefined);\n return queued;\n }\n\n private async writeIndex(items: StageRecord[]): Promise<void> {\n const index: StageIndex = { items };\n await this.indexStorage().savePackage(STAGE_INDEX, index as unknown as Manifest);\n }\n\n private async writeTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<void> {\n const writeStream = await handler.writeTarball(filename, { signal });\n\n // `StorageHandler.writeTarball` only promises a Writable. The two bundled\n // plugins happen to emit 'open' before accepting writes, but nothing in the\n // interface says a plugin must, and waiting for it would hang staging\n // forever on one that does not. Piping straight away is correct either way:\n // a stream that is not ready yet buffers the writes.\n await pipeline(Readable.from(tarball), writeStream);\n\n // Both plugins rename the temporary file inside an async 'close' handler\n // that nobody awaits, so 'close' does not guarantee the tarball is in its\n // final place yet. Give the rename a bounded chance to land.\n await this.waitForTarball(handler, filename);\n }\n\n private async waitForTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n attempts = 20\n ): Promise<void> {\n for (let attempt = 0; attempt < attempts; attempt++) {\n if (await handler.hasTarball(filename)) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw errorUtils.getInternalError(`staged tarball ${filename} was never persisted`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stage-storage.mjs","names":[],"sources":["../src/stage-storage.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { randomUUID } from 'node:crypto';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils, validationUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { STORAGE } from './lib/storage-utils';\n\nconst debug = buildDebug('verdaccio:storage:stage');\n\n/**\n * Storage namespace holding every staged item.\n *\n * It is deliberately NOT registered through `storagePlugin.add()`: doing so\n * would list it in the plugin database and leak it into search results and the\n * web UI package list. The staged items are enumerated through {@link STAGE_INDEX}\n * instead.\n */\nexport const STAGE_NAMESPACE = '.stage';\n\n/**\n * Logical name used when reading/writing the index document. Storage handlers\n * are scoped to a folder and ignore this argument when resolving the path, but\n * it is what shows up in plugin logs, so keep it descriptive.\n */\nconst STAGE_INDEX = 'stage-index';\n\n/**\n * Metadata of a package version awaiting review.\n *\n * Mirrors the `StagePackageVersion` schema documented by npmjs, plus\n * {@link StageRecord.tarballFilename}, which is internal and must be stripped\n * before serving the record over the API (see {@link toStagePackageVersion}).\n */\nexport interface StageRecord {\n /** Unique identifier. Must be a UUID: the npm CLI validates the format client side. */\n id: string;\n packageName: string;\n version: string;\n /** dist-tag the version will be published under once approved. */\n tag: string;\n /** ISO timestamp of when the version was staged. */\n createdAt: string;\n /** Username of whoever staged the version. */\n actor: string;\n actorType: 'user';\n access: 'public' | 'private';\n /** sha1 of the tarball, as reported by `npm stage list`. */\n shasum: string;\n /** Internal: name of the tarball inside the staged item folder. */\n tarballFilename: string;\n}\n\n/** Shape served to clients: {@link StageRecord} without the internal fields. */\nexport type StagePackageVersion = Omit<StageRecord, 'tarballFilename'> & {\n status: 'staged';\n};\n\n/**\n * The publish payload kept for a staged item, minus `_attachments` (the tarball\n * is stored separately as a real tarball, not as base64 inside this document).\n */\nexport type StagedPackument = Omit<Manifest, '_attachments'>;\n\n/** Persisted document of a single staged item. */\ninterface StageDocument {\n record: StageRecord;\n packument: StagedPackument;\n}\n\n/** Persisted index of every staged item, newest first. */\ninterface StageIndex {\n items: StageRecord[];\n}\n\nexport interface AddStageInput {\n packageName: string;\n version: string;\n tag: string;\n actor: string;\n access: 'public' | 'private';\n shasum: string;\n tarballFilename: string;\n packument: StagedPackument;\n}\n\nexport interface ListStageFilter {\n /** Restrict the result to a single package name. */\n packageName?: string;\n}\n\n/** Strip the internal fields before the record leaves the registry. */\nexport function toStagePackageVersion(record: StageRecord): StagePackageVersion {\n const { tarballFilename: _tarballFilename, ...rest } = record;\n return { ...rest, status: 'staged' };\n}\n\nfunction isNotFound(err: any): boolean {\n return err?.code === 'ENOENT' || err?.code === 404 || err?.status === 404;\n}\n\n/**\n * Persistence for the staged publish workflow (`npm stage`).\n *\n * Everything goes through {@link pluginUtils.StorageHandler}, so it works with\n * any storage plugin (local-storage, memory, S3, GCS) without the plugin having\n * to know that staging exists. On disk it looks like:\n *\n * ```\n * <storage>/.stage/package.json index of every staged item\n * <storage>/.stage/<uuid>/package.json the staged item (record + packument)\n * <storage>/.stage/<uuid>/<name>-<v>.tgz the staged tarball\n * ```\n */\nexport class StageStorage {\n private readonly plugin: pluginUtils.Storage<any>;\n private readonly logger: Logger;\n /**\n * Serializes index mutations within this process.\n *\n * `StorageHandler.updatePackage()` takes a lock in local-storage but releases\n * it before we get to save, and other plugins do not lock at all, so a\n * read-modify-write on the index would race. This keeps a single Verdaccio\n * process consistent; several processes sharing one storage backend are NOT\n * supported.\n */\n private indexQueue: Promise<unknown> = Promise.resolve();\n\n public constructor(plugin: pluginUtils.Storage<any>, logger: Logger) {\n this.plugin = plugin;\n this.logger = logger;\n }\n\n /** Stage a new version. Returns the persisted record, including its generated id. */\n public async add(\n input: AddStageInput,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<StageRecord> {\n // the filename reaches us from the `_attachments` key of the request, so it\n // is attacker controlled. The regular publish path asserts the same thing\n // before writing; without it a name like `..` escapes the item folder and\n // the write fails inside an unawaited stream handler, taking the process\n // down rather than the request.\n if (validationUtils.validateName(input.tarballFilename) === false) {\n throw errorUtils.getBadRequest(`invalid tarball name ${input.tarballFilename}`);\n }\n\n const record: StageRecord = {\n id: randomUUID(),\n packageName: input.packageName,\n version: input.version,\n tag: input.tag,\n createdAt: new Date().toISOString(),\n actor: input.actor,\n actorType: 'user',\n access: input.access,\n shasum: input.shasum,\n tarballFilename: input.tarballFilename,\n };\n debug('staging %o@%o as %o', record.packageName, record.version, record.id);\n\n return this.enqueueIndexMutation(async () => {\n const items = await this.readIndex();\n const alreadyStaged = items.find(\n (item) => item.packageName === record.packageName && item.version === record.version\n );\n if (alreadyStaged) {\n throw errorUtils.getConflict(`${record.packageName}@${record.version} is already staged`);\n }\n\n const handler = this.itemStorage(record.id);\n // the tarball goes first: if it fails there is no record pointing at a\n // tarball that never landed.\n await this.writeTarball(handler, record.tarballFilename, tarball, { signal });\n const document: StageDocument = { record, packument: input.packument };\n await handler.savePackage(record.id, document as unknown as Manifest);\n await this.writeIndex([record, ...items]);\n\n this.logger.info(\n { packageName: record.packageName, version: record.version, stageId: record.id },\n 'staged @{packageName}@@{version} as @{stageId}'\n );\n return record;\n });\n }\n\n /** Every staged item, newest first, optionally narrowed to one package. */\n public async list(filter: ListStageFilter = {}): Promise<StageRecord[]> {\n const items = await this.readIndex();\n if (typeof filter.packageName === 'string') {\n return items.filter((item) => item.packageName === filter.packageName);\n }\n return items;\n }\n\n /** A single staged item, or `undefined` when the id is unknown. */\n public async get(id: string): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.id === id);\n }\n\n /** Whether a version of a package is already staged (used to answer 409). */\n public async findByVersion(\n packageName: string,\n version: string\n ): Promise<StageRecord | undefined> {\n const items = await this.readIndex();\n return items.find((item) => item.packageName === packageName && item.version === version);\n }\n\n /** The publish payload kept for a staged item, needed to approve it. */\n public async getPackument(id: string): Promise<StagedPackument | undefined> {\n const document = await this.readDocument(id);\n return document?.packument;\n }\n\n /** Read the staged tarball as a stream. */\n public async readTarball(id: string, { signal }: { signal: AbortSignal }): Promise<Readable> {\n const record = await this.get(id);\n if (!record) {\n throw errorUtils.getNotFound('no such staged package version');\n }\n return this.itemStorage(id).readTarball(record.tarballFilename, { signal });\n }\n\n /**\n * Drop a staged item and its tarball.\n *\n * @returns `false` when the id was unknown, `true` when something was removed.\n */\n public async remove(id: string): Promise<boolean> {\n const record = await this.get(id);\n if (!record) {\n return false;\n }\n const handler = this.itemStorage(id);\n // the index entry goes first: a leftover folder is invisible, whereas a\n // leftover index entry would surface a staged item that cannot be read.\n await this.updateIndex((items) => items.filter((item) => item.id !== id));\n // the folder can only be dropped once it is empty, so both files go first\n for (const file of [record.tarballFilename, STORAGE.PACKAGE_FILE_NAME]) {\n try {\n await handler.deletePackage(file);\n } catch (err: any) {\n debug('could not delete staged file %o: %s', file, err?.message);\n }\n }\n try {\n await handler.removePackage(id);\n } catch (err: any) {\n debug('could not delete staged folder %o: %s', id, err?.message);\n }\n debug('removed staged item %o', id);\n return true;\n }\n\n private itemStorage(id: string): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(`${STAGE_NAMESPACE}/${id}`);\n }\n\n private indexStorage(): pluginUtils.StorageHandler {\n return this.plugin.getPackageStorage(STAGE_NAMESPACE);\n }\n\n private async readDocument(id: string): Promise<StageDocument | undefined> {\n try {\n const document = (await this.itemStorage(id).readPackage(id)) as unknown as StageDocument;\n return document;\n } catch (err: any) {\n if (isNotFound(err)) {\n return undefined;\n }\n throw err;\n }\n }\n\n private async readIndex(): Promise<StageRecord[]> {\n try {\n const index = (await this.indexStorage().readPackage(STAGE_INDEX)) as unknown as StageIndex;\n return Array.isArray(index?.items) ? index.items : [];\n } catch (err: any) {\n if (isNotFound(err)) {\n debug('no stage index yet');\n return [];\n }\n throw err;\n }\n }\n\n /** Read-modify-write the index, serialized against other callers (see {@link indexQueue}). */\n private async updateIndex(\n mutate: (items: StageRecord[]) => StageRecord[]\n ): Promise<StageRecord[]> {\n return this.enqueueIndexMutation(async (): Promise<StageRecord[]> => {\n const items = mutate(await this.readIndex());\n await this.writeIndex(items);\n return items;\n });\n }\n\n private async enqueueIndexMutation<T>(run: () => Promise<T>): Promise<T> {\n const queued = this.indexQueue.then(run, run);\n // keep the chain alive even when a caller rejects\n this.indexQueue = queued.catch(() => undefined);\n return queued;\n }\n\n private async writeIndex(items: StageRecord[]): Promise<void> {\n const index: StageIndex = { items };\n await this.indexStorage().savePackage(STAGE_INDEX, index as unknown as Manifest);\n }\n\n private async writeTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n tarball: Buffer,\n { signal }: { signal: AbortSignal }\n ): Promise<void> {\n const writeStream = await handler.writeTarball(filename, { signal });\n\n // `StorageHandler.writeTarball` only promises a Writable. The two bundled\n // plugins happen to emit 'open' before accepting writes, but nothing in the\n // interface says a plugin must, and waiting for it would hang staging\n // forever on one that does not. Piping straight away is correct either way:\n // a stream that is not ready yet buffers the writes.\n await pipeline(Readable.from(tarball), writeStream);\n\n // Both plugins rename the temporary file inside an async 'close' handler\n // that nobody awaits, so 'close' does not guarantee the tarball is in its\n // final place yet. Give the rename a bounded chance to land.\n await this.waitForTarball(handler, filename);\n }\n\n private async waitForTarball(\n handler: pluginUtils.StorageHandler,\n filename: string,\n attempts = 20\n ): Promise<void> {\n for (let attempt = 0; attempt < attempts; attempt++) {\n if (await handler.hasTarball(filename)) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw errorUtils.getInternalError(`staged tarball ${filename} was never persisted`);\n }\n}\n"],"mappings":"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"}
|
package/build/storage.d.ts
CHANGED
|
@@ -87,6 +87,8 @@ declare class Storage {
|
|
|
87
87
|
* defeat it.
|
|
88
88
|
*/
|
|
89
89
|
getStageStorage(): StageStorage;
|
|
90
|
+
/** Resolve a locally known version without fetching lifecycle information from uplinks. */
|
|
91
|
+
getPackageVersionStatus(name: string, version: string): Promise<'published' | 'staged'>;
|
|
90
92
|
/**
|
|
91
93
|
* Retrieve a wrapper that provide access to the package location.
|
|
92
94
|
* @param {Object} pkgName package name.
|
package/build/storage.js
CHANGED
|
@@ -510,6 +510,22 @@ var Storage = class {
|
|
|
510
510
|
if (this.stageStorage === null) this.stageStorage = new require_stage_storage.StageStorage(this.localStorage.getStoragePlugin(), this.logger);
|
|
511
511
|
return this.stageStorage;
|
|
512
512
|
}
|
|
513
|
+
/** Resolve a locally known version without fetching lifecycle information from uplinks. */
|
|
514
|
+
async getPackageVersionStatus(name, version) {
|
|
515
|
+
const staged = this.config.flags?.stage ? await this.getStageStorage().findByVersion(name, version) : void 0;
|
|
516
|
+
let manifest;
|
|
517
|
+
try {
|
|
518
|
+
manifest = await this.getPackageLocalMetadata(name);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
if (error.status !== _verdaccio_core.HTTP_STATUS.NOT_FOUND) throw error;
|
|
521
|
+
}
|
|
522
|
+
if (manifest) {
|
|
523
|
+
const [filtered] = await this.applyFilters(manifest);
|
|
524
|
+
if (Object.hasOwn(filtered.versions, version)) return "published";
|
|
525
|
+
}
|
|
526
|
+
if (staged) return "staged";
|
|
527
|
+
throw _verdaccio_core.errorUtils.getNotFound("no such package version");
|
|
528
|
+
}
|
|
513
529
|
/**
|
|
514
530
|
* Retrieve a wrapper that provide access to the package location.
|
|
515
531
|
* @param {Object} pkgName package name.
|
|
@@ -729,7 +745,12 @@ var Storage = class {
|
|
|
729
745
|
}
|
|
730
746
|
async updateManifest(manifest, options) {
|
|
731
747
|
debug$1("update manifest %o for user %o", manifest._id, options.requestOptions.username);
|
|
732
|
-
|
|
748
|
+
const isDeprecation = require_storage_utils.isDeprecatedManifest(manifest);
|
|
749
|
+
if (isDeprecation && !options.allowUnpublish) {
|
|
750
|
+
const localManifest = await this.getPackageLocalMetadata(options.name);
|
|
751
|
+
if (Object.keys(localManifest.versions).some((version) => (0, lodash_es.isNil)(manifest.versions[version]))) throw _verdaccio_core.errorUtils.getBadRequest(_verdaccio_core.API_ERROR.UNSUPORTED_REGISTRY_CALL);
|
|
752
|
+
}
|
|
753
|
+
if (isDeprecation) {
|
|
733
754
|
debug$1("update manifest deprecate");
|
|
734
755
|
await this.deprecate(manifest, { ...options });
|
|
735
756
|
} else if (require_publish_utils.isPublishablePackage(manifest) === false && Array.isArray(manifest.maintainers)) {
|
|
@@ -751,6 +772,7 @@ var Storage = class {
|
|
|
751
772
|
}
|
|
752
773
|
return message;
|
|
753
774
|
} else if (_verdaccio_core.validationUtils.validateUnPublishSingleVersion(manifest)) {
|
|
775
|
+
if (!options.allowUnpublish) throw _verdaccio_core.errorUtils.getBadRequest(_verdaccio_core.API_ERROR.UNSUPORTED_REGISTRY_CALL);
|
|
754
776
|
debug$1("unpublish a version");
|
|
755
777
|
await this.unPublishAPackage(manifest, { ...options });
|
|
756
778
|
} else {
|
|
@@ -852,6 +874,8 @@ var Storage = class {
|
|
|
852
874
|
}
|
|
853
875
|
const [versionToPublish] = Object.keys(versions);
|
|
854
876
|
const [firstAttachmentKey] = Object.keys(_attachments);
|
|
877
|
+
const tarballName = (0, node_path.basename)(firstAttachmentKey);
|
|
878
|
+
if (!_verdaccio_core.validationUtils.validateName(firstAttachmentKey) || !_verdaccio_core.validationUtils.validateName(tarballName)) throw _verdaccio_core.errorUtils.getBadRequest(`invalid tarball name ${firstAttachmentKey}`);
|
|
855
879
|
const buffer = this.getBufferManifest(body._attachments[firstAttachmentKey].data);
|
|
856
880
|
try {
|
|
857
881
|
const localManifest = await this.getPackagelocalByName(name);
|
|
@@ -895,7 +919,7 @@ var Storage = class {
|
|
|
895
919
|
}
|
|
896
920
|
try {
|
|
897
921
|
const readable = node_stream.Readable.from(buffer);
|
|
898
|
-
await this.uploadTarball(name,
|
|
922
|
+
await this.uploadTarball(name, tarballName, readable, { signal: options.signal });
|
|
899
923
|
} catch (err) {
|
|
900
924
|
this.logger.error({ err }, "upload tarball has failed: @{err.message}");
|
|
901
925
|
throw err;
|
|
@@ -935,9 +959,13 @@ var Storage = class {
|
|
|
935
959
|
});
|
|
936
960
|
return stream;
|
|
937
961
|
}).then((stream) => {
|
|
962
|
+
if (stream.errored) throw stream.errored;
|
|
938
963
|
(0, node_stream_promises.pipeline)(contentReadable, stream, { signal }).then(() => {
|
|
939
964
|
debug$1("success pipe upload tarball");
|
|
940
965
|
}).catch(reject);
|
|
966
|
+
}).catch((err) => {
|
|
967
|
+
contentReadable.destroy();
|
|
968
|
+
reject(err);
|
|
941
969
|
});
|
|
942
970
|
});
|
|
943
971
|
}
|
|
@@ -969,20 +997,16 @@ var Storage = class {
|
|
|
969
997
|
else {
|
|
970
998
|
const localStorageWriteStream = await storage.writeTarball(filename, { signal });
|
|
971
999
|
let uploadFailed = false;
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
filename,
|
|
980
|
-
pkgName
|
|
981
|
-
}, "error uploading tarball @{filename} for @{pkgName}: @{err.message}");
|
|
982
|
-
}
|
|
1000
|
+
(0, node_stream_promises.pipeline)(uploadStream, transformHash, localStorageWriteStream, { signal }).catch((err) => {
|
|
1001
|
+
uploadFailed = true;
|
|
1002
|
+
this.logger.warn({
|
|
1003
|
+
err,
|
|
1004
|
+
filename,
|
|
1005
|
+
pkgName
|
|
1006
|
+
}, "error uploading tarball @{filename} for @{pkgName}: @{err.message}");
|
|
983
1007
|
});
|
|
984
1008
|
localStorageWriteStream.on("close", async () => {
|
|
985
|
-
if (uploadFailed) {
|
|
1009
|
+
if (uploadFailed || !localStorageWriteStream.writableFinished) {
|
|
986
1010
|
debug$1("skip metadata update for failed upload %o for %o", filename, pkgName);
|
|
987
1011
|
return;
|
|
988
1012
|
}
|