mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
feat!: replace deprecated request dependency by got (#3100)
This commit is contained in:
parent
743ccff5ef
commit
292c0a37fc
348 changed files with 18047 additions and 17715 deletions
54
.changeset/angry-nails-appear.md
Normal file
54
.changeset/angry-nails-appear.md
Normal file
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
'@verdaccio/api': major
|
||||
'@verdaccio/auth': major
|
||||
'@verdaccio/cli': major
|
||||
'@verdaccio/config': major
|
||||
'@verdaccio/core': major
|
||||
'@verdaccio/file-locking': major
|
||||
'@verdaccio/readme': major
|
||||
'@verdaccio/tarball': major
|
||||
'@verdaccio/types': major
|
||||
'@verdaccio/url': major
|
||||
'@verdaccio/hooks': major
|
||||
'@verdaccio/loaders': major
|
||||
'@verdaccio/logger': major
|
||||
'@verdaccio/logger-prettify': major
|
||||
'@verdaccio/middleware': major
|
||||
'@verdaccio/node-api': major
|
||||
'verdaccio-audit': major
|
||||
'verdaccio-auth-memory': major
|
||||
'verdaccio-htpasswd': major
|
||||
'@verdaccio/local-storage': major
|
||||
'verdaccio-memory': major
|
||||
'@verdaccio/ui-theme': major
|
||||
'@verdaccio/proxy': major
|
||||
'@verdaccio/server': major
|
||||
'@verdaccio/store': major
|
||||
'@verdaccio/utils': major
|
||||
'verdaccio': major
|
||||
'@verdaccio/web': major
|
||||
'@verdaccio/e2e-cli': major
|
||||
'@verdaccio/e2e-ui': major
|
||||
---
|
||||
|
||||
feat!: replace deprecated request dependency by got
|
||||
|
||||
This is a big refactoring of the core, fetching dependencies, improve code, more tests and better stability. This is essential for the next release, will take some time but would allow modularize more the core.
|
||||
|
||||
## Notes
|
||||
|
||||
- Remove deprecated `request` by other `got`, retry improved, custom Agent ( got does not include it built-in)
|
||||
- Remove `async` dependency from storage (used by core) it was linked with proxy somehow safe to remove now
|
||||
- Refactor with promises instead callback wherever is possible
|
||||
- ~Document the API~
|
||||
- Improve testing, integration tests
|
||||
- Bugfix
|
||||
- Clean up old validations
|
||||
- Improve performance
|
||||
|
||||
## 💥 Breaking changes
|
||||
|
||||
- Plugin API methods were callbacks based are returning promises, this will break current storage plugins, check documentation for upgrade.
|
||||
- Write Tarball, Read Tarball methods parameters change, a new set of options like `AbortController` signals are being provided to the `addAbortSignal` can be internally used with Streams when a request is aborted. eg: `addAbortSignal(signal, fs.createReadStream(pathName));`
|
||||
- `@verdaccio/streams` stream abort support is legacy is being deprecated removed
|
||||
- Remove AWS and Google Cloud packages for future refactoring [#2574](https://github.com/verdaccio/verdaccio/pull/2574).
|
|
@ -10,7 +10,6 @@
|
|||
"verdaccio-htpasswd": "11.0.0-alpha.0",
|
||||
"@verdaccio/local-storage": "11.0.0-alpha.0",
|
||||
"@verdaccio/readme": "11.0.0-alpha.0",
|
||||
"@verdaccio/streams": "11.0.0-alpha.0",
|
||||
"@verdaccio/types": "11.0.0-alpha.0",
|
||||
"@verdaccio/hooks": "6.0.0-alpha.0",
|
||||
"@verdaccio/loaders": "6.0.0-alpha.0",
|
||||
|
|
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -93,8 +93,7 @@ jobs:
|
|||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
## Node 16 breaks UI test, jest issue
|
||||
node_version: [16, 17]
|
||||
node_version: [16, 18]
|
||||
name: ${{ matrix.os }} / Node ${{ matrix.node_version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
|
78
.vscode/launch.json
vendored
78
.vscode/launch.json
vendored
|
@ -4,88 +4,12 @@
|
|||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Attach",
|
||||
"port": 9229,
|
||||
"request": "attach",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "pwa-node"
|
||||
},
|
||||
{
|
||||
"name": "Verdaccio Debug",
|
||||
"name": "Attach",
|
||||
"port": 9229,
|
||||
"request": "attach",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"type": "pwa-node"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "CLI Babel Registry",
|
||||
"stopOnEntry": false,
|
||||
"program": "${workspaceFolder}/debug/bootstrap.js",
|
||||
"args": ["-l", "0.0.0.0:4873"],
|
||||
"env": {
|
||||
"BABEL_ENV": "registry"
|
||||
},
|
||||
"preLaunchTask": "npm: build:webui",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Unit Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/node_modules/bin/jest",
|
||||
"stopOnEntry": false,
|
||||
"args": ["--debug=true"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": null,
|
||||
"runtimeArgs": ["--nolazy"],
|
||||
"env": {
|
||||
"NODE_ENV": "test",
|
||||
"TZ": "UTC"
|
||||
},
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Functional Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/node_modules/.bin/jest",
|
||||
"stopOnEntry": false,
|
||||
"args": [
|
||||
"--config",
|
||||
"./test/jest.config.functional.js",
|
||||
"--testPathPattern",
|
||||
"./test/functional/index*",
|
||||
"--debug=false",
|
||||
"--verbose",
|
||||
"--useStderr",
|
||||
"--detectOpenHandles"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {
|
||||
"BABEL_ENV": "testOldEnv",
|
||||
"VERDACCIO_DEBUG": "true",
|
||||
"VERDACCIO_DEBUG_INJECT": "true",
|
||||
"NODE_DEBUG": "TO_DEBUG_REQUEST_REMOVE_THIS_request"
|
||||
},
|
||||
"preLaunchTask": "pre-test",
|
||||
"console": "integratedTerminal",
|
||||
"runtimeExecutable": null,
|
||||
"runtimeArgs": ["--nolazy"]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Verdaccio Compiled",
|
||||
"preLaunchTask": "npm: code:build",
|
||||
"program": "${workspaceRoot}/bin/verdaccio",
|
||||
"args": ["-l", "0.0.0.0:4873"],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
21
.vscode/tasks.json
vendored
21
.vscode/tasks.json
vendored
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "build:webui",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "code:build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "pre-test",
|
||||
"dependsOn": ["npm: code:build", "npm: test:clean"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -7,4 +7,9 @@ module.exports = {
|
|||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!**/partials/**', '!**/fixture/**'],
|
||||
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
lines: 90,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
"@types/jsonwebtoken": "8.5.1",
|
||||
"@types/request": "2.48.8",
|
||||
"@types/semver": "7.3.9",
|
||||
"@types/node-fetch": "2.5.3",
|
||||
"@types/supertest": "2.0.12",
|
||||
"@types/testing-library__jest-dom": "5.14.2",
|
||||
"@types/validator": "13.7.1",
|
||||
|
@ -103,7 +104,6 @@
|
|||
"ts-node": "10.4.0",
|
||||
"typescript": "4.5.5",
|
||||
"update-ts-references": "2.4.1",
|
||||
"verdaccio": "5.5.0",
|
||||
"verdaccio-audit": "workspace:*",
|
||||
"verdaccio-auth-memory": "workspace:*",
|
||||
"verdaccio-htpasswd": "workspace:*",
|
||||
|
@ -116,7 +116,7 @@
|
|||
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
||||
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
||||
"lint": "eslint --max-warnings 46 \"**/*.{js,jsx,ts,tsx}\"",
|
||||
"lint": "eslint --max-warnings 100 \"**/*.{js,jsx,ts,tsx}\"",
|
||||
"test": "pnpm recursive test --filter ./packages",
|
||||
"test:e2e:cli": "pnpm test --filter ...@verdaccio/e2e-cli",
|
||||
"test:e2e:ui": "pnpm test --filter ...@verdaccio/e2e-ui",
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
const debug = require('debug')('verdaccio:test');
|
||||
|
||||
const setup = debug;
|
||||
const logger = {
|
||||
child: jest.fn(() => ({
|
||||
debug,
|
||||
trace: debug,
|
||||
warn: debug,
|
||||
info: debug,
|
||||
error: debug,
|
||||
fatal: debug,
|
||||
})),
|
||||
debug: debug,
|
||||
trace: debug,
|
||||
warn: debug,
|
||||
info: debug,
|
||||
error: debug,
|
||||
fatal: debug,
|
||||
};
|
||||
|
||||
export { setup, logger };
|
|
@ -1,3 +1,10 @@
|
|||
const config = require('../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
||||
module.exports = Object.assign({}, config, {
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
// FIXME: increase to 90
|
||||
lines: 60,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
|
@ -42,7 +42,6 @@
|
|||
"@verdaccio/auth": "workspace:6.0.0-6-next.22",
|
||||
"@verdaccio/config": "workspace:6.0.0-6-next.14",
|
||||
"@verdaccio/core": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/hooks": "workspace:6.0.0-6-next.13",
|
||||
"@verdaccio/logger": "workspace:6.0.0-6-next.11",
|
||||
"@verdaccio/middleware": "workspace:6.0.0-6-next.22",
|
||||
"@verdaccio/store": "workspace:6.0.0-6-next.22",
|
||||
|
@ -61,7 +60,9 @@
|
|||
"@verdaccio/server": "workspace:6.0.0-6-next.31",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.12",
|
||||
"@verdaccio/test-helper": "workspace:1.1.0-6-next.1",
|
||||
"supertest": "6.2.2"
|
||||
"supertest": "6.2.2",
|
||||
"nock": "13.2.8",
|
||||
"mockdate": "3.0.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
|
|
@ -3,106 +3,119 @@ import _ from 'lodash';
|
|||
import mime from 'mime';
|
||||
|
||||
import { IAuth } from '@verdaccio/auth';
|
||||
import { VerdaccioError, constants } from '@verdaccio/core';
|
||||
import { constants, errorUtils } from '@verdaccio/core';
|
||||
import { allow, media } from '@verdaccio/middleware';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { Package } from '@verdaccio/types';
|
||||
|
||||
import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types/custom';
|
||||
|
||||
export default function (route: Router, auth: IAuth, storage: Storage): void {
|
||||
const can = allow(auth);
|
||||
const tag_package_version = function (
|
||||
const addTagPackageVersionMiddleware = async function (
|
||||
req: $RequestExtend,
|
||||
res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): $NextFunctionVer {
|
||||
): Promise<$NextFunctionVer> {
|
||||
if (_.isString(req.body) === false) {
|
||||
return next('route');
|
||||
return next(errorUtils.getBadRequest('version is missing'));
|
||||
}
|
||||
|
||||
const tags = {};
|
||||
tags[req.params.tag] = req.body;
|
||||
storage.mergeTags(req.params.package, tags, function (err: Error): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
await storage.mergeTagsNext(req.params.package, tags);
|
||||
res.status(constants.HTTP_STATUS.CREATED);
|
||||
return next({ ok: constants.API_MESSAGE.TAG_ADDED });
|
||||
});
|
||||
return next({
|
||||
ok: constants.API_MESSAGE.TAG_ADDED,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
|
||||
// tagging a package.
|
||||
route.put('/:package/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
|
||||
|
||||
route.post(
|
||||
'/-/package/:package/dist-tags/:tag',
|
||||
route.put(
|
||||
'/:package/:tag',
|
||||
can('publish'),
|
||||
media(mime.getType('json')),
|
||||
tag_package_version
|
||||
addTagPackageVersionMiddleware
|
||||
);
|
||||
|
||||
route.put(
|
||||
'/-/package/:package/dist-tags/:tag',
|
||||
can('publish'),
|
||||
media(mime.getType('json')),
|
||||
tag_package_version
|
||||
addTagPackageVersionMiddleware
|
||||
);
|
||||
|
||||
route.delete(
|
||||
'/-/package/:package/dist-tags/:tag',
|
||||
can('publish'),
|
||||
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
async function (
|
||||
req: $RequestExtend,
|
||||
res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): Promise<void> {
|
||||
const tags = {};
|
||||
tags[req.params.tag] = null;
|
||||
storage.mergeTags(req.params.package, tags, function (err: VerdaccioError): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
await storage.mergeTagsNext(req.params.package, tags);
|
||||
res.status(constants.HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: constants.API_MESSAGE.TAG_REMOVED,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/-/package/:package/dist-tags',
|
||||
can('access'),
|
||||
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
storage.getPackage({
|
||||
name: req.params.package,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
callback: function (err: VerdaccioError, info: Package): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(info[constants.DIST_TAGS]);
|
||||
},
|
||||
});
|
||||
async function (
|
||||
req: $RequestExtend,
|
||||
res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): Promise<void> {
|
||||
const name = req.params.package;
|
||||
const requestOptions = {
|
||||
protocol: req.protocol,
|
||||
headers: req.headers as any,
|
||||
// FIXME: if we migrate to req.hostname, the port is not longer included.
|
||||
host: req.host,
|
||||
remoteAddress: req.socket.remoteAddress,
|
||||
};
|
||||
try {
|
||||
const manifest = await storage.getPackageByOptions({
|
||||
name,
|
||||
uplinksLook: true,
|
||||
requestOptions,
|
||||
});
|
||||
next(manifest[constants.DIST_TAGS]);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
route.post(
|
||||
'/-/package/:package/dist-tags',
|
||||
can('publish'),
|
||||
function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
storage.mergeTags(
|
||||
req.params.package,
|
||||
req.body,
|
||||
function (err: VerdaccioError): $NextFunctionVer {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.status(constants.HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: constants.API_MESSAGE.TAG_UPDATED,
|
||||
});
|
||||
}
|
||||
);
|
||||
async function (
|
||||
req: $RequestExtend,
|
||||
res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): Promise<void> {
|
||||
try {
|
||||
await storage.mergeTagsNext(req.params.package, req.body);
|
||||
res.status(constants.HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: constants.API_MESSAGE.TAG_UPDATED,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import bodyParser from 'body-parser';
|
||||
import express, { Router } from 'express';
|
||||
import semver from 'semver';
|
||||
|
||||
import { IAuth } from '@verdaccio/auth';
|
||||
import {
|
||||
|
@ -25,10 +24,6 @@ import v1Search from './v1/search';
|
|||
import token from './v1/token';
|
||||
import whoami from './whoami';
|
||||
|
||||
if (semver.lte(process.version, 'v15.0.0')) {
|
||||
global.AbortController = require('abortcontroller-polyfill/dist/cjs-ponyfill').AbortController;
|
||||
}
|
||||
|
||||
export default function (config: Config, auth: IAuth, storage: Storage): Router {
|
||||
/* eslint new-cap:off */
|
||||
const app = express.Router();
|
||||
|
@ -62,7 +57,7 @@ export default function (config: Config, auth: IAuth, storage: Storage): Router
|
|||
search(app);
|
||||
user(app, auth, config);
|
||||
distTags(app, auth, storage);
|
||||
publish(app, auth, storage, config);
|
||||
publish(app, auth, storage);
|
||||
ping(app);
|
||||
stars(app, storage);
|
||||
// @ts-ignore
|
||||
|
|
|
@ -2,7 +2,7 @@ import buildDebug from 'debug';
|
|||
import { Router } from 'express';
|
||||
|
||||
import { IAuth } from '@verdaccio/auth';
|
||||
import { HEADERS, errorUtils } from '@verdaccio/core';
|
||||
import { HEADERS, HEADER_TYPE } from '@verdaccio/core';
|
||||
import { allow } from '@verdaccio/middleware';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
|
||||
|
@ -10,27 +10,6 @@ import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types/cust
|
|||
|
||||
const debug = buildDebug('verdaccio:api:package');
|
||||
|
||||
const downloadStream = (
|
||||
packageName: string,
|
||||
filename: string,
|
||||
storage: any,
|
||||
_req: $RequestExtend,
|
||||
res: $ResponseExtend
|
||||
): void => {
|
||||
const stream = storage.getTarball(packageName, filename);
|
||||
|
||||
stream.on('content-length', function (content): void {
|
||||
res.header('Content-Length', content);
|
||||
});
|
||||
|
||||
stream.on('error', function (err): void {
|
||||
return res.locals.report_error(err);
|
||||
});
|
||||
|
||||
res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM);
|
||||
stream.pipe(res);
|
||||
};
|
||||
|
||||
export default function (route: Router, auth: IAuth, storage: Storage): void {
|
||||
const can = allow(auth);
|
||||
|
||||
|
@ -44,28 +23,22 @@ export default function (route: Router, auth: IAuth, storage: Storage): void {
|
|||
): Promise<void> {
|
||||
debug('init package by version');
|
||||
const name = req.params.package;
|
||||
let queryVersion = req.params.version;
|
||||
let version = req.params.version;
|
||||
const write = req.query.write === 'true';
|
||||
const requestOptions = {
|
||||
protocol: req.protocol,
|
||||
headers: req.headers as any,
|
||||
// FIXME: if we migrate to req.hostname, the port is not longer included.
|
||||
host: req.host,
|
||||
remoteAddress: req.socket.remoteAddress,
|
||||
byPassCache: write,
|
||||
};
|
||||
|
||||
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.getPackageByOptions) {
|
||||
throw errorUtils.getInternalError(
|
||||
'getPackageByOptions not implemented, check pr-2750 for more details'
|
||||
);
|
||||
}
|
||||
|
||||
const manifest = await storage.getPackageByOptions({
|
||||
name,
|
||||
uplinksLook: true,
|
||||
req,
|
||||
version: queryVersion,
|
||||
version,
|
||||
requestOptions,
|
||||
});
|
||||
next(manifest);
|
||||
|
@ -78,18 +51,72 @@ export default function (route: Router, auth: IAuth, storage: Storage): void {
|
|||
route.get(
|
||||
'/:scopedPackage/-/:scope/:filename',
|
||||
can('access'),
|
||||
function (req: $RequestExtend, res: $ResponseExtend): void {
|
||||
const { scopedPackage, filename } = req.params;
|
||||
async function (req: $RequestExtend, res: $ResponseExtend, next): Promise<void> {
|
||||
const { pkg, filename } = req.params;
|
||||
const abort = new AbortController();
|
||||
try {
|
||||
const stream = (await storage.getTarballNext(pkg, filename, {
|
||||
signal: abort.signal,
|
||||
// enableRemote: true,
|
||||
})) as any;
|
||||
|
||||
downloadStream(scopedPackage, filename, storage, req, res);
|
||||
stream.on('content-length', (size) => {
|
||||
res.header(HEADER_TYPE.CONTENT_LENGTH, size);
|
||||
});
|
||||
|
||||
stream.once('error', (err) => {
|
||||
res.locals.report_error(err);
|
||||
next(err);
|
||||
});
|
||||
|
||||
req.on('abort', () => {
|
||||
debug('request aborted for %o', req.url);
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM);
|
||||
stream.pipe(res);
|
||||
} catch (err: any) {
|
||||
// console.log('catch API error request', err);
|
||||
res.locals.report_error(err);
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
route.get(
|
||||
'/:package/-/:filename',
|
||||
'/:pkg/-/:filename',
|
||||
can('access'),
|
||||
function (req: $RequestExtend, res: $ResponseExtend): void {
|
||||
downloadStream(req.params.package, req.params.filename, storage, req, res);
|
||||
async function (req: $RequestExtend, res: $ResponseExtend, next): Promise<void> {
|
||||
const { pkg, filename } = req.params;
|
||||
const abort = new AbortController();
|
||||
try {
|
||||
const stream = (await storage.getTarballNext(pkg, filename, {
|
||||
signal: abort.signal,
|
||||
// enableRemote: true,
|
||||
})) as any;
|
||||
|
||||
stream.on('content-length', (size) => {
|
||||
res.header(HEADER_TYPE.CONTENT_LENGTH, size);
|
||||
});
|
||||
|
||||
stream.once('error', (err) => {
|
||||
res.locals.report_error(err);
|
||||
next(err);
|
||||
});
|
||||
|
||||
req.on('abort', () => {
|
||||
debug('request aborted for %o', req.url);
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM);
|
||||
stream.pipe(res);
|
||||
} catch (err: any) {
|
||||
// console.log('catch API error request', err);
|
||||
res.locals.report_error(err);
|
||||
next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,40 +1,21 @@
|
|||
import buildDebug from 'debug';
|
||||
import { Router } from 'express';
|
||||
import _ from 'lodash';
|
||||
import mime from 'mime';
|
||||
import Path from 'path';
|
||||
|
||||
import { IAuth } from '@verdaccio/auth';
|
||||
import {
|
||||
API_ERROR,
|
||||
API_MESSAGE,
|
||||
DIST_TAGS,
|
||||
HEADERS,
|
||||
HTTP_STATUS,
|
||||
errorUtils,
|
||||
} from '@verdaccio/core';
|
||||
import { notify } from '@verdaccio/hooks';
|
||||
import { API_MESSAGE, HTTP_STATUS } from '@verdaccio/core';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { allow, expectJson, media } from '@verdaccio/middleware';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { Callback, CallbackAction, Config, MergeTags, Package, Version } from '@verdaccio/types';
|
||||
import { hasDiffOneKey, isObject, validateMetadata } from '@verdaccio/utils';
|
||||
|
||||
import { $NextFunctionVer, $RequestExtend, $ResponseExtend } from '../types/custom';
|
||||
import star from './star';
|
||||
import { isPublishablePackage, isRelatedToDeprecation } from './utils';
|
||||
|
||||
// import star from './star';
|
||||
// import { isPublishablePackage, isRelatedToDeprecation } from './utils';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:publish');
|
||||
|
||||
export default function publish(
|
||||
router: Router,
|
||||
auth: IAuth,
|
||||
storage: Storage,
|
||||
config: Config
|
||||
): void {
|
||||
const can = allow(auth);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Publish a package / update package / un/start a package
|
||||
*
|
||||
* There are multiples scenarios here to be considered:
|
||||
|
@ -73,13 +54,27 @@ export default function publish(
|
|||
*
|
||||
* Example flow of unpublish.
|
||||
*
|
||||
* npm http fetch GET 200 http://localhost:4873/@scope%2ftest1?write=true 1680ms
|
||||
* npm http fetch PUT 201 http://localhost:4873/@scope%2ftest1/-rev/14-5d500cfce92f90fd
|
||||
* 956606ms attempt #2
|
||||
* npm http fetch GET 200 http://localhost:4873/@scope%2ftest1?write=true 1601ms
|
||||
* npm http fetch DELETE 201 http://localhost:4873/@scope%2ftest1/-/test1-1.0.3.tgz/-rev/16
|
||||
* -e11c8db282b2d992 19ms
|
||||
* There are two possible flows:
|
||||
*
|
||||
* - Remove all pacakges (entirely)
|
||||
* eg: npm unpublish package-name@* --force
|
||||
* eg: npm unpublish package-name --force
|
||||
*
|
||||
* npm http fetch GET 200 http://localhost:4873/custom-name?write=true 1680ms
|
||||
* npm http fetch DELETE 201 http://localhost:4873/custom-name/-/test1-1.0.3.tgz/-rev/16-e11c8db282b2d992 19ms
|
||||
*
|
||||
* - Remove a specific version
|
||||
* eg: npm unpublish package-name@1.0.0 --force
|
||||
*
|
||||
* Get fresh manifest
|
||||
* npm http fetch GET 200 http://localhost:4873/custom-name?write=true 1680ms
|
||||
* Update manifest without the version to be unpublished
|
||||
* npm http fetch PUT 201 http://localhost:4873/custom-name/-rev/14-5d500cfce92f90fd 956606ms
|
||||
* Get fresh manifest (revision should be different)
|
||||
* npm http fetch GET 200 http://localhost:4873/custom-name?write=true 1601ms
|
||||
* Remove the tarball
|
||||
* npm http fetch DELETE 201 http://localhost:4873/custom-name/-/test1-1.0.3.tgz/-rev/16-e11c8db282b2d992 19ms
|
||||
*
|
||||
* 3. Star a package
|
||||
*
|
||||
* Permissions: start a package depends of the publish and unpublish permissions, there is no
|
||||
|
@ -98,12 +93,24 @@ export default function publish(
|
|||
}
|
||||
*
|
||||
*/
|
||||
export default function publish(router: Router, auth: IAuth, storage: Storage): void {
|
||||
const can = allow(auth);
|
||||
// publish (update manifest) v6
|
||||
router.put(
|
||||
'/:package/:_rev?/:revision?',
|
||||
'/:package',
|
||||
can('publish'),
|
||||
media(mime.getType('json')),
|
||||
expectJson,
|
||||
publishPackage(storage, config, auth)
|
||||
publishPackageNext(storage)
|
||||
);
|
||||
|
||||
// unpublish a pacakge v6
|
||||
router.put(
|
||||
'/:package/-rev/:revision',
|
||||
can('unpublish'),
|
||||
media(mime.getType('json')),
|
||||
expectJson,
|
||||
publishPackageNext(storage)
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -111,330 +118,102 @@ export default function publish(
|
|||
*
|
||||
* This scenario happens when the first call detect there is only one version remaining
|
||||
* in the metadata, then the client decides to DELETE the resource
|
||||
* npm http fetch GET 304 http://localhost:4873/@scope%2ftest1?write=true 1076ms (from cache)
|
||||
npm http fetch DELETE 201 http://localhost:4873/@scope%2ftest1/-rev/18-d8ebe3020bd4ac9c 22ms
|
||||
* npm http fetch GET 304 http://localhost:4873/package-name?write=true 1076ms (from cache)
|
||||
* npm http fetch DELETE 201 http://localhost:4873/package-name/-rev/18-d8ebe3020bd4ac9c 22ms
|
||||
*/
|
||||
router.delete('/:package/-rev/*', can('unpublish'), unPublishPackage(storage));
|
||||
// v6
|
||||
router.delete(
|
||||
'/:package/-rev/:revision',
|
||||
can('unpublish'),
|
||||
async function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
|
||||
const packageName = req.params.package;
|
||||
const rev = req.params.revision;
|
||||
|
||||
// removing a tarball
|
||||
logger.debug({ packageName }, `unpublishing @{packageName}`);
|
||||
try {
|
||||
await storage.removePackage(packageName, rev);
|
||||
debug('package %s unpublished', packageName);
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({ ok: API_MESSAGE.PKG_REMOVED });
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
Remove a tarball, this happens when npm unpublish a package unique version.
|
||||
npm http fetch DELETE 201 http://localhost:4873/package-name/-rev/18-d8ebe3020bd4ac9c 22ms
|
||||
*/
|
||||
router.delete(
|
||||
'/:package/-/:filename/-rev/:revision',
|
||||
can('unpublish'),
|
||||
can('publish'),
|
||||
removeTarball(storage)
|
||||
);
|
||||
|
||||
// uploading package tarball
|
||||
router.put(
|
||||
'/:package/-/:filename/*',
|
||||
can('publish'),
|
||||
media(HEADERS.OCTET_STREAM),
|
||||
uploadPackageTarball(storage)
|
||||
);
|
||||
|
||||
// adding a version
|
||||
router.put(
|
||||
'/:package/:version/-tag/:tag',
|
||||
can('publish'),
|
||||
media(mime.getType('json')),
|
||||
expectJson,
|
||||
addVersion(storage)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a package
|
||||
*/
|
||||
export function publishPackage(storage: Storage, config: Config, auth: IAuth): any {
|
||||
const starApi = star(storage);
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const packageName = req.params.package;
|
||||
|
||||
debug('publishing or updating a new version for %o', packageName);
|
||||
|
||||
/**
|
||||
* Write tarball of stream data from package clients.
|
||||
*/
|
||||
const createTarball = function (filename: string, data, cb: Callback): void {
|
||||
const stream = storage.addTarball(packageName, filename);
|
||||
stream.on('error', function (err) {
|
||||
debug(
|
||||
'error on stream a tarball %o for %o with error %o',
|
||||
filename,
|
||||
packageName,
|
||||
err.message
|
||||
);
|
||||
cb(err);
|
||||
});
|
||||
stream.on('success', function () {
|
||||
debug('success on stream a tarball %o for %o', filename, packageName);
|
||||
cb();
|
||||
});
|
||||
// this is dumb and memory-consuming, but what choices do we have?
|
||||
// flow: we need first refactor this file before decides which type use here
|
||||
stream.end(Buffer.from(data.data, 'base64'));
|
||||
stream.done();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add new package version in storage
|
||||
*/
|
||||
const createVersion = function (version: string, metadata: Version, cb: CallbackAction): void {
|
||||
debug('add a new package version %o to storage %o', version, metadata);
|
||||
storage.addVersion(packageName, version, metadata, null, cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add new tags in storage
|
||||
*/
|
||||
const addTags = function (tags: MergeTags, cb: CallbackAction): void {
|
||||
debug('add new tag %o to storage', packageName);
|
||||
storage.mergeTags(packageName, tags, cb);
|
||||
};
|
||||
|
||||
const afterChange = function (error, okMessage, metadata: Package): void {
|
||||
const metadataCopy: Package = { ...metadata };
|
||||
debug('after change metadata %o', metadata);
|
||||
|
||||
const { _attachments, versions } = metadataCopy;
|
||||
|
||||
// `npm star` wouldn't have attachments
|
||||
// and `npm deprecate` would have attachments as a empty object, i.e {}
|
||||
if (_.isNil(_attachments) || JSON.stringify(_attachments) === '{}') {
|
||||
debug('no attachments detected');
|
||||
if (error) {
|
||||
debug('no_attachments: after change error with %o', error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
debug('no_attachments: after change success');
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: okMessage,
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* npm-registry-client 0.3+ embeds tarball into the json upload
|
||||
* issue https://github.com/rlidwka/sinopia/issues/31, dealing with it here:
|
||||
*/
|
||||
|
||||
const isInvalidBodyFormat =
|
||||
isObject(_attachments) === false ||
|
||||
hasDiffOneKey(_attachments) ||
|
||||
isObject(versions) === false ||
|
||||
hasDiffOneKey(versions);
|
||||
|
||||
if (isInvalidBodyFormat) {
|
||||
// npm is doing something strange again
|
||||
// if this happens in normal circumstances, report it as a bug
|
||||
debug('invalid body format');
|
||||
logger.info({ packageName }, `wrong package format on publish a package @{packageName}`);
|
||||
return next(errorUtils.getBadRequest(API_ERROR.UNSUPORTED_REGISTRY_CALL));
|
||||
}
|
||||
|
||||
if (error && error.status !== HTTP_STATUS.CONFLICT) {
|
||||
debug('error on change or update a package with %o', error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
// at this point document is either created or existed before
|
||||
const [firstAttachmentKey] = Object.keys(_attachments);
|
||||
|
||||
createTarball(
|
||||
Path.basename(firstAttachmentKey),
|
||||
_attachments[firstAttachmentKey],
|
||||
function (error) {
|
||||
debug('creating a tarball %o', firstAttachmentKey);
|
||||
if (error) {
|
||||
debug('error on create a tarball for %o with error %o', packageName, error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
const versionToPublish = Object.keys(versions)[0];
|
||||
|
||||
versions[versionToPublish].readme =
|
||||
_.isNil(metadataCopy.readme) === false ? String(metadataCopy.readme) : '';
|
||||
|
||||
createVersion(versionToPublish, versions[versionToPublish], function (error) {
|
||||
if (error) {
|
||||
debug('error on create a version for %o with error %o', packageName, error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
addTags(metadataCopy[DIST_TAGS], async function (error) {
|
||||
if (error) {
|
||||
debug('error on create a tag for %o with error %o', packageName, error.message);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
try {
|
||||
await notify(
|
||||
metadataCopy,
|
||||
config,
|
||||
req.remote_user,
|
||||
`${metadataCopy.name}@${versionToPublish}`
|
||||
);
|
||||
} catch (error: any) {
|
||||
debug(
|
||||
'error on notify add a new tag %o',
|
||||
`${metadataCopy.name}@${versionToPublish}`
|
||||
);
|
||||
logger.error({ error }, 'notify batch service has failed: @{error}');
|
||||
}
|
||||
|
||||
debug('add a tag succesfully for %o', `${metadataCopy.name}@${versionToPublish}`);
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({ ok: okMessage, success: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (isPublishablePackage(req.body) === false && isObject(req.body.users)) {
|
||||
debug('starting star a package');
|
||||
return starApi(req, res, next);
|
||||
}
|
||||
|
||||
try {
|
||||
debug('pre validation metadata to publish %o', req.body);
|
||||
const metadata = validateMetadata(req.body, packageName);
|
||||
debug('post validation metadata to publish %o', metadata);
|
||||
// treating deprecation as updating a package
|
||||
if (req.params._rev || isRelatedToDeprecation(req.body)) {
|
||||
debug('updating a new version for %o', packageName);
|
||||
// we check unpublish permissions, an update is basically remove versions
|
||||
const remote = req.remote_user;
|
||||
auth.allow_unpublish({ packageName }, remote, (error) => {
|
||||
debug('allowed to unpublish a package %o', packageName);
|
||||
if (error) {
|
||||
debug('not allowed to unpublish a version for %o', packageName);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
debug('update a package');
|
||||
storage.changePackage(packageName, metadata, req.params.revision, function (error) {
|
||||
afterChange(error, API_MESSAGE.PKG_CHANGED, metadata);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
debug('adding a new version for the package %o', packageName);
|
||||
storage.addPackage(packageName, metadata, function (error) {
|
||||
debug('package metadata updated %o', metadata);
|
||||
afterChange(error, API_MESSAGE.PKG_CREATED, metadata);
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
debug('error on publish, bad package format %o', packageName);
|
||||
logger.error({ packageName }, 'error on publish, bad package data for @{packageName}');
|
||||
return next(errorUtils.getBadData(API_ERROR.BAD_PACKAGE_DATA));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* un-publish a package
|
||||
*/
|
||||
export function unPublishPackage(storage: Storage) {
|
||||
return async function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer) {
|
||||
const packageName = req.params.package;
|
||||
|
||||
logger.debug({ packageName }, `unpublishing @{packageName}`);
|
||||
try {
|
||||
await storage.removePackage(packageName);
|
||||
} catch (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({ ok: API_MESSAGE.PKG_REMOVED });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tarball
|
||||
*/
|
||||
export function removeTarball(storage: Storage) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const packageName = req.params.package;
|
||||
const { filename, revision } = req.params;
|
||||
|
||||
logger.debug(
|
||||
{ packageName, filename, revision },
|
||||
`removing a tarball for @{packageName}-@{tarballName}-@{revision}`
|
||||
);
|
||||
storage.removeTarball(packageName, filename, revision, function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
async function (
|
||||
req: $RequestExtend,
|
||||
res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): Promise<void> {
|
||||
const packageName = req.params.package;
|
||||
const { filename, revision } = req.params;
|
||||
|
||||
logger.debug(
|
||||
{ packageName, filename, revision },
|
||||
`success remove tarball for @{packageName}-@{tarballName}-@{revision}`
|
||||
`removing a tarball for @{packageName}-@{tarballName}-@{revision}`
|
||||
);
|
||||
return next({ ok: API_MESSAGE.TARBALL_REMOVED });
|
||||
});
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Adds a new version
|
||||
*/
|
||||
export function addVersion(storage: Storage) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
const { version, tag } = req.params;
|
||||
const packageName = req.params.package;
|
||||
debug('add a new version %o and tag %o for %o', version, tag, packageName);
|
||||
try {
|
||||
await storage.removeTarball(packageName, filename, revision);
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
|
||||
storage.addVersion(packageName, version, req.body, tag, function (error) {
|
||||
if (error) {
|
||||
debug('error on add new version');
|
||||
return next(error);
|
||||
logger.debug(
|
||||
{ packageName, filename, revision },
|
||||
`success remove tarball for @{packageName}-@{tarballName}-@{revision}`
|
||||
);
|
||||
return next({ ok: API_MESSAGE.TARBALL_REMOVED });
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
debug('success on add new version');
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: API_MESSAGE.PKG_PUBLISHED,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* uploadPackageTarball
|
||||
*/
|
||||
export function uploadPackageTarball(storage: Storage) {
|
||||
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
|
||||
export function publishPackageNext(storage: Storage): any {
|
||||
return async function (
|
||||
req: $RequestExtend,
|
||||
_res: $ResponseExtend,
|
||||
next: $NextFunctionVer
|
||||
): Promise<void> {
|
||||
const ac = new AbortController();
|
||||
const packageName = req.params.package;
|
||||
const stream = storage.addTarball(packageName, req.params.filename);
|
||||
req.pipe(stream);
|
||||
const { revision } = req.params;
|
||||
const metadata = req.body;
|
||||
|
||||
// checking if end event came before closing
|
||||
let complete = false;
|
||||
req.on('end', function () {
|
||||
complete = true;
|
||||
stream.done();
|
||||
});
|
||||
|
||||
req.on('close', function () {
|
||||
if (!complete) {
|
||||
stream.abort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', function (err) {
|
||||
return res.locals.report_error(err);
|
||||
});
|
||||
|
||||
stream.on('success', function () {
|
||||
res.status(HTTP_STATUS.CREATED);
|
||||
return next({
|
||||
ok: API_MESSAGE.TARBALL_UPLOADED,
|
||||
try {
|
||||
debug('publishing %s', packageName);
|
||||
await storage.updateManifest(metadata, {
|
||||
name: packageName,
|
||||
revision,
|
||||
signal: ac.signal,
|
||||
requestOptions: {
|
||||
host: req.hostname,
|
||||
protocol: req.protocol,
|
||||
// @ts-ignore
|
||||
headers: req.headers,
|
||||
},
|
||||
});
|
||||
});
|
||||
_res.status(HTTP_STATUS.CREATED);
|
||||
|
||||
return next({
|
||||
// TODO: this could be also Package Updated based on the
|
||||
// action, deprecate, star, publish new version, or create a package
|
||||
// the mssage some return from the method
|
||||
ok: API_MESSAGE.PKG_CREATED,
|
||||
success: true,
|
||||
});
|
||||
} catch (err: any) {
|
||||
// TODO: review if we need the abort controller here
|
||||
ac.abort();
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import buildDebug from 'debug';
|
||||
import { Response } from 'express';
|
||||
import _ from 'lodash';
|
||||
|
@ -27,61 +28,61 @@ export default function (
|
|||
return (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
|
||||
const name = req.params.package;
|
||||
debug('starring a package for %o', name);
|
||||
const afterChangePackage = function (err?: Error) {
|
||||
if (err) {
|
||||
debug('error on update package for %o', name);
|
||||
return next(err);
|
||||
}
|
||||
// const afterChangePackage = function (err?: Error) {
|
||||
// if (err) {
|
||||
// debug('error on update package for %o', name);
|
||||
// return next(err);
|
||||
// }
|
||||
|
||||
debug('succes update package for %o', name);
|
||||
res.status(HTTP_STATUS.OK);
|
||||
next({
|
||||
success: true,
|
||||
});
|
||||
};
|
||||
// debug('succes update package for %o', name);
|
||||
// res.status(HTTP_STATUS.OK);
|
||||
// next({
|
||||
// success: true,
|
||||
// });
|
||||
// };
|
||||
|
||||
debug('get package info package for %o', name);
|
||||
// @ts-ignore
|
||||
storage.getPackage({
|
||||
name,
|
||||
req,
|
||||
callback: function (err, info) {
|
||||
if (err) {
|
||||
debug('error on get package info package for %o', name);
|
||||
return next(err);
|
||||
}
|
||||
const newStarUser = req.body[USERS];
|
||||
const remoteUsername = req.remote_user.name;
|
||||
const localStarUsers = info[USERS];
|
||||
// Check is star or unstar
|
||||
const isStar = Object.keys(newStarUser).includes(remoteUsername);
|
||||
debug('is start? %o', isStar);
|
||||
if (
|
||||
_.isNil(localStarUsers) === false &&
|
||||
validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)
|
||||
) {
|
||||
return afterChangePackage();
|
||||
}
|
||||
const users = isStar
|
||||
? {
|
||||
...localStarUsers,
|
||||
[remoteUsername]: true,
|
||||
}
|
||||
: _.reduce(
|
||||
localStarUsers,
|
||||
(users, value, key) => {
|
||||
if (key !== remoteUsername) {
|
||||
users[key] = value;
|
||||
}
|
||||
return users;
|
||||
},
|
||||
{}
|
||||
);
|
||||
debug('update package for %o', name);
|
||||
storage.changePackage(name, { ...info, users }, req.body._rev, function (err) {
|
||||
afterChangePackage(err);
|
||||
});
|
||||
},
|
||||
});
|
||||
// storage.getPackage({
|
||||
// name,
|
||||
// req,
|
||||
// callback: function (err, info) {
|
||||
// if (err) {
|
||||
// debug('error on get package info package for %o', name);
|
||||
// return next(err);
|
||||
// }
|
||||
// const newStarUser = req.body[USERS];
|
||||
// const remoteUsername = req.remote_user.name;
|
||||
// const localStarUsers = info[USERS];
|
||||
// // Check is star or unstar
|
||||
// const isStar = Object.keys(newStarUser).includes(remoteUsername);
|
||||
// debug('is start? %o', isStar);
|
||||
// if (
|
||||
// _.isNil(localStarUsers) === false &&
|
||||
// validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)
|
||||
// ) {
|
||||
// return afterChangePackage();
|
||||
// }
|
||||
// const users = isStar
|
||||
// ? {
|
||||
// ...localStarUsers,
|
||||
// [remoteUsername]: true,
|
||||
// }
|
||||
// : _.reduce(
|
||||
// localStarUsers,
|
||||
// (users, value, key) => {
|
||||
// if (key !== remoteUsername) {
|
||||
// users[key] = value;
|
||||
// }
|
||||
// return users;
|
||||
// },
|
||||
// {}
|
||||
// );
|
||||
// debug('update package for %o', name);
|
||||
// storage.changePackage(name, { ...info, users }, req.body._rev, function (err) {
|
||||
// afterChangePackage(err);
|
||||
// });
|
||||
// },
|
||||
// });
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,34 +3,32 @@ import _ from 'lodash';
|
|||
|
||||
import { HTTP_STATUS, USERS } from '@verdaccio/core';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { Package } from '@verdaccio/types';
|
||||
import { Version } from '@verdaccio/types';
|
||||
|
||||
import { $NextFunctionVer, $RequestExtend } from '../types/custom';
|
||||
|
||||
type Packages = Package[];
|
||||
|
||||
export default function (route: Router, storage: Storage): void {
|
||||
route.get(
|
||||
'/-/_view/starredByUser',
|
||||
(req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
|
||||
async (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> => {
|
||||
const remoteUsername = req.remote_user.name;
|
||||
|
||||
storage.getLocalDatabase((err, localPackages: Packages) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
try {
|
||||
const localPackages: Version[] = await storage.getLocalDatabaseNext();
|
||||
|
||||
const filteredPackages: Packages = localPackages.filter((localPackage: Package) =>
|
||||
const filteredPackages: Version[] = localPackages.filter((localPackage: Version) =>
|
||||
_.keys(localPackage[USERS]).includes(remoteUsername)
|
||||
);
|
||||
|
||||
res.status(HTTP_STATUS.OK);
|
||||
next({
|
||||
rows: filteredPackages.map((filteredPackage: Package) => ({
|
||||
rows: filteredPackages.map((filteredPackage: Version) => ({
|
||||
value: filteredPackage.name,
|
||||
})),
|
||||
});
|
||||
});
|
||||
} catch (err: any) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createRemoteUser } from '@verdaccio/config';
|
|||
import { API_ERROR, API_MESSAGE, HTTP_STATUS, errorUtils } from '@verdaccio/core';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { Config, RemoteUser } from '@verdaccio/types';
|
||||
import { getAuthenticatedMessage, validatePassword } from '@verdaccio/utils';
|
||||
import { getAuthenticatedMessage, mask, validatePassword } from '@verdaccio/utils';
|
||||
|
||||
import { $NextFunctionVer, $RequestExtend } from '../types/custom';
|
||||
|
||||
|
@ -28,6 +28,23 @@ export default function (route: Router, auth: IAuth, config: Config): void {
|
|||
}
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* body example
|
||||
* req.body = {
|
||||
_id: "org.couchdb.user:jjjj",
|
||||
name: "jjjj",
|
||||
password: "jjjj",
|
||||
type: "user",
|
||||
roles: [],
|
||||
date: "2022-07-08T15:51:04.002Z",
|
||||
}
|
||||
*
|
||||
* @export
|
||||
* @param {Router} route
|
||||
* @param {IAuth} auth
|
||||
* @param {Config} config
|
||||
*/
|
||||
route.put(
|
||||
'/-/user/:org_couchdb_user/:_rev?/:revision?',
|
||||
function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
|
||||
|
@ -92,7 +109,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
|
|||
|
||||
const token =
|
||||
name && password ? await getApiToken(auth, config, user, password) : undefined;
|
||||
debug('adduser: new token %o', token);
|
||||
debug('adduser: new token %o', mask(token as string, 4));
|
||||
if (!token) {
|
||||
return next(errorUtils.getUnauthorized());
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { Package } from '@verdaccio/types';
|
||||
|
||||
/**
|
||||
* Check whether the package metadta has enough data to be published
|
||||
* @param pkg metadata
|
||||
*/
|
||||
|
||||
export function isPublishablePackage(pkg: Package): boolean {
|
||||
// TODO: we can do better, no need get keys
|
||||
const keys: string[] = Object.keys(pkg);
|
||||
|
||||
return _.includes(keys, 'versions');
|
||||
}
|
||||
|
||||
export function isRelatedToDeprecation(pkgInfo: Package): boolean {
|
||||
const { versions } = pkgInfo;
|
||||
for (const version in versions) {
|
||||
if (Object.prototype.hasOwnProperty.call(versions[version], 'deprecated')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -5,7 +5,7 @@ import { IAuth } from '@verdaccio/auth';
|
|||
import { HTTP_STATUS, searchUtils } from '@verdaccio/core';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { Package } from '@verdaccio/types';
|
||||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:search');
|
||||
|
||||
|
@ -16,7 +16,7 @@ const debug = buildDebug('verdaccio:api:search');
|
|||
* req: 'GET /-/v1/search?text=react&size=20&frpom=0&quality=0.65&popularity=0.98&maintenance=0.5'
|
||||
*/
|
||||
export default function (route, auth: IAuth, storage: Storage): void {
|
||||
function checkAccess(pkg: any, auth: any, remoteUser): Promise<Package | null> {
|
||||
function checkAccess(pkg: any, auth: any, remoteUser): Promise<Manifest | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
auth.allow_access({ packageName: pkg?.package?.name }, remoteUser, function (err, allowed) {
|
||||
if (err) {
|
||||
|
@ -49,7 +49,7 @@ export default function (route, auth: IAuth, storage: Storage): void {
|
|||
from = parseInt(from, 10) || 0;
|
||||
|
||||
try {
|
||||
data = await storage.searchManager?.search({
|
||||
data = await storage.search({
|
||||
query,
|
||||
url,
|
||||
abort,
|
||||
|
|
|
@ -1,36 +1,20 @@
|
|||
import buildDebug from 'debug';
|
||||
import { Response, Router } from 'express';
|
||||
|
||||
import { errorUtils } from '@verdaccio/core';
|
||||
|
||||
import { $NextFunctionVer, $RequestExtend } from '../types/custom';
|
||||
|
||||
const debug = buildDebug('verdaccio:api:user');
|
||||
|
||||
export default function (route: Router): void {
|
||||
route.get('/whoami', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
|
||||
debug('whoami: reditect');
|
||||
if (req.headers.referer === 'whoami') {
|
||||
const username = req.remote_user.name;
|
||||
// FIXME: this service should return 401 if user missing
|
||||
// if (!username) {
|
||||
// debug('whoami: user not found');
|
||||
// return next(getUnauthorized('Unauthorized'));
|
||||
// }
|
||||
debug('whoami: logged by user');
|
||||
return next({ username: username });
|
||||
} else {
|
||||
debug('whoami: redirect next route');
|
||||
// redirect to the route below
|
||||
return next('route');
|
||||
route.get('/-/whoami', (req: $RequestExtend, _res: Response, next: $NextFunctionVer): any => {
|
||||
// remote_user is set by the auth middleware
|
||||
const username = req?.remote_user?.name;
|
||||
if (!username) {
|
||||
debug('whoami: user not found');
|
||||
return next(errorUtils.getUnauthorized('Unauthorized'));
|
||||
}
|
||||
});
|
||||
|
||||
route.get('/-/whoami', (req: $RequestExtend, res: Response, next: $NextFunctionVer): any => {
|
||||
const username = req.remote_user.name;
|
||||
// FIXME: this service should return 401 if user missing
|
||||
// if (!username) {
|
||||
// debug('whoami: user not found');
|
||||
// return next(getUnauthorized('Unauthorized'));
|
||||
// }
|
||||
|
||||
debug('whoami: response %o', username);
|
||||
return next({ username: username });
|
||||
|
|
|
@ -1,83 +1,126 @@
|
|||
import bodyParser from 'body-parser';
|
||||
import express, { Application } from 'express';
|
||||
import { Application } from 'express';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import supertest from 'supertest';
|
||||
|
||||
import { Auth, IAuth } from '@verdaccio/auth';
|
||||
import { Config, parseConfigFile } from '@verdaccio/config';
|
||||
import { HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
|
||||
import { errorReportingMiddleware, final, handleError } from '@verdaccio/middleware';
|
||||
import { parseConfigFile } from '@verdaccio/config';
|
||||
import { HEADERS, HEADER_TYPE, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/core';
|
||||
import { setup } from '@verdaccio/logger';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { generatePackageMetadata } from '@verdaccio/test-helper';
|
||||
import {
|
||||
generatePackageMetadata,
|
||||
initializeServer as initializeServerHelper,
|
||||
} from '@verdaccio/test-helper';
|
||||
import { GenericBody } from '@verdaccio/types';
|
||||
import { buildToken, generateRandomHexString } from '@verdaccio/utils';
|
||||
|
||||
import apiEndpoints from '../../src';
|
||||
import apiMiddleware from '../../src';
|
||||
|
||||
const getConf = (conf) => {
|
||||
setup();
|
||||
|
||||
export const getConf = (conf) => {
|
||||
const configPath = path.join(__dirname, 'config', conf);
|
||||
|
||||
return parseConfigFile(configPath);
|
||||
const config = parseConfigFile(configPath);
|
||||
// custom config to avoid conflict with other tests
|
||||
config.auth.htpasswd.file = `${config.auth.htpasswd.file}-${generateRandomHexString()}`;
|
||||
return config;
|
||||
};
|
||||
|
||||
// TODO: replace by @verdaccio/test-helper
|
||||
export async function initializeServer(configName): Promise<Application> {
|
||||
const app = express();
|
||||
const config = new Config(getConf(configName));
|
||||
const storage = new Storage(config);
|
||||
await storage.init(config, []);
|
||||
const auth: IAuth = new Auth(config);
|
||||
// TODO: this might not be need it, used in apiEndpoints
|
||||
app.use(bodyParser.json({ strict: false, limit: '10mb' }));
|
||||
// @ts-ignore
|
||||
app.use(errorReportingMiddleware);
|
||||
// @ts-ignore
|
||||
app.use(apiEndpoints(config, auth, storage));
|
||||
// @ts-ignore
|
||||
app.use(handleError);
|
||||
// @ts-ignore
|
||||
app.use(final);
|
||||
|
||||
app.use(function (request, response) {
|
||||
response.status(590);
|
||||
console.log('respo', response);
|
||||
response.json({ error: 'cannot handle this' });
|
||||
});
|
||||
|
||||
return app;
|
||||
return initializeServerHelper(getConf(configName), [apiMiddleware], Storage);
|
||||
}
|
||||
|
||||
export function publishVersion(app, _configFile, pkgName, version): supertest.Test {
|
||||
const pkgMetadata = generatePackageMetadata(pkgName, version);
|
||||
export function createUser(app, name: string, password: string): supertest.Test {
|
||||
return supertest(app)
|
||||
.put(`/-/user/org.couchdb.user:${name}`)
|
||||
.send({
|
||||
name: name,
|
||||
password: password,
|
||||
})
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.CREATED);
|
||||
}
|
||||
|
||||
export async function getNewToken(app: any, credentials: any): Promise<string> {
|
||||
const response = await createUser(app, credentials.name, credentials.password);
|
||||
const { token, ok } = response.body;
|
||||
expect(ok).toBeDefined();
|
||||
expect(token).toBeDefined();
|
||||
expect(typeof token).toBe('string');
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function generateTokenCLI(app, token, payload): Promise<any> {
|
||||
return supertest(app)
|
||||
.post('/-/npm/v1/tokens')
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(JSON.stringify(payload))
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET);
|
||||
}
|
||||
|
||||
export async function deleteTokenCLI(app, token, tokenToDelete): Promise<any> {
|
||||
return supertest(app)
|
||||
.delete(`/-/npm/v1/tokens/token/${tokenToDelete}`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
}
|
||||
|
||||
export function publishVersionWithToken(
|
||||
app,
|
||||
pkgName: string,
|
||||
version: string,
|
||||
token: string,
|
||||
distTags?: GenericBody
|
||||
): supertest.Test {
|
||||
const pkgMetadata = generatePackageMetadata(pkgName, version, distTags);
|
||||
|
||||
return supertest(app)
|
||||
.put(`/${encodeURIComponent(pkgName)}`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
||||
.send(JSON.stringify(pkgMetadata))
|
||||
.set('accept', HEADERS.GZIP)
|
||||
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON);
|
||||
}
|
||||
|
||||
export function publishVersion(
|
||||
app,
|
||||
pkgName: string,
|
||||
version: string,
|
||||
distTags?: GenericBody
|
||||
): supertest.Test {
|
||||
const pkgMetadata = generatePackageMetadata(pkgName, version, distTags);
|
||||
|
||||
return supertest(app)
|
||||
.put(`/${encodeURIComponent(pkgName)}`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(JSON.stringify(pkgMetadata))
|
||||
.set('accept', HEADERS.GZIP)
|
||||
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
|
||||
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON);
|
||||
}
|
||||
|
||||
export async function publishTaggedVersion(
|
||||
app,
|
||||
configFile,
|
||||
pkgName: string,
|
||||
version: string,
|
||||
tag: string
|
||||
) {
|
||||
const pkgMetadata = generatePackageMetadata(pkgName, version, {
|
||||
[tag]: version,
|
||||
});
|
||||
|
||||
export function getDisTags(app, pkgName) {
|
||||
return supertest(app)
|
||||
.put(
|
||||
`/${encodeURIComponent(pkgName)}/${encodeURIComponent(version)}/-tag/${encodeURIComponent(
|
||||
tag
|
||||
)}`
|
||||
)
|
||||
.get(`/-/package/${encodeURIComponent(pkgName)}/dist-tags`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(JSON.stringify(pkgMetadata))
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.set('accept', HEADERS.GZIP)
|
||||
.set(HEADER_TYPE.ACCEPT_ENCODING, HEADERS.JSON)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
}
|
||||
|
||||
export function getPackage(
|
||||
app: any,
|
||||
token: string,
|
||||
pkgName: string,
|
||||
statusCode: number = HTTP_STATUS.OK
|
||||
): supertest.Test {
|
||||
const test = supertest(app).get(`/${pkgName}`);
|
||||
|
||||
if (_.isNil(token) === false || _.isEmpty(token) === false) {
|
||||
test.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token));
|
||||
}
|
||||
|
||||
return test.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET).expect(statusCode);
|
||||
}
|
||||
|
|
25
packages/api/test/integration/config/distTag.yaml
Normal file
25
packages/api/test/integration/config/distTag.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
storage: ./storage
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd-distTag
|
||||
|
||||
web:
|
||||
enable: true
|
||||
title: verdaccio
|
||||
|
||||
publish:
|
||||
allow_offline: false
|
||||
|
||||
uplinks:
|
||||
|
||||
log: { type: stdout, format: pretty, level: trace }
|
||||
|
||||
packages:
|
||||
'@*/*':
|
||||
access: $anonymous
|
||||
publish: $anonymous
|
||||
'**':
|
||||
access: $anonymous
|
||||
publish: $anonymous
|
||||
_debug: true
|
|
@ -1,13 +1,8 @@
|
|||
store:
|
||||
memory:
|
||||
limit: 1000
|
||||
storage: ./storage
|
||||
|
||||
auth:
|
||||
auth-memory:
|
||||
users:
|
||||
test:
|
||||
name: test
|
||||
password: test
|
||||
htpasswd:
|
||||
file: ./htpasswd-package
|
||||
|
||||
web:
|
||||
enable: true
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
store:
|
||||
memory:
|
||||
limit: 10
|
||||
|
||||
auth:
|
||||
auth-memory:
|
||||
users:
|
||||
htpasswd:
|
||||
file: ./htpasswd-ping
|
||||
web:
|
||||
enable: true
|
||||
title: verdaccio
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
store:
|
||||
memory:
|
||||
limit: 1000
|
||||
|
||||
auth:
|
||||
auth-memory:
|
||||
users:
|
||||
test:
|
||||
name: test
|
||||
password: test
|
||||
|
||||
htpasswd:
|
||||
file: ./htpasswd-publish
|
||||
web:
|
||||
enable: true
|
||||
title: verdaccio
|
||||
|
@ -24,7 +16,9 @@ packages:
|
|||
'@*/*':
|
||||
access: $anonymous
|
||||
publish: $anonymous
|
||||
unpublish: $anonymous
|
||||
'**':
|
||||
access: $anonymous
|
||||
publish: $anonymous
|
||||
unpublish: $anonymous
|
||||
_debug: true
|
||||
|
|
29
packages/api/test/integration/config/search.yaml
Normal file
29
packages/api/test/integration/config/search.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
storage: ./storage
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd-search
|
||||
|
||||
web:
|
||||
enable: true
|
||||
title: verdaccio
|
||||
|
||||
uplinks:
|
||||
|
||||
log: { type: stdout, format: pretty, level: trace }
|
||||
|
||||
packages:
|
||||
'private-*':
|
||||
access: $all
|
||||
publish: jota
|
||||
'@private/*':
|
||||
access: $all
|
||||
publish: jota
|
||||
'@*/*':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
'**':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
|
||||
_debug: true
|
|
@ -1,6 +1,3 @@
|
|||
storage: ./storage
|
||||
plugins: ./plugins
|
||||
|
||||
security:
|
||||
api:
|
||||
jwt:
|
||||
|
@ -9,6 +6,8 @@ security:
|
|||
# to avoid invalid verification token, more info on JWT page
|
||||
notBefore: 0
|
||||
|
||||
storage: ./storage
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
|
@ -20,7 +19,9 @@ packages:
|
|||
'only-you-can-publish':
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
log: { type: stdout, format: pretty, level: warn }
|
||||
|
||||
log: { type: stdout, format: pretty, level: debug }
|
||||
|
||||
## enable token for testing
|
||||
flags:
|
||||
## enable token for testing
|
||||
token: true
|
19
packages/api/test/integration/config/token.yaml
Normal file
19
packages/api/test/integration/config/token.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
storage: ./storage
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
|
||||
packages:
|
||||
'@token/*':
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
'only-you-can-publish':
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
|
||||
log: { type: stdout, format: pretty, level: debug }
|
||||
|
||||
## enable token for testing
|
||||
flags:
|
||||
token: true
|
|
@ -8,6 +8,10 @@ auth:
|
|||
htpasswd:
|
||||
file: ./htpasswd
|
||||
|
||||
uplinks:
|
||||
ver:
|
||||
url: https://registry.verdaccio.org
|
||||
|
||||
security:
|
||||
api:
|
||||
jwt:
|
||||
|
@ -18,18 +22,16 @@ packages:
|
|||
'@*/*':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
proxy: remote
|
||||
'vue':
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
proxy: remote
|
||||
proxy: ver
|
||||
'**':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
proxy: remote
|
||||
|
||||
middlewares:
|
||||
audit:
|
||||
enabled: true
|
||||
|
||||
log: { type: stdout, format: pretty, level: warn }
|
||||
log: { type: stdout, format: pretty, level: info }
|
|
@ -1,21 +1,13 @@
|
|||
store:
|
||||
memory:
|
||||
limit: 1000
|
||||
|
||||
auth:
|
||||
auth-memory:
|
||||
users:
|
||||
test:
|
||||
name: test
|
||||
password: test
|
||||
|
||||
htpasswd:
|
||||
file: ./htpasswd-user
|
||||
web:
|
||||
enable: true
|
||||
title: verdaccio
|
||||
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.npmjs.org/
|
||||
ver:
|
||||
url: https://registry.verdaccio.org
|
||||
|
||||
log: { type: stdout, format: pretty, level: trace }
|
||||
|
||||
|
@ -24,13 +16,15 @@ packages:
|
|||
access: $all
|
||||
publish: $all
|
||||
unpublish: $all
|
||||
proxy: npmjs
|
||||
'verdaccio':
|
||||
access: $all
|
||||
publish: $all
|
||||
'vue':
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
proxy: ver
|
||||
'**':
|
||||
access: $all
|
||||
publish: $all
|
||||
unpublish: $all
|
||||
proxy: npmjs
|
||||
_debug: true
|
||||
|
|
|
@ -1,14 +1,3 @@
|
|||
store:
|
||||
memory:
|
||||
limit: 1000
|
||||
|
||||
auth:
|
||||
auth-memory:
|
||||
users:
|
||||
test:
|
||||
name: test
|
||||
password: test
|
||||
|
||||
web:
|
||||
enable: true
|
||||
title: verdaccio
|
||||
|
@ -19,6 +8,10 @@ uplinks:
|
|||
|
||||
log: { type: stdout, format: pretty, level: trace }
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd-whoami
|
||||
|
||||
packages:
|
||||
'@*/*':
|
||||
access: $all
|
||||
|
|
76
packages/api/test/integration/distTag.spec.ts
Normal file
76
packages/api/test/integration/distTag.spec.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import supertest from 'supertest';
|
||||
|
||||
import { API_MESSAGE, HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
|
||||
|
||||
import { getDisTags, initializeServer, publishVersion } from './_helper';
|
||||
|
||||
describe('package', () => {
|
||||
let app;
|
||||
beforeEach(async () => {
|
||||
app = await initializeServer('distTag.yaml');
|
||||
});
|
||||
|
||||
test.each([['foo'], ['@scope/foo']])('should display dist-tag (npm dist-tag ls)', async (pkg) => {
|
||||
await publishVersion(app, pkg, '1.0.0');
|
||||
await publishVersion(app, pkg, '1.0.1');
|
||||
const response = await getDisTags(app, pkg);
|
||||
expect(response.body).toEqual({ latest: '1.0.1' });
|
||||
});
|
||||
|
||||
test('should add a version to a tag (npm dist-tag add)', async () => {
|
||||
await publishVersion(app, encodeURIComponent('foo'), '1.0.0');
|
||||
await publishVersion(app, encodeURIComponent('foo'), '1.0.1');
|
||||
|
||||
const response = await supertest(app)
|
||||
.put(`/${encodeURIComponent('foo')}/test`)
|
||||
.set(HEADERS.ACCEPT, HEADERS.GZIP)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(JSON.stringify('1.0.1'))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.CREATED);
|
||||
expect(response.body.ok).toEqual(API_MESSAGE.TAG_ADDED);
|
||||
const response2 = await getDisTags(app, 'foo');
|
||||
expect(response2.body).toEqual({ latest: '1.0.1', test: '1.0.1' });
|
||||
});
|
||||
|
||||
test('should fails if a version is missing (npm dist-tag add)', async () => {
|
||||
await publishVersion(app, encodeURIComponent('foo'), '1.0.0');
|
||||
await publishVersion(app, encodeURIComponent('foo'), '1.0.1');
|
||||
|
||||
await supertest(app)
|
||||
.put(`/${encodeURIComponent('foo')}/test`)
|
||||
.set(HEADERS.ACCEPT, HEADERS.GZIP)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(JSON.stringify({}))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.BAD_REQUEST);
|
||||
});
|
||||
|
||||
test('should delete a previous added tag (npm dist-tag rm)', async () => {
|
||||
await publishVersion(app, encodeURIComponent('foo'), '1.0.0');
|
||||
await publishVersion(app, encodeURIComponent('foo'), '1.0.1');
|
||||
|
||||
const response = await supertest(app)
|
||||
.put(`/${encodeURIComponent('foo')}/beta`)
|
||||
.set(HEADERS.ACCEPT, HEADERS.GZIP)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(JSON.stringify('1.0.1'))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.CREATED);
|
||||
expect(response.body.ok).toEqual(API_MESSAGE.TAG_ADDED);
|
||||
|
||||
const response2 = await getDisTags(app, 'foo');
|
||||
expect(response2.body).toEqual({ latest: '1.0.1', beta: '1.0.1' });
|
||||
|
||||
const response3 = await supertest(app)
|
||||
.delete(`/-/package/${encodeURIComponent('foo')}/dist-tags/beta`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.CREATED);
|
||||
expect(response3.body.ok).toEqual(API_MESSAGE.TAG_REMOVED);
|
||||
|
||||
const response4 = await getDisTags(app, 'foo');
|
||||
expect(response4.body).toEqual({ latest: '1.0.1' });
|
||||
});
|
||||
});
|
|
@ -2,32 +2,7 @@ import supertest from 'supertest';
|
|||
|
||||
import { HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
|
||||
|
||||
import { $RequestExtend, $ResponseExtend } from '../../types/custom';
|
||||
import { initializeServer, publishTaggedVersion, publishVersion } from './_helper';
|
||||
|
||||
const mockApiJWTmiddleware = jest.fn(
|
||||
() =>
|
||||
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
|
||||
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
|
||||
_next();
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock('@verdaccio/auth', () => ({
|
||||
Auth: class {
|
||||
apiJWTmiddleware() {
|
||||
return mockApiJWTmiddleware();
|
||||
}
|
||||
allow_access(_d, _f, cb) {
|
||||
// always allow access
|
||||
cb(null, true);
|
||||
}
|
||||
allow_publish(_d, _f, cb) {
|
||||
// always allow publish
|
||||
cb(null, true);
|
||||
}
|
||||
},
|
||||
}));
|
||||
import { initializeServer, publishVersion } from './_helper';
|
||||
|
||||
describe('package', () => {
|
||||
let app;
|
||||
|
@ -35,57 +10,39 @@ describe('package', () => {
|
|||
app = await initializeServer('package.yaml');
|
||||
});
|
||||
|
||||
test('should return a package', async () => {
|
||||
await publishVersion(app, 'package.yaml', 'foo', '1.0.0');
|
||||
return new Promise((resolve) => {
|
||||
supertest(app)
|
||||
.get('/foo')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.then((response) => {
|
||||
expect(response.body.name).toEqual('foo');
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should return a package by version', async () => {
|
||||
await publishVersion(app, 'package.yaml', 'foo2', '1.0.0');
|
||||
return new Promise((resolve) => {
|
||||
supertest(app)
|
||||
.get('/foo2/1.0.0')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.then((response) => {
|
||||
expect(response.body.name).toEqual('foo2');
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME: investigate the 404
|
||||
test.skip('should return a package by dist-tag', async (done) => {
|
||||
// await publishVersion(app, 'package.yaml', 'foo3', '1.0.0');
|
||||
await publishVersion(app, 'package.yaml', 'foo-tagged', '1.0.0');
|
||||
await publishTaggedVersion(app, 'package.yaml', 'foo-tagged', '1.0.1', 'test');
|
||||
return supertest(app)
|
||||
.get('/foo-tagged/1.0.1')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
test.each([['foo'], ['@scope/foo']])('should return a foo private package', async (pkg) => {
|
||||
await publishVersion(app, pkg, '1.0.0');
|
||||
const response = await supertest(app)
|
||||
.get(`/${pkg}`)
|
||||
.set(HEADERS.ACCEPT, HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.then((response) => {
|
||||
expect(response.body.name).toEqual('foo3');
|
||||
done();
|
||||
});
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response.body.name).toEqual(pkg);
|
||||
});
|
||||
|
||||
test('should return 404', async () => {
|
||||
return supertest(app)
|
||||
.get('/404-not-found')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.NOT_FOUND);
|
||||
});
|
||||
test.each([['foo'], ['@scope/foo']])(
|
||||
'should return a foo private package by version',
|
||||
async (pkg) => {
|
||||
await publishVersion(app, pkg, '1.0.0');
|
||||
const response = await supertest(app)
|
||||
.get(`/${pkg}`)
|
||||
.set(HEADERS.ACCEPT, HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response.body.name).toEqual(pkg);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([['foo'], ['@scope/foo']])(
|
||||
'should return a foo private package by version',
|
||||
async (pkg) => {
|
||||
await publishVersion(app, pkg, '1.0.0');
|
||||
const response = await supertest(app)
|
||||
.get(`/${pkg}`)
|
||||
.set(HEADERS.ACCEPT, HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response.body.name).toEqual(pkg);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,11 +6,12 @@ import { initializeServer } from './_helper';
|
|||
|
||||
describe('ping', () => {
|
||||
test('should return the reply the ping', async () => {
|
||||
return supertest(await initializeServer('ping.yaml'))
|
||||
const app = await initializeServer('ping.yaml');
|
||||
const response = await supertest(app)
|
||||
.get('/-/ping')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.then((response) => expect(response.body).toEqual({}));
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response.body).toEqual({});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -60,9 +60,9 @@ describe('publish', () => {
|
|||
describe('handle invalid publish formats', () => {
|
||||
const pkgName = 'test';
|
||||
const pkgMetadata = generatePackageMetadata(pkgName, '1.0.0');
|
||||
test.skip('should fail on publish a bad _attachments package', async (done) => {
|
||||
test('should fail on publish a bad _attachments package', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
return supertest(app)
|
||||
const response = await supertest(app)
|
||||
.put(`/${encodeURIComponent(pkgName)}`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(
|
||||
|
@ -73,12 +73,8 @@ describe('publish', () => {
|
|||
)
|
||||
)
|
||||
.set('accept', HEADERS.GZIP)
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.then((response) => {
|
||||
console.log('response.body', response.body);
|
||||
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
|
||||
done();
|
||||
});
|
||||
.expect(HTTP_STATUS.BAD_REQUEST);
|
||||
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
|
||||
});
|
||||
|
||||
test('should fail on publish a bad versions package', async () => {
|
||||
|
@ -97,7 +93,6 @@ describe('publish', () => {
|
|||
.set('accept', HEADERS.GZIP)
|
||||
.expect(HTTP_STATUS.BAD_REQUEST)
|
||||
.then((response) => {
|
||||
console.log('response.body', response.body);
|
||||
expect(response.body.error).toEqual(API_ERROR.UNSUPORTED_REGISTRY_CALL);
|
||||
resolve(response);
|
||||
});
|
||||
|
@ -109,7 +104,7 @@ describe('publish', () => {
|
|||
test('should publish a package', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
return new Promise((resolve) => {
|
||||
publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
|
||||
publishVersion(app, 'foo', '1.0.0')
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.then((response) => {
|
||||
expect(response.body.ok).toEqual(API_MESSAGE.PKG_CREATED);
|
||||
|
@ -126,13 +121,7 @@ describe('publish', () => {
|
|||
supertest(app)
|
||||
.put(`/${encodeURIComponent(pkgName)}`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.send(
|
||||
JSON.stringify(
|
||||
Object.assign({}, pkgMetadata, {
|
||||
_attachments: null,
|
||||
})
|
||||
)
|
||||
)
|
||||
.send(JSON.stringify(Object.assign({}, pkgMetadata)))
|
||||
.set('accept', HEADERS.GZIP)
|
||||
.expect(HTTP_STATUS.CREATED)
|
||||
.then((response) => {
|
||||
|
@ -173,12 +162,11 @@ describe('publish', () => {
|
|||
|
||||
test('should fails on publish a duplicated package', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
|
||||
await publishVersion(app, 'foo', '1.0.0');
|
||||
return new Promise((resolve) => {
|
||||
publishVersion(app, 'publish.yaml', 'foo', '1.0.0')
|
||||
publishVersion(app, 'foo', '1.0.0')
|
||||
.expect(HTTP_STATUS.CONFLICT)
|
||||
.then((response) => {
|
||||
console.log('response.body', response.body);
|
||||
expect(response.body.error).toEqual(API_ERROR.PACKAGE_EXIST);
|
||||
resolve(response);
|
||||
});
|
||||
|
@ -186,14 +174,61 @@ describe('publish', () => {
|
|||
});
|
||||
|
||||
describe('unpublish a package', () => {
|
||||
let app;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await initializeServer('publish.yaml');
|
||||
await publishVersion(app, 'publish.yaml', 'foo', '1.0.0');
|
||||
test('should unpublish entirely a package', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
await publishVersion(app, 'foo', '1.0.0');
|
||||
const response = await supertest(app)
|
||||
// FIXME: should be filtered by revision to avoid
|
||||
// conflicts
|
||||
.delete(`/${encodeURIComponent('foo')}/-rev/xxx`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HTTP_STATUS.CREATED);
|
||||
expect(response.body.ok).toEqual(API_MESSAGE.PKG_REMOVED);
|
||||
// package should be completely un published
|
||||
await supertest(app)
|
||||
.get('/foo')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.NOT_FOUND);
|
||||
});
|
||||
|
||||
test('should unpublish a package', () => {});
|
||||
test('should fails unpublish entirely a package', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
const response = await supertest(app)
|
||||
.delete(`/${encodeURIComponent('foo')}/-rev/1cf3-fe3`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HTTP_STATUS.NOT_FOUND);
|
||||
expect(response.body.error).toEqual(API_ERROR.NO_PACKAGE);
|
||||
});
|
||||
|
||||
test('should fails remove a tarball of a package does not exist', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
const response = await supertest(app)
|
||||
.delete(`/foo/-/foo-1.0.3.tgz/-rev/revision`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HTTP_STATUS.NOT_FOUND);
|
||||
expect(response.body.error).toEqual(API_ERROR.NO_PACKAGE);
|
||||
});
|
||||
|
||||
test('should fails on try remove a tarball does not exist', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
await publishVersion(app, 'foo', '1.0.0');
|
||||
const response = await supertest(app)
|
||||
.delete(`/foo/-/foo-1.0.3.tgz/-rev/revision`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HTTP_STATUS.NOT_FOUND);
|
||||
expect(response.body.error).toEqual(API_ERROR.NO_SUCH_FILE);
|
||||
});
|
||||
|
||||
test('should remove a tarball that does exist', async () => {
|
||||
const app = await initializeServer('publish.yaml');
|
||||
await publishVersion(app, 'foo', '1.0.0');
|
||||
const response = await supertest(app)
|
||||
.delete(`/foo/-/foo-1.0.0.tgz/-rev/revision`)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HTTP_STATUS.CREATED);
|
||||
expect(response.body.ok).toEqual(API_MESSAGE.TARBALL_REMOVED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('star a package', () => {});
|
||||
|
|
126
packages/api/test/integration/search.spec.ts
Normal file
126
packages/api/test/integration/search.spec.ts
Normal file
|
@ -0,0 +1,126 @@
|
|||
import MockDate from 'mockdate';
|
||||
import supertest from 'supertest';
|
||||
|
||||
import { HEADERS, HEADER_TYPE, HTTP_STATUS } from '@verdaccio/core';
|
||||
|
||||
import { createUser, initializeServer, publishVersionWithToken } from './_helper';
|
||||
|
||||
describe('search', () => {
|
||||
let app;
|
||||
beforeEach(async () => {
|
||||
app = await initializeServer('search.yaml');
|
||||
});
|
||||
|
||||
describe('search authenticated', () => {
|
||||
test.each([['foo']])('should return a foo private package', async (pkg) => {
|
||||
const mockDate = '2018-01-14T11:17:40.712Z';
|
||||
MockDate.set(mockDate);
|
||||
const res = await createUser(app, 'test', 'test');
|
||||
await publishVersionWithToken(app, pkg, '1.0.0', res.body.token);
|
||||
// this should not be displayed as part of the search
|
||||
await publishVersionWithToken(app, 'private-auth', '1.0.0', res.body.token);
|
||||
const response = await supertest(app)
|
||||
.get(
|
||||
`/-/v1/search?text=${encodeURIComponent(
|
||||
pkg
|
||||
)}&size=2000&from=0&quality=1&popularity=0.1&maintenance=0.1`
|
||||
)
|
||||
.set(HEADERS.ACCEPT, HEADERS.JSON)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response.body).toEqual({
|
||||
objects: [
|
||||
{
|
||||
package: {
|
||||
author: {
|
||||
email: 'user@domain.com',
|
||||
name: 'User NPM',
|
||||
},
|
||||
date: mockDate,
|
||||
description: 'package generated',
|
||||
keywords: [],
|
||||
links: {
|
||||
npm: '',
|
||||
},
|
||||
name: pkg,
|
||||
publisher: {},
|
||||
scope: '',
|
||||
version: '1.0.0',
|
||||
},
|
||||
score: {
|
||||
detail: {
|
||||
maintenance: 0,
|
||||
popularity: 1,
|
||||
quality: 1,
|
||||
},
|
||||
final: 1,
|
||||
},
|
||||
searchScore: 1,
|
||||
verdaccioPkgCached: false,
|
||||
verdaccioPrivate: true,
|
||||
},
|
||||
],
|
||||
time: 'Sun, 14 Jan 2018 11:17:40 GMT',
|
||||
total: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test.each([['@scope/foo']])('should return a scoped foo private package', async (pkg) => {
|
||||
const mockDate = '2018-01-14T11:17:40.712Z';
|
||||
MockDate.set(mockDate);
|
||||
const res = await createUser(app, 'test', 'test');
|
||||
await publishVersionWithToken(app, pkg, '1.0.0', res.body.token);
|
||||
// this should not be displayed as part of the search
|
||||
await publishVersionWithToken(app, '@private/auth', '1.0.0', res.body.token);
|
||||
const response = await supertest(app)
|
||||
.get(
|
||||
`/-/v1/search?text=${encodeURIComponent(
|
||||
pkg
|
||||
)}&size=2000&from=0&quality=1&popularity=0.1&maintenance=0.1`
|
||||
)
|
||||
.set(HEADERS.ACCEPT, HEADERS.JSON)
|
||||
.set(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON)
|
||||
.expect(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response.body).toEqual({
|
||||
objects: [
|
||||
{
|
||||
package: {
|
||||
author: {
|
||||
email: 'user@domain.com',
|
||||
name: 'User NPM',
|
||||
},
|
||||
date: mockDate,
|
||||
description: 'package generated',
|
||||
keywords: [],
|
||||
links: {
|
||||
npm: '',
|
||||
},
|
||||
name: pkg,
|
||||
publisher: {},
|
||||
scope: '@scope',
|
||||
version: '1.0.0',
|
||||
},
|
||||
score: {
|
||||
detail: {
|
||||
maintenance: 0,
|
||||
popularity: 1,
|
||||
quality: 1,
|
||||
},
|
||||
final: 1,
|
||||
},
|
||||
searchScore: 1,
|
||||
verdaccioPkgCached: false,
|
||||
verdaccioPrivate: true,
|
||||
},
|
||||
],
|
||||
time: 'Sun, 14 Jan 2018 11:17:40 GMT',
|
||||
total: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('error handling', () => {
|
||||
test.todo('should able to abort the request');
|
||||
});
|
||||
});
|
124
packages/api/test/integration/token.spec.ts
Normal file
124
packages/api/test/integration/token.spec.ts
Normal file
|
@ -0,0 +1,124 @@
|
|||
import _ from 'lodash';
|
||||
import supertest from 'supertest';
|
||||
|
||||
import {
|
||||
API_ERROR,
|
||||
HEADERS,
|
||||
HEADER_TYPE,
|
||||
HTTP_STATUS,
|
||||
SUPPORT_ERRORS,
|
||||
TOKEN_BEARER,
|
||||
} from '@verdaccio/core';
|
||||
import { buildToken } from '@verdaccio/utils';
|
||||
|
||||
import { deleteTokenCLI, generateTokenCLI, getNewToken, initializeServer } from './_helper';
|
||||
|
||||
describe('token', () => {
|
||||
describe('basics', () => {
|
||||
test.each([['token.yaml'], ['token.jwt.yaml']])('should list empty tokens', async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const token = await getNewToken(app, { name: 'jota_token', password: 'secretPass' });
|
||||
const response = await supertest(app)
|
||||
.get('/-/npm/v1/tokens')
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response.body.objects).toHaveLength(0);
|
||||
});
|
||||
|
||||
test.each([['token.yaml'], ['token.jwt.yaml']])('should generate one token', async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'jota_token', password: 'secretPass' };
|
||||
const token = await getNewToken(app, credentials);
|
||||
await generateTokenCLI(app, token, {
|
||||
password: credentials.password,
|
||||
readonly: false,
|
||||
cidr_whitelist: [],
|
||||
});
|
||||
const response = await supertest(app)
|
||||
.get('/-/npm/v1/tokens')
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
const { objects, urls } = response.body;
|
||||
|
||||
expect(objects).toHaveLength(1);
|
||||
const [tokenGenerated] = objects;
|
||||
expect(tokenGenerated.user).toEqual(credentials.name);
|
||||
expect(tokenGenerated.readonly).toBeFalsy();
|
||||
expect(tokenGenerated.token).toMatch(/.../);
|
||||
expect(_.isString(tokenGenerated.created)).toBeTruthy();
|
||||
|
||||
// we don't support pagination yet
|
||||
expect(urls.next).toEqual('');
|
||||
});
|
||||
|
||||
test.each([['token.yaml'], ['token.jwt.yaml']])('should delete a token', async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'jota_token', password: 'secretPass' };
|
||||
const token = await getNewToken(app, credentials);
|
||||
const response = await generateTokenCLI(app, token, {
|
||||
password: credentials.password,
|
||||
readonly: false,
|
||||
cidr_whitelist: [],
|
||||
});
|
||||
|
||||
const key = response.body.key;
|
||||
await deleteTokenCLI(app, token, key);
|
||||
const response2 = await supertest(app)
|
||||
.get('/-/npm/v1/tokens')
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, token))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
const { objects } = response2.body;
|
||||
expect(objects).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle errors', () => {
|
||||
test.each([['token.yaml'], ['token.jwt.yaml']])('should delete a token', async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'jota_token', password: 'secretPass' };
|
||||
const token = await getNewToken(app, credentials);
|
||||
const resp = await generateTokenCLI(app, token, {
|
||||
password: 'wrongPassword',
|
||||
readonly: false,
|
||||
cidr_whitelist: [],
|
||||
});
|
||||
expect(resp.body.error).toEqual(API_ERROR.BAD_USERNAME_PASSWORD);
|
||||
});
|
||||
|
||||
test.each([['token.yaml'], ['token.jwt.yaml']])(
|
||||
'should fail if readonly is missing',
|
||||
async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'jota_token', password: 'secretPass' };
|
||||
const token = await getNewToken(app, credentials);
|
||||
const resp = await generateTokenCLI(app, token, {
|
||||
password: credentials.password,
|
||||
cidr_whitelist: [],
|
||||
});
|
||||
expect(resp.body.error).toEqual(SUPPORT_ERRORS.PARAMETERS_NOT_VALID);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test.each([['token.yaml'], ['token.jwt.yaml']])(
|
||||
'should fail if cidr_whitelist is missing',
|
||||
async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'jota_token', password: 'secretPass' };
|
||||
const token = await getNewToken(app, credentials);
|
||||
const resp = await generateTokenCLI(app, token, {
|
||||
password: credentials.password,
|
||||
readonly: false,
|
||||
});
|
||||
expect(resp.body.error).toEqual(SUPPORT_ERRORS.PARAMETERS_NOT_VALID);
|
||||
}
|
||||
);
|
||||
|
||||
test.todo('handle failure if delete token');
|
||||
test.todo('handle failure if getApiToken fails');
|
||||
test.todo('handle failure if token creating fails');
|
||||
test.todo('handle failure if token list fails');
|
||||
});
|
87
packages/api/test/integration/user.jwt.spec.ts
Normal file
87
packages/api/test/integration/user.jwt.spec.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import nock from 'nock';
|
||||
import supertest from 'supertest';
|
||||
|
||||
import { API_ERROR, HEADERS, HEADER_TYPE, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/core';
|
||||
import { generateRemotePackageMetadata } from '@verdaccio/test-helper';
|
||||
import { buildToken } from '@verdaccio/utils';
|
||||
|
||||
import { createUser, getPackage, initializeServer } from './_helper';
|
||||
|
||||
const FORBIDDEN_VUE = 'authorization required to access package vue';
|
||||
|
||||
describe('token', () => {
|
||||
describe('basics', () => {
|
||||
const FAKE_TOKEN: string = buildToken(TOKEN_BEARER, 'fake');
|
||||
test.each([['user.yaml'], ['user.jwt.yaml']])('should test add a new user', async (conf) => {
|
||||
const upstreamManifest = generateRemotePackageMetadata(
|
||||
'vue',
|
||||
'1.0.0',
|
||||
'https://registry.verdaccio.org'
|
||||
);
|
||||
nock('https://registry.verdaccio.org').get(`/vue`).reply(201, upstreamManifest);
|
||||
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'JotaJWT', password: 'secretPass' };
|
||||
const response = await createUser(app, credentials.name, credentials.password);
|
||||
expect(response.body.ok).toMatch(`user '${credentials.name}' created`);
|
||||
|
||||
const vueResponse = await getPackage(app, response.body.token, 'vue');
|
||||
expect(vueResponse.body).toBeDefined();
|
||||
expect(vueResponse.body.name).toMatch('vue');
|
||||
|
||||
const vueFailResp = await getPackage(app, FAKE_TOKEN, 'vue', HTTP_STATUS.UNAUTHORIZED);
|
||||
expect(vueFailResp.body.error).toMatch(FORBIDDEN_VUE);
|
||||
});
|
||||
|
||||
test.each([['user.yaml'], ['user.jwt.yaml']])('should test add a new user', async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'JotaJWT', password: 'secretPass' };
|
||||
const response = await createUser(app, credentials.name, credentials.password);
|
||||
expect(response.body.ok).toMatch(`user '${credentials.name}' created`);
|
||||
const response2 = await supertest(app)
|
||||
.put(`/-/user/org.couchdb.user:${credentials.name}`)
|
||||
.send({
|
||||
name: credentials.name,
|
||||
password: credentials.password,
|
||||
})
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.CONFLICT);
|
||||
expect(response2.body.error).toBe(API_ERROR.USERNAME_ALREADY_REGISTERED);
|
||||
});
|
||||
|
||||
test.each([['user.yaml'], ['user.jwt.yaml']])(
|
||||
'should fails on login if user credentials are invalid even if jwt',
|
||||
async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'newFailsUser', password: 'secretPass' };
|
||||
const response = await createUser(app, credentials.name, credentials.password);
|
||||
expect(response.body.ok).toMatch(`user '${credentials.name}' created`);
|
||||
const response2 = await supertest(app)
|
||||
.put(`/-/user/org.couchdb.user:${credentials.name}`)
|
||||
.send({
|
||||
name: credentials.name,
|
||||
password: 'BAD_PASSWORD',
|
||||
})
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.UNAUTHORIZED);
|
||||
expect(response2.body.error).toBe(API_ERROR.UNAUTHORIZED_ACCESS);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([['user.yaml'], ['user.jwt.yaml']])(
|
||||
'should verify if user is logged',
|
||||
async (conf) => {
|
||||
const app = await initializeServer(conf);
|
||||
const credentials = { name: 'jota', password: 'secretPass' };
|
||||
const response = await createUser(app, credentials.name, credentials.password);
|
||||
expect(response.body.ok).toMatch(`user '${credentials.name}' created`);
|
||||
const response2 = await supertest(app)
|
||||
.get(`/-/user/org.couchdb.user:${credentials.name}`)
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, response.body.token))
|
||||
.expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK);
|
||||
expect(response2.body.ok).toBe(`you are authenticated as '${credentials.name}'`);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -47,6 +47,7 @@ jest.mock('@verdaccio/auth', () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// FIXME: This might be covered with user.jwt.spec
|
||||
describe('user', () => {
|
||||
const credentials = { name: 'test', password: 'test' };
|
||||
|
||||
|
|
|
@ -1,53 +1,35 @@
|
|||
import supertest from 'supertest';
|
||||
|
||||
import { HEADERS, HTTP_STATUS } from '@verdaccio/core';
|
||||
import { HEADERS, HTTP_STATUS, TOKEN_BEARER } from '@verdaccio/core';
|
||||
import { buildToken } from '@verdaccio/utils';
|
||||
|
||||
import { $RequestExtend, $ResponseExtend } from '../../types/custom';
|
||||
import { initializeServer } from './_helper';
|
||||
|
||||
const mockApiJWTmiddleware = jest.fn(
|
||||
() =>
|
||||
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
|
||||
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
|
||||
_next();
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock('@verdaccio/auth', () => ({
|
||||
Auth: class {
|
||||
apiJWTmiddleware() {
|
||||
return mockApiJWTmiddleware();
|
||||
}
|
||||
allow_access(_d, f_, cb) {
|
||||
cb(null, true);
|
||||
}
|
||||
},
|
||||
}));
|
||||
import { createUser, initializeServer } from './_helper';
|
||||
|
||||
describe('whoami', () => {
|
||||
test.skip('should test referer /whoami endpoint', async (done) => {
|
||||
return supertest(await initializeServer('whoami.yaml'))
|
||||
.get('/whoami')
|
||||
.set('referer', 'whoami')
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
test.skip('should test no referer /whoami endpoint', async (done) => {
|
||||
return supertest(await initializeServer('whoami.yaml'))
|
||||
.get('/whoami')
|
||||
.expect(HTTP_STATUS.NOT_FOUND)
|
||||
.end(done);
|
||||
});
|
||||
|
||||
test('should return the logged username', async () => {
|
||||
return supertest(await initializeServer('whoami.yaml'))
|
||||
const app = await initializeServer('whoami.yaml');
|
||||
// @ts-expect-error internal property
|
||||
const { _body } = await createUser(app, 'test', 'test');
|
||||
return supertest(app)
|
||||
.get('/-/whoami')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.set(HEADERS.AUTHORIZATION, buildToken(TOKEN_BEARER, _body.token))
|
||||
.expect('Content-Type', HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.then((response) => {
|
||||
expect(response.body.username).toEqual('foo');
|
||||
expect(response.body.username).toEqual('test');
|
||||
});
|
||||
});
|
||||
|
||||
test('should fails with 401 if is not logged in', async () => {
|
||||
const app = await initializeServer('whoami.yaml');
|
||||
// @ts-expect-error internal property
|
||||
const { _body } = await createUser(app, 'test', 'test');
|
||||
return supertest(app)
|
||||
.get('/-/whoami')
|
||||
.set('Accept', HEADERS.JSON)
|
||||
.set(HEADERS.AUTHORIZATION, buildToken('invalid-token', _body.token))
|
||||
.expect('Content-Type', HEADERS.JSON_CHARSET)
|
||||
.expect(HTTP_STATUS.UNAUTHORIZED);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Publish endpoints - publish package should change the existing package 1`] = `[MockFunction]`;
|
||||
|
||||
exports[`Publish endpoints - publish package should publish a new a new package 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"verdaccio",
|
||||
Object {
|
||||
"dist-tags": Object {},
|
||||
"name": "verdaccio",
|
||||
"time": Object {},
|
||||
"versions": Object {},
|
||||
},
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Publish endpoints - publish package test start should star a package 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"verdaccio",
|
||||
Object {
|
||||
"users": Object {
|
||||
"verdaccio": true,
|
||||
},
|
||||
},
|
||||
"15-e53a77096b0ee33e",
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
252
packages/api/test/unit/publish.disabled.ts
Normal file
252
packages/api/test/unit/publish.disabled.ts
Normal file
|
@ -0,0 +1,252 @@
|
|||
// import { API_ERROR, HTTP_STATUS, errorUtils } from '@verdaccio/core';
|
||||
|
||||
// import { addVersion, publishPackage, removeTarball, unPublishPackage } from '../../src/publish';
|
||||
|
||||
// const REVISION_MOCK = '15-e53a77096b0ee33e';
|
||||
|
||||
// require('@verdaccio/logger').setup([{ type: 'stdout', format: 'pretty', level: 'info' }]);
|
||||
|
||||
// describe('Publish endpoints - add a tag', () => {
|
||||
// let req;
|
||||
// let res;
|
||||
// let next;
|
||||
|
||||
// beforeEach(() => {
|
||||
// req = {
|
||||
// params: {
|
||||
// version: '1.0.0',
|
||||
// tag: 'tag',
|
||||
// package: 'verdaccio',
|
||||
// },
|
||||
// body: '',
|
||||
// };
|
||||
// res = {
|
||||
// status: jest.fn(),
|
||||
// };
|
||||
|
||||
// next = jest.fn();
|
||||
// });
|
||||
|
||||
// test('should add a version', (done) => {
|
||||
// const storage = {
|
||||
// addVersion: (packageName, version, body, tag, cb) => {
|
||||
// expect(packageName).toEqual(req.params.package);
|
||||
// expect(version).toEqual(req.params.version);
|
||||
// expect(body).toEqual(req.body);
|
||||
// expect(tag).toEqual(req.params.tag);
|
||||
// cb();
|
||||
// done();
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// addVersion(storage)(req, res, next);
|
||||
|
||||
// expect(res.status).toHaveBeenLastCalledWith(HTTP_STATUS.CREATED);
|
||||
// expect(next).toHaveBeenLastCalledWith({ ok: 'package published' });
|
||||
// });
|
||||
|
||||
// test('when failed to add a version', (done) => {
|
||||
// const storage = {
|
||||
// addVersion: (packageName, version, body, tag, cb) => {
|
||||
// const error = {
|
||||
// message: 'failure',
|
||||
// };
|
||||
// cb(error);
|
||||
// done();
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// addVersion(storage)(req, res, next);
|
||||
|
||||
// expect(next).toHaveBeenLastCalledWith({ message: 'failure' });
|
||||
// });
|
||||
// });
|
||||
|
||||
// /**
|
||||
// * Delete tarball: '/:package/-/:filename/-rev/:revision'
|
||||
// */
|
||||
// describe('Publish endpoints - delete tarball', () => {
|
||||
// let req;
|
||||
// let res;
|
||||
// let next;
|
||||
|
||||
// beforeEach(() => {
|
||||
// req = {
|
||||
// params: {
|
||||
// filename: 'verdaccio.gzip',
|
||||
// package: 'verdaccio',
|
||||
// revision: REVISION_MOCK,
|
||||
// },
|
||||
// };
|
||||
// res = { status: jest.fn() };
|
||||
// next = jest.fn();
|
||||
// });
|
||||
|
||||
// test('should delete tarball successfully', (done) => {
|
||||
// const storage = {
|
||||
// removeTarball(packageName, filename, revision, cb) {
|
||||
// expect(packageName).toEqual(req.params.package);
|
||||
// expect(filename).toEqual(req.params.filename);
|
||||
// expect(revision).toEqual(req.params.revision);
|
||||
// cb();
|
||||
// done();
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// removeTarball(storage)(req, res, next);
|
||||
// expect(res.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
|
||||
// expect(next).toHaveBeenCalledWith({ ok: 'tarball removed' });
|
||||
// });
|
||||
|
||||
// test('failed while deleting the tarball', (done) => {
|
||||
// const error = {
|
||||
// message: 'deletion failed',
|
||||
// };
|
||||
// const storage = {
|
||||
// removeTarball(packageName, filename, revision, cb) {
|
||||
// cb(error);
|
||||
// done();
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// removeTarball(storage)(req, res, next);
|
||||
// expect(next).toHaveBeenCalledWith(error);
|
||||
// });
|
||||
// });
|
||||
|
||||
// /**
|
||||
// * Un-publish package: '/:package/-rev/*'
|
||||
// */
|
||||
// describe('Publish endpoints - un-publish package', () => {
|
||||
// let req;
|
||||
// let res;
|
||||
// let next;
|
||||
|
||||
// beforeEach(() => {
|
||||
// req = {
|
||||
// params: {
|
||||
// package: 'verdaccio',
|
||||
// },
|
||||
// };
|
||||
// res = { status: jest.fn() };
|
||||
// next = jest.fn();
|
||||
// });
|
||||
|
||||
// test('should un-publish package successfully', async () => {
|
||||
// const storage = {
|
||||
// removePackage(packageName) {
|
||||
// expect(packageName).toEqual(req.params.package);
|
||||
// return Promise.resolve();
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// await unPublishPackage(storage)(req, res, next);
|
||||
// expect(res.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
|
||||
// expect(next).toHaveBeenCalledWith({ ok: 'package removed' });
|
||||
// });
|
||||
|
||||
// test('un-publish failed', async () => {
|
||||
// const storage = {
|
||||
// removePackage(packageName) {
|
||||
// expect(packageName).toEqual(req.params.package);
|
||||
// return Promise.reject(errorUtils.getInternalError());
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// await unPublishPackage(storage)(req, res, next);
|
||||
// expect(next).toHaveBeenCalledWith(errorUtils.getInternalError());
|
||||
// });
|
||||
// });
|
||||
|
||||
// /**
|
||||
// * Publish package: '/:package/:_rev?/:revision?'
|
||||
// */
|
||||
// describe('Publish endpoints - publish package', () => {
|
||||
// let req;
|
||||
// let res;
|
||||
// let next;
|
||||
|
||||
// beforeEach(() => {
|
||||
// req = {
|
||||
// body: {
|
||||
// name: 'verdaccio',
|
||||
// },
|
||||
// params: {
|
||||
// package: 'verdaccio',
|
||||
// },
|
||||
// };
|
||||
// res = { status: jest.fn() };
|
||||
// next = jest.fn();
|
||||
// });
|
||||
|
||||
// test('should change the existing package', () => {
|
||||
// const storage = {
|
||||
// changePackage: jest.fn(),
|
||||
// };
|
||||
|
||||
// req.params._rev = REVISION_MOCK;
|
||||
|
||||
// // @ts-ignore
|
||||
// publishPackage(storage)(req, res, next);
|
||||
// expect(storage.changePackage).toMatchSnapshot();
|
||||
// });
|
||||
|
||||
// test('should publish a new a new package', () => {
|
||||
// const storage = {
|
||||
// addPackage: jest.fn(),
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// publishPackage(storage)(req, res, next);
|
||||
// expect(storage.addPackage).toMatchSnapshot();
|
||||
// });
|
||||
|
||||
// test('should throw an error while publishing package', () => {
|
||||
// const storage = {
|
||||
// addPackage() {
|
||||
// throw new Error();
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// publishPackage(storage)(req, res, next);
|
||||
// expect(next).toHaveBeenCalledWith(new Error(API_ERROR.BAD_PACKAGE_DATA));
|
||||
// });
|
||||
|
||||
// describe('test start', () => {
|
||||
// test('should star a package', () => {
|
||||
// const storage = {
|
||||
// changePackage: jest.fn(),
|
||||
// getPackage({ callback }) {
|
||||
// callback(null, {
|
||||
// users: {},
|
||||
// });
|
||||
// },
|
||||
// };
|
||||
// req = {
|
||||
// params: {
|
||||
// package: 'verdaccio',
|
||||
// },
|
||||
// body: {
|
||||
// _rev: REVISION_MOCK,
|
||||
// users: {
|
||||
// verdaccio: true,
|
||||
// },
|
||||
// },
|
||||
// remote_user: {
|
||||
// name: 'verdaccio',
|
||||
// },
|
||||
// };
|
||||
|
||||
// // @ts-ignore
|
||||
// publishPackage(storage)(req, res, next);
|
||||
// expect(storage.changePackage).toMatchSnapshot();
|
||||
// });
|
||||
// });
|
||||
// });
|
|
@ -1,300 +0,0 @@
|
|||
import { API_ERROR, HTTP_STATUS, errorUtils } from '@verdaccio/core';
|
||||
|
||||
import {
|
||||
addVersion,
|
||||
publishPackage,
|
||||
removeTarball,
|
||||
unPublishPackage,
|
||||
uploadPackageTarball,
|
||||
} from '../../src/publish';
|
||||
|
||||
const REVISION_MOCK = '15-e53a77096b0ee33e';
|
||||
|
||||
require('@verdaccio/logger').setup([{ type: 'stdout', format: 'pretty', level: 'info' }]);
|
||||
|
||||
describe('Publish endpoints - add a tag', () => {
|
||||
let req;
|
||||
let res;
|
||||
let next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {
|
||||
version: '1.0.0',
|
||||
tag: 'tag',
|
||||
package: 'verdaccio',
|
||||
},
|
||||
body: '',
|
||||
};
|
||||
res = {
|
||||
status: jest.fn(),
|
||||
};
|
||||
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should add a version', (done) => {
|
||||
const storage = {
|
||||
addVersion: (packageName, version, body, tag, cb) => {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
expect(version).toEqual(req.params.version);
|
||||
expect(body).toEqual(req.body);
|
||||
expect(tag).toEqual(req.params.tag);
|
||||
cb();
|
||||
done();
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
addVersion(storage)(req, res, next);
|
||||
|
||||
expect(res.status).toHaveBeenLastCalledWith(HTTP_STATUS.CREATED);
|
||||
expect(next).toHaveBeenLastCalledWith({ ok: 'package published' });
|
||||
});
|
||||
|
||||
test('when failed to add a version', (done) => {
|
||||
const storage = {
|
||||
addVersion: (packageName, version, body, tag, cb) => {
|
||||
const error = {
|
||||
message: 'failure',
|
||||
};
|
||||
cb(error);
|
||||
done();
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
addVersion(storage)(req, res, next);
|
||||
|
||||
expect(next).toHaveBeenLastCalledWith({ message: 'failure' });
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* upload package: '/:package/-/:filename/*'
|
||||
*/
|
||||
describe('Publish endpoints - upload package tarball', () => {
|
||||
let req;
|
||||
let res;
|
||||
let next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {
|
||||
filename: 'verdaccio.gzip',
|
||||
package: 'verdaccio',
|
||||
},
|
||||
pipe: jest.fn(),
|
||||
on: jest.fn(),
|
||||
};
|
||||
res = { status: jest.fn(), locals: { report_error: jest.fn() } };
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should upload package tarball successfully', () => {
|
||||
const stream = {
|
||||
done: jest.fn(),
|
||||
abort: jest.fn(),
|
||||
on: jest.fn(() => (status, cb) => cb()),
|
||||
};
|
||||
const storage = {
|
||||
addTarball(packageName, filename) {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
expect(filename).toEqual(req.params.filename);
|
||||
return stream;
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
uploadPackageTarball(storage)(req, res, next);
|
||||
expect(req.pipe).toHaveBeenCalled();
|
||||
expect(req.on).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete tarball: '/:package/-/:filename/-rev/:revision'
|
||||
*/
|
||||
describe('Publish endpoints - delete tarball', () => {
|
||||
let req;
|
||||
let res;
|
||||
let next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {
|
||||
filename: 'verdaccio.gzip',
|
||||
package: 'verdaccio',
|
||||
revision: REVISION_MOCK,
|
||||
},
|
||||
};
|
||||
res = { status: jest.fn() };
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should delete tarball successfully', (done) => {
|
||||
const storage = {
|
||||
removeTarball(packageName, filename, revision, cb) {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
expect(filename).toEqual(req.params.filename);
|
||||
expect(revision).toEqual(req.params.revision);
|
||||
cb();
|
||||
done();
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
removeTarball(storage)(req, res, next);
|
||||
expect(res.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
|
||||
expect(next).toHaveBeenCalledWith({ ok: 'tarball removed' });
|
||||
});
|
||||
|
||||
test('failed while deleting the tarball', (done) => {
|
||||
const error = {
|
||||
message: 'deletion failed',
|
||||
};
|
||||
const storage = {
|
||||
removeTarball(packageName, filename, revision, cb) {
|
||||
cb(error);
|
||||
done();
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
removeTarball(storage)(req, res, next);
|
||||
expect(next).toHaveBeenCalledWith(error);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Un-publish package: '/:package/-rev/*'
|
||||
*/
|
||||
describe('Publish endpoints - un-publish package', () => {
|
||||
let req;
|
||||
let res;
|
||||
let next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {
|
||||
package: 'verdaccio',
|
||||
},
|
||||
};
|
||||
res = { status: jest.fn() };
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should un-publish package successfully', async () => {
|
||||
const storage = {
|
||||
removePackage(packageName) {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
await unPublishPackage(storage)(req, res, next);
|
||||
expect(res.status).toHaveBeenCalledWith(HTTP_STATUS.CREATED);
|
||||
expect(next).toHaveBeenCalledWith({ ok: 'package removed' });
|
||||
});
|
||||
|
||||
test('un-publish failed', async () => {
|
||||
const storage = {
|
||||
removePackage(packageName) {
|
||||
expect(packageName).toEqual(req.params.package);
|
||||
return Promise.reject(errorUtils.getInternalError());
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
await unPublishPackage(storage)(req, res, next);
|
||||
expect(next).toHaveBeenCalledWith(errorUtils.getInternalError());
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Publish package: '/:package/:_rev?/:revision?'
|
||||
*/
|
||||
describe('Publish endpoints - publish package', () => {
|
||||
let req;
|
||||
let res;
|
||||
let next;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: {
|
||||
name: 'verdaccio',
|
||||
},
|
||||
params: {
|
||||
package: 'verdaccio',
|
||||
},
|
||||
};
|
||||
res = { status: jest.fn() };
|
||||
next = jest.fn();
|
||||
});
|
||||
|
||||
test('should change the existing package', () => {
|
||||
const storage = {
|
||||
changePackage: jest.fn(),
|
||||
};
|
||||
|
||||
req.params._rev = REVISION_MOCK;
|
||||
|
||||
// @ts-ignore
|
||||
publishPackage(storage)(req, res, next);
|
||||
expect(storage.changePackage).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should publish a new a new package', () => {
|
||||
const storage = {
|
||||
addPackage: jest.fn(),
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
publishPackage(storage)(req, res, next);
|
||||
expect(storage.addPackage).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should throw an error while publishing package', () => {
|
||||
const storage = {
|
||||
addPackage() {
|
||||
throw new Error();
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
publishPackage(storage)(req, res, next);
|
||||
expect(next).toHaveBeenCalledWith(new Error(API_ERROR.BAD_PACKAGE_DATA));
|
||||
});
|
||||
|
||||
describe('test start', () => {
|
||||
test('should star a package', () => {
|
||||
const storage = {
|
||||
changePackage: jest.fn(),
|
||||
getPackage({ callback }) {
|
||||
callback(null, {
|
||||
users: {},
|
||||
});
|
||||
},
|
||||
};
|
||||
req = {
|
||||
params: {
|
||||
package: 'verdaccio',
|
||||
},
|
||||
body: {
|
||||
_rev: REVISION_MOCK,
|
||||
users: {
|
||||
verdaccio: true,
|
||||
},
|
||||
},
|
||||
remote_user: {
|
||||
name: 'verdaccio',
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
publishPackage(storage)(req, res, next);
|
||||
expect(storage.changePackage).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,3 +1,10 @@
|
|||
const config = require('../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {});
|
||||
module.exports = Object.assign({}, config, {
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
// FIXME: increase to 90
|
||||
lines: 43,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
|
|
|
@ -497,10 +497,10 @@ class Auth implements IAuth {
|
|||
next: Function
|
||||
): void {
|
||||
debug('handle legacy api middleware');
|
||||
debug('api middleware secret %o', secret);
|
||||
debug('api middleware authorization %o', authorization);
|
||||
debug('api middleware secret %o', typeof secret === 'string');
|
||||
debug('api middleware authorization %o', typeof authorization === 'string');
|
||||
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
|
||||
debug('api middleware credentials %o', credentials);
|
||||
debug('api middleware credentials %o', credentials?.name);
|
||||
if (credentials) {
|
||||
const { user, password } = credentials;
|
||||
debug('authenticating %o', user);
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
module.exports = require('../../jest/config');
|
||||
const config = require('../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
// FIXME: increase to 90
|
||||
lines: 4,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"types": "build/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Command, Option } from 'clipanion';
|
|||
import { findConfigFile, parseConfigFile } from '@verdaccio/config';
|
||||
import server from '@verdaccio/fastify-migration';
|
||||
import { logger, setup } from '@verdaccio/logger';
|
||||
import { ConfigRuntime } from '@verdaccio/types';
|
||||
import { ConfigYaml } from '@verdaccio/types';
|
||||
|
||||
export const DEFAULT_PROCESS_NAME: string = 'verdaccio';
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class FastifyServer extends Command {
|
|||
description: 'use this configuration file (default: ./config.yaml)',
|
||||
});
|
||||
|
||||
private initLogger(logConfig: ConfigRuntime) {
|
||||
private initLogger(logConfig: ConfigYaml) {
|
||||
try {
|
||||
if (logConfig.log) {
|
||||
throw Error('logger as array not longer supported');
|
||||
|
|
|
@ -2,9 +2,9 @@ import { Command, Option } from 'clipanion';
|
|||
|
||||
import { findConfigFile, parseConfigFile } from '@verdaccio/config';
|
||||
import { logger, setup } from '@verdaccio/logger';
|
||||
import { LoggerConfigItem } from '@verdaccio/logger/src/logger';
|
||||
import { LoggerConfigItem } from '@verdaccio/logger';
|
||||
import { initServer } from '@verdaccio/node-api';
|
||||
import { ConfigRuntime } from '@verdaccio/types';
|
||||
import { ConfigYaml } from '@verdaccio/types';
|
||||
|
||||
export const DEFAULT_PROCESS_NAME: string = 'verdaccio';
|
||||
|
||||
|
@ -45,7 +45,7 @@ export class InitCommand extends Command {
|
|||
description: 'use this configuration file (default: ./config.yaml)',
|
||||
});
|
||||
|
||||
private initLogger(logConfig: ConfigRuntime) {
|
||||
private initLogger(logConfig: ConfigYaml) {
|
||||
try {
|
||||
// @ts-expect-error
|
||||
if (logConfig.logs) {
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
module.exports = require('../../jest/config');
|
||||
const config = require('../../jest/config');
|
||||
|
||||
module.exports = Object.assign({}, config, {
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
// FIXME: increase to 90
|
||||
lines: 85,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"@verdaccio/core": "workspace:6.0.0-6-next.5",
|
||||
"@verdaccio/utils": "workspace:6.0.0-6-next.11",
|
||||
"debug": "4.3.3",
|
||||
"js-yaml": "3.14.1",
|
||||
"yaml": "2.1.1",
|
||||
"lodash": "4.17.21",
|
||||
"minimatch": "3.0.4",
|
||||
"yup": "0.32.11"
|
||||
|
|
72
packages/config/src/builder.ts
Normal file
72
packages/config/src/builder.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { merge } from 'lodash';
|
||||
|
||||
import {
|
||||
AuthConf,
|
||||
ConfigYaml,
|
||||
LoggerConfItem,
|
||||
PackageAccessYaml,
|
||||
Security,
|
||||
UpLinkConf,
|
||||
} from '@verdaccio/types';
|
||||
|
||||
import { fromJStoYAML } from '.';
|
||||
|
||||
/**
|
||||
* Helper configuration builder constructor, used to build the configuration for testing or
|
||||
* programatically creating a configuration.
|
||||
*/
|
||||
export default class ConfigBuilder {
|
||||
private config: ConfigYaml;
|
||||
|
||||
public constructor(config?: Partial<ConfigYaml>) {
|
||||
// @ts-ignore
|
||||
this.config = config ?? { uplinks: {}, packages: {}, security: {} };
|
||||
}
|
||||
|
||||
public static build(config?: Partial<ConfigYaml>): ConfigBuilder {
|
||||
return new ConfigBuilder(config);
|
||||
}
|
||||
|
||||
public addPackageAccess(pattern: string, pkgAccess: PackageAccessYaml) {
|
||||
// @ts-ignore
|
||||
this.config.packages[pattern] = pkgAccess;
|
||||
return this;
|
||||
}
|
||||
|
||||
public addUplink(id: string, uplink: UpLinkConf) {
|
||||
this.config.uplinks[id] = uplink;
|
||||
return this;
|
||||
}
|
||||
|
||||
public addSecurity(security: Partial<Security>) {
|
||||
this.config.security = merge(this.config.security, security);
|
||||
return this;
|
||||
}
|
||||
|
||||
public addAuth(auth: Partial<AuthConf>) {
|
||||
this.config.auth = merge(this.config.auth, auth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public addLogger(log: LoggerConfItem) {
|
||||
this.config.log = log;
|
||||
return this;
|
||||
}
|
||||
|
||||
public addStorage(storage: string | object) {
|
||||
if (typeof storage === 'string') {
|
||||
this.config.storage = storage;
|
||||
} else {
|
||||
this.config.store = storage;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public getConfig(): ConfigYaml {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
public getAsYaml(): string {
|
||||
return fromJStoYAML(this.config) as string;
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ web:
|
|||
# publicPath: http://somedomain.org/
|
||||
|
||||
# https://verdaccio.org/docs/configuration#authentication
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: /verdaccio/storage/htpasswd
|
||||
|
|
|
@ -6,7 +6,7 @@ import { APP_ERROR } from '@verdaccio/core';
|
|||
import {
|
||||
Config as AppConfig,
|
||||
AuthConf,
|
||||
ConfigRuntime,
|
||||
ConfigYaml,
|
||||
FlagsConfig,
|
||||
PackageAccess,
|
||||
PackageList,
|
||||
|
@ -38,8 +38,11 @@ class Config implements AppConfig {
|
|||
public users: any;
|
||||
public auth: AuthConf;
|
||||
public server_id: string;
|
||||
// @deprecated use configPath instead
|
||||
public config_path: string;
|
||||
public configPath: string;
|
||||
public storage: string | void;
|
||||
|
||||
public plugins: string | void;
|
||||
public security: Security;
|
||||
public serverSettings: ServerSettingsConf;
|
||||
|
@ -47,10 +50,15 @@ class Config implements AppConfig {
|
|||
public secret: string;
|
||||
public flags: FlagsConfig;
|
||||
|
||||
public constructor(config: ConfigRuntime) {
|
||||
public constructor(config: ConfigYaml & { config_path: string }) {
|
||||
const self = this;
|
||||
this.storage = process.env.VERDACCIO_STORAGE_PATH || config.storage;
|
||||
this.config_path = config.config_path;
|
||||
if (!config.configPath) {
|
||||
throw new Error('config_path is required');
|
||||
}
|
||||
this.config_path = config.config_path ?? (config.configPath as string);
|
||||
this.configPath = config.configPath;
|
||||
debug('config path: %s', this.configPath);
|
||||
this.plugins = config.plugins;
|
||||
this.security = _.merge(defaultSecurity, config.security);
|
||||
this.serverSettings = serverSettings;
|
||||
|
|
|
@ -2,7 +2,8 @@ export * from './config';
|
|||
export * from './config-path';
|
||||
export * from './token';
|
||||
export * from './package-access';
|
||||
export * from './parse';
|
||||
export { fromJStoYAML, parseConfigFile } from './parse';
|
||||
export * from './uplinks';
|
||||
export * from './security';
|
||||
export * from './user';
|
||||
export { default as ConfigBuilder } from './builder';
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface LegacyPackageList {
|
|||
[key: string]: PackageAccess;
|
||||
}
|
||||
|
||||
// @deprecated use @verdaccio/core:authUtils
|
||||
export const ROLES = {
|
||||
$ALL: '$all',
|
||||
ALL: 'all',
|
||||
|
@ -18,6 +19,7 @@ export const ROLES = {
|
|||
DEPRECATED_ANONYMOUS: '@anonymous',
|
||||
};
|
||||
|
||||
// @deprecated use @verdaccio/core:authUtils
|
||||
export const PACKAGE_ACCESS = {
|
||||
SCOPE: '@*/*',
|
||||
ALL: '**',
|
||||
|
|
|
@ -1,17 +1,35 @@
|
|||
import buildDebug from 'debug';
|
||||
import fs from 'fs';
|
||||
import YAML from 'js-yaml';
|
||||
import { isObject } from 'lodash';
|
||||
import YAML from 'yaml';
|
||||
|
||||
import { APP_ERROR } from '@verdaccio/core';
|
||||
import { ConfigRuntime, ConfigYaml } from '@verdaccio/types';
|
||||
import { ConfigYaml } from '@verdaccio/types';
|
||||
|
||||
import { fileExists } from './config-utils';
|
||||
|
||||
const debug = buildDebug('verdaccio:config:parse');
|
||||
|
||||
/**
|
||||
* Parse a config file from yaml to JSON.
|
||||
* @param configPath the absolute path of the configuration file
|
||||
*/
|
||||
export function parseConfigFile(configPath: string): ConfigRuntime {
|
||||
export function parseConfigFile(configPath: string): ConfigYaml & {
|
||||
// @deprecated use configPath instead
|
||||
config_path: string;
|
||||
configPath: string;
|
||||
} {
|
||||
debug('parse config file %s', configPath);
|
||||
if (!fileExists(configPath)) {
|
||||
throw new Error(`config file does not exist or not reachable`);
|
||||
}
|
||||
debug('parsing config file: %o', configPath);
|
||||
try {
|
||||
if (/\.ya?ml$/i.test(configPath)) {
|
||||
const yamlConfig = YAML.safeLoad(fs.readFileSync(configPath, 'utf8')) as ConfigYaml;
|
||||
const yamlConfig = YAML.parse(fs.readFileSync(configPath, 'utf8'), {
|
||||
strict: false,
|
||||
}) as ConfigYaml;
|
||||
|
||||
return Object.assign({}, yamlConfig, {
|
||||
configPath,
|
||||
// @deprecated use configPath instead
|
||||
|
@ -27,9 +45,19 @@ export function parseConfigFile(configPath: string): ConfigRuntime {
|
|||
});
|
||||
} catch (e: any) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||
debug('config module not found %o', configPath);
|
||||
e.message = APP_ERROR.CONFIG_NOT_VALID;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function fromJStoYAML(config: Partial<ConfigYaml>): string | null {
|
||||
debug('convert config from JSON to YAML');
|
||||
if (isObject(config)) {
|
||||
return YAML.stringify(config);
|
||||
} else {
|
||||
throw new Error(`config is not a valid object`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
export function spliceURL(...args: string[]): string {
|
||||
return Array.from(args)
|
||||
.reduce((lastResult, current) => lastResult + current)
|
||||
.replace(/([^:])(\/)+(.)/g, `$1/$3`);
|
||||
}
|
|
@ -24,7 +24,7 @@ export function uplinkSanityCheck(
|
|||
|
||||
for (const uplink in newUplinks) {
|
||||
if (Object.prototype.hasOwnProperty.call(newUplinks, uplink)) {
|
||||
if (_.isNil(newUplinks[uplink].cache)) {
|
||||
if (typeof newUplinks[uplink].cache === 'undefined') {
|
||||
newUplinks[uplink].cache = true;
|
||||
}
|
||||
newUsers = sanityCheckNames(uplink, newUsers);
|
||||
|
|
24
packages/config/test/__snapshots__/builder.spec.ts.snap
Normal file
24
packages/config/test/__snapshots__/builder.spec.ts.snap
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Config builder should create a configuration file as yaml 1`] = `
|
||||
"uplinks:
|
||||
upstream:
|
||||
url: https://registry.verdaccio.org
|
||||
upstream2:
|
||||
url: https://registry.verdaccio.org
|
||||
packages:
|
||||
upstream/*:
|
||||
access: public
|
||||
publish: foo, bar
|
||||
unpublish: foo, bar
|
||||
proxy: some
|
||||
security:
|
||||
api:
|
||||
legacy: true
|
||||
log:
|
||||
level: info
|
||||
type: stdout
|
||||
format: json
|
||||
storage: /tmp/verdaccio
|
||||
"
|
||||
`;
|
|
@ -0,0 +1,44 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`parse fromJStoYAML basic conversion roundtrip 1`] = `
|
||||
"storage: ./storage_default_storage
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: http://localhost:4873/
|
||||
packages:
|
||||
\\"@*/*\\":
|
||||
access: $all
|
||||
publish: $all
|
||||
proxy: npmjs
|
||||
forbidden-place:
|
||||
access: nobody
|
||||
publish: $all
|
||||
react:
|
||||
access: $all
|
||||
publish: $all
|
||||
proxy: npmjs
|
||||
corrupted-package:
|
||||
access: $all
|
||||
publish: $all
|
||||
proxy: npmjs
|
||||
jquery:
|
||||
access: $all
|
||||
publish: $all
|
||||
proxy: npmjs
|
||||
auth-package:
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
vue:
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
proxy: npmjs
|
||||
\\"*\\":
|
||||
access: $all
|
||||
publish: $all
|
||||
proxy: npmjs
|
||||
log:
|
||||
type: stdout
|
||||
format: pretty
|
||||
level: warn
|
||||
"
|
||||
`;
|
65
packages/config/test/builder.spec.ts
Normal file
65
packages/config/test/builder.spec.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { ConfigBuilder } from '../src';
|
||||
|
||||
describe('Config builder', () => {
|
||||
test('should create a configuration file as object', () => {
|
||||
const config = ConfigBuilder.build();
|
||||
config
|
||||
.addUplink('upstream', { url: 'https://registry.verdaccio.org' })
|
||||
.addUplink('upstream2', { url: 'https://registry.verdaccio.org' })
|
||||
.addPackageAccess('upstream/*', {
|
||||
access: 'public',
|
||||
publish: 'foo, bar',
|
||||
unpublish: 'foo, bar',
|
||||
proxy: 'some',
|
||||
})
|
||||
.addLogger({ level: 'info', type: 'stdout', format: 'json' })
|
||||
.addStorage('/tmp/verdaccio')
|
||||
.addSecurity({ api: { legacy: true } });
|
||||
expect(config.getConfig()).toEqual({
|
||||
security: {
|
||||
api: {
|
||||
legacy: true,
|
||||
},
|
||||
},
|
||||
storage: '/tmp/verdaccio',
|
||||
packages: {
|
||||
'upstream/*': {
|
||||
access: 'public',
|
||||
publish: 'foo, bar',
|
||||
unpublish: 'foo, bar',
|
||||
proxy: 'some',
|
||||
},
|
||||
},
|
||||
uplinks: {
|
||||
upstream: {
|
||||
url: 'https://registry.verdaccio.org',
|
||||
},
|
||||
upstream2: {
|
||||
url: 'https://registry.verdaccio.org',
|
||||
},
|
||||
},
|
||||
log: {
|
||||
level: 'info',
|
||||
type: 'stdout',
|
||||
format: 'json',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should create a configuration file as yaml', () => {
|
||||
const config = ConfigBuilder.build();
|
||||
config
|
||||
.addUplink('upstream', { url: 'https://registry.verdaccio.org' })
|
||||
.addUplink('upstream2', { url: 'https://registry.verdaccio.org' })
|
||||
.addPackageAccess('upstream/*', {
|
||||
access: 'public',
|
||||
publish: 'foo, bar',
|
||||
unpublish: 'foo, bar',
|
||||
proxy: 'some',
|
||||
})
|
||||
.addLogger({ level: 'info', type: 'stdout', format: 'json' })
|
||||
.addStorage('/tmp/verdaccio')
|
||||
.addSecurity({ api: { legacy: true } });
|
||||
expect(config.getAsYaml()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,44 +1,68 @@
|
|||
import { parseConfigFile } from '../src';
|
||||
import { writeFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import { fileUtils } from '@verdaccio/core';
|
||||
|
||||
import { fromJStoYAML, parseConfigFile } from '../src';
|
||||
import { parseConfigurationFile } from './utils';
|
||||
|
||||
describe('Package access utilities', () => {
|
||||
describe('JSON format', () => {
|
||||
test('parse default.json', () => {
|
||||
const config = parseConfigFile(parseConfigurationFile('default.json'));
|
||||
describe('parse', () => {
|
||||
describe('parseConfigFile', () => {
|
||||
describe('JSON format', () => {
|
||||
test('parse default.json', () => {
|
||||
const config = parseConfigFile(parseConfigurationFile('default.json'));
|
||||
|
||||
expect(config.storage).toBeDefined();
|
||||
expect(config.storage).toBeDefined();
|
||||
});
|
||||
|
||||
test('parse invalid.json', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('invalid.json'));
|
||||
}).toThrow(/CONFIG: it does not look like a valid config file/);
|
||||
});
|
||||
|
||||
test('parse not-exists.json', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('not-exists.json'));
|
||||
}).toThrow(/config file does not exist or not reachable/);
|
||||
});
|
||||
});
|
||||
|
||||
test('parse invalid.json', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('invalid.json'));
|
||||
}).toThrow(/CONFIG: it does not look like a valid config file/);
|
||||
});
|
||||
describe('JavaScript format', () => {
|
||||
test('parse default.js', () => {
|
||||
const config = parseConfigFile(parseConfigurationFile('default.js'));
|
||||
|
||||
test('parse not-exists.json', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('not-exists.json'));
|
||||
}).toThrow(/Cannot find module/);
|
||||
expect(config.storage).toBeDefined();
|
||||
});
|
||||
|
||||
test('parse invalid.js', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('invalid.js'));
|
||||
}).toThrow(/CONFIG: it does not look like a valid config file/);
|
||||
});
|
||||
|
||||
test('parse not-exists.js', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('not-exists.js'));
|
||||
}).toThrow(/config file does not exist or not reachable/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('JavaScript format', () => {
|
||||
test('parse default.js', () => {
|
||||
const config = parseConfigFile(parseConfigurationFile('default.js'));
|
||||
|
||||
expect(config.storage).toBeDefined();
|
||||
});
|
||||
|
||||
test('parse invalid.js', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('invalid.js'));
|
||||
}).toThrow(/CONFIG: it does not look like a valid config file/);
|
||||
});
|
||||
|
||||
test('parse not-exists.js', () => {
|
||||
expect(function () {
|
||||
parseConfigFile(parseConfigurationFile('not-exists.js'));
|
||||
}).toThrow(/Cannot find module/);
|
||||
describe('fromJStoYAML', () => {
|
||||
test('basic conversion roundtrip', async () => {
|
||||
// from to js to yaml
|
||||
const config = require('./partials/config/js/from-js-to-yaml');
|
||||
const yaml = fromJStoYAML(config) as string;
|
||||
expect(yaml).toMatchSnapshot();
|
||||
const tempFolder = await fileUtils.createTempFolder('fromJStoYAML-test');
|
||||
const configPath = path.join(tempFolder, 'config.yaml');
|
||||
await writeFile(configPath, yaml);
|
||||
const parsed = parseConfigFile(configPath);
|
||||
expect(parsed.configPath).toEqual(path.join(tempFolder, 'config.yaml'));
|
||||
expect(parsed.storage).toEqual('./storage_default_storage');
|
||||
expect(parsed.uplinks).toEqual({ npmjs: { url: 'http://localhost:4873/' } });
|
||||
expect(parsed.log).toEqual({ type: 'stdout', format: 'pretty', level: 'warn' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
21
packages/config/test/config-utils.spec.ts
Normal file
21
packages/config/test/config-utils.spec.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import path from 'path';
|
||||
|
||||
import { fileExists, folderExists } from '../src/config-utils';
|
||||
|
||||
describe('config-utils', () => {
|
||||
test('folderExists', () => {
|
||||
expect(folderExists(path.join(__dirname, './partials/exist'))).toBeTruthy();
|
||||
});
|
||||
|
||||
test('folderExists == false', () => {
|
||||
expect(folderExists(path.join(__dirname, './partials/NOT_exist'))).toBeFalsy();
|
||||
});
|
||||
|
||||
test('fileExists', () => {
|
||||
expect(fileExists(path.join(__dirname, './partials/exist/README.md'))).toBeTruthy();
|
||||
});
|
||||
|
||||
test('fileExists == false', () => {
|
||||
expect(fileExists(path.join(__dirname, './partials/exist/NOT_EXIST.md'))).toBeFalsy();
|
||||
});
|
||||
});
|
15
packages/config/test/partials/config/js/from-js-to-yaml.js
Normal file
15
packages/config/test/partials/config/js/from-js-to-yaml.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
module.exports = {
|
||||
storage: './storage_default_storage',
|
||||
uplinks: { npmjs: { url: 'http://localhost:4873/' } },
|
||||
packages: {
|
||||
'@*/*': { access: '$all', publish: '$all', proxy: 'npmjs' },
|
||||
'forbidden-place': { access: 'nobody', publish: '$all' },
|
||||
react: { access: '$all', publish: '$all', proxy: 'npmjs' },
|
||||
'corrupted-package': { access: '$all', publish: '$all', proxy: 'npmjs' },
|
||||
jquery: { access: '$all', publish: '$all', proxy: 'npmjs' },
|
||||
'auth-package': { access: '$authenticated', publish: '$authenticated' },
|
||||
vue: { access: '$authenticated', publish: '$authenticated', proxy: 'npmjs' },
|
||||
'*': { access: '$all', publish: '$all', proxy: 'npmjs' },
|
||||
},
|
||||
log: { type: 'stdout', format: 'pretty', level: 'warn' },
|
||||
};
|
1
packages/config/test/partials/exist/README.md
Normal file
1
packages/config/test/partials/exist/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
just for testing purpose
|
|
@ -1,42 +1,27 @@
|
|||
import { ROLES, createAnonymousRemoteUser, createRemoteUser } from '../src';
|
||||
import { spliceURL } from '../src/string';
|
||||
|
||||
describe('spliceURL', () => {
|
||||
test('should splice two strings and generate a url', () => {
|
||||
const url: string = spliceURL('http://domain.com', '/-/static/logo.png');
|
||||
|
||||
expect(url).toMatch('http://domain.com/-/static/logo.png');
|
||||
});
|
||||
|
||||
test('should splice a empty strings and generate a url', () => {
|
||||
const url: string = spliceURL('', '/-/static/logo.png');
|
||||
|
||||
expect(url).toMatch('/-/static/logo.png');
|
||||
});
|
||||
|
||||
describe('createRemoteUser and createAnonymousRemoteUser', () => {
|
||||
test('should create a remote user with default groups', () => {
|
||||
expect(createRemoteUser('12345', ['foo', 'bar'])).toEqual({
|
||||
groups: [
|
||||
'foo',
|
||||
'bar',
|
||||
ROLES.$ALL,
|
||||
ROLES.$AUTH,
|
||||
ROLES.DEPRECATED_ALL,
|
||||
ROLES.DEPRECATED_AUTH,
|
||||
ROLES.ALL,
|
||||
],
|
||||
name: '12345',
|
||||
real_groups: ['foo', 'bar'],
|
||||
});
|
||||
describe('createRemoteUser and createAnonymousRemoteUser', () => {
|
||||
test('should create a remote user with default groups', () => {
|
||||
expect(createRemoteUser('12345', ['foo', 'bar'])).toEqual({
|
||||
groups: [
|
||||
'foo',
|
||||
'bar',
|
||||
ROLES.$ALL,
|
||||
ROLES.$AUTH,
|
||||
ROLES.DEPRECATED_ALL,
|
||||
ROLES.DEPRECATED_AUTH,
|
||||
ROLES.ALL,
|
||||
],
|
||||
name: '12345',
|
||||
real_groups: ['foo', 'bar'],
|
||||
});
|
||||
});
|
||||
|
||||
test('should create a anonymous remote user with default groups', () => {
|
||||
expect(createAnonymousRemoteUser()).toEqual({
|
||||
groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONYMOUS],
|
||||
name: undefined,
|
||||
real_groups: [],
|
||||
});
|
||||
test('should create a anonymous remote user with default groups', () => {
|
||||
expect(createAnonymousRemoteUser()).toEqual({
|
||||
groups: [ROLES.$ALL, ROLES.$ANONYMOUS, ROLES.DEPRECATED_ALL, ROLES.DEPRECATED_ANONYMOUS],
|
||||
name: undefined,
|
||||
real_groups: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -37,15 +37,17 @@
|
|||
"http-errors": "1.8.1",
|
||||
"http-status-codes": "2.2.0",
|
||||
"semver": "7.3.5",
|
||||
"ajv": "8.11.0",
|
||||
"process-warning": "1.0.0",
|
||||
"core-js": "3.20.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lodash": "4.17.21",
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.12"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
|
|
|
@ -19,6 +19,7 @@ export const CHARACTER_ENCODING = {
|
|||
UTF8: 'utf8',
|
||||
};
|
||||
|
||||
// @deprecated use Bearer instead
|
||||
export const TOKEN_BASIC = 'Basic';
|
||||
export const TOKEN_BEARER = 'Bearer';
|
||||
|
||||
|
@ -42,6 +43,7 @@ export const HEADERS = {
|
|||
CSP: 'Content-Security-Policy',
|
||||
CTO: 'X-Content-Type-Options',
|
||||
XSS: 'X-XSS-Protection',
|
||||
NONE_MATCH: 'If-None-Match',
|
||||
ETAG: 'ETag',
|
||||
JSON_CHARSET: 'application/json; charset=utf-8',
|
||||
OCTET_STREAM: 'application/octet-stream; charset=utf-8',
|
||||
|
@ -82,6 +84,7 @@ export const API_MESSAGE = {
|
|||
TAG_UPDATED: 'tags updated',
|
||||
TAG_REMOVED: 'tag removed',
|
||||
TAG_ADDED: 'package tagged',
|
||||
OK: 'ok',
|
||||
LOGGED_OUT: 'Logged out',
|
||||
};
|
||||
|
||||
|
@ -89,3 +92,18 @@ export const LOG_STATUS_MESSAGE =
|
|||
"@{status}, user: @{user}(@{remoteIP}), req: '@{request.method} @{request.url}'";
|
||||
export const LOG_VERDACCIO_ERROR = `${LOG_STATUS_MESSAGE}, error: @{!error}`;
|
||||
export const LOG_VERDACCIO_BYTES = `${LOG_STATUS_MESSAGE}, bytes: @{bytes.in}/@{bytes.out}`;
|
||||
|
||||
export const ROLES = {
|
||||
$ALL: '$all',
|
||||
ALL: 'all',
|
||||
$AUTH: '$authenticated',
|
||||
$ANONYMOUS: '$anonymous',
|
||||
DEPRECATED_ALL: '@all',
|
||||
DEPRECATED_AUTH: '@authenticated',
|
||||
DEPRECATED_ANONYMOUS: '@anonymous',
|
||||
};
|
||||
|
||||
export const PACKAGE_ACCESS = {
|
||||
SCOPE: '@*/*',
|
||||
ALL: '**',
|
||||
};
|
||||
|
|
|
@ -20,10 +20,12 @@ export const API_ERROR = {
|
|||
NOT_PACKAGE_UPLINK: 'package does not exist on uplink',
|
||||
UPLINK_OFFLINE_PUBLISH: 'one of the uplinks is down, refuse to publish',
|
||||
UPLINK_OFFLINE: 'uplink is offline',
|
||||
NOT_MODIFIED_NO_DATA: 'no data',
|
||||
CONTENT_MISMATCH: 'content length mismatch',
|
||||
NOT_FILE_UPLINK: "file doesn't exist on uplink",
|
||||
MAX_USERS_REACHED: 'maximum amount of users reached',
|
||||
VERSION_NOT_EXIST: "this version doesn't exist",
|
||||
NO_SUCH_FILE: 'no such file available',
|
||||
UNSUPORTED_REGISTRY_CALL: 'unsupported registry call',
|
||||
FILE_NOT_FOUND: 'File not found',
|
||||
REGISTRATION_DISABLED: 'user registration disabled',
|
||||
|
|
|
@ -1,3 +1,29 @@
|
|||
import { mkdir, mkdtemp } from 'fs/promises';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
export const Files = {
|
||||
DatabaseName: '.verdaccio-db.json',
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a temporary folder.
|
||||
* @param prefix The prefix of the folder name.
|
||||
* @returns string
|
||||
*/
|
||||
export async function createTempFolder(prefix: string): Promise<string> {
|
||||
return await mkdtemp(path.join(os.tmpdir(), prefix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create temporary folder for an asset.
|
||||
* @param prefix
|
||||
* @param folder name
|
||||
* @returns
|
||||
*/
|
||||
export async function createTempStorageFolder(prefix: string, folder = 'storage'): Promise<string> {
|
||||
const tempFolder = await createTempFolder(prefix);
|
||||
const storageFolder = path.join(tempFolder, folder);
|
||||
await mkdir(storageFolder);
|
||||
return storageFolder;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
import semver from 'semver';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { Package } from '@verdaccio/types';
|
||||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
import { DIST_TAGS } from './constants';
|
||||
|
||||
/**
|
||||
* Extract the tarball name from a registry dist url
|
||||
* 'https://registry.npmjs.org/test/-/test-0.0.2.tgz'
|
||||
* @param tarball tarball url
|
||||
* @returns tarball filename
|
||||
*/
|
||||
export function extractTarballName(tarball: string) {
|
||||
const urlObject: any = new URL(tarball);
|
||||
const filename = urlObject.pathname.replace(/^.*\//, '');
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function filters out bad semver versions and sorts the array.
|
||||
* @return {Array} sorted Array
|
||||
|
@ -24,7 +37,7 @@ export function semverSort(listVersions: string[]): string[] {
|
|||
* Get the latest publihsed version of a package.
|
||||
* @param package metadata
|
||||
**/
|
||||
export function getLatest(pkg: Package): string {
|
||||
export function getLatest(pkg: Manifest): string {
|
||||
const listVersions: string[] = Object.keys(pkg.versions);
|
||||
if (listVersions.length < 1) {
|
||||
throw Error('cannot get lastest version of none');
|
||||
|
@ -42,8 +55,10 @@ export function getLatest(pkg: Package): string {
|
|||
* @param {*} local
|
||||
* @param {*} upstream
|
||||
* @param {*} config sds
|
||||
* @deprecated use @verdaccio/storage mergeVersions method
|
||||
*/
|
||||
export function mergeVersions(local: Package, upstream: Package) {
|
||||
// @deprecated
|
||||
export function mergeVersions(local: Manifest, upstream: Manifest) {
|
||||
// copy new versions to a cache
|
||||
// NOTE: if a certain version was updated, we can't refresh it reliably
|
||||
for (const i in upstream.versions) {
|
||||
|
|
38
packages/core/core/src/schemes/publish-manifest.ts
Normal file
38
packages/core/core/src/schemes/publish-manifest.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import Ajv, { JSONSchemaType } from 'ajv';
|
||||
|
||||
const ajv = new Ajv();
|
||||
|
||||
// FIXME: this could extend from @verdaccio/types but we need
|
||||
// schemas from @verdaccio/types to be able to validate them
|
||||
interface Manifest {
|
||||
name: string;
|
||||
versions: object;
|
||||
_attachments: object;
|
||||
}
|
||||
|
||||
const schema: JSONSchemaType<Manifest> = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
versions: { type: 'object', maxProperties: 1 },
|
||||
_attachments: { type: 'object', maxProperties: 1 },
|
||||
},
|
||||
required: ['name', 'versions', '_attachments'],
|
||||
additionalProperties: true,
|
||||
};
|
||||
|
||||
// validate is a type guard for MyData - type is inferred from schema type
|
||||
const validate = ajv.compile(schema);
|
||||
|
||||
/**
|
||||
* Validate if a manifest has the correct structure when a new package
|
||||
* is being created. The properties name, versions and _attachments must contain 1 element.
|
||||
* @param data a manifest object
|
||||
* @returns boolean
|
||||
*/
|
||||
export function validatePublishSingleVersion(manifest: any) {
|
||||
if (!manifest) {
|
||||
return false;
|
||||
}
|
||||
return validate(manifest);
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import assert from 'assert';
|
||||
|
||||
import { Package } from '@verdaccio/types';
|
||||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
import { DIST_TAGS } from './constants';
|
||||
|
||||
export { validatePublishSingleVersion } from './schemes/publish-manifest';
|
||||
|
||||
export function isPackageNameScoped(name: string): boolean {
|
||||
return name.startsWith('@');
|
||||
}
|
||||
|
@ -62,27 +64,29 @@ export function validatePackage(name: string): boolean {
|
|||
/**
|
||||
* Validate the package metadata, add additional properties whether are missing within
|
||||
* the metadata properties.
|
||||
* @param {*} object
|
||||
* @param {*} manifest
|
||||
* @param {*} name
|
||||
* @return {Object} the object with additional properties as dist-tags ad versions
|
||||
* FUTURE: rename to normalizeMetadata
|
||||
*/
|
||||
export function validateMetadata(object: Package, name: string): Package {
|
||||
assert(isObject(object), 'not a json object');
|
||||
assert.strictEqual(object.name, name);
|
||||
export function normalizeMetadata(manifest: Manifest, name: string): Manifest {
|
||||
assert.strictEqual(manifest.name, name);
|
||||
const _manifest = { ...manifest };
|
||||
|
||||
if (!isObject(object[DIST_TAGS])) {
|
||||
object[DIST_TAGS] = {};
|
||||
if (!isObject(manifest[DIST_TAGS])) {
|
||||
_manifest[DIST_TAGS] = {};
|
||||
}
|
||||
|
||||
if (!isObject(object['versions'])) {
|
||||
object['versions'] = {};
|
||||
// This may not be nee dit
|
||||
if (!isObject(manifest['versions'])) {
|
||||
_manifest['versions'] = {};
|
||||
}
|
||||
|
||||
if (!isObject(object['time'])) {
|
||||
object['time'] = {};
|
||||
if (!isObject(manifest['time'])) {
|
||||
_manifest['time'] = {};
|
||||
}
|
||||
|
||||
return object;
|
||||
return _manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,7 +95,7 @@ export function validateMetadata(object: Package, name: string): Package {
|
|||
* @return {Boolean}
|
||||
*/
|
||||
export function isObject(obj: any): boolean {
|
||||
if (obj === null || typeof obj === 'undefined') {
|
||||
if (obj === null || typeof obj === 'undefined' || typeof obj === 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
42
packages/core/core/test/pkg-utils.spec.ts
Normal file
42
packages/core/core/test/pkg-utils.spec.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { DIST_TAGS, pkgUtils } from '../src';
|
||||
|
||||
describe('pkg-utils', () => {
|
||||
test('extractTarballName', () => {
|
||||
expect(pkgUtils.extractTarballName('https://registry.npmjs.org/test/-/test-0.0.2.tgz')).toBe(
|
||||
'test-0.0.2.tgz'
|
||||
);
|
||||
});
|
||||
|
||||
test('extractTarballName with no tarball should not fails', () => {
|
||||
expect(pkgUtils.extractTarballName('https://registry.npmjs.org/')).toBe('');
|
||||
});
|
||||
|
||||
test('extractTarballName fails', () => {
|
||||
expect(() =>
|
||||
pkgUtils.extractTarballName('xxxxregistry.npmjs.org/test/-/test-0.0.2.tgz')
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test('getLatest fails if no versions', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error
|
||||
pkgUtils.getLatest({
|
||||
versions: {},
|
||||
})
|
||||
).toThrow('cannot get lastest version of none');
|
||||
});
|
||||
|
||||
test('getLatest get latest', () => {
|
||||
expect(
|
||||
pkgUtils.getLatest({
|
||||
versions: {
|
||||
// @ts-expect-error
|
||||
'1.0.0': {},
|
||||
},
|
||||
[DIST_TAGS]: {
|
||||
latest: '1.0.0',
|
||||
},
|
||||
})
|
||||
).toBe('1.0.0');
|
||||
});
|
||||
});
|
|
@ -1,4 +1,11 @@
|
|||
import { isObject, validateName, validatePackage } from '../src/validation-utils';
|
||||
import { DIST_TAGS } from '../src/constants';
|
||||
import { validatePublishSingleVersion } from '../src/schemes/publish-manifest';
|
||||
import {
|
||||
isObject,
|
||||
normalizeMetadata,
|
||||
validateName,
|
||||
validatePackage,
|
||||
} from '../src/validation-utils';
|
||||
|
||||
describe('validatePackage', () => {
|
||||
test('should validate package names', () => {
|
||||
|
@ -19,13 +26,41 @@ describe('validatePackage', () => {
|
|||
describe('isObject', () => {
|
||||
test('isObject metadata', () => {
|
||||
expect(isObject({ foo: 'bar' })).toBeTruthy();
|
||||
expect(isObject('foo')).toBeTruthy();
|
||||
// expect(isObject('foo')).toBeTruthy();
|
||||
expect(isObject(['foo'])).toBeFalsy();
|
||||
expect(isObject(null)).toBeFalsy();
|
||||
expect(isObject(undefined)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeMetadata', () => {
|
||||
test('should fills an empty metadata object', () => {
|
||||
// intended to fail with flow, do not remove
|
||||
// @ts-ignore
|
||||
expect(Object.keys(normalizeMetadata({}))).toContain(DIST_TAGS);
|
||||
// @ts-ignore
|
||||
expect(Object.keys(normalizeMetadata({}))).toContain('versions');
|
||||
// @ts-ignore
|
||||
expect(Object.keys(normalizeMetadata({}))).toContain('time');
|
||||
});
|
||||
|
||||
test.skip('should fails the assertions is not an object', () => {
|
||||
expect(function () {
|
||||
// @ts-ignore
|
||||
normalizeMetadata('');
|
||||
// @ts-ignore
|
||||
}).toThrow(expect.hasAssertions());
|
||||
});
|
||||
|
||||
test('should fails the assertions is name does not match', () => {
|
||||
expect(function () {
|
||||
// @ts-ignore
|
||||
normalizeMetadata({}, 'no-name');
|
||||
// @ts-ignore
|
||||
}).toThrow(expect.hasAssertions());
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateName', () => {
|
||||
test('should fails with no string', () => {
|
||||
// intended to fail with Typescript, do not remove
|
||||
|
@ -72,3 +107,62 @@ describe('validateName', () => {
|
|||
expect(validateName('pk:g')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validatePublishSingleVersion', () => {
|
||||
test('should be valid', () => {
|
||||
expect(
|
||||
validatePublishSingleVersion({
|
||||
name: 'foo-pkg',
|
||||
_attachments: { '2': {} },
|
||||
versions: { '1': {} },
|
||||
})
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should be invalid if name is missing', () => {
|
||||
expect(
|
||||
validatePublishSingleVersion({
|
||||
_attachments: { '2': {} },
|
||||
versions: { '1': {} },
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should be invalid if _attachments is missing', () => {
|
||||
expect(
|
||||
validatePublishSingleVersion({
|
||||
name: 'foo-pkg',
|
||||
versions: { '1': {} },
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should be invalid if versions is missing', () => {
|
||||
expect(
|
||||
validatePublishSingleVersion({
|
||||
name: 'foo-pkg',
|
||||
_attachments: { '1': {} },
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should be invalid if versions is more than 1', () => {
|
||||
expect(
|
||||
validatePublishSingleVersion({
|
||||
name: 'foo-pkg',
|
||||
versions: { '1': {}, '2': {} },
|
||||
_attachments: { '1': {} },
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should be invalid if _attachments is more than 1', () => {
|
||||
expect(
|
||||
validatePublishSingleVersion({
|
||||
name: 'foo-pkg',
|
||||
_attachments: { '1': {}, '2': {} },
|
||||
versions: { '1': {} },
|
||||
})
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"watch": "pnpm build:js -- --watch",
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"watch": "pnpm build:js -- --watch",
|
||||
|
|
|
@ -29,6 +29,7 @@ describe('readme', () => {
|
|||
});
|
||||
|
||||
test('should handle wrong text', () => {
|
||||
// @ts-expect-error
|
||||
expect(parseReadme(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"extends": "../../../.babelrc"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
node_modules
|
||||
coverage/
|
||||
lib/
|
||||
.nyc_output
|
||||
tests-report/
|
||||
build/
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"rules": {
|
||||
"@typescript-eslint/no-use-before-define": "off"
|
||||
}
|
||||
}
|
1
packages/core/streams/.gitignore
vendored
1
packages/core/streams/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
lib/
|
|
@ -1,273 +0,0 @@
|
|||
# Change Log
|
||||
|
||||
## 11.0.0-6-next.5
|
||||
|
||||
### Major Changes
|
||||
|
||||
- 794af76c: Remove Node 12 support
|
||||
|
||||
- We need move to the new `undici` and does not support Node.js 12
|
||||
|
||||
## 11.0.0-6-next.4
|
||||
|
||||
### Major Changes
|
||||
|
||||
- 459b6fa7: refactor: search v1 endpoint and local-database
|
||||
|
||||
- refactor search `api v1` endpoint, improve performance
|
||||
- remove usage of `async` dependency https://github.com/verdaccio/verdaccio/issues/1225
|
||||
- refactor method storage class
|
||||
- create new module `core` to reduce the ammount of modules with utilities
|
||||
- use `undici` instead `node-fetch`
|
||||
- use `fastify` instead `express` for functional test
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- plugin storage API changes
|
||||
- remove old search endpoint (return 404)
|
||||
- filter local private packages at plugin level
|
||||
|
||||
The storage api changes for methods `get`, `add`, `remove` as promise base. The `search` methods also changes and recieves a `query` object that contains all query params from the client.
|
||||
|
||||
```ts
|
||||
export interface IPluginStorage<T> extends IPlugin {
|
||||
add(name: string): Promise<void>;
|
||||
remove(name: string): Promise<void>;
|
||||
get(): Promise<any>;
|
||||
init(): Promise<void>;
|
||||
getSecret(): Promise<string>;
|
||||
setSecret(secret: string): Promise<any>;
|
||||
getPackageStorage(packageInfo: string): IPackageStorage;
|
||||
search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]>;
|
||||
saveToken(token: Token): Promise<any>;
|
||||
deleteToken(user: string, tokenKey: string): Promise<any>;
|
||||
readTokens(filter: TokenFilter): Promise<Token[]>;
|
||||
}
|
||||
```
|
||||
|
||||
## 10.0.0-alpha.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fecbb9be: chore: add release step to private regisry on merge changeset pr
|
||||
|
||||
## 10.0.0-alpha.2
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 54c58d1e: feat: add server rate limit protection to all request
|
||||
|
||||
To modify custom values, use the server settings property.
|
||||
|
||||
```markdown
|
||||
server:
|
||||
|
||||
## https://www.npmjs.com/package/express-rate-limit#configuration-options
|
||||
|
||||
rateLimit:
|
||||
windowMs: 1000
|
||||
max: 10000
|
||||
```
|
||||
|
||||
The values are intended to be high, if you want to improve security of your server consider
|
||||
using different values.
|
||||
|
||||
## 10.0.0-alpha.1
|
||||
|
||||
### Major Changes
|
||||
|
||||
- d87fa026: feat!: experiments config renamed to flags
|
||||
|
||||
- The `experiments` configuration is renamed to `flags`. The functionality is exactly the same.
|
||||
|
||||
```js
|
||||
flags: token: false;
|
||||
search: false;
|
||||
```
|
||||
|
||||
- The `self_path` property from the config file is being removed in favor of `config_file` full path.
|
||||
- Refactor `config` module, better types and utilities
|
||||
|
||||
- da1ee9c8: - Replace signature handler for legacy tokens by removing deprecated crypto.createDecipher by createCipheriv
|
||||
|
||||
- Introduce environment variables for legacy tokens
|
||||
|
||||
### Code Improvements
|
||||
|
||||
- Add debug library for improve developer experience
|
||||
|
||||
### Breaking change
|
||||
|
||||
- The new signature invalidates all previous tokens generated by Verdaccio 4 or previous versions.
|
||||
- The secret key must have 32 characters long.
|
||||
|
||||
### New environment variables
|
||||
|
||||
- `VERDACCIO_LEGACY_ALGORITHM`: Allows to define the specific algorithm for the token signature which by default is `aes-256-ctr`
|
||||
- `VERDACCIO_LEGACY_ENCRYPTION_KEY`: By default, the token stores in the database, but using this variable allows to get it from memory
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 26b494cb: feat: add typescript project references settings
|
||||
|
||||
Reading https://ebaytech.berlin/optimizing-multi-package-apps-with-typescript-project-references-d5c57a3b4440 I realized I can use project references to solve the issue to pre-compile modules on develop mode.
|
||||
|
||||
It allows to navigate (IDE) trough the packages without need compile the packages.
|
||||
|
||||
Add two `tsconfig`, one using the previous existing configuration that is able to produce declaration files (`tsconfig.build`) and a new one `tsconfig` which is enables [_projects references_](https://www.typescriptlang.org/docs/handbook/project-references.html).
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b57b4338: Enable prerelease mode with **changesets**
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01)
|
||||
|
||||
**Note:** Version bump only for package @verdaccio/streams
|
||||
|
||||
# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- add es6 imports ([932a22d](https://github.com/verdaccio/monorepo/commit/932a22d))
|
||||
- lint warnings ([444a99e](https://github.com/verdaccio/monorepo/commit/444a99e))
|
||||
|
||||
### Features
|
||||
|
||||
- drop node v6 support ([bb319c4](https://github.com/verdaccio/monorepo/commit/bb319c4))
|
||||
- **build:** use typescript, jest 24 and babel 7 as stack BREAKING CHANGE: typescript build system requires a major release to avoid issues with old installations ([4743a9a](https://github.com/verdaccio/monorepo/commit/4743a9a))
|
||||
- add stream library ([434628f](https://github.com/verdaccio/monorepo/commit/434628f))
|
||||
- migration to typescript ([748ca92](https://github.com/verdaccio/monorepo/commit/748ca92))
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
# [2.0.0](https://github.com/verdaccio/streams/compare/v2.0.0-beta.0...v2.0.0) (2019-03-29)
|
||||
|
||||
### Features
|
||||
|
||||
- drop node v6 support ([5771eed](https://github.com/verdaccio/streams/commit/5771eed))
|
||||
|
||||
<a name="2.0.0-beta.0"></a>
|
||||
|
||||
# [2.0.0-beta.0](https://github.com/verdaccio/streams/compare/v1.0.0...v2.0.0-beta.0) (2019-01-27)
|
||||
|
||||
### Features
|
||||
|
||||
- migration to typescript ([4e1e959](https://github.com/verdaccio/streams/commit/4e1e959))
|
||||
- **build:** use typescript, jest 24 and babel 7 as stack ([c93a980](https://github.com/verdaccio/streams/commit/c93a980))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- **build:** typescript build system requires a major release to avoid issues with old installations
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Verdaccio
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,19 +0,0 @@
|
|||
# Streams
|
||||
|
||||
[![CircleCI](https://circleci.com/gh/verdaccio/streams.svg?style=svg)](https://circleci.com/gh/ayusharma/@verdaccio/streams)
|
||||
[![codecov](https://codecov.io/gh/verdaccio/streams/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/streams)
|
||||
[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/streams/latest.svg)](https://www.npmjs.com/package/@verdaccio/streams)
|
||||
[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio)
|
||||
[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/)
|
||||
![MIT](https://img.shields.io/github/license/mashape/apistatus.svg)
|
||||
[![node](https://img.shields.io/node/v/@verdaccio/streams/latest.svg)](https://www.npmjs.com/package/@verdaccio/streams)
|
||||
|
||||
This project provides an extension of `PassThrough` stream.
|
||||
|
||||
## Detail
|
||||
|
||||
It provides 2 additional methods `abort()` and `done()`. Those implementations are widely use in the verdaccio core for handle `tarballs`.
|
||||
|
||||
## License
|
||||
|
||||
MIT (http://www.opensource.org/licenses/mit-license.php)
|
|
@ -1,52 +0,0 @@
|
|||
{
|
||||
"name": "@verdaccio/streams",
|
||||
"version": "11.0.0-6-next.5",
|
||||
"description": "Stream extension for Verdaccio",
|
||||
"keywords": [
|
||||
"private",
|
||||
"package",
|
||||
"repository",
|
||||
"registry",
|
||||
"enterprise",
|
||||
"modules",
|
||||
"proxy",
|
||||
"server",
|
||||
"verdaccio"
|
||||
],
|
||||
"main": "./build/index.js",
|
||||
"types": "./build/index.d.ts",
|
||||
"author": "Juan Picado <juanpicado19@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://verdaccio.org",
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6"
|
||||
},
|
||||
"repository": {
|
||||
"type": "https",
|
||||
"url": "https://github.com/verdaccio/verdaccio",
|
||||
"directory": "packages/core/streams"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/verdaccio/verdaccio/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@verdaccio/types": "workspace:11.0.0-6-next.12"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
"watch": "pnpm build:js -- --watch",
|
||||
"build": "pnpm run build:js && pnpm run build:types"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/verdaccio"
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
import { PassThrough, TransformOptions } from 'stream';
|
||||
|
||||
export interface IReadTarball {
|
||||
abort?: () => void;
|
||||
}
|
||||
|
||||
export interface IUploadTarball {
|
||||
done?: () => void;
|
||||
abort?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This stream is used to read tarballs from repository.
|
||||
* @param {*} options
|
||||
* @return {Stream}
|
||||
*/
|
||||
class ReadTarball extends PassThrough implements IReadTarball {
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
public constructor(options: TransformOptions) {
|
||||
super(options);
|
||||
// called when data is not needed anymore
|
||||
addAbstractMethods(this, 'abort');
|
||||
}
|
||||
|
||||
public abort(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* This stream is used to upload tarballs to a repository.
|
||||
* @param {*} options
|
||||
* @return {Stream}
|
||||
*/
|
||||
class UploadTarball extends PassThrough implements IUploadTarball {
|
||||
/**
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
public constructor(options: any) {
|
||||
super(options);
|
||||
// called when user closes connection before upload finishes
|
||||
addAbstractMethods(this, 'abort');
|
||||
|
||||
// called when upload finishes successfully
|
||||
addAbstractMethods(this, 'done');
|
||||
}
|
||||
|
||||
public abort(): void {}
|
||||
public done(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function intercepts abstract calls and replays them allowing.
|
||||
* us to attach those functions after we are ready to do so
|
||||
* @param {*} self
|
||||
* @param {*} name
|
||||
*/
|
||||
// Perhaps someone knows a better way to write this
|
||||
function addAbstractMethods(self: any, name: any): void {
|
||||
self._called_methods = self._called_methods || {};
|
||||
|
||||
self.__defineGetter__(name, function () {
|
||||
return function (): void {
|
||||
self._called_methods[name] = true;
|
||||
};
|
||||
});
|
||||
|
||||
self.__defineSetter__(name, function (fn: any) {
|
||||
delete self[name];
|
||||
|
||||
self[name] = fn;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
||||
if (self._called_methods && self._called_methods[name]) {
|
||||
delete self._called_methods[name];
|
||||
|
||||
self[name]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { ReadTarball, UploadTarball };
|
|
@ -1,29 +0,0 @@
|
|||
import { ReadTarball, UploadTarball } from '../src/index';
|
||||
|
||||
describe('mystreams', () => {
|
||||
test('should delay events on ReadTarball abort', (cb) => {
|
||||
const readTballStream = new ReadTarball({});
|
||||
readTballStream.abort();
|
||||
setTimeout(function () {
|
||||
readTballStream.abort = function (): void {
|
||||
cb();
|
||||
};
|
||||
readTballStream.abort = function (): never {
|
||||
throw Error('fail');
|
||||
};
|
||||
}, 10);
|
||||
});
|
||||
|
||||
test('should delay events on UploadTarball abort', (cb) => {
|
||||
const uploadTballStream = new UploadTarball({});
|
||||
uploadTballStream.abort();
|
||||
setTimeout(function () {
|
||||
uploadTballStream.abort = function (): void {
|
||||
cb();
|
||||
};
|
||||
uploadTballStream.abort = function (): never {
|
||||
throw Error('fail');
|
||||
};
|
||||
}, 10);
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.reference.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
|
@ -46,7 +46,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
|
|
190
packages/core/types/index.d.ts
vendored
190
packages/core/types/index.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
/// <reference types="node" />
|
||||
import { PassThrough } from 'stream';
|
||||
import { PassThrough, PipelinePromise, Readable, Stream, Writable } from 'stream';
|
||||
|
||||
declare module '@verdaccio/types' {
|
||||
type StringValue = string | void | null;
|
||||
|
@ -71,9 +71,17 @@ declare module '@verdaccio/types' {
|
|||
bodyAfter?: string[];
|
||||
} & CommonWebConf;
|
||||
|
||||
interface Signatures {
|
||||
keyid: string;
|
||||
sig: string;
|
||||
}
|
||||
|
||||
interface Dist {
|
||||
'npm-signature'?: string;
|
||||
fileCount?: number;
|
||||
integrity?: string;
|
||||
shasum: string;
|
||||
unpackedSize?: number;
|
||||
tarball: string;
|
||||
}
|
||||
|
||||
|
@ -156,8 +164,8 @@ declare module '@verdaccio/types' {
|
|||
}
|
||||
|
||||
interface AttachMentsItem {
|
||||
content_type?: string;
|
||||
data?: string;
|
||||
content_type?: string;
|
||||
length?: number;
|
||||
shasum?: string;
|
||||
version?: string;
|
||||
|
@ -205,29 +213,79 @@ declare module '@verdaccio/types' {
|
|||
_rev: string;
|
||||
}
|
||||
|
||||
interface Manifest {
|
||||
interface PublishManifest {
|
||||
/**
|
||||
* The `_attachments` object has different usages:
|
||||
*
|
||||
* - When a package is published, it contains the tarball as an string, this string is used to be
|
||||
* converted as a tarball, usually attached to the package but not stored in the database.
|
||||
* - If user runs `npm star` the _attachments will be at the manifest body but empty.
|
||||
*
|
||||
* It has also an internal usage:
|
||||
*
|
||||
* - Used as a cache for the tarball, quick access to the tarball shasum, etc. Instead
|
||||
* iterate versions and find the right one, just using the tarball as a key which is what
|
||||
* the package manager sends to the registry.
|
||||
*
|
||||
* - A `_attachments` object is added every time a private tarball is published, upstream cached tarballs are
|
||||
* not being part of this object, only for published private packages.
|
||||
*
|
||||
* Note: This field is removed when the package is accesed through the web user interface.
|
||||
* */
|
||||
_attachments: AttachMents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents upstream manifest from another registry
|
||||
*/
|
||||
interface FullRemoteManifest {
|
||||
_id?: string;
|
||||
_rev?: string;
|
||||
name: string;
|
||||
versions: Versions;
|
||||
description?: string;
|
||||
'dist-tags': GenericBody;
|
||||
time: GenericBody;
|
||||
versions: Versions;
|
||||
maintainers?: Author[];
|
||||
/** store the latest readme **/
|
||||
readme?: string;
|
||||
/** store star assigned to this packages by users */
|
||||
users?: PackageUsers;
|
||||
// TODO: not clear what access exactly means
|
||||
access?: any;
|
||||
bugs?: { url: string };
|
||||
license?: string;
|
||||
homepage?: string;
|
||||
repository?: string | { type?: string; url: string; directory?: string };
|
||||
keywords?: string[];
|
||||
}
|
||||
|
||||
interface Manifest extends FullRemoteManifest, PublishManifest {
|
||||
// private fields only used by verdaccio
|
||||
/**
|
||||
* store fast access to the dist url of an specific tarball, instead search version
|
||||
* by id, just the tarball id is faster.
|
||||
*
|
||||
* The _distfiles is created only when a package is being sync from an upstream.
|
||||
* also used to fetch tarballs from upstream, the private publish tarballs are not stored in
|
||||
* this object because they are not published in the upstream registry.
|
||||
*/
|
||||
_distfiles: DistFiles;
|
||||
_attachments: AttachMents;
|
||||
/**
|
||||
* Store access cache metadata, to avoid to fetch the same metadata multiple times.
|
||||
*
|
||||
* The key represents the uplink id which is composed of a etag and a fetched timestamp.
|
||||
*
|
||||
* The fetched timestamp is the time when the metadata was fetched, used to avoid to fetch the
|
||||
* same metadata until the metadata is older than the last fetch.
|
||||
*/
|
||||
_uplinks: UpLinks;
|
||||
/**
|
||||
* store the revision of the manifest
|
||||
*/
|
||||
_rev: string;
|
||||
}
|
||||
|
||||
interface IUploadTarball extends PassThrough {
|
||||
abort(): void;
|
||||
done(): void;
|
||||
}
|
||||
|
||||
interface IReadTarball extends PassThrough {
|
||||
abort(): void;
|
||||
}
|
||||
|
||||
interface UpLinkTokenConf {
|
||||
type: 'Bearer' | 'Basic';
|
||||
token?: string;
|
||||
|
@ -262,6 +320,14 @@ declare module '@verdaccio/types' {
|
|||
unpublish: string[];
|
||||
}
|
||||
|
||||
interface PackageAccessYaml {
|
||||
storage?: string;
|
||||
publish?: string;
|
||||
proxy?: string;
|
||||
access?: string;
|
||||
unpublish?: string;
|
||||
}
|
||||
|
||||
// info passed to the auth plugin when a package is package is being published
|
||||
interface AllowAccess {
|
||||
name: string;
|
||||
|
@ -275,12 +341,15 @@ declare module '@verdaccio/types' {
|
|||
[key: string]: PackageAccess;
|
||||
}
|
||||
|
||||
interface PackageListYaml {
|
||||
[key: string]: PackageAccessYaml;
|
||||
}
|
||||
interface UpLinksConfList {
|
||||
[key: string]: UpLinkConf;
|
||||
}
|
||||
|
||||
type LoggerType = 'stdout' | 'stderr' | 'file';
|
||||
type LoggerFormat = 'pretty' | 'pretty-timestamped' | 'file';
|
||||
type LoggerFormat = 'pretty' | 'pretty-timestamped' | 'file' | 'json';
|
||||
type LoggerLevel = 'http' | 'fatal' | 'warn' | 'info' | 'debug' | 'trace';
|
||||
|
||||
interface LoggerConfItem {
|
||||
|
@ -326,7 +395,7 @@ declare module '@verdaccio/types' {
|
|||
user: string;
|
||||
}
|
||||
|
||||
type IPackageStorage = ILocalPackageManager | void;
|
||||
type IPackageStorage = ILocalPackageManager | undefined;
|
||||
type IPackageStorageManager = ILocalPackageManager;
|
||||
type IPluginStorage<T> = ILocalData<T>;
|
||||
|
||||
|
@ -418,16 +487,19 @@ declare module '@verdaccio/types' {
|
|||
basePath: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* YAML configuration file available options.
|
||||
*/
|
||||
interface ConfigYaml {
|
||||
_debug?: boolean;
|
||||
storage?: string | void;
|
||||
packages: PackageList;
|
||||
packages?: PackageListYaml;
|
||||
uplinks: UpLinksConfList;
|
||||
// FUTURE: log should be mandatory
|
||||
log?: LoggerConfItem;
|
||||
web?: WebConf;
|
||||
auth?: AuthConf;
|
||||
security: Security;
|
||||
security?: Security;
|
||||
publish?: PublishOptions;
|
||||
store?: any;
|
||||
listen?: ListenAddress;
|
||||
|
@ -444,19 +516,32 @@ declare module '@verdaccio/types' {
|
|||
url_prefix?: string;
|
||||
server?: ServerSettingsConf;
|
||||
flags?: FlagsConfig;
|
||||
// internal objects, added by internal yaml to JS config parser
|
||||
// @deprecated use configPath instead
|
||||
config_path?: string;
|
||||
// save the configuration file path
|
||||
configPath?: string;
|
||||
}
|
||||
|
||||
interface ConfigRuntime extends ConfigYaml {
|
||||
config_path: string;
|
||||
}
|
||||
|
||||
interface Config extends ConfigYaml, ConfigRuntime {
|
||||
/**
|
||||
* Configuration object with additonal methods for configuration, includes yaml and internal medatada.
|
||||
* @interface Config
|
||||
* @extends {ConfigYaml}
|
||||
*/
|
||||
interface Config extends Omit<ConfigYaml, 'packages' | 'security' | 'configPath'> {
|
||||
user_agent: string;
|
||||
server_id: string;
|
||||
secret: string;
|
||||
// deprecated
|
||||
// save the configuration file path, it's fails without thi configPath
|
||||
configPath: string;
|
||||
// packages from yaml file looks different from packages inside the config file
|
||||
packages: PackageList;
|
||||
// security object defaults is added by the config file but optional in the yaml file
|
||||
security: Security;
|
||||
// @deprecated (pending adding the replacement)
|
||||
checkSecretKey(token: string): string;
|
||||
getMatchedPackagesSpec(storage: string): PackageAccess | void;
|
||||
// TODO: verify how to handle this in the future
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
@ -508,55 +593,24 @@ declare module '@verdaccio/types' {
|
|||
getPackageStorage(packageInfo: string): IPackageStorage;
|
||||
}
|
||||
|
||||
type StorageUpdateCallback = (data: Package, cb: CallbackAction) => void;
|
||||
type StorageUpdateHandler = (name: string, cb: StorageUpdateCallback) => void;
|
||||
type StorageWriteCallback = (name: string, json: Package, callback: Callback) => void;
|
||||
type PackageTransformer = (pkg: Package) => Package;
|
||||
type ReadPackageCallback = (err: any | null, data?: Package) => void;
|
||||
|
||||
interface ILocalPackageManager {
|
||||
logger: Logger;
|
||||
writeTarball(pkgName: string): IUploadTarball;
|
||||
readTarball(pkgName: string): IReadTarball;
|
||||
readPackage(fileName: string, callback: ReadPackageCallback): void;
|
||||
createPackage(pkgName: string, value: Package, cb: CallbackAction): void;
|
||||
deletePackage(fileName: string): Promise<void>;
|
||||
removePackage(): Promise<void>;
|
||||
// @deprecated
|
||||
updatePackage(
|
||||
pkgFileName: string,
|
||||
updateHandler: StorageUpdateCallback,
|
||||
onWrite: StorageWriteCallback,
|
||||
transformPackage: PackageTransformer,
|
||||
onEnd: Callback
|
||||
): void;
|
||||
// @deprecated
|
||||
savePackage(fileName: string, json: Package, callback: CallbackAction): void;
|
||||
// next packages migration (this list is meant to replace the callback parent functions)
|
||||
updatePackageNext(
|
||||
updatePackage(
|
||||
packageName: string,
|
||||
handleUpdate: (manifest: Package) => Promise<Package>
|
||||
): Promise<Package>;
|
||||
savePackageNext(name: string, value: Package): Promise<void>;
|
||||
}
|
||||
|
||||
interface TarballActions {
|
||||
addTarball(name: string, filename: string): IUploadTarball;
|
||||
getTarball(name: string, filename: string): IReadTarball;
|
||||
removeTarball(name: string, filename: string, revision: string, callback: Callback): void;
|
||||
}
|
||||
|
||||
interface StoragePackageActions extends TarballActions {
|
||||
addVersion(
|
||||
name: string,
|
||||
version: string,
|
||||
metadata: Version,
|
||||
tag: StringValue,
|
||||
callback: Callback
|
||||
): void;
|
||||
mergeTags(name: string, tags: MergeTags, callback: Callback): void;
|
||||
removePackage(name: string, callback: Callback): void;
|
||||
changePackage(name: string, metadata: Package, revision: string, callback: Callback): void;
|
||||
handleUpdate: (manifest: Manifest) => Promise<Manifest>
|
||||
): Promise<Manifest>;
|
||||
readPackage(name: string): Promise<Manifest>;
|
||||
savePackage(pkgName: string, value: Manifest): Promise<void>;
|
||||
readTarball(pkgName: string, { signal }): Promise<Readable>;
|
||||
createPackage(name: string, manifest: Manifest): Promise<void>;
|
||||
writeTarball(tarballName: string, { signal }): Promise<Writable>;
|
||||
// verify if tarball exist in the storage
|
||||
hasTarball(fileName: string): Promise<boolean>;
|
||||
// verify if package exist in the storage
|
||||
hasPackage(): Promise<boolean>;
|
||||
}
|
||||
|
||||
// @deprecated use IBasicAuth from @verdaccio/auth
|
||||
|
@ -628,7 +682,7 @@ declare module '@verdaccio/types' {
|
|||
}
|
||||
|
||||
interface IPluginStorageFilter<T> extends IPlugin<T> {
|
||||
filter_metadata(packageInfo: Package): Promise<Package>;
|
||||
filter_metadata(packageInfo: Manifest): Promise<Package>;
|
||||
}
|
||||
|
||||
export type SearchResultWeb = {
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build",
|
||||
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.build.json",
|
||||
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
||||
"build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
|
||||
|
|
|
@ -93,6 +93,7 @@ export type RequestOptions = {
|
|||
host: string;
|
||||
protocol: string;
|
||||
headers: { [key: string]: string };
|
||||
remoteAddress?: string;
|
||||
};
|
||||
|
||||
export function getPublicUrl(url_prefix: string = '', requestOptions: RequestOptions): string {
|
||||
|
|
|
@ -8,47 +8,34 @@ async function distTagsRoute(fastify: FastifyInstance) {
|
|||
// @ts-ignore
|
||||
const { packageName } = request.params;
|
||||
debug('dist-tags: response %o', packageName);
|
||||
fastify.storage.getPackage({
|
||||
const requestOptions = {
|
||||
protocol: request.protocol,
|
||||
headers: request.headers as any,
|
||||
host: request.hostname,
|
||||
remoteAddress: request.socket.remoteAddress,
|
||||
};
|
||||
const manifest = fastify.storage.getPackageByOptions({
|
||||
name: packageName,
|
||||
uplinksLook: true,
|
||||
req: request.raw,
|
||||
callback: function (err, info): void {
|
||||
if (err) {
|
||||
reply.send(err);
|
||||
}
|
||||
reply.code(fastify.statusCode.OK).send(info[fastify.constants.DIST_TAGS]);
|
||||
},
|
||||
keepUpLinkData: true,
|
||||
requestOptions,
|
||||
});
|
||||
reply.code(fastify.statusCode.OK).send(manifest[fastify.constants.DIST_TAGS]);
|
||||
});
|
||||
|
||||
fastify.post('/-/package/:packageName/dist-tags', async (request, reply) => {
|
||||
fastify.post('/-/package/:packageName/dist-tags', async (request) => {
|
||||
// @ts-ignore
|
||||
const { packageName } = request.params;
|
||||
// @ts-ignore
|
||||
fastify.storage.mergeTags(packageName, request.body, function (err): void {
|
||||
if (err) {
|
||||
reply.send(err);
|
||||
}
|
||||
reply
|
||||
.code(fastify.statusCode.CREATED)
|
||||
.send({ ok: fastify.constants.API_MESSAGE.TAG_UPDATED });
|
||||
});
|
||||
await fastify.storage.mergeTags(packageName, request.body);
|
||||
return { ok: fastify.constants.API_MESSAGE.TAG_UPDATED };
|
||||
});
|
||||
|
||||
fastify.delete('/-/package/:packageName/dist-tags', async (request, reply) => {
|
||||
// @ts-ignore
|
||||
const { packageName } = request.params;
|
||||
fastify.storage.getPackage({
|
||||
name: packageName,
|
||||
uplinksLook: true,
|
||||
req: request.raw,
|
||||
callback: function (err, info): void {
|
||||
if (err) {
|
||||
reply.send(err);
|
||||
}
|
||||
reply.send(info[fastify.constants.DIST_TAGS]);
|
||||
},
|
||||
});
|
||||
// const { packageName } = request.params;
|
||||
|
||||
reply.code(fastify.statusCode.NOT_FOUND);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ async function manifestRoute(fastify: FastifyInstance) {
|
|||
debug('pkg name %s ', packageName);
|
||||
const data = await storage?.getPackageByOptions({
|
||||
name: packageName,
|
||||
// remove on refactor getPackageByOptions
|
||||
// @ts-ignore
|
||||
req: request.raw,
|
||||
uplinksLook: true,
|
||||
requestOptions: {
|
||||
|
@ -32,6 +34,8 @@ async function manifestRoute(fastify: FastifyInstance) {
|
|||
debug('pkg name %s, with version / tag: %s ', packageName, version);
|
||||
const data = await storage?.getPackageByOptions({
|
||||
name: packageName,
|
||||
// remove on refactor getPackageByOptions
|
||||
// @ts-ignore
|
||||
req: request.raw,
|
||||
version,
|
||||
uplinksLook: true,
|
||||
|
|
|
@ -20,7 +20,7 @@ async function searchRoute(fastify: FastifyInstance) {
|
|||
const { url, query } = request.query;
|
||||
const storage = fastify.storage;
|
||||
|
||||
const data = await storage.searchManager?.search({
|
||||
const data = await storage.search({
|
||||
query,
|
||||
url,
|
||||
abort,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import buildDebug from 'debug';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
|
@ -8,16 +9,16 @@ async function tarballRoute(fastify: FastifyInstance) {
|
|||
// @ts-ignore
|
||||
const { package: pkg, filename } = request.params;
|
||||
debug('stream tarball for %s@%s', pkg, filename);
|
||||
const stream = fastify.storage.getTarball(pkg, filename);
|
||||
return reply.send(stream);
|
||||
// const stream = fastify.storage.getTarball(pkg, filename);
|
||||
// return reply.send(stream);
|
||||
});
|
||||
|
||||
fastify.get('/:scopedPackage/-/:scope/:filename', async (request, reply) => {
|
||||
// @ts-ignore
|
||||
const { scopedPackage, filename } = request.params;
|
||||
debug('stream scope tarball for %s@%s', scopedPackage, filename);
|
||||
const stream = fastify.storage.getTarball(scopedPackage, filename);
|
||||
return reply.send(stream);
|
||||
// const stream = fastify.storage.getTarball(scopedPackage, filename);
|
||||
// return reply.send(stream);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue