Compare commits

...

21 Commits

Author SHA1 Message Date
github-actions[bot]
1ef60915e6 chore: update versions (6-next) (#2245)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2021-05-24 09:01:22 +02:00
Juan Picado
55ee3fdd97 [Fastify] Add ping endpoint (#2246)
* chore: add ping endpoint

* Update package.json

* rebase from master

* add debug code and logger

* Update index.ts

* Create heavy-ravens-lay.md

* endpoint as plugin

* chore: format

* Update packages/core/server/debug/index.ts

Co-authored-by: Manuel Spigolon <behemoth89@gmail.com>

* add logger instance to fastify

* 4873 port

* format file

* add logger

Co-authored-by: Manuel Spigolon <behemoth89@gmail.com>
2021-05-22 06:56:37 +02:00
Juan Picado
4ebf18816a chore: restore changesets version 2021-05-16 19:03:39 +02:00
Juan Picado
276a0a63a7 chore: set max eslint warnings
this will avoid increase number of warnings, only reduce this number along the warnings are being fixed
2021-05-16 17:26:26 +02:00
Juan Picado
f3f00052d8 chore: eslint config module (#2263)
* build: eslint config as package

* chore: fix formatting
2021-05-16 17:11:08 +02:00
Leonardo Metzger
d2c65da9c7 fix: Fix the name validation of a package tarball (#2242)
Co-authored-by: Juan Picado <juanpicado19@gmail.com>
2021-05-15 16:58:06 +02:00
Juan Picado
2924ffa235 chore: remove website (#2244) 2021-05-13 08:38:18 +02:00
Leonardo Metzger
6b1a28deb8 fix: Fix the prefix used to delete from s3 when unpublishing packages (#2243) 2021-05-12 20:15:18 +02:00
Juan Picado
a54c18c02a additional tests for config pkg (#2237)
* add test config

* add test for home creation

* more checks

* add test

* skip peer platform

* lint

* fix windows
2021-05-09 00:44:07 +02:00
Juan Picado
52b47868e3 add more test proxy (#2236)
* add more test proxy

* test tarball and search

* format

* refactor uplinks
2021-05-08 19:21:21 +02:00
Juan Picado
8582548559 chore: add test for proxy (#2235)
* chore: add test for proxy

* format
2021-05-08 09:47:30 +02:00
Juan Picado
32f66cc2c7 chore: update format 2021-05-07 07:42:46 +02:00
github-actions[bot]
6a01bdbcc9 chore: update versions (6-next) (#2231)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2021-05-07 07:41:38 +02:00
Leonardo Metzger
5ddfa5264c fix: fix the exact search phrase (#2225)
* fix: Fix the exact search phrase

* fix: Add changeset to fix of exact search phrase

Co-authored-by: Juan Picado <juanpicado19@gmail.com>
2021-05-07 06:43:29 +02:00
Vitaly Baev
1cc00cf2ab Fixed broken link (#2232) 2021-05-06 18:34:17 +02:00
github-actions[bot]
086c4a7c3c chore: update versions (6-next) (#2229)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2021-05-06 08:37:13 +02:00
Omri Bar-Zik
babe5c3f6c docs: Fix README.md install command (#2230) 2021-05-06 08:37:00 +02:00
Juan Picado
0da7031e77 feat: allow disable login on ui (#2228) 2021-05-05 23:23:03 +02:00
Juan Picado
393125baa1 chore: remove snapshots 2021-05-05 22:51:49 +02:00
github-actions[bot]
7fb5c45243 chore: update versions (6-next) (#2227)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2021-05-05 21:16:22 +02:00
Juan Picado
aecbd226de feat: allow ui hide package managers on sidebar (#2226)
* feat: allow ui hide package managers on sidebar

* add test

* add changeset

* chore: remove snapshot

* chore: update config
2021-05-04 21:15:07 +02:00
411 changed files with 4907 additions and 33398 deletions

View File

@@ -0,0 +1,15 @@
---
'@verdaccio/types': minor
'@verdaccio/ui-theme': minor
'@verdaccio/web': minor
---
allow disable login on ui and endpoints
To be able disable the login, set `login: false`, anything else would enable login. This flag will disable access via UI and web endpoints.
```yml
web:
title: verdaccio
login: false
```

View File

@@ -0,0 +1,5 @@
---
'verdaccio-aws-s3-storage': patch
---
Fix the prefix used to delete from s3 when unpublishing packages

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/utils': patch
---
Fixed the validation of the name when searching for a tarball that have scoped package name

View File

@@ -0,0 +1,6 @@
---
'@verdaccio/cli': minor
'@verdaccio/fastify-migration': minor
---
[Fastify] Add ping endpoint

View File

@@ -33,23 +33,27 @@
"verdaccio-aws-s3-storage": "11.0.0-alpha.0",
"verdaccio-google-cloud": "11.0.0-alpha.0",
"verdaccio-memory": "11.0.0-alpha.0",
"@verdaccio/website": "0.0.1",
"@verdaccio/ui-theme": "6.0.0-alpha.1",
"@verdaccio/e2e-cli": "1.0.0",
"@verdaccio/e2e-ui": "1.0.0",
"@verdaccio/cli-standalone": "6.0.0-alpha.3",
"@verdaccio/tarball": "11.0.0-alpha.3",
"@verdaccio/url": "11.0.0-alpha.3",
"@verdaccio/fastify-migration": "6.0.0-6-next.9"
"@verdaccio/fastify-migration": "6.0.0-6-next.9",
"@verdaccio/eslint-config": "1.0.0"
},
"changesets": [
"afraid-mice-obey",
"big-lobsters-sin",
"calm-pants-impress",
"few-cooks-destroy",
"fifty-jars-rest",
"gentle-parrots-lay",
"gentle-trains-switch",
"gold-vans-tease",
"healthy-bikes-behave",
"healthy-poets-compare",
"heavy-ravens-lay",
"hip-hounds-destroy",
"late-adults-love",
"late-parents-act",
@@ -61,11 +65,13 @@
"plenty-spiders-melt",
"plenty-tables-refuse",
"pretty-hounds-tap",
"red-chefs-float",
"shiny-chefs-heal",
"smart-apricots-kneel",
"spicy-frogs-press",
"tender-bags-call",
"three-pots-sit",
"two-dolls-check"
"two-dolls-check",
"wild-jokes-beam"
]
}

View File

@@ -0,0 +1,11 @@
---
'@verdaccio/store': patch
'@verdaccio/web': patch
---
Fix the search by exact name of the package
Full package name queries was not finding anithing. It was happening
becouse of stemmer of [lunr.js](https://lunrjs.com/).
To fix this, the stemmer of [lunr.js](https://lunrjs.com/) was removed from search pipeline.

View File

@@ -0,0 +1,32 @@
---
'@verdaccio/types': minor
'@verdaccio/ui-theme': minor
'@verdaccio/web': minor
---
web: allow ui hide package managers on sidebar
If there is a package manager of preference over others, you can define the package managers to be displayed on the detail page and sidebar, just define in the `config.yaml` and web section the list of package managers to be displayed.
```
web:
title: Verdaccio
sort_packages: asc
primary_color: #cccccc
pkgManagers:
- pnpm
- yarn
# - npm
```
To disable all package managers, just define empty:
```
web:
title: Verdaccio
sort_packages: asc
primary_color: #cccccc
pkgManagers:
```
and the section would be hidden.

102
.eslintrc
View File

@@ -1,102 +0,0 @@
{
"extends": [
"eslint:recommended",
"google",
"plugin:react/recommended",
"plugin:jest/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
"plugin:jsx-a11y/recommended",
"prettier"
],
"plugins": ["import", "jest", "jsx-a11y", "react-hooks"],
"env": {
"es6": true,
"node": true,
"jest": true
},
"globals": {
"__APP_VERSION__": true
},
"parserOptions": {
"allowImportExportEverywhere": true,
"sourceType": "module",
"ecmaVersion": 11,
"ecmaFeatures": {
"impliedStrict": true,
"jsx": true
}
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".jsx", ".ts", ".tsx"]
}
}
},
"parser": "@typescript-eslint/parser",
"rules": {
"curly": ["error", "all"],
"react/prop-types": 0,
"jest/no-export": 0,
"jest/no-test-callback": 0,
"jest/expect-expect": 0,
"jest/no-try-expect": 0,
"jest/no-done-callback": "off",
"jest/no-conditional-expect": "off",
"keyword-spacing": "off",
"no-tabs": "off",
"no-useless-escape": "off",
"padded-blocks": "off",
"require-jsdoc": "off",
"valid-jsdoc": "off",
"import/order": ["error"],
"eol-last": "error",
"no-irregular-whitespace": "error",
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
"no-trailing-spaces": "error",
"camelcase": "off",
"guard-for-in": "error",
"new-cap": "error",
"max-len": ["error", 100],
"no-console": ["error", { "allow": ["warn"] }],
"no-constant-condition": "error",
"no-debugger": "error",
"no-empty": "error",
"no-fallthrough": "error",
"no-invalid-this": "error",
"no-new-require": "error",
"no-undef": "error",
"no-unreachable": "error",
"no-var": "error",
"one-var": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"handle-callback-err": 0,
"prefer-const": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-inferrable-types": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-this-alias": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/array-type": ["error"],
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/indent": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/ban-types": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
// rules to fix
"no-unused-vars": ["warn", { "vars": "all", "args": "none" }],
"jest/no-identical-title": ["warn"],
"prefer-promise-reject-errors": ["warn"],
"jest/no-disabled-tests": ["warn"],
"jest/no-commented-out-tests": ["warn"],
"@typescript-eslint/prefer-optional-chain": ["warn"],
"@typescript-eslint/explicit-member-accessibility": ["warn"],
"@typescript-eslint/no-unused-vars": ["warn"]
}
}

3
.eslintrc.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@verdaccio/eslint-config'],
};

View File

@@ -100,8 +100,6 @@ jobs:
path: ~/.pnpm-store
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install
## we run scripts due gatsby needs it
## when website is excluded we can add --ignore-scripts
run: pnpm recursive install --frozen-lockfile --ignore-scripts
- name: build
run: pnpm build
@@ -194,27 +192,6 @@ jobs:
run: pnpm recursive install --frozen-lockfile
- name: Test CLI
run: pnpm test:e2e:cli
website:
needs: [format, lint]
runs-on: ubuntu-latest
name: website build node 14
steps:
- uses: actions/checkout@v2.3.1
- uses: actions/setup-node@v1
with:
node-version: 14
- name: Install pnpm
run: npm i pnpm@latest -g
- uses: actions/cache@v2
with:
path: ~/.pnpm-store
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install
run: pnpm recursive install --frozen-lockfile
- name: Build website
run: |
cd website
pnpm build:website
test-windows:
needs: [format, lint]
runs-on: windows-latest

View File

@@ -32,7 +32,7 @@ Google Cloud Storage** or create your own plugin.
Install with npm:
```bash
npm install --global verdaccio@6-next --https://registry.verdaccio.org/
npm install --global verdaccio@6-next --registry https://registry.verdaccio.org/
```
> Published on a temporary registry while setup is ready to publish on npmjs

View File

@@ -10,7 +10,7 @@ let _localMemory = require('./local-memory');
let _localMemory2 = _interopRequireDefault(_localMemory);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
return obj && obj.__esModule ? obj : {default: obj};
}
exports.LocalMemory = _localMemory2.default;

View File

@@ -9,7 +9,7 @@ let _memoryHandler = require('./memory-handler');
let _memoryHandler2 = _interopRequireDefault(_memoryHandler);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
return obj && obj.__esModule ? obj : {default: obj};
}
const DEFAULT_LIMIT = 1000;
@@ -43,8 +43,8 @@ class LocalMemory {
cb(null);
} else {
this.logger.info(
{ limit: this.limit },
'Storage memory has reached limit of @{limit} packages'
{limit: this.limit},
'Storage memory has reached limit of @{limit} packages',
);
cb(new Error('Storage memory has reached limit of limit packages'));
}

View File

@@ -16,7 +16,7 @@ let _memoryFs2 = _interopRequireDefault(_memoryFs);
let _streams = require('@verdaccio/streams');
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
return obj && obj.__esModule ? obj : {default: obj};
}
// $FlowFixMe
@@ -111,8 +111,8 @@ class MemoryHandler {
const uploadStream = new _streams.UploadTarball();
const temporalName = `/${name}`;
process.nextTick(function () {
fs.exists(temporalName, function (exists) {
process.nextTick(function() {
fs.exists(temporalName, function(exists) {
if (exists) {
return uploadStream.emit('error', fSError(fileExist));
}
@@ -122,7 +122,7 @@ class MemoryHandler {
uploadStream.pipe(file);
uploadStream.done = function () {
uploadStream.done = function() {
const onEnd = function onEnd() {
uploadStream.emit('success');
};
@@ -130,7 +130,7 @@ class MemoryHandler {
uploadStream.on('end', onEnd);
};
uploadStream.abort = function () {
uploadStream.abort = function() {
uploadStream.emit('error', fSError('transmision aborted', 400));
file.end();
};
@@ -150,8 +150,8 @@ class MemoryHandler {
const readTarballStream = new _streams.ReadTarball();
process.nextTick(function () {
fs.exists(pathName, function (exists) {
process.nextTick(function() {
fs.exists(pathName, function(exists) {
if (!exists) {
readTarballStream.emit('error', noPackageFoundError());
} else {
@@ -164,7 +164,7 @@ class MemoryHandler {
readTarballStream.emit('error', error);
});
readTarballStream.abort = function () {
readTarballStream.abort = function() {
readStream.destroy(fSError('read has been aborted', 400));
};
}

View File

@@ -2,4 +2,4 @@
> Before run examples, build the local image by running `pnpm docker`.
- [Docker + Nginx + Verdaccio](v5/reverse_proxy/nginx/README.md)
- [Docker + Nginx + Verdaccio](reverse_proxy/nginx/README.md)

View File

@@ -39,7 +39,7 @@
"@babel/register": "7.13.14",
"@babel/runtime": "7.13.10",
"@changesets/changelog-github": "^0.2.8",
"@changesets/cli": "^2.15.0",
"@changesets/cli": "2.15.0",
"@changesets/get-dependents-graph": "^1.2.0",
"@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.2.0",
@@ -71,6 +71,7 @@
"@typescript-eslint/parser": "4.13.0",
"@verdaccio/types": "workspace:*",
"@verdaccio/ui-theme": "workspace:*",
"@verdaccio/eslint-config": "workspace:*",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.1.0",
"babel-jest": "26.6.3",
@@ -78,20 +79,21 @@
"babel-plugin-emotion": "11.0.0",
"codecov": "3.8.1",
"concurrently": "^5.3.0",
"core-js": "^3.10.1",
"core-js": "^3.12.1",
"cross-env": "7.0.3",
"detect-secrets": "1.0.6",
"eslint": "7.19.0",
"eslint": "7.26.0",
"eslint-config-google": "0.14.0",
"eslint-config-prettier": "7.2.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-babel": "5.3.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest": "24.1.3",
"eslint-plugin-import": "2.23.2",
"eslint-plugin-jest": "24.3.6",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react": "7.23.2",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"eslint-plugin-verdaccio": "9.6.1",
"eslint-plugin-verdaccio": "10.0.0",
"eslint-plugin-prettier": "3.4.0",
"fs-extra": "9.1.0",
"get-stdin": "7.0.0",
"husky": "2.7.0",
@@ -107,14 +109,14 @@
"nock": "12.0.3",
"nodemon": "^2.0.7",
"npm-run-all": "4.1.5",
"prettier": "2.2.1",
"prettier": "2.3.0",
"rimraf": "3.0.2",
"selfsigned": "1.10.8",
"supertest": "4.0.2",
"ts-node": "^9.1.1",
"typescript": "^4.2.4",
"update-ts-references": "2.3.0",
"verdaccio": "^5.0.1",
"verdaccio": "^5.0.4",
"verdaccio-audit": "workspace:*",
"verdaccio-auth-memory": "workspace:*",
"verdaccio-htpasswd": "workspace:*",
@@ -126,7 +128,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 \"**/*.{js,jsx,ts,tsx}\"",
"lint": "eslint --max-warnings 165 \"**/*.{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",
@@ -139,9 +141,6 @@
"start:ts": "ts-node packages/verdaccio/src/start.ts -- --listen 8000",
"debug": "node --inspect packages/verdaccio/debug/bootstrap.js",
"debug:break": "node --inspect-brk packages/verdaccio/debug/bootstrap.js",
"website:lint": "cd website && yarn lint",
"website:develop": "cd website && yarn develop",
"website:build": "cd website && yarn build",
"changeset": "changeset",
"changeset:check": "changeset status --since-master",
"ci:version": "run-s ci:version:changeset ci:version:install",

View File

@@ -1,5 +1,25 @@
# @verdaccio/api
## 6.0.0-6-next.12
### Patch Changes
- Updated dependencies [d2c65da9]
- @verdaccio/utils@6.0.0-6-next.5
- @verdaccio/auth@6.0.0-6-next.9
- @verdaccio/config@6.0.0-6-next.7
- @verdaccio/tarball@11.0.0-6-next.6
- @verdaccio/middleware@6.0.0-6-next.9
- @verdaccio/store@6.0.0-6-next.10
- @verdaccio/hooks@6.0.0-6-next.4
## 6.0.0-6-next.11
### Patch Changes
- Updated dependencies [5ddfa526]
- @verdaccio/store@6.0.0-6-next.9
## 6.0.0-6-next.10
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/api",
"version": "6.0.0-6-next.10",
"version": "6.0.0-6-next.12",
"description": "loaders logic",
"main": "./build/index.js",
"types": "build/index.d.ts",
@@ -39,15 +39,15 @@
},
"license": "MIT",
"dependencies": {
"@verdaccio/auth": "workspace:6.0.0-6-next.8",
"@verdaccio/auth": "workspace:6.0.0-6-next.9",
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/hooks": "workspace:6.0.0-6-next.4",
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
"@verdaccio/middleware": "workspace:6.0.0-6-next.8",
"@verdaccio/store": "workspace:6.0.0-6-next.8",
"@verdaccio/tarball": "workspace:11.0.0-6-next.5",
"@verdaccio/utils": "workspace:6.0.0-6-next.4",
"@verdaccio/middleware": "workspace:6.0.0-6-next.9",
"@verdaccio/store": "workspace:6.0.0-6-next.10",
"@verdaccio/tarball": "workspace:11.0.0-6-next.6",
"@verdaccio/utils": "workspace:6.0.0-6-next.5",
"cookies": "0.8.0",
"debug": "^4.1.1",
"express": "4.17.1",
@@ -56,8 +56,8 @@
"semver": "7.3.2"
},
"devDependencies": {
"@verdaccio/server": "workspace:6.0.0-6-next.12",
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/server": "workspace:6.0.0-6-next.16",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"body-parser": "1.19.0",
"lodash": "^4.17.20",
"supertest": "next"

View File

@@ -41,7 +41,7 @@ export default function (route, auth, storage): void {
route.get('/-/v1/search', (req, res) => {
// TODO: implement proper result scoring weighted by quality, popularity and
// maintenance query parameters
let [text, size, from /* , quality, popularity, maintenance */] = [
let [text, size, from] = [
'text',
'size',
'from' /* , 'quality', 'popularity', 'maintenance' */,

View File

@@ -5,10 +5,11 @@ import { $ResponseExtend, $RequestExtend } 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();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
}
);
jest.mock('@verdaccio/auth', () => ({

View File

@@ -11,10 +11,11 @@ import { $ResponseExtend, $RequestExtend } from '../../types/custom';
import { initializeServer, publishVersion } from './_helper';
const mockApiJWTmiddleware = jest.fn(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
}
);
jest.setTimeout(50000000);

View File

@@ -16,10 +16,11 @@ import { $RequestExtend, $ResponseExtend } from '../../types/custom';
import { initializeServer } from './_helper';
const mockApiJWTmiddleware = jest.fn(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test', groups: [], real_groups: [] };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test', groups: [], real_groups: [] };
_next();
}
);
const mockAuthenticate = jest.fn(() => (_name, _password, callback): void => {
@@ -53,10 +54,11 @@ describe('user', () => {
test('should test add a new user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
@@ -85,10 +87,11 @@ describe('user', () => {
test('should test fails on add a existing user with login', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
supertest(await initializeServer('user.yaml'))
.put('/-/user/org.couchdb.user:jotaNew')
@@ -124,10 +127,11 @@ describe('user', () => {
test('should test fails add a new user with missing name', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
mockAddUser.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getBadRequest(API_ERROR.USERNAME_PASSWORD_REQUIRED));
@@ -153,10 +157,11 @@ describe('user', () => {
test('should test fails add a new user with missing password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: undefined };
_next();
}
);
const credentialsShort = _.cloneDeep(credentials);
delete credentialsShort.password;
@@ -181,10 +186,11 @@ describe('user', () => {
test('should test fails add a new user with wrong password', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
@@ -210,10 +216,11 @@ describe('user', () => {
test('should be able to logout an user', async (done) => {
mockApiJWTmiddleware.mockImplementationOnce(
() => (req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'test' };
_next();
}
);
mockAuthenticate.mockImplementationOnce(() => (_name, _password, callback): void => {
return callback(getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));

View File

@@ -6,10 +6,11 @@ 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();
}
() =>
(req: $RequestExtend, res: $ResponseExtend, _next): void => {
req.remote_user = { name: 'foo', groups: [], real_groups: [] };
_next();
}
);
jest.mock('@verdaccio/auth', () => ({

View File

@@ -1,5 +1,14 @@
# @verdaccio/auth
## 6.0.0-6-next.9
### Patch Changes
- Updated dependencies [d2c65da9]
- @verdaccio/utils@6.0.0-6-next.5
- @verdaccio/config@6.0.0-6-next.7
- @verdaccio/loaders@6.0.0-6-next.4
## 6.0.0-6-next.8
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/auth",
"version": "6.0.0-6-next.8",
"version": "6.0.0-6-next.9",
"description": "logger",
"main": "./build/index.js",
"types": "build/index.d.ts",
@@ -40,10 +40,10 @@
"license": "MIT",
"dependencies": {
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/loaders": "workspace:6.0.0-6-next.4",
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
"@verdaccio/utils": "workspace:6.0.0-6-next.4",
"@verdaccio/utils": "workspace:6.0.0-6-next.5",
"verdaccio-htpasswd": "workspace:11.0.0-alpha.6",
"debug": "^4.1.1",
"express": "4.17.1",
@@ -51,8 +51,8 @@
"lodash": "4.17.15"
},
"devDependencies": {
"@verdaccio/mock": "workspace:6.0.0-6-next.6",
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/mock": "workspace:6.0.0-6-next.7",
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"funding": {
"type": "opencollective",

View File

@@ -1,5 +1,40 @@
# @verdaccio/cli
## 6.0.0-6-next.17
### Minor Changes
- 55ee3fdd: [Fastify] Add ping endpoint
### Patch Changes
- Updated dependencies [55ee3fdd]
- @verdaccio/fastify-migration@6.0.0-6-next.10
- @verdaccio/config@6.0.0-6-next.7
- @verdaccio/node-api@6.0.0-6-next.17
## 6.0.0-6-next.16
### Patch Changes
- @verdaccio/node-api@6.0.0-6-next.16
## 6.0.0-6-next.15
### Patch Changes
- @verdaccio/fastify-migration@6.0.0-6-next.9
- @verdaccio/logger@6.0.0-6-next.4
- @verdaccio/node-api@6.0.0-6-next.15
## 6.0.0-6-next.14
### Patch Changes
- @verdaccio/fastify-migration@6.0.0-6-next.9
- @verdaccio/logger@6.0.0-6-next.4
- @verdaccio/node-api@6.0.0-6-next.14
## 6.0.0-6-next.13
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/cli",
"version": "6.0.0-6-next.13",
"version": "6.0.0-6-next.17",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
@@ -40,19 +40,23 @@
"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 --F --watch",
"build": "pnpm run build:js && pnpm run build:types"
"build": "pnpm run build:js && pnpm run build:types",
"start": "ts-node src/index.ts"
},
"dependencies": {
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
"@verdaccio/node-api": "workspace:6.0.0-6-next.13",
"@verdaccio/fastify-migration": "workspace:6.0.0-6-next.9",
"@verdaccio/node-api": "workspace:6.0.0-6-next.17",
"@verdaccio/fastify-migration": "workspace:6.0.0-6-next.10",
"commander": "6.2.0",
"clipanion": "3.0.0-rc.11",
"envinfo": "7.4.0",
"kleur": "3.0.3",
"semver": "7.3.2"
},
"devDependencies": {
"ts-node": "9.1.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/verdaccio"

View File

@@ -1,6 +1,8 @@
import { Command, Option } from 'clipanion';
import { findConfigFile, parseConfigFile } from '@verdaccio/config';
import { setup, logger } from '@verdaccio/logger';
import server from '@verdaccio/fastify-migration';
import { ConfigRuntime } from '@verdaccio/types';
export const DEFAULT_PROCESS_NAME: string = 'verdaccio';
@@ -19,17 +21,29 @@ export class NewServer extends Command {
description: 'use this configuration file (default: ./config.yaml)',
});
private initLogger(logConfig: ConfigRuntime) {
try {
if (logConfig.logs) {
process.emitWarning('config.logs is deprecated, rename configuration to "config.log"');
}
// FUTURE: remove fallback when is ready
setup(logConfig.log || logConfig.logs);
} catch {
throw new Error('error on init logger');
}
}
public async execute() {
try {
const configPathLocation = findConfigFile(this.config as string);
const configParsed = parseConfigFile(configPathLocation);
const { web } = configParsed;
this.initLogger(configParsed);
process.title = web?.title || DEFAULT_PROCESS_NAME;
// const { version, name } = require('../../package.json');
const ser = await server();
await ser.listen(4000);
console.log('fastify running on port 4000');
const ser = await server({ logger });
await ser.listen(4873);
} catch (err) {
console.error(err);
process.exit(1);

View File

@@ -1,5 +1,12 @@
# @verdaccio/config
## 6.0.0-6-next.7
### Patch Changes
- Updated dependencies [d2c65da9]
- @verdaccio/utils@6.0.0-6-next.5
## 6.0.0-6-next.6
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/config",
"version": "6.0.0-6-next.6",
"version": "6.0.0-6-next.7",
"description": "logger",
"main": "./build/index.js",
"types": "build/index.d.ts",
@@ -40,7 +40,7 @@
},
"dependencies": {
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/utils": "workspace:6.0.0-6-next.4",
"@verdaccio/utils": "workspace:6.0.0-6-next.5",
"debug": "^4.2.0",
"js-yaml": "3.14.0",
"lodash": "^4.17.20",

View File

@@ -1,6 +1,5 @@
import fs from 'fs';
import path from 'path';
import Path from 'path';
import _ from 'lodash';
import buildDebug from 'debug';
@@ -27,28 +26,34 @@ const debug = buildDebug('verdaccio:config');
* Find and get the first config file that match.
* @return {String} the config file path
*/
function findConfigFile(configPath: string | undefined): string {
function findConfigFile(configPath?: string): string {
// console.log(process.env);
if (typeof configPath !== 'undefined') {
return Path.resolve(configPath);
return path.resolve(configPath);
}
const configPaths: SetupDirectory[] = getConfigPaths();
debug('%o posible locations found', configPaths.length);
if (_.isEmpty(configPaths)) {
// this should never happens
throw new Error('no configuration files can be processed');
}
const primaryConf: any = _.find(configPaths, (configLocation: any) =>
// find the first location that already exist
const primaryConf: SetupDirectory | void = _.find(configPaths, (configLocation: SetupDirectory) =>
fileExists(configLocation.path)
);
if (_.isNil(primaryConf) === false) {
if (typeof primaryConf !== 'undefined') {
debug('previous location exist already %s', primaryConf?.path);
return primaryConf.path;
}
// @ts-ignore
return createConfigFile(_.head(configPaths)).path;
}
function createConfigFile(configLocation: any): SetupDirectory {
function createConfigFile(configLocation: SetupDirectory): SetupDirectory {
createConfigFolder(configLocation);
const defaultConfig = updateStorageLinks(configLocation, readDefaultConfig());
@@ -60,13 +65,18 @@ function createConfigFile(configLocation: any): SetupDirectory {
export function readDefaultConfig(): Buffer {
const pathDefaultConf: string = path.resolve(__dirname, 'conf/default.yaml');
try {
debug('default configuration file %s', pathDefaultConf);
fs.accessSync(pathDefaultConf, fs.constants.R_OK);
} catch {
throw new TypeError('configuration file does not have enough permissions for reading');
}
// @ts-ignore
return fs.readFileSync(pathDefaultConf, CHARACTER_ENCODING.UTF8);
}
function createConfigFolder(configLocation): void {
fs.mkdirSync(Path.dirname(configLocation.path), { recursive: true });
fs.mkdirSync(path.dirname(configLocation.path), { recursive: true });
debug(`Creating default config file in %o`, configLocation?.path);
}
@@ -78,64 +88,89 @@ function updateStorageLinks(configLocation, defaultConfig): string {
// $XDG_DATA_HOME defines the base directory relative to which user specific data
// files should be stored, If $XDG_DATA_HOME is either not set or empty, a default
// equal to $HOME/.local/share should be used.
// $FlowFixMe
let dataDir =
process.env.XDG_DATA_HOME || Path.join(process.env.HOME as string, '.local', 'share');
process.env.XDG_DATA_HOME || path.join(process.env.HOME as string, '.local', 'share');
if (folderExists(dataDir)) {
dataDir = Path.resolve(Path.join(dataDir, pkgJSON.name, 'storage'));
debug(`previous storage located`);
debug(`update storage links to %s`, dataDir);
dataDir = path.resolve(path.join(dataDir, pkgJSON.name, 'storage'));
return defaultConfig.replace(/^storage: .\/storage$/m, `storage: ${dataDir}`);
}
debug(`could not find a previous storage location, skip override`);
return defaultConfig;
}
/**
* Return a list of configuration locations by platform.
* @returns
*/
function getConfigPaths(): SetupDirectory[] {
const listPaths: SetupDirectory[] = [
const listPaths: (SetupDirectory | void)[] = [
getXDGDirectory(),
getWindowsDirectory(),
getRelativeDefaultDirectory(),
getOldDirectory(),
].reduce(function (acc, currentValue: any): SetupDirectory[] {
if (_.isUndefined(currentValue) === false) {
];
return listPaths.reduce(function (acc, currentValue: SetupDirectory | void): SetupDirectory[] {
if (typeof currentValue !== 'undefined') {
debug('directory detected path %s for type %s', currentValue?.path, currentValue.type);
acc.push(currentValue);
}
return acc;
}, [] as SetupDirectory[]);
return listPaths;
}
/**
* Get XDG_CONFIG_HOME or HOME location (usually unix)
* @returns
*/
const getXDGDirectory = (): SetupDirectory | void => {
const XDGConfig = getXDGHome() || (process.env.HOME && Path.join(process.env.HOME, '.config'));
if (XDGConfig && folderExists(XDGConfig)) {
const xDGConfigPath =
process.env.XDG_CONFIG_HOME || (process.env.HOME && path.join(process.env.HOME, '.config'));
if (xDGConfigPath && folderExists(xDGConfigPath)) {
debug('XDGConfig folder path %s', xDGConfigPath);
return {
path: Path.join(XDGConfig, pkgJSON.name, CONFIG_FILE),
path: path.join(xDGConfigPath, pkgJSON.name, CONFIG_FILE),
type: XDG,
};
}
};
const getXDGHome = (): string | void => process.env.XDG_CONFIG_HOME;
/**
* Detect windows location, APPDATA
* usually something like C:\User\<Build User>\AppData\Local
* @returns
*/
const getWindowsDirectory = (): SetupDirectory | void => {
if (process.platform === WIN32 && process.env.APPDATA && folderExists(process.env.APPDATA)) {
debug('is windows appdata: %s', process.env.APPDATA);
return {
path: Path.resolve(Path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
path: path.resolve(path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
type: WIN,
};
}
};
/**
* Return relative directory, this is the default.
* It will cretate config in your {currentLocation/verdaccio/config.yaml}
* @returns
*/
const getRelativeDefaultDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', pkgJSON.name, CONFIG_FILE)),
path: path.resolve(path.join('.', pkgJSON.name, CONFIG_FILE)),
type: 'def',
};
};
/**
* This should never happens, consider it DEPRECATED
* @returns
*/
const getOldDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', CONFIG_FILE)),
path: path.resolve(path.join('.', CONFIG_FILE)),
type: 'old',
};
};

View File

@@ -106,7 +106,7 @@ class Config implements AppConfig {
/**
* Store or create whether receive a secret key
*/
public checkSecretKey(secret: string): string {
public checkSecretKey(secret?: string): string {
debug('check secret key');
if (_.isString(secret) && _.isEmpty(secret) === false) {
this.secret = secret;

View File

@@ -0,0 +1,105 @@
import os from 'os';
import { findConfigFile } from '../src/config-path';
const mockmkDir = jest.fn();
const mockaccessSync = jest.fn();
const mockwriteFile = jest.fn();
jest.mock('fs', () => {
const fsOri = jest.requireActual('fs');
return {
...fsOri,
statSync: (path) => ({
isDirectory: () => {
if (path.match(/fail/)) {
throw Error('file does not exist');
}
return true;
},
}),
accessSync: (a) => mockaccessSync(a),
mkdirSync: (a) => mockmkDir(a),
writeFileSync: (a) => mockwriteFile(a),
};
});
jest.mock('fs');
describe('config-path', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
describe('findConfigFile', () => {
if (os.platform() !== 'win32') {
describe('using defiled location from arguments', () => {
test('with custom location', () => {
expect(findConfigFile('/home/user/custom/location/config.yaml')).toEqual(
'/home/user/custom/location/config.yaml'
);
expect(mockwriteFile).not.toHaveBeenCalled();
expect(mockmkDir).not.toHaveBeenCalled();
});
});
describe('whith env variables', () => {
test('with XDG_CONFIG_HOME if directory exist but config file is missing', () => {
process.env.XDG_CONFIG_HOME = '/home/user';
expect(findConfigFile()).toEqual('/home/user/verdaccio/config.yaml');
expect(mockwriteFile).toHaveBeenCalledWith('/home/user/verdaccio/config.yaml');
expect(mockmkDir).toHaveBeenCalledWith('/home/user/verdaccio');
});
test('with HOME if directory exist but config file is missing', () => {
delete process.env.XDG_CONFIG_HOME;
process.env.HOME = '/home/user';
expect(findConfigFile()).toEqual('/home/user/.config/verdaccio/config.yaml');
expect(mockwriteFile).toHaveBeenCalledWith('/home/user/.config/verdaccio/config.yaml');
expect(mockmkDir).toHaveBeenCalledWith('/home/user/.config/verdaccio');
});
describe('error handling', () => {
test('XDG_CONFIG_HOME is not directory fallback to default', () => {
process.env.XDG_CONFIG_HOME = '/home/user/fail';
mockaccessSync.mockImplementation(() => {});
mockwriteFile.mockImplementation(() => {});
expect(findConfigFile()).toMatch('packages/config/verdaccio/config.yaml');
});
test('no permissions on read default config file', () => {
process.env.XDG_CONFIG_HOME = '/home/user';
mockaccessSync.mockImplementation(() => {
throw new Error('error on write file');
});
expect(function () {
findConfigFile();
}).toThrow(/configuration file does not have enough permissions for reading/);
});
});
});
describe('with no env variables', () => {
test('with relative location', () => {
mockaccessSync.mockImplementation(() => {});
delete process.env.XDG_CONFIG_HOME;
delete process.env.HOME;
process.env.APPDATA = '/app/data/';
expect(findConfigFile()).toMatch('packages/config/verdaccio/config.yaml');
expect(mockwriteFile).toHaveBeenCalled();
expect(mockmkDir).toHaveBeenCalled();
});
});
} else {
test('with windows as directory exist but config file is missing', () => {
delete process.env.XDG_CONFIG_HOME;
delete process.env.HOME;
process.env.APPDATA = '/app/data/';
expect(findConfigFile()).toEqual('D:\\app\\data\\verdaccio\\config.yaml');
expect(mockwriteFile).toHaveBeenCalledWith('D:\\app\\data\\verdaccio\\config.yaml');
expect(mockmkDir).toHaveBeenCalledWith('D:\\app\\data\\verdaccio');
});
}
});
});

View File

@@ -9,6 +9,7 @@ import {
parseConfigFile,
ROLES,
WEB_TITLE,
getMatchedPackagesSpec,
} from '../src';
import { parseConfigurationFile } from './utils';
@@ -23,56 +24,56 @@ const checkDefaultUplink = (config) => {
expect(config.uplinks[DEFAULT_UPLINK].url).toMatch(DEFAULT_REGISTRY);
};
const checkDefaultConfPackages = (config) => {
// auth
expect(_.isObject(config.auth)).toBeTruthy();
expect(_.isObject(config.auth.htpasswd)).toBeTruthy();
expect(config.auth.htpasswd.file).toMatch(/htpasswd/);
// web
expect(_.isObject(config.web)).toBeTruthy();
expect(config.web.title).toBe(WEB_TITLE);
expect(config.web.enable).toBeUndefined();
// packages
expect(_.isObject(config.packages)).toBeTruthy();
expect(Object.keys(config.packages).join('|')).toBe('@*/*|**');
expect(config.packages['@*/*'].access).toBeDefined();
expect(config.packages['@*/*'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['@*/*'].publish).toBeDefined();
expect(config.packages['@*/*'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['@*/*'].proxy).toBeDefined();
expect(config.packages['@*/*'].proxy).toContainEqual(DEFAULT_UPLINK);
expect(config.packages['**'].access).toBeDefined();
expect(config.packages['**'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['**'].publish).toBeDefined();
expect(config.packages['**'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['**'].proxy).toBeDefined();
expect(config.packages['**'].proxy).toContainEqual(DEFAULT_UPLINK);
// uplinks
expect(config.uplinks[DEFAULT_UPLINK]).toBeDefined();
expect(config.uplinks[DEFAULT_UPLINK].url).toEqual(DEFAULT_REGISTRY);
// audit
expect(config.middlewares).toBeDefined();
expect(config.middlewares.audit).toBeDefined();
expect(config.middlewares.audit.enabled).toBeTruthy();
// logs
expect(config.logs).toBeDefined();
expect(config.logs.type).toEqual('stdout');
expect(config.logs.format).toEqual('pretty');
expect(config.logs.level).toEqual('http');
// must not be enabled by default
expect(config.notify).toBeUndefined();
expect(config.store).toBeUndefined();
expect(config.publish).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.experiments).toBeUndefined();
expect(config.security).toEqual(defaultSecurity);
};
describe('check basic content parsed file', () => {
const checkDefaultConfPackages = (config) => {
// auth
expect(_.isObject(config.auth)).toBeTruthy();
expect(_.isObject(config.auth.htpasswd)).toBeTruthy();
expect(config.auth.htpasswd.file).toMatch(/htpasswd/);
// web
expect(_.isObject(config.web)).toBeTruthy();
expect(config.web.title).toBe(WEB_TITLE);
expect(config.web.enable).toBeUndefined();
// packages
expect(_.isObject(config.packages)).toBeTruthy();
expect(Object.keys(config.packages).join('|')).toBe('@*/*|**');
expect(config.packages['@*/*'].access).toBeDefined();
expect(config.packages['@*/*'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['@*/*'].publish).toBeDefined();
expect(config.packages['@*/*'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['@*/*'].proxy).toBeDefined();
expect(config.packages['@*/*'].proxy).toContainEqual(DEFAULT_UPLINK);
expect(config.packages['**'].access).toBeDefined();
expect(config.packages['**'].access).toContainEqual(ROLES.$ALL);
expect(config.packages['**'].publish).toBeDefined();
expect(config.packages['**'].publish).toContainEqual(ROLES.$AUTH);
expect(config.packages['**'].proxy).toBeDefined();
expect(config.packages['**'].proxy).toContainEqual(DEFAULT_UPLINK);
// uplinks
expect(config.uplinks[DEFAULT_UPLINK]).toBeDefined();
expect(config.uplinks[DEFAULT_UPLINK].url).toEqual(DEFAULT_REGISTRY);
// audit
expect(config.middlewares).toBeDefined();
expect(config.middlewares.audit).toBeDefined();
expect(config.middlewares.audit.enabled).toBeTruthy();
// logs
expect(config.logs).toBeDefined();
expect(config.logs.type).toEqual('stdout');
expect(config.logs.format).toEqual('pretty');
expect(config.logs.level).toEqual('http');
// must not be enabled by default
expect(config.notify).toBeUndefined();
expect(config.store).toBeUndefined();
expect(config.publish).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.url_prefix).toBeUndefined();
expect(config.experiments).toBeUndefined();
expect(config.security).toEqual(defaultSecurity);
};
test('parse default.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
checkDefaultUplink(config);
@@ -81,6 +82,57 @@ describe('check basic content parsed file', () => {
checkDefaultConfPackages(config);
});
test('parse docker.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('docker')));
checkDefaultUplink(config);
expect(config.storage).toBe('/verdaccio/storage/data');
expect(config.auth.htpasswd.file).toBe('/verdaccio/storage/htpasswd');
checkDefaultConfPackages(config);
});
});
describe('checkSecretKey', () => {
test('with default.yaml and pre selected secret', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
expect(config.checkSecretKey('12345')).toEqual('12345');
});
test('with default.yaml and void secret', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
});
test('with default.yaml and emtpy string secret', () => {
const config = new Config(parseConfigFile(resolveConf('default')));
expect(typeof config.checkSecretKey('') === 'string').toBeTruthy();
});
});
describe('getMatchedPackagesSpec', () => {
test('should match with react as defined in config file', () => {
const configParsed = parseConfigFile(parseConfigurationFile('config-getMatchedPackagesSpec'));
const config = new Config(configParsed);
expect(config.getMatchedPackagesSpec('react')).toEqual({
access: ['admin'],
proxy: ['facebook'],
publish: ['admin'],
unpublish: false,
});
});
test('should not match with react as defined in config file', () => {
const configParsed = parseConfigFile(parseConfigurationFile('config-getMatchedPackagesSpec'));
const config = new Config(configParsed);
expect(config.getMatchedPackagesSpec('somePackage')).toEqual({
access: [ROLES.$ALL],
proxy: ['npmjs'],
publish: [ROLES.$AUTH],
unpublish: false,
});
});
});
describe('VERDACCIO_STORAGE_PATH', () => {
test('should set storage to value set in VERDACCIO_STORAGE_PATH environment variable', () => {
const storageLocation = '/tmp/verdaccio';
process.env.VERDACCIO_STORAGE_PATH = storageLocation;
@@ -106,12 +158,4 @@ describe('check basic content parsed file', () => {
expect(config.storage).toBe(storageLocation);
delete process.env.VERDACCIO_STORAGE_PATH;
});
test('parse docker.yaml', () => {
const config = new Config(parseConfigFile(resolveConf('docker')));
checkDefaultUplink(config);
expect(config.storage).toBe('/verdaccio/storage/data');
expect(config.auth.htpasswd.file).toBe('/verdaccio/storage/htpasswd');
checkDefaultConfPackages(config);
});
});

View File

@@ -88,26 +88,17 @@ describe('Package access utilities', () => {
() => {
const { packages } = parseConfigFile(parseConfigurationFile('deprecated-pkgs-basic'));
const access = normalisePackageAccess(packages);
expect(access).toBeDefined();
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
const all = access[`${PACKAGE_ACCESS.ALL}`];
const react = access['react-*'];
expect(react).toBeDefined();
expect(react.access).toBeDefined();
// Intended checks, Typescript should catch this, we test the runtime part
// @ts-ignore
expect(react.access).toEqual([]);
// @ts-ignore
expect(react.publish[0]).toBe('admin');
expect(react.proxy).toBeDefined();
// @ts-ignore
expect(react.proxy).toEqual([]);
expect(react.storage).toBeDefined();
expect(react.storage).toBe('react-storage');
expect(scoped).toBeDefined();
expect(scoped.storage).not.toBeDefined();
@@ -126,7 +117,6 @@ describe('Package access utilities', () => {
const scoped = access[`${PACKAGE_ACCESS.SCOPE}`];
expect(scoped).toBeUndefined();
// ** should be added by default **
const all = access[`${PACKAGE_ACCESS.ALL}`];
expect(all).toBeDefined();
@@ -141,23 +131,23 @@ describe('Package access utilities', () => {
describe('getMatchedPackagesSpec', () => {
test('should test basic config', () => {
const { packages } = parseConfigFile(parseConfigurationFile('pkgs-custom'));
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('react', packages).proxy).toMatch('facebook');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('angular', packages).proxy).toMatch('google');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('vue', packages).proxy).toMatch('npmjs');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('@scope/vue', packages).proxy).toMatch('npmjs');
});
test('should test no ** wildcard on config', () => {
const { packages } = parseConfigFile(parseConfigurationFile('pkgs-nosuper-wildcard-custom'));
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('react', packages).proxy).toMatch('facebook');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('angular', packages).proxy).toMatch('google');
// @ts-ignore
// @ts-expect-error
expect(getMatchedPackagesSpec('@fake/angular', packages).proxy).toMatch('npmjs');
expect(getMatchedPackagesSpec('vue', packages)).toBeUndefined();
expect(getMatchedPackagesSpec('@scope/vue', packages)).toBeUndefined();

View File

@@ -0,0 +1,17 @@
packages:
'react':
access: admin
publish: admin
proxy: facebook
'angular':
access: admin
publish: admin
proxy: google
'@*/*':
access: $all
publish: $authenticated
proxy: npmjs
'**':
access: $all
publish: $authenticated
proxy: npmjs

View File

@@ -40,7 +40,7 @@
"lockfile": "1.0.4"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -43,7 +43,7 @@
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"mockdate": "^3.0.2"
},
"scripts": {

View File

@@ -31,7 +31,7 @@ describe('HTPasswd', () => {
let wrapper;
beforeEach(() => {
wrapper = new HTPasswd(getDefaultConfig(), (stuff as unknown) as VerdaccioConfigApp);
wrapper = new HTPasswd(getDefaultConfig(), stuff as unknown as VerdaccioConfigApp);
jest.resetModules();
crypto.randomBytes = jest.fn(() => {

View File

@@ -47,7 +47,7 @@
},
"devDependencies": {
"@types/minimatch": "^3.0.3",
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"minimatch": "^3.0.4",
"rmdir-sync": "^1.0.1"
},

View File

@@ -45,7 +45,7 @@
"marked": "1.1.1"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -0,0 +1,6 @@
# @verdaccio/fastify-migration
## 6.0.0-6-next.10
### Minor Changes
- 55ee3fdd: [Fastify] Add ping endpoint

View File

@@ -0,0 +1,39 @@
storage: ./storage
plugins: ./plugins
web:
title: Verdaccio
auth:
htpasswd:
file: ./htpasswd
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'@*/*':
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: npmjs
'**':
access: $all
publish: $authenticated
unpublish: $authenticated
proxy: npmjs
server:
keepAliveTimeout: 60
middlewares:
audit:
enabled: true
log: { type: stdout, format: pretty, level: http }
flags:
token: false
search: false
i18n:
web: en-US

View File

@@ -0,0 +1,30 @@
import path from 'path';
import buildDebug from 'debug';
import { parseConfigFile } from '@verdaccio/config';
import { setup, logger } from '@verdaccio/logger';
import server from '../src/index';
const debug = buildDebug('verdaccio:fastify:debug');
/**
* This file is intended for fast development and debug, it should
* be removed eventually and the app start from @verdaccio/cli package.
*/
(async () => {
try {
const configFile = path.join(__dirname, './fastify-conf.yaml');
debug('configFile %s', configFile);
const configParsed = parseConfigFile(configFile);
setup(configParsed.log);
logger.info(`config location ${configFile}`);
debug('configParsed %s', configParsed);
process.title = 'fastify-verdaccio';
const ser = await server({ logger });
await ser.listen(4873);
logger.info('fastify running on port 4873');
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
process.exit(1);
}
})();

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/fastify-migration",
"version": "6.0.0-6-next.9",
"version": "6.0.0-6-next.10",
"description": "Fastify server migration package",
"keywords": [
"private",
@@ -34,10 +34,15 @@
"access": "public"
},
"dependencies": {
"fastify": "3.14.2"
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
"fastify": "3.15.1",
"fastify-plugin": "3.0.0",
"debug": "4.3.1"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"ts-node": "9.1.1"
},
"scripts": {
"clean": "rimraf ./build",
@@ -45,7 +50,8 @@
"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"
"build": "pnpm run build:js && pnpm run build:types",
"start": "ts-node debug/index.ts"
},
"funding": {
"type": "opencollective",

View File

@@ -0,0 +1,10 @@
import { logger } from '@verdaccio/logger';
async function pingRoute(fastify) {
fastify.get('/-/ping', async () => {
logger.http('ping endpoint');
return {};
});
}
export default pingRoute;

View File

@@ -1,12 +1,14 @@
import fastify from 'fastify';
import buildDebug from 'debug';
async function startServer() {
const app = fastify();
import ping from './endpoints/ping';
app.get('/', async (request, reply) => {
return { hello: 'world' };
});
const debug = buildDebug('verdaccio:fastify');
async function startServer({ logger }) {
debug('start server');
const app = fastify({ logger });
app.register(ping);
return app;
}

View File

@@ -34,7 +34,7 @@
"access": "public"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -1,5 +1,12 @@
# Change Log
## 11.0.0-6-next.6
### Patch Changes
- Updated dependencies [d2c65da9]
- @verdaccio/utils@6.0.0-6-next.5
## 11.0.0-6-next.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/tarball",
"version": "11.0.0-6-next.5",
"version": "11.0.0-6-next.6",
"description": "tarball utilities resolver",
"keywords": [
"private",
@@ -38,12 +38,12 @@
"lodash": "^4.17.21",
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/url": "workspace:11.0.0-6-next.4",
"@verdaccio/utils": "workspace:6.0.0-6-next.4"
"@verdaccio/utils": "workspace:6.0.0-6-next.5"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"express": "^4.17.1",
"node-mocks-http": "^1.10.1"
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"express": "4.17.1",
"node-mocks-http": "1.10.1"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -1,5 +1,50 @@
# Change Log
## 11.0.0-6-next.7
### Minor Changes
- 0da7031e: allow disable login on ui and endpoints
To be able disable the login, set `login: false`, anything else would enable login. This flag will disable access via UI and web endpoints.
```yml
web:
title: verdaccio
login: false
```
## 11.0.0-6-next.6
### Minor Changes
- aecbd226: web: allow ui hide package managers on sidebar
If there is a package manager of preference over others, you can define the package managers to be displayed on the detail page and sidebar, just define in the `config.yaml` and web section the list of package managers to be displayed.
```
web:
title: Verdaccio
sort_packages: asc
primary_color: #cccccc
pkgManagers:
- pnpm
- yarn
# - npm
```
To disable all package managers, just define empty:
```
web:
title: Verdaccio
sort_packages: asc
primary_color: #cccccc
pkgManagers:
```
and the section would be hidden.
## 11.0.0-6-next.5
### Patch Changes

View File

@@ -15,6 +15,51 @@ declare module '@verdaccio/types' {
url?: string;
}
type PackageManagers = 'pnpm' | 'yarn' | 'npm';
// FUTURE: WebConf and TemplateUIOptions should be merged .
type CommonWebConf = {
title?: string;
logo?: string;
favicon?: string;
gravatar?: boolean;
sort_packages?: string;
darkMode?: boolean;
url_prefix?: string;
language?: string;
login?: boolean;
scope?: string;
pkgManagers?: PackageManagers[];
};
/**
* Options are passed to the index.html
*/
export type TemplateUIOptions = {
uri?: string;
darkMode?: boolean;
protocol?: string;
host?: string;
base: string;
primaryColor?: string;
version?: string;
logoURI?: string;
} & CommonWebConf;
/**
* Options on config.yaml for web
*/
type WebConf = {
// FIXME: rename to primaryColor and move it to CommonWebConf
primary_color?: string;
enable?: boolean;
scriptsHead?: string[];
scriptsBodyAfter?: string[];
metaScripts?: string[];
bodyBefore?: string[];
bodyAfter?: string[];
} & CommonWebConf;
interface Dist {
integrity?: string;
shasum: string;
@@ -276,23 +321,6 @@ declare module '@verdaccio/types' {
interface ListenAddress {
[key: string]: string;
}
interface WebConf {
enable?: boolean;
title?: string;
logo?: string;
favicon?: string;
gravatar?: boolean;
sort_packages?: string;
scriptsHead?: string[];
scriptsBodyAfter?: string[];
metaScripts?: string[];
bodyBefore?: string[];
bodyAfter?: string[];
darkMode?: boolean;
primary_color?: string;
}
interface HttpsConfKeyCert {
key: string;
cert: string;

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/types",
"version": "11.0.0-6-next.5",
"version": "11.0.0-6-next.7",
"description": "verdaccio types definitions",
"keywords": [
"private",

View File

@@ -41,7 +41,7 @@
},
"devDependencies": {
"node-mocks-http": "^1.10.1",
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -38,10 +38,10 @@
"request": "2.87.0"
},
"devDependencies": {
"@verdaccio/auth": "workspace:6.0.0-6-next.8",
"@verdaccio/auth": "workspace:6.0.0-6-next.9",
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"nock": "^13.0.4"
},
"scripts": {

View File

@@ -19,9 +19,9 @@
},
"devDependencies": {
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/mock": "workspace:6.0.0-6-next.6",
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/mock": "workspace:6.0.0-6-next.7",
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"homepage": "https://verdaccio.org",
"keywords": [

View File

@@ -59,86 +59,84 @@ export function loadPlugin<T extends IPlugin<T>>(
sanityCheck: any,
prefix: string = 'verdaccio'
): any[] {
return Object.keys(pluginConfigs).map(
(pluginId: string): IPlugin<T> => {
let plugin;
return Object.keys(pluginConfigs).map((pluginId: string): IPlugin<T> => {
let plugin;
const localPlugin = Path.resolve(__dirname + '/../plugins', pluginId);
// try local plugins first
plugin = tryLoad(localPlugin);
const localPlugin = Path.resolve(__dirname + '/../plugins', pluginId);
// try local plugins first
plugin = tryLoad(localPlugin);
// try the external plugin directory
if (plugin === null && config.plugins) {
const pluginDir = config.plugins;
const externalFilePlugin = Path.resolve(pluginDir, pluginId);
plugin = tryLoad(externalFilePlugin);
// npm package
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
plugin = tryLoad(Path.resolve(pluginDir, `${prefix}-${pluginId}`));
// compatibility for old sinopia plugins
if (!plugin) {
plugin = tryLoad(Path.resolve(pluginDir, `sinopia-${pluginId}`));
}
}
}
// try the external plugin directory
if (plugin === null && config.plugins) {
const pluginDir = config.plugins;
const externalFilePlugin = Path.resolve(pluginDir, pluginId);
plugin = tryLoad(externalFilePlugin);
// npm package
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
plugin = tryLoad(`${prefix}-${pluginId}`);
plugin = tryLoad(Path.resolve(pluginDir, `${prefix}-${pluginId}`));
// compatibility for old sinopia plugins
if (!plugin) {
plugin = tryLoad(`sinopia-${pluginId}`);
plugin = tryLoad(Path.resolve(pluginDir, `sinopia-${pluginId}`));
}
}
if (plugin === null) {
plugin = tryLoad(pluginId);
}
// relative to config path
if (plugin === null && pluginId.match(/^\.\.?($|\/)/)) {
plugin = tryLoad(Path.resolve(Path.dirname(config.config_path), pluginId));
}
if (plugin === null) {
logger.error(
{ content: pluginId, prefix },
'plugin not found. try npm install @{prefix}-@{content}'
);
throw Error(`
${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`);
}
if (!isValid(plugin)) {
logger.error(
{ content: pluginId },
'@{prefix}-@{content} plugin does not have the right code structure'
);
throw Error(`"${pluginId}" plugin does not have the right code structure`);
}
/* eslint new-cap:off */
try {
plugin = isES6(plugin)
? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params)
: plugin(pluginConfigs[pluginId], params);
} catch (error) {
plugin = null;
logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}');
}
/* eslint new-cap:off */
if (plugin === null || !sanityCheck(plugin)) {
logger.error(
{ content: pluginId, prefix },
"@{prefix}-@{content} doesn't look like a valid plugin"
);
throw Error(`sanity check has failed, "${pluginId}" is not a valid plugin`);
}
debug('Plugin successfully loaded: %o-%o', pluginId, prefix);
return plugin;
}
);
// npm package
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
plugin = tryLoad(`${prefix}-${pluginId}`);
// compatibility for old sinopia plugins
if (!plugin) {
plugin = tryLoad(`sinopia-${pluginId}`);
}
}
if (plugin === null) {
plugin = tryLoad(pluginId);
}
// relative to config path
if (plugin === null && pluginId.match(/^\.\.?($|\/)/)) {
plugin = tryLoad(Path.resolve(Path.dirname(config.config_path), pluginId));
}
if (plugin === null) {
logger.error(
{ content: pluginId, prefix },
'plugin not found. try npm install @{prefix}-@{content}'
);
throw Error(`
${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`);
}
if (!isValid(plugin)) {
logger.error(
{ content: pluginId },
'@{prefix}-@{content} plugin does not have the right code structure'
);
throw Error(`"${pluginId}" plugin does not have the right code structure`);
}
/* eslint new-cap:off */
try {
plugin = isES6(plugin)
? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params)
: plugin(pluginConfigs[pluginId], params);
} catch (error) {
plugin = null;
logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}');
}
/* eslint new-cap:off */
if (plugin === null || !sanityCheck(plugin)) {
logger.error(
{ content: pluginId, prefix },
"@{prefix}-@{content} doesn't look like a valid plugin"
);
throw Error(`sanity check has failed, "${pluginId}" is not a valid plugin`);
}
debug('Plugin successfully loaded: %o-%o', pluginId, prefix);
return plugin;
});
}

View File

@@ -46,7 +46,7 @@
},
"devDependencies": {
"@types/pino": "^6.3.3",
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"funding": {
"type": "opencollective",

View File

@@ -1,5 +1,13 @@
# @verdaccio/middleware
## 6.0.0-6-next.9
### Patch Changes
- Updated dependencies [d2c65da9]
- @verdaccio/utils@6.0.0-6-next.5
- @verdaccio/auth@6.0.0-6-next.9
## 6.0.0-6-next.8
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/middleware",
"version": "6.0.0-6-next.8",
"version": "6.0.0-6-next.9",
"description": "loaders logic",
"main": "./build/index.js",
"types": "build/index.d.ts",
@@ -39,10 +39,10 @@
},
"dependencies": {
"debug": "^4.3.1",
"@verdaccio/auth": "workspace:6.0.0-6-next.8",
"@verdaccio/auth": "workspace:6.0.0-6-next.9",
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
"@verdaccio/utils": "workspace:6.0.0-6-next.4",
"@verdaccio/utils": "workspace:6.0.0-6-next.5",
"lodash": "4.17.15"
},
"funding": {

View File

@@ -1,5 +1,13 @@
# @verdaccio/mock
## 6.0.0-6-next.7
### Patch Changes
- Updated dependencies [d2c65da9]
- @verdaccio/utils@6.0.0-6-next.5
- @verdaccio/config@6.0.0-6-next.7
## 6.0.0-6-next.6
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/mock",
"version": "6.0.0-6-next.6",
"version": "6.0.0-6-next.7",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
@@ -40,8 +40,8 @@
},
"dependencies": {
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/utils": "workspace:6.0.0-6-next.4",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/utils": "workspace:6.0.0-6-next.5",
"debug": "^4.2.0",
"fs-extra": "^8.1.0",
"lodash": "^4.17.20",
@@ -49,7 +49,7 @@
"supertest": "^4.0.2"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"funding": {
"type": "opencollective",

View File

@@ -1,5 +1,32 @@
# @verdaccio/node-api
## 6.0.0-6-next.17
### Patch Changes
- @verdaccio/config@6.0.0-6-next.7
- @verdaccio/server@6.0.0-6-next.16
## 6.0.0-6-next.16
### Patch Changes
- @verdaccio/server@6.0.0-6-next.15
## 6.0.0-6-next.15
### Patch Changes
- @verdaccio/logger@6.0.0-6-next.4
- @verdaccio/server@6.0.0-6-next.14
## 6.0.0-6-next.14
### Patch Changes
- @verdaccio/logger@6.0.0-6-next.4
- @verdaccio/server@6.0.0-6-next.13
## 6.0.0-6-next.13
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/node-api",
"version": "6.0.0-6-next.13",
"version": "6.0.0-6-next.17",
"description": "node API",
"main": "build/index.js",
"types": "build/index.d.ts",
@@ -40,16 +40,16 @@
"license": "MIT",
"dependencies": {
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/server": "workspace:6.0.0-6-next.12",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/server": "workspace:6.0.0-6-next.16",
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
"core-js": "^3.6.5",
"debug": "^4.2.0",
"lodash": "^4.17.20"
},
"devDependencies": {
"@verdaccio/mock": "workspace:6.0.0-6-next.6",
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/mock": "workspace:6.0.0-6-next.7",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"jest-mock-process": "^1.4.0",
"selfsigned": "1.10.7",
"supertest": "^6.1.3"

View File

@@ -39,7 +39,7 @@
},
"devDependencies": {
"@types/activedirectory2": "^1.2.1",
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -51,7 +51,7 @@ class ActiveDirectoryPlugin implements IPluginAuth<ActiveDirectoryConfig> {
connection.getGroupMembershipForUser(username, (err, groups: object[]): void => {
if (err) {
this.logger.warn(`AD - Active Directory group check failed with error: ${err}`);
return cb(getInternalError((err as unknown) as string));
return cb(getInternalError(err as unknown as string));
}
const requestedGroups = Array.isArray(groupName) ? groupName : [groupName];

View File

@@ -83,7 +83,7 @@ describe('Active Directory Plugin', () => {
const errorMessage = 'Unknown error retrieving groups';
ActiveDirectory.prototype.authenticate = jest.fn((_1, _2, cb) => cb(null, true));
ActiveDirectory.prototype.getGroupMembershipForUser = jest.fn((_, cb) =>
cb((errorMessage as unknown) as object, null)
cb(errorMessage as unknown as object, null)
) as jest.Mock;
adPluginSingleGroup.authenticate('', '', (error, authUser) => {

View File

@@ -36,7 +36,7 @@
"node-fetch": "^2.6.0"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"body-parser": "^1.19.0",
"nock": "^12.0.3",
"supertest": "^4.0.2"

View File

@@ -35,7 +35,7 @@
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -1,5 +1,11 @@
# Change Log
## 11.0.0-6-next.5
### Patch Changes
- 6b1a28de: Fix the prefix used to delete from s3 when unpublishing packages
## 11.0.0-6-next.4
### Major Changes

View File

@@ -1,6 +1,6 @@
{
"name": "verdaccio-aws-s3-storage",
"version": "11.0.0-6-next.4",
"version": "11.0.0-6-next.5",
"description": "AWS S3 storage implementation for Verdaccio",
"keywords": [
"private",
@@ -36,7 +36,7 @@
"aws-sdk": "^2.607.0"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"recursive-readdir": "2.2.2"
},
"scripts": {

View File

@@ -29,14 +29,8 @@ export default class S3PackageManager implements ILocalPackageManager {
this.config = config;
this.packageName = packageName;
this.logger = logger;
const {
endpoint,
region,
s3ForcePathStyle,
accessKeyId,
secretAccessKey,
sessionToken,
} = config;
const { endpoint, region, s3ForcePathStyle, accessKeyId, secretAccessKey, sessionToken } =
config;
this.s3 = new S3({
endpoint,
@@ -172,7 +166,7 @@ export default class S3PackageManager implements ILocalPackageManager {
this.s3,
{
Bucket: this.config.bucket,
Prefix: `${this.packagePath}`,
Prefix: addTrailingSlash(this.packagePath),
},
function (err) {
if (err && is404Error(err as VerdaccioError)) {

View File

@@ -79,7 +79,7 @@ describe.skip('S3 package manager', () => {
describe('savePackage() group', () => {
test('savePackage()', (done) => {
const data = ('{data:5}' as unknown) as Package;
const data = '{data:5}' as unknown as Package;
const packageManager = new S3PackageManager(config, 'first-package', logger);
packageManager.savePackage('pkg.1.0.0.tar.gz', data, (err) => {

View File

@@ -231,7 +231,7 @@ describe('S3PackageManager with mocked s3', function () {
expect(mockListObject).toHaveBeenCalledWith(
{
Bucket: 'test-bucket',
Prefix: 'testKeyPrefix/@company/test-package',
Prefix: 'testKeyPrefix/@company/test-package/',
},
expect.any(Function)
);
@@ -270,7 +270,7 @@ describe('S3PackageManager with mocked s3', function () {
expect(mockListObject).toHaveBeenCalledWith(
{
Bucket: 'test-bucket',
Prefix: 'testKeyPrefix/customFolder/@company/test-package',
Prefix: 'testKeyPrefix/customFolder/@company/test-package/',
},
expect.any(Function)
);

View File

@@ -37,7 +37,7 @@
"@verdaccio/streams": "workspace:11.0.0-alpha.3"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5",
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"memory-fs": "0.5.0"
},
"optionalDependencies": {

View File

@@ -118,14 +118,12 @@ class GoogleCloudDatabase implements IPluginStorage<VerdaccioConfigGoogleStorage
// "{\"secret\":\"181bc38698078f880564be1e4d7ec107ac8a3b344a924c6d86cea4a84a885ae0\"}"
return entities.secret;
})
.catch(
(err: Error): Promise<string> => {
const error: VerdaccioError = getInternalError(err.message);
.catch((err: Error): Promise<string> => {
const error: VerdaccioError = getInternalError(err.message);
this.logger.warn({ error }, 'gcloud: [datastore getSecret] init error @{error}');
return Promise.reject(getServiceUnavailable('[getSecret] permissions error'));
}
);
this.logger.warn({ error }, 'gcloud: [datastore getSecret] init error @{error}');
return Promise.reject(getServiceUnavailable('[getSecret] permissions error'));
});
}
public setSecret(secret: string): Promise<CommitResponse> {
@@ -194,17 +192,15 @@ class GoogleCloudDatabase implements IPluginStorage<VerdaccioConfigGoogleStorage
// };
this.helper
.getEntities(this.kind)
.then(
async (entities: any): Promise<void> => {
for (const item of entities) {
if (item.name === name) {
await this._deleteItem(name, item);
// deletedItems.push(deletedItem);
}
.then(async (entities: any): Promise<void> => {
for (const item of entities) {
if (item.name === name) {
await this._deleteItem(name, item);
// deletedItems.push(deletedItem);
}
cb(null);
}
)
cb(null);
})
.catch((err: Error): void => {
cb(getInternalError(err.message));
});

View File

@@ -90,16 +90,14 @@ class GoogleCloudStorageHandler implements IPackageStorageManager {
onEnd(getInternalError(err.message));
}
)
.catch(
(err: Error): Callback => {
this.logger.error(
{ name, error: err },
'gcloud: trying to update @{name} and was not found on storage err: @{error}'
);
// @ts-ignore
return onEnd(getNotFound());
}
);
.catch((err: Error): Callback => {
this.logger.error(
{ name, error: err },
'gcloud: trying to update @{name} and was not found on storage err: @{error}'
);
// @ts-ignore
return onEnd(getNotFound());
});
}
public deletePackage(fileName: string, cb: CallbackAction): void {
@@ -197,28 +195,26 @@ class GoogleCloudStorageHandler implements IPackageStorageManager {
/* eslint-disable no-async-promise-executor */
private _savePackage(name: string, metadata: Package): Promise<null | VerdaccioError> {
return new Promise(
async (resolve, reject): Promise<void> => {
const file = this.helper.buildFilePath(name, pkgFileName);
try {
await file.save(this._convertToString(metadata), {
validation: this.config.validation || defaultValidation,
/**
* When resumable is `undefined` - it will default to `true`as
* per GC Storage documentation:
* `Resumable uploads are automatically enabled and must be shut
* off explicitly by setting options.resumable to false`
* @see
* https://cloud.google.com/nodejs/docs/reference/storage/2.5.x/File#createWriteStream
*/
resumable: this.config.resumable,
});
resolve(null);
} catch (err) {
reject(getInternalError(err.message));
}
return new Promise(async (resolve, reject): Promise<void> => {
const file = this.helper.buildFilePath(name, pkgFileName);
try {
await file.save(this._convertToString(metadata), {
validation: this.config.validation || defaultValidation,
/**
* When resumable is `undefined` - it will default to `true`as
* per GC Storage documentation:
* `Resumable uploads are automatically enabled and must be shut
* off explicitly by setting options.resumable to false`
* @see
* https://cloud.google.com/nodejs/docs/reference/storage/2.5.x/File#createWriteStream
*/
resumable: this.config.resumable,
});
resolve(null);
} catch (err) {
reject(getInternalError(err.message));
}
);
});
}
/* eslint-enable no-async-promise-executor */
@@ -244,48 +240,44 @@ class GoogleCloudStorageHandler implements IPackageStorageManager {
/* eslint-disable no-async-promise-executor */
private _fileExist(name: string, fileName: string): Promise<boolean> {
return new Promise(
async (resolve, reject): Promise<void> => {
const file: File = this.helper.buildFilePath(name, fileName);
try {
// @ts-ignore
const data = await file.exists();
const exist = data[0];
return new Promise(async (resolve, reject): Promise<void> => {
const file: File = this.helper.buildFilePath(name, fileName);
try {
// @ts-ignore
const data = await file.exists();
const exist = data[0];
resolve(exist);
this.logger.debug(
{ name: name, exist },
'gcloud: check whether @{name} exist successfully: @{exist}'
);
} catch (err) {
this.logger.error(
{ name: file.name, err: err.message },
'gcloud: check exist package @{name} has failed, cause: @{err}'
);
resolve(exist);
this.logger.debug(
{ name: name, exist },
'gcloud: check whether @{name} exist successfully: @{exist}'
);
} catch (err) {
this.logger.error(
{ name: file.name, err: err.message },
'gcloud: check exist package @{name} has failed, cause: @{err}'
);
reject(getInternalError(err.message));
}
reject(getInternalError(err.message));
}
);
});
}
private async _readPackage(name: string): Promise<Package> {
return new Promise(
async (resolve, reject): Promise<void> => {
const file = this.helper.buildFilePath(name, pkgFileName);
return new Promise(async (resolve, reject): Promise<void> => {
const file = this.helper.buildFilePath(name, pkgFileName);
try {
const content: DownloadResponse = await file.download();
this.logger.debug({ name: this.name }, 'gcloud: @{name} was found on storage');
const response: Package = JSON.parse(content[0].toString('utf8'));
try {
const content: DownloadResponse = await file.download();
this.logger.debug({ name: this.name }, 'gcloud: @{name} was found on storage');
const response: Package = JSON.parse(content[0].toString('utf8'));
resolve(response);
} catch (err) {
this.logger.debug({ name: this.name }, 'gcloud: @{name} package not found on storage');
reject(getNotFound());
}
resolve(response);
} catch (err) {
this.logger.debug({ name: this.name }, 'gcloud: @{name} package not found on storage');
reject(getNotFound());
}
);
});
}
/* eslint-disable no-async-promise-executor */

View File

@@ -38,7 +38,7 @@
"memfs": "3.2.0"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7"
},
"scripts": {
"clean": "rimraf ./build",

View File

@@ -1,5 +1,50 @@
# @verdaccio/ui-theme
## 6.0.0-6-next.8
### Minor Changes
- 0da7031e: allow disable login on ui and endpoints
To be able disable the login, set `login: false`, anything else would enable login. This flag will disable access via UI and web endpoints.
```yml
web:
title: verdaccio
login: false
```
## 6.0.0-6-next.7
### Minor Changes
- aecbd226: web: allow ui hide package managers on sidebar
If there is a package manager of preference over others, you can define the package managers to be displayed on the detail page and sidebar, just define in the `config.yaml` and web section the list of package managers to be displayed.
```
web:
title: Verdaccio
sort_packages: asc
primary_color: #cccccc
pkgManagers:
- pnpm
- yarn
# - npm
```
To disable all package managers, just define empty:
```
web:
title: Verdaccio
sort_packages: asc
primary_color: #cccccc
pkgManagers:
```
and the section would be hidden.
## 6.0.0-6-next.6
### Patch Changes

View File

@@ -15,6 +15,7 @@ global.__VERDACCIO_BASENAME_UI_OPTIONS = {
darkMode: false,
language: 'en-US',
uri: 'http://localhost:4873',
pkgManagers: ['pnpm', 'yarn', 'npm'],
title: 'Verdaccio Dev UI',
scope: '',
version: 'v1.0.0',

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/ui-theme",
"version": "6.0.0-6-next.6",
"version": "6.0.0-6-next.8",
"description": "Verdaccio User Interface",
"author": {
"name": "Verdaccio Core Team",
@@ -29,7 +29,7 @@
"@testing-library/dom": "^7.29.0",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "10.4.9",
"@verdaccio/node-api": "workspace:6.0.0-6-next.13",
"@verdaccio/node-api": "workspace:6.0.0-6-next.17",
"autosuggest-highlight": "3.1.1",
"babel-loader": "^8.2.2",
"babel-plugin-dynamic-import-node": "^2.3.3",

View File

@@ -5,6 +5,7 @@ import {
render,
fireEvent,
waitFor,
screen,
waitForElementToBeRemoved,
} from 'verdaccio-ui/utils/test-react-testing-library';
@@ -23,7 +24,7 @@ const props = {
/* eslint-disable react/jsx-no-bind*/
describe('<Header /> component with logged in state', () => {
test('should load the component in logged out state', () => {
const { container, queryByTestId, getByText } = render(
render(
<Router>
<AppContextProvider>
<Header />
@@ -31,13 +32,13 @@ describe('<Header /> component with logged in state', () => {
</Router>
);
expect(container.firstChild).toMatchSnapshot();
expect(queryByTestId('header--menu-accountcircle')).toBeNull();
expect(getByText('Login')).toBeTruthy();
expect(screen.queryByTestId('header--menu-accountcircle')).toBeNull();
expect(screen.getByText('Login')).toBeTruthy();
expect(screen.queryByTestId('header--button-login')).toBeInTheDocument();
});
test('should load the component in logged in state', () => {
const { container, getByTestId, queryByText } = render(
const { getByTestId, queryByText } = render(
<Router>
<AppContextProvider user={props.user}>
<Header />
@@ -45,7 +46,6 @@ describe('<Header /> component with logged in state', () => {
</Router>
);
expect(container.firstChild).toMatchSnapshot();
expect(getByTestId('header--menu-accountcircle')).toBeTruthy();
expect(queryByText('Login')).toBeNull();
});
@@ -137,5 +137,22 @@ describe('<Header /> component with logged in state', () => {
expect(hasRegistrationInfoModalBeenRemoved).not.toBeDefined();
});
test('should hide login if is disabled', () => {
// @ts-expect-error
window.__VERDACCIO_BASENAME_UI_OPTIONS = {
base: 'foo',
login: false,
};
render(
<Router>
<AppContextProvider user={props.user}>
<Header />
</AppContextProvider>
</Router>
);
expect(screen.queryByTestId('header--button-login')).not.toBeInTheDocument();
});
test.todo('autocompletion should display suggestions according to the type value');
});

View File

@@ -49,6 +49,7 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
<InnerNavBar>
<HeaderLeft />
<HeaderRight
hasLogin={configOptions?.login}
onLogout={handleLogout}
onOpenRegistryInfoDialog={() => setOpenInfoDialog(true)}
onToggleLogin={() => setShowLoginModal(!showLoginModal)}

View File

@@ -12,6 +12,7 @@ import { RightSide } from './styles';
interface Props {
withoutSearch?: boolean;
username?: string | null;
hasLogin?: boolean;
onToggleLogin: () => void;
onOpenRegistryInfoDialog: () => void;
onToggleMobileNav: () => void;
@@ -22,6 +23,7 @@ const HeaderRight: React.FC<Props> = ({
withoutSearch = false,
username,
onToggleLogin,
hasLogin,
onLogout,
onToggleMobileNav,
onOpenRegistryInfoDialog,
@@ -29,6 +31,7 @@ const HeaderRight: React.FC<Props> = ({
const themeContext = useContext(ThemeContext);
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const hideLoginSection = hasLogin === false;
const { t } = useTranslation();
@@ -90,19 +93,23 @@ const HeaderRight: React.FC<Props> = ({
tooltipIconType={themeContext.isDarkMode ? 'dark-mode' : 'light-mode'}
/>
{username ? (
<HeaderMenu
anchorEl={anchorEl}
isMenuOpen={isMenuOpen}
onLoggedInMenu={handleLoggedInMenu}
onLoggedInMenuClose={handleLoggedInMenuClose}
onLogout={onLogout}
username={username}
/>
) : (
<Button color="inherit" data-testid="header--button-login" onClick={handleToggleLogin}>
{t('button.login')}
</Button>
{!hideLoginSection && (
<>
{username ? (
<HeaderMenu
anchorEl={anchorEl}
isMenuOpen={isMenuOpen}
onLoggedInMenu={handleLoggedInMenu}
onLoggedInMenuClose={handleLoggedInMenuClose}
onLogout={onLogout}
username={username}
/>
) : (
<Button color="inherit" data-testid="header--button-login" onClick={handleToggleLogin}>
{t('button.login')}
</Button>
)}
</>
)}
</RightSide>
);

View File

@@ -34,7 +34,7 @@ const ActionBar: React.FC = () => {
}
return (
<Box alignItems="center" display="flex" marginBottom="8px">
<Box alignItems="center" display="flex" marginBottom="14px">
{actions.map((action) => (
<ActionBarAction key={action.link} {...action} />
))}

View File

@@ -21,8 +21,13 @@ export interface FilterOptionsState<Option> {
}
function createFilterOptions(config?: CreateFilterOptionsConfig) {
const { ignoreAccents = true, ignoreCase = true, trim = false, limit, matchFrom = 'any' } =
config || {};
const {
ignoreAccents = true,
ignoreCase = true,
trim = false,
limit,
matchFrom = 'any',
} = config || {};
return <Option extends {}>(
options: Option[],

View File

@@ -6,9 +6,10 @@ import { Theme } from './theme';
const resetStyles = makeStyles(({ theme }: { theme?: Theme }) => ({
'@global': {
// eslint-disable-next-line max-len
'html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video': {
fontFamily: '"Roboto", Helvetica Neue, Arial, sans-serif',
},
'html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video':
{
fontFamily: '"Roboto", Helvetica Neue, Arial, sans-serif',
},
strong: {
fontWeight: theme && theme.fontWeight.semiBold,
},

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { render } from 'verdaccio-ui/utils/test-react-testing-library';
import { render, screen } from 'verdaccio-ui/utils/test-react-testing-library';
import { DetailContext } from '../../context';
import { DetailContextProps } from '../../version-config';
@@ -22,11 +22,14 @@ const ComponentToBeRendered: React.FC = () => (
/* eslint-disable react/jsx-no-bind*/
describe('<Install />', () => {
test('renders correctly', () => {
const { container } = render(<ComponentToBeRendered />);
expect(container.firstChild).toMatchSnapshot();
render(<ComponentToBeRendered />);
expect(screen.getByText('pnpm install foo')).toBeInTheDocument();
expect(screen.getByText('yarn add foo')).toBeInTheDocument();
expect(screen.getByText('npm install foo')).toBeInTheDocument();
});
test('should have 3 children', () => {
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['yarn', 'pnpm', 'npm'];
const { getByTestId } = render(<ComponentToBeRendered />);
const installListItems = getByTestId('installList');
// installitems + subHeader = 4
@@ -34,13 +37,17 @@ describe('<Install />', () => {
});
test('should have the element NPM', () => {
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['npm'];
const { getByTestId, queryByText } = render(<ComponentToBeRendered />);
expect(getByTestId('installListItem-npm')).toBeTruthy();
expect(queryByText(`npm install ${detailContextValue.packageName}`)).toBeTruthy();
expect(queryByText('Install using npm')).toBeTruthy();
expect(screen.queryByText('pnpm install foo')).not.toBeInTheDocument();
expect(screen.queryByText('yarn add foo')).not.toBeInTheDocument();
});
test('should have the element YARN', () => {
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['yarn'];
const { getByTestId, queryByText } = render(<ComponentToBeRendered />);
expect(getByTestId('installListItem-yarn')).toBeTruthy();
expect(queryByText(`yarn add ${detailContextValue.packageName}`)).toBeTruthy();
@@ -48,6 +55,7 @@ describe('<Install />', () => {
});
test('should have the element PNPM', () => {
window.__VERDACCIO_BASENAME_UI_OPTIONS.pkgManagers = ['pnpm'];
const { getByTestId, queryByText } = render(<ComponentToBeRendered />);
expect(getByTestId('installListItem-pnpm')).toBeTruthy();
expect(queryByText(`pnpm install ${detailContextValue.packageName}`)).toBeTruthy();

View File

@@ -5,18 +5,20 @@ import { useTranslation } from 'react-i18next';
import List from 'verdaccio-ui/components/List';
import Text from 'verdaccio-ui/components/Text';
import { Theme } from 'verdaccio-ui/design-tokens/theme';
import { useConfig } from 'verdaccio-ui/providers/config';
import { DetailContext } from '../..';
import InstallListItem, { DependencyManager } from './InstallListItem';
const StyledText = styled(Text)<{ theme?: Theme }>((props) => ({
fontWeight: props.theme && props.theme.fontWeight.bold,
fontWeight: props.theme?.fontWeight.bold,
textTransform: 'capitalize',
}));
const Install: React.FC = () => {
const { t } = useTranslation();
const { configOptions } = useConfig();
const detailContext = useContext(DetailContext);
const { packageMeta, packageName } = detailContext;
@@ -25,15 +27,26 @@ const Install: React.FC = () => {
return null;
}
return (
const hasNpm = configOptions?.pkgManagers?.includes('npm');
const hasYarn = configOptions?.pkgManagers?.includes('yarn');
const hasPnpm = configOptions?.pkgManagers?.includes('pnpm') ?? true;
const hasPkgManagers = hasNpm | hasPnpm | hasYarn;
return hasPkgManagers ? (
<List
data-testid={'installList'}
subheader={<StyledText variant={'subtitle1'}>{t('sidebar.installation.title')}</StyledText>}>
<InstallListItem dependencyManager={DependencyManager.NPM} packageName={packageName} />
<InstallListItem dependencyManager={DependencyManager.YARN} packageName={packageName} />
<InstallListItem dependencyManager={DependencyManager.PNPM} packageName={packageName} />
{hasNpm && (
<InstallListItem dependencyManager={DependencyManager.NPM} packageName={packageName} />
)}
{hasYarn && (
<InstallListItem dependencyManager={DependencyManager.YARN} packageName={packageName} />
)}
{hasPnpm && (
<InstallListItem dependencyManager={DependencyManager.PNPM} packageName={packageName} />
)}
</List>
);
) : null;
};
export default Install;

View File

@@ -1,266 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Install /> renders correctly 1`] = `
.emotion-0 {
font-weight: 700;
text-transform: capitalize;
}
.emotion-2 {
padding: 0;
}
.emotion-2:hover {
background-color: transparent;
}
.emotion-4 {
border-radius: 0px;
padding: 0;
}
.emotion-4 img {
background-color: transparent;
}
.emotion-6 {
padding: 0 10px;
margin: 0;
}
.emotion-8 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
}
.emotion-10 {
display: inline-block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
height: 21px;
font-size: 1rem;
}
<ul
class="MuiList-root MuiList-padding MuiList-subheader"
data-testid="installList"
>
<span
class="MuiTypography-root emotion-0 emotion-1 MuiTypography-subtitle1"
>
Installation
</span>
<div
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root emotion-2 emotion-3 MuiListItem-gutters MuiListItem-button"
data-testid="installListItem-npm"
role="button"
tabindex="0"
>
<div
class="MuiAvatar-root MuiAvatar-circle emotion-4 emotion-5"
>
<img
alt="npm"
class="MuiAvatar-img"
src="[object Object]"
/>
</div>
<div
class="MuiListItemText-root emotion-6 emotion-7 MuiListItemText-multiline"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<div
class="emotion-8 emotion-9"
>
<span
class="emotion-10 emotion-11"
>
npm install foo
</span>
<button
class="MuiButtonBase-root MuiIconButton-root"
data-testid="copy-icon"
tabindex="0"
title="Copy to clipboard"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</span>
<p
class="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Install using npm
</p>
</div>
<span
class="MuiTouchRipple-root"
/>
</div>
<div
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root emotion-2 emotion-3 MuiListItem-gutters MuiListItem-button"
data-testid="installListItem-yarn"
role="button"
tabindex="0"
>
<div
class="MuiAvatar-root MuiAvatar-circle emotion-4 emotion-5"
>
<img
alt="yarn"
class="MuiAvatar-img"
src="[object Object]"
/>
</div>
<div
class="MuiListItemText-root emotion-6 emotion-7 MuiListItemText-multiline"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<div
class="emotion-8 emotion-9"
>
<span
class="emotion-10 emotion-11"
>
yarn add foo
</span>
<button
class="MuiButtonBase-root MuiIconButton-root"
data-testid="copy-icon"
tabindex="0"
title="Copy to clipboard"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</span>
<p
class="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Install using yarn
</p>
</div>
<span
class="MuiTouchRipple-root"
/>
</div>
<div
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root emotion-2 emotion-3 MuiListItem-gutters MuiListItem-button"
data-testid="installListItem-pnpm"
role="button"
tabindex="0"
>
<div
class="MuiAvatar-root MuiAvatar-circle emotion-4 emotion-5"
>
<img
alt="pnpm"
class="MuiAvatar-img"
src="[object Object]"
/>
</div>
<div
class="MuiListItemText-root emotion-6 emotion-7 MuiListItemText-multiline"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
<div
class="emotion-8 emotion-9"
>
<span
class="emotion-10 emotion-11"
>
pnpm install foo
</span>
<button
class="MuiButtonBase-root MuiIconButton-root"
data-testid="copy-icon"
tabindex="0"
title="Copy to clipboard"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</span>
<p
class="MuiTypography-root MuiListItemText-secondary MuiTypography-body2 MuiTypography-colorTextSecondary MuiTypography-displayBlock"
>
Install using pnpm
</p>
</div>
<span
class="MuiTouchRipple-root"
/>
</div>
</ul>
`;

View File

@@ -22,18 +22,8 @@ const cache = new CellMeasurerCache({
/* eslint-disable verdaccio/jsx-no-style */
const PackageList: React.FC<Props> = ({ packages }) => {
const renderRow = ({ index, key, parent, style }: ListRowProps) => {
const {
name,
version,
description,
time,
keywords,
dist,
homepage,
bugs,
author,
license,
} = packages[index];
const { name, version, description, time, keywords, dist, homepage, bugs, author, license } =
packages[index];
// TODO: move format license to API side.
const formattedLicense = formatLicense(license);

View File

@@ -1,26 +1,12 @@
import { TemplateUIOptions } from '@verdaccio/types';
import isEmpty from 'lodash/isEmpty';
import isNil from 'lodash/isNil';
import React, { createContext, FunctionComponent, useContext, useMemo, useState } from 'react';
import { PRIMARY_COLOR } from 'verdaccio-ui/utils/colors';
export type VerdaccioOptions = {
url_prefix: string;
base: string;
scope: string;
title: string;
primaryColor: string;
darkMode: boolean;
uri?: string;
language?: string;
version?: string;
protocol?: string;
host?: string;
logo?: string;
};
type ConfigProviderProps = {
configOptions: VerdaccioOptions;
configOptions: TemplateUIOptions;
setConfigOptions: Function;
};
@@ -28,8 +14,10 @@ const defaultValues: ConfigProviderProps = {
configOptions: {
primaryColor: PRIMARY_COLOR,
darkMode: false,
pkgManagers: ['yarn', 'pnpm', 'npm'],
scope: '',
base: '',
login: true,
url_prefix: '',
title: 'Verdaccio',
},
@@ -59,7 +47,6 @@ const AppConfigurationProvider: FunctionComponent = ({ children }) => {
[configOptions]
);
// @ts-ignore
return (
<AppConfigurationContext.Provider value={value}>{children}</AppConfigurationContext.Provider>
);

View File

@@ -1,24 +1,24 @@
import { SyntheticEvent } from 'react';
export const copyToClipBoardUtility = (str: string): ((e: SyntheticEvent<HTMLElement>) => void) => (
event: SyntheticEvent<HTMLElement>
): void => {
event.preventDefault();
export const copyToClipBoardUtility =
(str: string): ((e: SyntheticEvent<HTMLElement>) => void) =>
(event: SyntheticEvent<HTMLElement>): void => {
event.preventDefault();
const node = document.createElement('div');
node.innerText = str;
if (document.body) {
document.body.appendChild(node);
const node = document.createElement('div');
node.innerText = str;
if (document.body) {
document.body.appendChild(node);
const range = document.createRange();
const selection = window.getSelection() as Selection;
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('copy');
document.body.removeChild(node);
}
};
const range = document.createRange();
const selection = window.getSelection() as Selection;
range.selectNodeContents(node);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('copy');
document.body.removeChild(node);
}
};
export function getCLISetConfigRegistry(
command: string,

View File

@@ -2,6 +2,10 @@ web:
title: Verdaccio Local Dev
sort_packages: asc
primary_color: #CCC
login: true
pkgManagers:
- npm
- yarn
plugins: ../

View File

@@ -8,10 +8,11 @@ import config from './webpack.dev.config.babel';
const compiler = webpack(config);
const spinner = ora('Compiler is running...').start();
const port = 4873;
compiler.hooks.done.tap('Verdaccio Dev Server', () => {
if (!global.rebuild) {
spinner.stop();
console.log('Dev Server Listening at http://localhost:4873/');
console.log(`Dev Server Listening at http://localhost:${port}/`);
global.rebuild = true;
}
});
@@ -40,7 +41,7 @@ new WebpackDevServer(compiler, {
target: 'http://localhost:8000',
},
],
}).listen(4873, 'localhost', function (err) {
}).listen(port, 'localhost', function (err) {
if (err) {
return console.log(err);
}

View File

@@ -1,5 +1,8 @@
import fs from 'fs';
import FriendlyErrorsPlugin from 'friendly-errors-webpack-plugin';
import HTMLWebpackPlugin from 'html-webpack-plugin';
import yalm from 'js-yaml';
import StyleLintPlugin from 'stylelint-webpack-plugin';
import webpack from 'webpack';
@@ -8,6 +11,7 @@ import env from '../config/env';
import getPackageJson from './getPackageJson';
import baseConfig from './webpack.config';
const configJsonFormat = yalm.safeLoad(fs.readFileSync('./tools/_verdaccio.config.yaml', 'utf8'));
export default {
...baseConfig,
mode: 'development',
@@ -35,8 +39,7 @@ export default {
}),
new HTMLWebpackPlugin({
__UI_OPTIONS: JSON.stringify({
title: 'Verdaccio Dev UI',
scope: '',
...configJsonFormat.web,
filename: 'index.html',
verdaccioURL: '//localhost:4873',
base: new URL('/', 'http://localhost:4873'),

View File

@@ -1,13 +1,21 @@
// FIXME: this should comes from @verdaccio/types
type PackageManagers = 'pnpm' | 'yarn' | 'npm';
export interface VerdaccioOptions {
// @deprecated
url_prefix: string;
// @deprecated
base: string;
basePath: string;
basename: string;
scope: string;
title: string;
primaryColor: string;
darkMode: boolean;
uri?: string;
login?: boolean;
language?: string;
darkMode?: boolean;
version?: string;
protocol?: string;
host?: string;
logo?: string;
pkgManagers?: PackageManagers[];
}
declare global {

View File

@@ -1,5 +1,13 @@
# @verdaccio/proxy
## 6.0.0-6-next.9
### Patch Changes
- Updated dependencies [d2c65da9]
- @verdaccio/utils@6.0.0-6-next.5
- @verdaccio/config@6.0.0-6-next.7
## 6.0.0-6-next.8
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/proxy",
"version": "6.0.0-6-next.8",
"version": "6.0.0-6-next.9",
"description": "verdaccio proxy fetcher",
"main": "./build/index.js",
"types": "build/index.d.ts",
@@ -40,17 +40,19 @@
},
"dependencies": {
"@verdaccio/commons-api": "workspace:11.0.0-alpha.3",
"@verdaccio/config": "workspace:6.0.0-6-next.6",
"@verdaccio/config": "workspace:6.0.0-6-next.7",
"@verdaccio/local-storage": "workspace:11.0.0-6-next.6",
"@verdaccio/logger": "workspace:6.0.0-6-next.4",
"@verdaccio/streams": "workspace:11.0.0-alpha.3",
"@verdaccio/utils": "workspace:6.0.0-6-next.4",
"@verdaccio/utils": "workspace:6.0.0-6-next.5",
"JSONStream": "1.3.5",
"lodash": "^4.17.20",
"lodash": "4.17.20",
"request": "2.87.0"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.5"
"@verdaccio/types": "workspace:11.0.0-6-next.7",
"nock": "13.0.11",
"node-mocks-http": "1.10.1"
},
"funding": {
"type": "opencollective",

Some files were not shown because too many files have changed in this diff Show More