mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
refactor: fetch package endpoint and basic integration fastify (#2750)
* refactor: download manifest endpoint and integrate fastify * fix file not found issue * add temporary migration method to storage memory * sanitize fs methods * restore tests * chore: restore sanitilize will do later * add tests * add changeset * lint * trying something test * chore: lint * restore code * fix e2e * fix lint
This commit is contained in:
parent
5bb049a79b
commit
a828271d63
24 changed files with 601 additions and 77 deletions
37
.changeset/bright-poems-obey.md
Normal file
37
.changeset/bright-poems-obey.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
'@verdaccio/api': major
|
||||
'@verdaccio/fastify-migration': major
|
||||
'@verdaccio/tarball': major
|
||||
'@verdaccio/local-storage': major
|
||||
'verdaccio-memory': major
|
||||
'@verdaccio/server': major
|
||||
'@verdaccio/store': major
|
||||
'@verdaccio/utils': major
|
||||
---
|
||||
|
||||
refactor: download manifest endpoint and integrate fastify
|
||||
|
||||
Much simpler API for fetching a package
|
||||
|
||||
```
|
||||
const manifest = await storage.getPackageNext({
|
||||
name,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
version: queryVersion,
|
||||
requestOptions,
|
||||
});
|
||||
```
|
||||
|
||||
> not perfect, the `req` still is being passed to the proxy (this has to be refactored at proxy package) and then removed from here, in proxy we pass the request instance to the `request` library.
|
||||
|
||||
### Details
|
||||
|
||||
- `async/await` sugar for getPackage()
|
||||
- Improve and reuse code between current implementation and new fastify endpoint (add scaffolding for request manifest)
|
||||
- Improve performance
|
||||
- Add new tests
|
||||
|
||||
### Breaking changes
|
||||
|
||||
All storage plugins will stop to work since the storage uses `getPackageNext` method which is Promise based, I won't replace this now because will force me to update all plugins, I'll follow up in another PR. Currently will throw http 500
|
|
@ -15,7 +15,6 @@
|
|||
'@verdaccio/logger': patch
|
||||
'@verdaccio/logger-prettify': patch
|
||||
'@verdaccio/middleware': patch
|
||||
'@verdaccio/mock': patch
|
||||
'@verdaccio/node-api': patch
|
||||
'@verdaccio/proxy': patch
|
||||
'@verdaccio/server': patch
|
||||
|
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -204,8 +204,8 @@ jobs:
|
|||
run: pnpm recursive install --frozen-lockfile
|
||||
- name: Test CLI
|
||||
run: pnpm test:e2e:cli
|
||||
# env:
|
||||
# DEBUG: verdaccio*
|
||||
env:
|
||||
DEBUG: verdaccio*
|
||||
test-windows:
|
||||
needs: [format, lint]
|
||||
runs-on: windows-latest
|
||||
|
|
|
@ -19,6 +19,7 @@ node_modules/
|
|||
packages/core/local-storage/_storage/**
|
||||
packages/partials/storage_default_storage/
|
||||
packages/standalone/dist/bundle.js
|
||||
packages/verdaccio/dist/bundle.js
|
||||
docker-examples/v5/reverse_proxy/nginx/relative_path/storage/*
|
||||
build/
|
||||
.vscode/
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
"@verdaccio/logger": "workspace:6.0.0-6-next.7",
|
||||
"@verdaccio/middleware": "workspace:6.0.0-6-next.15",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.16",
|
||||
"@verdaccio/tarball": "workspace:11.0.0-6-next.10",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.9",
|
||||
"abortcontroller-polyfill": "1.7.3",
|
||||
"cookies": "0.8.0",
|
||||
|
|
|
@ -56,7 +56,7 @@ export default function (config: Config, auth: IAuth, storage: Storage): Router
|
|||
app.use(encodeScopePackage);
|
||||
// for "npm whoami"
|
||||
whoami(app);
|
||||
pkg(app, auth, storage, config);
|
||||
pkg(app, auth, storage);
|
||||
profile(app, auth);
|
||||
// @deprecated endpoint, 404 by default
|
||||
search(app);
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
import buildDebug from 'debug';
|
||||
import { Router } from 'express';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IAuth } from '@verdaccio/auth';
|
||||
import { API_ERROR, DIST_TAGS, HEADERS, errorUtils } from '@verdaccio/core';
|
||||
import { HEADERS, errorUtils } from '@verdaccio/core';
|
||||
import { allow } from '@verdaccio/middleware';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { convertDistRemoteToLocalTarballUrls } from '@verdaccio/tarball';
|
||||
import { Config, Package } from '@verdaccio/types';
|
||||
import { getVersion } from '@verdaccio/utils';
|
||||
|
||||
import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types/custom';
|
||||
|
||||
|
@ -18,7 +14,7 @@ const downloadStream = (
|
|||
packageName: string,
|
||||
filename: string,
|
||||
storage: any,
|
||||
req: $RequestExtend,
|
||||
_req: $RequestExtend,
|
||||
res: $ResponseExtend
|
||||
): void => {
|
||||
const stream = storage.getTarball(packageName, filename);
|
||||
|
@ -35,13 +31,17 @@ const downloadStream = (
|
|||
stream.pipe(res);
|
||||
};
|
||||
|
||||
export default function (route: Router, auth: IAuth, storage: Storage, config: Config): void {
|
||||
export default function (route: Router, auth: IAuth, storage: Storage): void {
|
||||
const can = allow(auth);
|
||||
// TODO: anonymous user?
|
||||
|
||||
route.get(
|
||||
'/:package/:version?',
|
||||
can('access'),
|
||||
function (req: $RequestExtend, _res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
async function (
|
||||
req: $RequestExtend,
|
||||
_res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): Promise<void> {
|
||||
debug('init package by version');
|
||||
const name = req.params.package;
|
||||
let queryVersion = req.params.version;
|
||||
|
@ -51,57 +51,27 @@ export default function (route: Router, auth: IAuth, storage: Storage, config: C
|
|||
// FIXME: if we migrate to req.hostname, the port is not longer included.
|
||||
host: req.host,
|
||||
};
|
||||
const getPackageMetaCallback = function (err, metadata: Package): void {
|
||||
if (err) {
|
||||
debug('error on fetch metadata for %o with error %o', name, err.message);
|
||||
return next(err);
|
||||
|
||||
try {
|
||||
// TODO: this is just temporary while I migrate all plugins to use the new API
|
||||
// the method will be renamed to getPackage again but Promise Based.
|
||||
if (!storage.getPackageNext) {
|
||||
throw errorUtils.getInternalError(
|
||||
'getPackageNext not implemented, check pr-2750 for more details'
|
||||
);
|
||||
}
|
||||
debug('convert dist remote to local with prefix %o', config?.url_prefix);
|
||||
metadata = convertDistRemoteToLocalTarballUrls(
|
||||
metadata,
|
||||
|
||||
const manifest = await storage.getPackageNext({
|
||||
name,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
version: queryVersion,
|
||||
requestOptions,
|
||||
config?.url_prefix
|
||||
);
|
||||
|
||||
debug('query by param version: %o', queryVersion);
|
||||
if (_.isNil(queryVersion)) {
|
||||
debug('param %o version found', queryVersion);
|
||||
return next(metadata);
|
||||
}
|
||||
|
||||
let version = getVersion(metadata.versions, queryVersion);
|
||||
debug('query by latest version %o and result %o', queryVersion, version);
|
||||
if (_.isNil(version) === false) {
|
||||
debug('latest version found %o', version);
|
||||
return next(version);
|
||||
}
|
||||
|
||||
if (_.isNil(metadata[DIST_TAGS]) === false) {
|
||||
if (_.isNil(metadata[DIST_TAGS][queryVersion]) === false) {
|
||||
queryVersion = metadata[DIST_TAGS][queryVersion];
|
||||
debug('dist-tag version found %o', queryVersion);
|
||||
version = getVersion(metadata.versions, queryVersion);
|
||||
if (_.isNil(version) === false) {
|
||||
debug('dist-tag found %o', version);
|
||||
return next(version);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug('dist tag not detected');
|
||||
}
|
||||
|
||||
debug('package version not found %o', queryVersion);
|
||||
return next(errorUtils.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`));
|
||||
};
|
||||
|
||||
debug('get package name %o', name);
|
||||
debug('uplinks look up enabled');
|
||||
storage.getPackage({
|
||||
name,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
callback: getPackageMetaCallback,
|
||||
});
|
||||
});
|
||||
next(manifest);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
|
48
packages/core/server/src/endpoints/package.ts
Normal file
48
packages/core/server/src/endpoints/package.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import buildDebug from 'debug';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
import { Package, Version } from '@verdaccio/types';
|
||||
|
||||
const debug = buildDebug('verdaccio:web:api:sidebar');
|
||||
export type $SidebarPackage = Package & { latest: Version };
|
||||
|
||||
async function manifestRoute(fastify: FastifyInstance) {
|
||||
fastify.get('/:packageName', async (request) => {
|
||||
// @ts-ignore
|
||||
const { packageName } = request.params;
|
||||
const storage = fastify.storage;
|
||||
debug('pkg name %s ', packageName);
|
||||
const data = await storage?.getPackageNext({
|
||||
name: packageName,
|
||||
req: request.raw,
|
||||
uplinksLook: true,
|
||||
requestOptions: {
|
||||
protocol: request.protocol,
|
||||
headers: request.headers as any,
|
||||
host: request.hostname,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
});
|
||||
|
||||
fastify.get('/:packageName/:version', async (request) => {
|
||||
// @ts-ignore
|
||||
const { packageName, version } = request.params;
|
||||
const storage = fastify.storage;
|
||||
debug('pkg name %s, with version / tag: %s ', packageName, version);
|
||||
const data = await storage?.getPackageNext({
|
||||
name: packageName,
|
||||
req: request.raw,
|
||||
version,
|
||||
uplinksLook: true,
|
||||
requestOptions: {
|
||||
protocol: request.protocol,
|
||||
headers: request.headers as any,
|
||||
host: request.hostname,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
export default manifestRoute;
|
|
@ -5,6 +5,7 @@ import { Config as AppConfig, createAnonymousRemoteUser } from '@verdaccio/confi
|
|||
import { Config as IConfig, RemoteUser } from '@verdaccio/types';
|
||||
|
||||
import distTags from './endpoints/dist-tags';
|
||||
import manifest from './endpoints/package';
|
||||
import ping from './endpoints/ping';
|
||||
import search from './endpoints/search';
|
||||
import tarball from './endpoints/tarball';
|
||||
|
@ -37,6 +38,7 @@ async function startServer({ logger, config }) {
|
|||
instance.register(user, { prefix: '/-/user' });
|
||||
instance.register(search);
|
||||
instance.register(whoami);
|
||||
instance.register(manifest);
|
||||
instance.register(tarball);
|
||||
instance.register(distTags);
|
||||
instance.register(readme, { prefix: '/-/verdaccio' });
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
import { RequestOptions } from '@verdaccio/url';
|
||||
|
||||
export { convertDistRemoteToLocalTarballUrls } from './convertDistRemoteToLocalTarballUrls';
|
||||
export { getLocalRegistryTarballUri } from './getLocalRegistryTarballUri';
|
||||
|
||||
export { RequestOptions };
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"debug": "4.3.3",
|
||||
"globby": "11.0.4",
|
||||
"lockfile": "1.0.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"lodash": "4.17.21",
|
||||
"lowdb": "1.0.0",
|
||||
"lru-cache": "6.0.0"
|
||||
|
|
|
@ -160,6 +160,21 @@ export default class LocalFS implements ILocalFSPackageManager {
|
|||
this._writeFile(this._getStorage(packageJSONFileName), this._convertToString(value), cb);
|
||||
}
|
||||
|
||||
public async readPackageNext(name: string): Promise<Package> {
|
||||
debug('read a package %o', name);
|
||||
try {
|
||||
const res = await this._readStorageFile(this._getStorage(packageJSONFileName));
|
||||
const data: any = JSON.parse(res.toString('utf8'));
|
||||
|
||||
debug('read storage file %o has succeed', name);
|
||||
return data;
|
||||
} catch (err: any) {
|
||||
debug('parse error');
|
||||
this.logger.error({ err, name }, 'error @{err.message} on parse @{name}');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
public readPackage(name: string, cb: Callback): void {
|
||||
debug('read a package %o', name);
|
||||
|
||||
|
@ -177,7 +192,7 @@ export default class LocalFS implements ILocalFSPackageManager {
|
|||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error({ err }, 'error on read storage file @{err.message}');
|
||||
debug('error on read storage file %o', err.message);
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -91,6 +91,15 @@ class MemoryHandler implements IPackageStorageManager {
|
|||
}
|
||||
}
|
||||
|
||||
public async readPackageNext(name: string): Promise<Package> {
|
||||
const json = this._getStorage(name);
|
||||
try {
|
||||
return typeof json === 'undefined' ? errorUtils.getNotFound() : null, parsePackage(json);
|
||||
} catch (err: any) {
|
||||
throw errorUtils.getNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
public readPackage(name: string, cb: ReadPackageCallback): void {
|
||||
debug('read package %o', name);
|
||||
const json = this._getStorage(name);
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
"@verdaccio/proxy": "workspace:6.0.0-6-next.15",
|
||||
"@verdaccio/streams": "workspace:11.0.0-6-next.5",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/tarball": "workspace:11.0.0-6-next.10",
|
||||
"JSONStream": "1.3.5",
|
||||
"abortcontroller-polyfill": "1.7.3",
|
||||
"async": "3.1.1",
|
||||
|
@ -61,9 +62,12 @@
|
|||
"@types/node": "16.11.11",
|
||||
"@verdaccio/mock": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.9",
|
||||
"@verdaccio/helper": "workspace:1.0.0",
|
||||
"undici": "4.7.3",
|
||||
"nock": "13.0.11",
|
||||
"undici-fetch": "1.0.0-rc.4",
|
||||
"tmp-promise": "3.0.3"
|
||||
"tmp-promise": "3.0.3",
|
||||
"node-mocks-http": "1.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
|
|
@ -706,11 +706,24 @@ class LocalStorage {
|
|||
return stream;
|
||||
}
|
||||
|
||||
public async getPackageMetadataNext(name: string): Promise<Package> {
|
||||
const storage: IPackageStorage = this._getLocalStorage(name);
|
||||
debug('get package metadata for %o', name);
|
||||
if (typeof storage === 'undefined') {
|
||||
// TODO: this might be a better an error to throw
|
||||
// if storage is not there cannot be 404.
|
||||
throw errorUtils.getNotFound();
|
||||
}
|
||||
|
||||
return await this._readPackageNext(name, storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a package by name.
|
||||
* @param {*} name
|
||||
* @param {*} callback
|
||||
* @return {Function}
|
||||
* @deprecated
|
||||
*/
|
||||
public getPackageMetadata(name: string, callback: Callback = (): void => {}): void {
|
||||
const storage: IPackageStorage = this._getLocalStorage(name);
|
||||
|
@ -790,6 +803,7 @@ class LocalStorage {
|
|||
* Read a json file from storage.
|
||||
* @param {Object} storage
|
||||
* @param {Function} callback
|
||||
* @deprecated
|
||||
*/
|
||||
private _readPackage(name: string, storage: any, callback: Callback): void {
|
||||
storage.readPackage(name, (err, result): void => {
|
||||
|
@ -805,6 +819,24 @@ class LocalStorage {
|
|||
});
|
||||
}
|
||||
|
||||
private async _readPackageNext(name: string, storage: any): Promise<Package> {
|
||||
try {
|
||||
const result: Package = await storage.readPackageNext(name);
|
||||
return normalizePackage(result);
|
||||
} catch (err: any) {
|
||||
if (err.code === STORAGE.NO_SUCH_FILE_ERROR || err.code === HTTP_STATUS.NOT_FOUND) {
|
||||
debug('package %s not found', name);
|
||||
throw errorUtils.getNotFound();
|
||||
}
|
||||
this.logger.error(
|
||||
{ err: err, file: STORAGE.PACKAGE_FILE_NAME },
|
||||
`error reading @{file}: @{!err.message}`
|
||||
);
|
||||
|
||||
throw errorUtils.getInternalError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve either a previous created local package or a boilerplate.
|
||||
* @param {*} pkgName
|
||||
|
|
|
@ -11,6 +11,7 @@ import { logger } from '@verdaccio/logger';
|
|||
import { ProxyStorage } from '@verdaccio/proxy';
|
||||
import { IProxy, ProxyList } from '@verdaccio/proxy';
|
||||
import { ReadTarball } from '@verdaccio/streams';
|
||||
import { convertDistRemoteToLocalTarballUrls } from '@verdaccio/tarball';
|
||||
import {
|
||||
Callback,
|
||||
CallbackAction,
|
||||
|
@ -28,7 +29,7 @@ import {
|
|||
Version,
|
||||
Versions,
|
||||
} from '@verdaccio/types';
|
||||
import { normalizeDistTags } from '@verdaccio/utils';
|
||||
import { getVersion, normalizeDistTags } from '@verdaccio/utils';
|
||||
|
||||
import { LocalStorage } from './local-storage';
|
||||
import { SearchInstance, SearchManager } from './search';
|
||||
|
@ -40,7 +41,7 @@ import {
|
|||
mergeUplinkTimeIntoLocal,
|
||||
publishPackage,
|
||||
} from './storage-utils';
|
||||
import { IGetPackageOptions, IPluginFilters, ISyncUplinks } from './type';
|
||||
import { IGetPackageOptions, IGetPackageOptionsNext, IPluginFilters, ISyncUplinks } from './type';
|
||||
import { setupUpLinks, updateVersionsHiddenUpLink } from './uplink-util';
|
||||
|
||||
if (semver.lte(process.version, 'v15.0.0')) {
|
||||
|
@ -101,9 +102,10 @@ class Storage {
|
|||
);
|
||||
debug('publishing a package for %o', name);
|
||||
await publishPackage(name, metadata, this.localStorage as LocalStorage);
|
||||
callback();
|
||||
// TODO: return published data and replace callback by a promise
|
||||
callback(null, true);
|
||||
} catch (err: any) {
|
||||
debug('error on add a package for %o with error %o', name, err?.error);
|
||||
debug('error on add a package for %o with error %o', name, err);
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
@ -344,6 +346,113 @@ class Storage {
|
|||
}
|
||||
}
|
||||
|
||||
public async getPackageNext(options: IGetPackageOptionsNext): Promise<Package | Version> {
|
||||
const { name } = options;
|
||||
debug('get package for %o', name);
|
||||
try {
|
||||
let data: Package;
|
||||
try {
|
||||
data = await this.localStorage.getPackageMetadataNext(name);
|
||||
} catch (err: any) {
|
||||
// we don't have package locally, so we need to fetch it from uplinks
|
||||
if (err && (!err.status || err.status >= HTTP_STATUS.INTERNAL_ERROR)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// time to sync with uplinks if we have any
|
||||
debug('sync uplinks for %o', name);
|
||||
// @ts-expect-error
|
||||
const [manifest, errors] = await this._syncUplinksMetadataNext(name, data, {
|
||||
req: options.req,
|
||||
uplinksLook: options.uplinksLook,
|
||||
keepUpLinkData: options.keepUpLinkData,
|
||||
});
|
||||
debug('no. sync uplinks errors %o', errors?.length);
|
||||
|
||||
// TODO: we could improve performance here, if a specific version is requested
|
||||
// we just convert that version and return it otherwise we convert
|
||||
// the whole manifest, bonus points for contribution :)
|
||||
// convert dist remotes to local bars
|
||||
const convertedManifest = convertDistRemoteToLocalTarballUrls(
|
||||
manifest,
|
||||
options.requestOptions,
|
||||
this.config.url_prefix
|
||||
);
|
||||
// if no version we return the whole manifest
|
||||
if (_.isNil(options.version)) {
|
||||
return convertedManifest;
|
||||
}
|
||||
|
||||
// we have version, so we need to return specific version
|
||||
const queryVersion = options.version as string;
|
||||
const version: Version | undefined = getVersion(convertedManifest.versions, options.version);
|
||||
debug('query by latest version %o and result %o', options.version, version);
|
||||
if (typeof version !== 'undefined') {
|
||||
debug('latest version found %o', version);
|
||||
return version;
|
||||
}
|
||||
|
||||
// the version could be a dist-tag eg: beta, alpha, so we find the matched version
|
||||
// on disg-tag list
|
||||
if (_.isNil(convertedManifest[DIST_TAGS]) === false) {
|
||||
if (_.isNil(convertedManifest[DIST_TAGS][queryVersion]) === false) {
|
||||
// the version found as a distag
|
||||
const matchedDisTagVersion: string = convertedManifest[DIST_TAGS][queryVersion];
|
||||
debug('dist-tag version found %o', matchedDisTagVersion);
|
||||
const disTagVersion: Version | undefined = getVersion(
|
||||
convertedManifest.versions,
|
||||
matchedDisTagVersion
|
||||
);
|
||||
if (typeof disTagVersion !== 'undefined') {
|
||||
debug('dist-tag found %o', disTagVersion);
|
||||
return disTagVersion;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug('dist tag not detected');
|
||||
}
|
||||
|
||||
// we didn't find the version, not found error
|
||||
debug('package version not found %o', queryVersion);
|
||||
throw errorUtils.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`);
|
||||
} catch (err: any) {
|
||||
this.logger.error(
|
||||
{ name, err: err.message },
|
||||
'error on get package for @{name} with error @{err}'
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private _syncUplinksMetadataNext(
|
||||
name: string,
|
||||
data: Package,
|
||||
{ uplinksLook, keepUpLinkData, req }
|
||||
): Promise<[Package, any[]]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._syncUplinksMetadata(
|
||||
name,
|
||||
data,
|
||||
{ req: req, uplinksLook },
|
||||
function getPackageSynUpLinksCallback(err, result: Package, uplinkErrors): void {
|
||||
if (err) {
|
||||
debug('error on sync package for %o with error %o', name, err?.message);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
const normalizedPkg = Object.assign({}, result, {
|
||||
...normalizeDistTags(cleanUpLinksRef(result, keepUpLinkData)),
|
||||
_attachments: {},
|
||||
});
|
||||
|
||||
debug('no. sync uplinks errors %o', uplinkErrors?.length);
|
||||
resolve([normalizedPkg, uplinkErrors]);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieve a package metadata for {name} package
|
||||
Function invokes localStorage.getPackage and uplink.get_package for every
|
||||
|
@ -451,6 +560,8 @@ class Storage {
|
|||
}
|
||||
}
|
||||
|
||||
// notas, debo migrar _syncUplinksMetadata algo mas lindo
|
||||
|
||||
/**
|
||||
* Function fetches package metadata from uplinks and synchronizes it with local data
|
||||
if package is available locally, it MUST be provided in pkginfo
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Callback, Config, IPluginStorageFilter } from '@verdaccio/types';
|
||||
|
||||
// @deprecated
|
||||
export interface IGetPackageOptions {
|
||||
callback: Callback;
|
||||
name: string;
|
||||
|
@ -7,6 +8,22 @@ export interface IGetPackageOptions {
|
|||
uplinksLook: boolean;
|
||||
req: any;
|
||||
}
|
||||
|
||||
export type IGetPackageOptionsNext = {
|
||||
// @deprecated remove this soon
|
||||
req: any;
|
||||
name: string;
|
||||
version?: string;
|
||||
keepUpLinkData?: boolean;
|
||||
uplinksLook: boolean;
|
||||
requestOptions: {
|
||||
// RequestOptions from url package
|
||||
host: string;
|
||||
protocol: string;
|
||||
headers: { [key: string]: string };
|
||||
};
|
||||
};
|
||||
|
||||
export interface ISyncUplinks {
|
||||
uplinksLook?: boolean;
|
||||
etag?: string;
|
||||
|
|
|
@ -8,8 +8,6 @@ import { SearchInstance } from '../src/search';
|
|||
|
||||
setup([]);
|
||||
|
||||
// jest.mock('@verdaccio/logger');
|
||||
|
||||
describe('search', () => {
|
||||
describe('search manager utils', () => {
|
||||
test('remove duplicates', () => {
|
||||
|
@ -18,6 +16,9 @@ describe('search', () => {
|
|||
package: {
|
||||
name: 'foo',
|
||||
},
|
||||
['dist-tags']: {
|
||||
latest: '1.0.0',
|
||||
},
|
||||
// @ts-expect-error
|
||||
score: {},
|
||||
searchScore: 0.4,
|
||||
|
|
245
packages/store/test/storage.spec.ts
Normal file
245
packages/store/test/storage.spec.ts
Normal file
|
@ -0,0 +1,245 @@
|
|||
import nock from 'nock';
|
||||
import * as httpMocks from 'node-mocks-http';
|
||||
|
||||
import { Config } from '@verdaccio/config';
|
||||
import { HEADERS, errorUtils } from '@verdaccio/core';
|
||||
import { generatePackageMetadata } from '@verdaccio/helper';
|
||||
import { setup } from '@verdaccio/logger';
|
||||
import { configExample, generateRamdonStorage } from '@verdaccio/mock';
|
||||
|
||||
import { Storage } from '../src';
|
||||
|
||||
setup([]);
|
||||
|
||||
const domain = 'http://localhost:4873';
|
||||
const fakeHost = 'localhost:4873';
|
||||
const fooManifest = generatePackageMetadata('foo', '1.0.0');
|
||||
|
||||
describe('storage', () => {
|
||||
beforeEach(() => {
|
||||
nock.cleanAll();
|
||||
nock.abortPendingRequests();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('add packages', () => {
|
||||
test('add package item', async () => {
|
||||
nock(domain).get('/foo').reply(404);
|
||||
const config = new Config(
|
||||
configExample({
|
||||
storage: generateRamdonStorage(),
|
||||
})
|
||||
);
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
|
||||
await storage.addPackage('foo', fooManifest, (err) => {
|
||||
expect(err).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: getPackageNext should replace getPackage eventually
|
||||
describe('get packages getPackageNext()', () => {
|
||||
describe('with uplinks', () => {
|
||||
test('should get 201 and merge from uplink', async () => {
|
||||
nock(domain).get('/foo').reply(201, fooManifest);
|
||||
const config = new Config(
|
||||
configExample({
|
||||
storage: generateRamdonStorage(),
|
||||
})
|
||||
);
|
||||
const req = httpMocks.createRequest({
|
||||
method: 'GET',
|
||||
connection: { remoteAddress: fakeHost },
|
||||
headers: {
|
||||
host: fakeHost,
|
||||
[HEADERS.FORWARDED_PROTO]: 'http',
|
||||
},
|
||||
url: '/',
|
||||
});
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
await expect(
|
||||
storage.getPackageNext({
|
||||
name: 'foo',
|
||||
uplinksLook: true,
|
||||
req,
|
||||
requestOptions: {
|
||||
headers: req.headers as any,
|
||||
protocol: req.protocol,
|
||||
host: req.get('host'),
|
||||
},
|
||||
})
|
||||
).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
|
||||
});
|
||||
|
||||
test('should get 201 and merge from uplink with version', async () => {
|
||||
nock(domain).get('/foo').reply(201, fooManifest);
|
||||
const config = new Config(
|
||||
configExample({
|
||||
storage: generateRamdonStorage(),
|
||||
})
|
||||
);
|
||||
const req = httpMocks.createRequest({
|
||||
method: 'GET',
|
||||
connection: { remoteAddress: fakeHost },
|
||||
headers: {
|
||||
host: fakeHost,
|
||||
[HEADERS.FORWARDED_PROTO]: 'http',
|
||||
},
|
||||
url: '/',
|
||||
});
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
await expect(
|
||||
storage.getPackageNext({
|
||||
name: 'foo',
|
||||
version: '1.0.0',
|
||||
uplinksLook: true,
|
||||
req,
|
||||
requestOptions: {
|
||||
headers: req.headers as any,
|
||||
protocol: req.protocol,
|
||||
host: req.get('host'),
|
||||
},
|
||||
})
|
||||
).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
|
||||
});
|
||||
|
||||
test('should get 201 and merge from uplink with dist-tag', async () => {
|
||||
nock(domain).get('/foo').reply(201, fooManifest);
|
||||
const config = new Config(
|
||||
configExample({
|
||||
storage: generateRamdonStorage(),
|
||||
})
|
||||
);
|
||||
const req = httpMocks.createRequest({
|
||||
method: 'GET',
|
||||
connection: { remoteAddress: fakeHost },
|
||||
headers: {
|
||||
host: fakeHost,
|
||||
[HEADERS.FORWARDED_PROTO]: 'http',
|
||||
},
|
||||
url: '/',
|
||||
});
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
await expect(
|
||||
storage.getPackageNext({
|
||||
name: 'foo',
|
||||
version: 'latest',
|
||||
uplinksLook: true,
|
||||
req,
|
||||
requestOptions: {
|
||||
headers: req.headers as any,
|
||||
protocol: req.protocol,
|
||||
host: req.get('host'),
|
||||
},
|
||||
})
|
||||
).resolves.toEqual(expect.objectContaining({ name: 'foo' }));
|
||||
});
|
||||
|
||||
test('should get 404 for version does not exist', async () => {
|
||||
nock(domain).get('/foo').reply(201, fooManifest);
|
||||
const config = new Config(
|
||||
configExample({
|
||||
storage: generateRamdonStorage(),
|
||||
})
|
||||
);
|
||||
const req = httpMocks.createRequest({
|
||||
method: 'GET',
|
||||
connection: { remoteAddress: fakeHost },
|
||||
headers: {
|
||||
host: fakeHost,
|
||||
[HEADERS.FORWARDED_PROTO]: 'http',
|
||||
},
|
||||
url: '/',
|
||||
});
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
await expect(
|
||||
storage.getPackageNext({
|
||||
name: 'foo',
|
||||
version: '1.0.0-does-not-exist',
|
||||
uplinksLook: true,
|
||||
req,
|
||||
requestOptions: {
|
||||
headers: req.headers as any,
|
||||
protocol: req.protocol,
|
||||
host: req.get('host'),
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(
|
||||
errorUtils.getNotFound("this version doesn't exist: 1.0.0-does-not-exist")
|
||||
);
|
||||
});
|
||||
|
||||
test('should get 404', async () => {
|
||||
nock(domain).get('/foo2').reply(404);
|
||||
const config = new Config(
|
||||
configExample({
|
||||
storage: generateRamdonStorage(),
|
||||
})
|
||||
);
|
||||
const req = httpMocks.createRequest({
|
||||
method: 'GET',
|
||||
connection: { remoteAddress: fakeHost },
|
||||
headers: {
|
||||
host: fakeHost,
|
||||
[HEADERS.FORWARDED_PROTO]: 'http',
|
||||
},
|
||||
url: '/',
|
||||
});
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
await expect(
|
||||
storage.getPackageNext({
|
||||
name: 'foo2',
|
||||
uplinksLook: true,
|
||||
req,
|
||||
requestOptions: {
|
||||
headers: req.headers as any,
|
||||
protocol: req.protocol,
|
||||
host: req.get('host'),
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(errorUtils.getNotFound());
|
||||
});
|
||||
|
||||
test('should get ETIMEDOUT with uplink', async () => {
|
||||
nock(domain).get('/foo2').replyWithError({
|
||||
code: 'ETIMEDOUT',
|
||||
errno: 'ETIMEDOUT',
|
||||
});
|
||||
const config = new Config(
|
||||
configExample({
|
||||
storage: generateRamdonStorage(),
|
||||
})
|
||||
);
|
||||
const req = httpMocks.createRequest({
|
||||
method: 'GET',
|
||||
connection: { remoteAddress: fakeHost },
|
||||
headers: {
|
||||
host: fakeHost,
|
||||
[HEADERS.FORWARDED_PROTO]: 'http',
|
||||
},
|
||||
url: '/',
|
||||
});
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config);
|
||||
await expect(
|
||||
storage.getPackageNext({
|
||||
name: 'foo2',
|
||||
uplinksLook: true,
|
||||
req,
|
||||
requestOptions: {
|
||||
headers: req.headers as any,
|
||||
protocol: req.protocol,
|
||||
host: req.get('host'),
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(errorUtils.getServiceUnavailable());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "@verdaccio/mock",
|
||||
"version": "6.0.0-6-next.12",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Juan Picado",
|
||||
"email": "juanpicado19@gmail.com"
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import buildDebug from 'debug';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
|
||||
import { parseConfigFile } from '@verdaccio/config';
|
||||
|
||||
const debug = buildDebug('verdaccio:mock:config');
|
||||
|
||||
/**
|
||||
* Override the default.yaml configuration file with any new config provided.
|
||||
*/
|
||||
|
@ -14,8 +17,9 @@ function configExample(
|
|||
const locationFile = location
|
||||
? path.join(location, configFile)
|
||||
: path.join(__dirname, `./config/yaml/${configFile}`);
|
||||
debug('config location: %s', locationFile);
|
||||
const config = parseConfigFile(locationFile);
|
||||
|
||||
debug('config file: %o', JSON.stringify(config));
|
||||
return _.assign({}, _.cloneDeep(config), externalConfig);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Package, Version, Versions } from '@verdaccio/types';
|
|||
* Gets version from a package object taking into account semver weirdness.
|
||||
* @return {String} return the semantic version of a package
|
||||
*/
|
||||
export function getVersion(versions: Versions, version: any): Version | void {
|
||||
export function getVersion(versions: Versions, version: any): Version | undefined {
|
||||
if (!versions) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
"@verdaccio/cli": "workspace:6.0.0-6-next.25",
|
||||
"@verdaccio/hooks": "workspace:6.0.0-6-next.9",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.7",
|
||||
"@verdaccio/mock": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/node-api": "workspace:6.0.0-6-next.24",
|
||||
"@verdaccio/ui-theme": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.9",
|
||||
|
@ -48,6 +47,7 @@
|
|||
"verdaccio-htpasswd": "workspace:11.0.0-6-next.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/mock": "workspace:6.0.0-6-next.12",
|
||||
"@verdaccio/auth": "workspace:6.0.0-6-next.15",
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.3",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.11",
|
||||
|
|
|
@ -196,7 +196,6 @@ importers:
|
|||
'@verdaccio/middleware': workspace:6.0.0-6-next.15
|
||||
'@verdaccio/server': workspace:6.0.0-6-next.23
|
||||
'@verdaccio/store': workspace:6.0.0-6-next.16
|
||||
'@verdaccio/tarball': workspace:11.0.0-6-next.10
|
||||
'@verdaccio/types': workspace:11.0.0-6-next.9
|
||||
'@verdaccio/utils': workspace:6.0.0-6-next.9
|
||||
abortcontroller-polyfill: 1.7.3
|
||||
|
@ -216,7 +215,6 @@ importers:
|
|||
'@verdaccio/logger': link:../logger
|
||||
'@verdaccio/middleware': link:../middleware
|
||||
'@verdaccio/store': link:../store
|
||||
'@verdaccio/tarball': link:../core/tarball
|
||||
'@verdaccio/utils': link:../utils
|
||||
abortcontroller-polyfill: 1.7.3
|
||||
body-parser: 1.19.0
|
||||
|
@ -694,6 +692,7 @@ importers:
|
|||
lowdb: 1.0.0
|
||||
lru-cache: 6.0.0
|
||||
minimatch: 3.0.4
|
||||
sanitize-filename: 1.6.3
|
||||
tmp-promise: 3.0.3
|
||||
dependencies:
|
||||
'@verdaccio/core': link:../../core/core
|
||||
|
@ -707,6 +706,7 @@ importers:
|
|||
lodash: 4.17.21
|
||||
lowdb: 1.0.0
|
||||
lru-cache: 6.0.0
|
||||
sanitize-filename: 1.6.3
|
||||
devDependencies:
|
||||
'@types/minimatch': 3.0.5
|
||||
'@verdaccio/config': link:../../config
|
||||
|
@ -1011,12 +1011,14 @@ importers:
|
|||
'@types/node': 16.11.11
|
||||
'@verdaccio/config': workspace:6.0.0-6-next.11
|
||||
'@verdaccio/core': workspace:6.0.0-6-next.3
|
||||
'@verdaccio/helper': workspace:1.0.0
|
||||
'@verdaccio/loaders': workspace:6.0.0-6-next.7
|
||||
'@verdaccio/local-storage': workspace:11.0.0-6-next.10
|
||||
'@verdaccio/logger': workspace:6.0.0-6-next.7
|
||||
'@verdaccio/mock': workspace:6.0.0-6-next.12
|
||||
'@verdaccio/proxy': workspace:6.0.0-6-next.15
|
||||
'@verdaccio/streams': workspace:11.0.0-6-next.5
|
||||
'@verdaccio/tarball': workspace:11.0.0-6-next.10
|
||||
'@verdaccio/types': workspace:11.0.0-6-next.9
|
||||
'@verdaccio/utils': workspace:6.0.0-6-next.9
|
||||
abortcontroller-polyfill: 1.7.3
|
||||
|
@ -1027,6 +1029,8 @@ importers:
|
|||
lunr: 2.3.9
|
||||
lunr-mutable-indexes: 2.3.2
|
||||
merge2: 1.4.1
|
||||
nock: 13.0.11
|
||||
node-mocks-http: 1.10.1
|
||||
semver: 7.3.5
|
||||
tmp-promise: 3.0.3
|
||||
undici: 4.7.3
|
||||
|
@ -1039,6 +1043,7 @@ importers:
|
|||
'@verdaccio/logger': link:../logger
|
||||
'@verdaccio/proxy': link:../proxy
|
||||
'@verdaccio/streams': link:../core/streams
|
||||
'@verdaccio/tarball': link:../core/tarball
|
||||
'@verdaccio/utils': link:../utils
|
||||
abortcontroller-polyfill: 1.7.3
|
||||
async: 3.1.1
|
||||
|
@ -1051,8 +1056,11 @@ importers:
|
|||
semver: 7.3.5
|
||||
devDependencies:
|
||||
'@types/node': 16.11.11
|
||||
'@verdaccio/helper': link:../tools/helpers
|
||||
'@verdaccio/mock': link:../tools/mock
|
||||
'@verdaccio/types': link:../core/types
|
||||
nock: 13.0.11
|
||||
node-mocks-http: 1.10.1
|
||||
tmp-promise: 3.0.3
|
||||
undici: 4.7.3
|
||||
undici-fetch: 1.0.0-rc.4
|
||||
|
@ -1179,7 +1187,6 @@ importers:
|
|||
'@verdaccio/cli': link:../cli
|
||||
'@verdaccio/hooks': link:../hooks
|
||||
'@verdaccio/logger': link:../logger
|
||||
'@verdaccio/mock': link:../tools/mock
|
||||
'@verdaccio/node-api': link:../node-api
|
||||
'@verdaccio/ui-theme': link:../plugins/ui-theme
|
||||
'@verdaccio/utils': link:../utils
|
||||
|
@ -1189,6 +1196,7 @@ importers:
|
|||
'@verdaccio/auth': link:../auth
|
||||
'@verdaccio/config': link:../config
|
||||
'@verdaccio/core': link:../core/core
|
||||
'@verdaccio/mock': link:../tools/mock
|
||||
'@verdaccio/store': link:../store
|
||||
fastify: 3.24.1
|
||||
|
||||
|
@ -19586,6 +19594,12 @@ packages:
|
|||
/safer-buffer/2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
/sanitize-filename/1.6.3:
|
||||
resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==}
|
||||
dependencies:
|
||||
truncate-utf8-bytes: 1.0.2
|
||||
dev: false
|
||||
|
||||
/sass-loader/10.2.0_sass@1.39.0:
|
||||
resolution: {integrity: sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
@ -21226,6 +21240,12 @@ packages:
|
|||
/trough/1.0.5:
|
||||
resolution: {integrity: sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==}
|
||||
|
||||
/truncate-utf8-bytes/1.0.2:
|
||||
resolution: {integrity: sha1-QFkjkJWS1W94pYGENLC3hInKXys=}
|
||||
dependencies:
|
||||
utf8-byte-length: 1.0.4
|
||||
dev: false
|
||||
|
||||
/ts-essentials/2.0.12:
|
||||
resolution: {integrity: sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==}
|
||||
dev: false
|
||||
|
@ -21847,6 +21867,10 @@ packages:
|
|||
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
/utf8-byte-length/1.0.4:
|
||||
resolution: {integrity: sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=}
|
||||
dev: false
|
||||
|
||||
/util-deprecate/1.0.2:
|
||||
resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=}
|
||||
|
||||
|
|
Loading…
Reference in a new issue