0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-30 22:34:10 -05:00

feat: migrate yarn workspaces (#1546)

This commit is contained in:
Juan Picado @jotadeveloper 2020-03-03 23:59:19 +01:00 committed by Juan Picado
parent 73585f0262
commit a70454c7b2
451 changed files with 78127 additions and 7204 deletions

143
.circleci/config.yml Normal file
View file

@ -0,0 +1,143 @@
version: 2.1
executors:
node_latest_browser:
docker:
- image: circleci/node:latest-browsers
node_latest:
docker:
- image: circleci/node:13
node_lts_12:
docker:
- image: circleci/node:12
default_executor: node_latest
aliases:
- &repo_path
~/verdaccio
- &defaults
working_directory: *repo_path
- &yarn_cache_key
yarn-sha-{{ checksum "yarn.lock" }}
- &coverage_key
coverage-{{ .Branch }}-{{ .Revision }}
- &ignore_non_dev_branches
filters:
tags:
only: /.*/
branches:
ignore:
- gh-pages
- /release\/.*/
commands:
restore_repo:
description: Restore repository from workspace
steps:
- attach_workspace:
at: *repo_path
run_test:
description: Run test and functional test
steps:
- run:
name: Test
command: yarn run test
- store_test_results:
path: reports/
jobs:
prepare:
<<: *defaults
executor: default_executor
steps:
- checkout
- restore_cache:
key: *yarn_cache_key
- run:
name: Install dependencies
command: yarn
- run:
name: Install dependencies
command: yarn bootstrap
- run:
name: Prepare CI
command: yarn lint
- run:
name: Clean project
command: yarn clean
- run:
name: Build project
command: yarn build
- save_cache:
key: *yarn_cache_key
paths:
- ~/.yarn
- ~/.cache/yarn
- node_modules
- persist_to_workspace:
root: *repo_path
paths:
- ./*
test_node_latest:
<<: *defaults
executor: node_latest
steps:
- restore_repo
- run_test
- save_cache:
key: *coverage_key
paths:
- coverage
test_node_lts_12:
<<: *defaults
executor: node_lts_12
steps:
- restore_repo
- run_test
# test_e2e_cli:
# <<: *defaults
# executor: node_latest
# steps:
# - restore_repo
# - run:
# name: Test End-to-End ClI
# command: yarn run test:e2e:cli
coverage:
<<: *defaults
executor: default_executor
steps:
- restore_repo
- restore_cache:
key: *coverage_key
- run:
name: Publish coverage
command: yarn run coverage:publish
- store_artifacts:
path: coverage
workflows:
version: 2
workflow:
jobs:
- prepare:
<<: *ignore_non_dev_branches
- test_node_latest:
requires:
- prepare
<<: *ignore_non_dev_branches
- test_node_lts_12:
requires:
- prepare
<<: *ignore_non_dev_branches
# - test_e2e_cli:
# requires:
# - prepare
# - test_node_latest
# - test_node_lts_12
# <<: *ignore_non_dev_branches
# - coverage:
# requires:
# - test_e2e_cli
# <<: *ignore_non_dev_branches

View file

@ -15,8 +15,7 @@ Dockerfile
*.png
*.jpg
*.sh
*.ico
test/unit/partials/
**/partials/**
**/fixtures/**
types/custom.d.ts
docker-examples/
LICENSE
**/mock/store/**

View file

@ -9,21 +9,25 @@ jobs:
strategy:
fail-fast: false
matrix:
node_version: [12, 14, 15]
node_version: [8, 10, 12, 13]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.3
- uses: actions/checkout@v2.3.1
- name: Use Node ${{ matrix.node_version }}
uses: actions/setup-node@v2.1.5
uses: actions/setup-node@v2.1.1
with:
node-version: ${{ matrix.node_version }}
node_version: ${{ matrix.node_version }}
- name: Install
run: yarn install --immutable
- name: Build
run: yarn code:build
run: yarn
- name: Bootstrap Lerna
run: yarn bootstrap
- name: Lint
run: yarn lint
- name: Clean
run: yarn clean
- name: Build
run: yarn build
- name: Test
run: yarn test

View file

@ -9,7 +9,7 @@ jobs:
steps:
- uses: actions/checkout@v2.3.3
- name: Build
run: docker build .
run: yarn docker
env:
VERDACCIO_BUILD_REGISTRY: https://registry.verdaccio.org

29
.github/workflows/release-canary.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Canary Release to Verdaccio
on: [push]
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Use Node (latest)
uses: actions/setup-node@v1
with:
node_version: 13
- name: Install
run: yarn install --no-lockfile
- name: Lint
run: yarn lint
- name: Clean
run: yarn clean
- name: Build
run: yarn build
- name: Test
run: yarn test
- name: Publish
run: |
echo "//registry.verdaccio.org/:_authToken=${{ secrets.VERDACCIO_TOKEN }}" > .npmrc
git update-index --assume-unchanged .npmrc
yarn publish:canary

View file

@ -1,32 +0,0 @@
name: Release
on:
push:
tags:
- '*'
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.3
- name: Use Node (latest)
uses: actions/setup-node@v2.1.5
with:
node-version: 14
- name: Install
run: yarn install
- name: Build
run: yarn code:build
- name: Lint
run: yarn lint
- name: Publish
run: sh scripts/publish.sh
env:
REGISTRY_AUTH_TOKEN: ${{ secrets.REGISTRY_AUTH_TOKEN }}
REGISTRY_URL: registry.npmjs.org
- name: Create release notes
run: sh scripts/github-release.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

25
.gitignore vendored
View file

@ -5,41 +5,22 @@ build/
### Test
test/unit/partials/store/test-*-storage/*
test/unit/partials/store/*-storage/*
test/unit/partials/store/storage_default_storage/*
.verdaccio-db.json
.sinopia-db.json
###
!bin/verdaccio
test-storage*
access-storage*
.verdaccio_test_env
node_modules
package-lock.json
npm_test-fails-add-tarball*
yarn-error.log
# Istanbul
# jest
reports/
coverage/
.nyc*
.idea/
# React
bundle.js
bundle.js.map
__tests__
# Compiled script
static/*
# This is the Yarn build state; it's local to each clone
/.yarn/build-state.yml
# This is the Yarn install state cache, it can be rebuilt anytime
/.yarn/install-state.gz
.history
packages/partials

View file

@ -1,41 +0,0 @@
{
"processors": ["stylelint-processor-styled-components"],
"extends": [
"stylelint-config-recommended"
],
"rules": {
"at-rule-no-unknown": true,
"block-no-empty": true,
"color-named": "always-where-possible",
"comment-no-empty": true,
"declaration-block-no-duplicate-properties": [
true,
{
ignore: ["consecutive-duplicates-with-different-values"]
}
],
"declaration-block-no-shorthand-property-overrides": true,
"font-family-no-duplicate-names": true,
"color-no-invalid-hex": true,
"font-family-no-missing-generic-family-keyword": true,
"function-calc-no-unspaced-operator": true,
"function-linear-gradient-no-nonstandard-direction": true,
"keyframe-declaration-no-important": true,
"property-no-vendor-prefix": true,
"media-feature-name-no-unknown": true,
"no-descending-specificity": [true, { "severity": "warning" }],
"no-duplicate-at-import-rules": true,
"no-duplicate-selectors": true,
"no-empty-source": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"property-no-unknown": true,
"selector-pseudo-class-no-unknown": true,
"selector-pseudo-element-no-unknown": true,
"selector-type-no-unknown": [true, { "severity": "warning" }],
"string-no-newline": true,
"unit-no-unknown": true
}
}

View file

@ -7,4 +7,4 @@
"**/coverage": true
},
"typescript.tsdk": "node_modules/typescript/lib"
}
}

2
.yarnrc Normal file
View file

@ -0,0 +1,2 @@
save-prefix ""
registry "https://registry.verdaccio.org"

View file

@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} node:14.16.1-alpine as builder
FROM node:12.18.3-alpine as builder
ENV NODE_ENV=production \
VERDACCIO_BUILD_REGISTRY=https://registry.verdaccio.org
@ -12,18 +12,14 @@ RUN apk --no-cache add openssl ca-certificates wget && \
WORKDIR /opt/verdaccio-build
COPY . .
RUN yarn config set npmRegistryServer $VERDACCIO_BUILD_REGISTRY && \
yarn config set enableProgressBars false && \
yarn config set enableTelemetry false && \
yarn install && \
RUN yarn config set registry $VERDACCIO_BUILD_REGISTRY && \
yarn install --production=false && \
yarn build && \
yarn lint && \
yarn code:docker-build && \
yarn cache clean && \
yarn workspaces focus --production
yarn install --production=true
FROM node:14.16.1-alpine
FROM node:12.18.3-alpine
LABEL maintainer="https://github.com/verdaccio/verdaccio"
ENV VERDACCIO_APPDIR=/opt/verdaccio \
@ -42,10 +38,11 @@ RUN mkdir -p /verdaccio/storage /verdaccio/plugins /verdaccio/conf
COPY --from=builder /opt/verdaccio-build .
ADD conf/docker.yaml /verdaccio/conf/config.yaml
RUN ls packages/config/src/conf
ADD packages/config/src/conf/docker.yaml /verdaccio/conf/config.yaml
RUN adduser -u $VERDACCIO_USER_UID -S -D -h $VERDACCIO_APPDIR -g "$VERDACCIO_USER_NAME user" -s /sbin/nologin $VERDACCIO_USER_NAME && \
chmod -R +x $VERDACCIO_APPDIR/bin $VERDACCIO_APPDIR/docker-bin && \
chmod -R +x $VERDACCIO_APPDIR/packages/verdaccio/bin $VERDACCIO_APPDIR/docker-bin && \
chown -R $VERDACCIO_USER_UID:root /verdaccio/storage && \
chmod -R g=u /verdaccio/storage /etc/passwd
@ -57,4 +54,4 @@ VOLUME /verdaccio/storage
ENTRYPOINT ["uid_entrypoint"]
CMD $VERDACCIO_APPDIR/bin/verdaccio --config /verdaccio/conf/config.yaml --listen $VERDACCIO_PROTOCOL://0.0.0.0:$VERDACCIO_PORT
CMD $VERDACCIO_APPDIR/packages/verdaccio/bin/verdaccio --config /verdaccio/conf/config.yaml --listen $VERDACCIO_PROTOCOL://0.0.0.0:$VERDACCIO_PORT

View file

@ -1,3 +0,0 @@
#!/usr/bin/env node
require('../build/lib/cli');

4
debug/.babelrc Normal file
View file

@ -0,0 +1,4 @@
{
"presets": [["@verdaccio"]],
"debug": true
}

2
debug/bootstrap.js vendored
View file

@ -3,4 +3,4 @@
require('@babel/register')({
extensions: [".ts", ".js"]
});
require('../src/lib/cli');
require('../packages/cli/src/index');

1
debug/debug.js Normal file
View file

@ -0,0 +1 @@
require('@verdaccio/cli');

9
debug/package.json Normal file
View file

@ -0,0 +1,9 @@
{
"name": "debug",
"private": true,
"version": "1.0.0",
"dependencies": {
"@verdaccio/babel-preset": "^8.2.0",
"@verdaccio/eslint-config": "^8.2.0"
}
}

14
lerna.json Normal file
View file

@ -0,0 +1,14 @@
{
"version": "5.0.0-alpha.0",
"packages": [
"packages/*"
],
"npmClient": "yarn",
"useWorkspaces": true,
"stream": true,
"command": {
"publish": {
"message": "chore: publish release %s"
}
}
}

View file

@ -1,7 +1,4 @@
{
"name": "verdaccio",
"version": "5.0.0",
"description": "A lightweight private npm proxy registry",
"author": {
"name": "Verdaccio Maintainers",
"email": "verdaccio.npm@gmail.com"
@ -11,205 +8,80 @@
"url": "git://github.com/verdaccio/verdaccio"
},
"homepage": "https://verdaccio.org",
"main": "build/index.js",
"bin": "./bin/verdaccio",
"private": true,
"workspaces": [
"packages/*"
],
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/verdaccio"
},
"dependencies": {
"@verdaccio/commons-api": "10.0.0",
"@verdaccio/local-storage": "10.0.1",
"@verdaccio/readme": "10.0.0",
"@verdaccio/streams": "10.0.0",
"@verdaccio/ui-theme": "3.0.1",
"JSONStream": "1.3.5",
"async": "3.2.0",
"body-parser": "1.19.0",
"clipanion": "3.0.0-rc.11",
"compression": "1.7.4",
"cookies": "0.8.0",
"cors": "2.8.5",
"dayjs": "1.10.4",
"debug": "^4.3.1",
"envinfo": "7.7.4",
"express": "4.17.1",
"fast-safe-stringify": "^2.0.7",
"handlebars": "4.7.7",
"http-errors": "1.8.0",
"js-yaml": "4.0.0",
"jsonwebtoken": "8.5.1",
"kleur": "4.1.4",
"lodash": "4.17.21",
"lru-cache": "6.0.0",
"lunr-mutable-indexes": "2.3.2",
"marked": "2.0.1",
"memoizee": "0.4.15",
"mime": "2.5.2",
"minimatch": "3.0.4",
"mkdirp": "1.0.4",
"mv": "2.1.1",
"pino": "6.11.2",
"pkginfo": "0.4.1",
"prettier-bytes": "^1.0.3",
"pretty-ms": "^5.0.0",
"request": "2.88.0",
"semver": "7.3.4",
"validator": "13.5.2",
"verdaccio-audit": "10.0.0",
"verdaccio-htpasswd": "10.0.0"
},
"devDependencies": {
"@babel/cli": "7.13.0",
"@babel/core": "7.13.8",
"@babel/node": "7.13.0",
"@babel/plugin-proposal-class-properties": "7.13.0",
"@babel/plugin-proposal-decorators": "7.13.5",
"@babel/plugin-proposal-export-namespace-from": "7.12.13",
"@babel/plugin-proposal-function-sent": "7.12.13",
"@babel/plugin-proposal-json-strings": "7.13.8",
"@babel/plugin-proposal-numeric-separator": "7.12.13",
"@babel/plugin-proposal-object-rest-spread": "7.13.8",
"@babel/plugin-proposal-throw-expressions": "7.12.13",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-syntax-import-meta": "7.10.4",
"@babel/plugin-transform-async-to-generator": "7.13.0",
"@babel/plugin-transform-classes": "7.13.0",
"@babel/plugin-transform-runtime": "7.13.9",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "7.13.9",
"@babel/preset-typescript": "7.13.0",
"@babel/register": "7.13.8",
"@babel/runtime": "7.13.9",
"@commitlint/cli": "12.0.1",
"@commitlint/config-conventional": "12.0.1",
"@octokit/rest": "16.43.2",
"@types/async": "3.2.4",
"@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.2.0",
"@octokit/rest": "16.28.9",
"@types/async": "3.0.3",
"@types/bunyan": "1.8.6",
"@types/express": "4.17.6",
"@types/http-errors": "1.8.0",
"@types/jest": "26.0.14",
"@types/lodash": "4.14.167",
"@types/express": "4.17.1",
"@types/http-errors": "1.6.3",
"@types/jest": "24.0.25",
"@types/lodash": "4.14.149",
"@types/mime": "2.0.1",
"@types/minimatch": "3.0.3",
"@types/node": "14.14.37",
"@types/pino": "6.3.6",
"@types/request": "2.48.5",
"@types/semver": "7.3.4",
"@typescript-eslint/eslint-plugin": "4.13.0",
"@typescript-eslint/parser": "4.13.0",
"@verdaccio/eslint-config": "^8.5.0",
"@verdaccio/types": "^9.7.2",
"all-contributors-cli": "6.20.0",
"babel-eslint": "10.1.0",
"babel-jest": "26.6.3",
"babel-loader": "^8.2.2",
"babel-plugin-dynamic-import-node": "2.3.3",
"codecov": "3.8.1",
"cross-env": "7.0.3",
"detect-secrets": "1.0.6",
"eslint": "7.19.0",
"eslint-config-google": "0.14.0",
"eslint-config-prettier": "7.2.0",
"eslint-plugin-babel": "5.3.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest": "24.1.3",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-simple-import-sort": "7.0.0",
"eslint-plugin-verdaccio": "9.6.1",
"fs-extra": "9.1.0",
"get-stdin": "8.0.0",
"@types/node": "12.12.21",
"@types/request": "2.48.3",
"@types/semver": "6.2.0",
"@types/express-serve-static-core": "4.17.1",
"@verdaccio/babel-preset": "^8.5.0",
"@verdaccio/eslint-config": "^9.0.0",
"@verdaccio/types": "^9.0.0",
"codecov": "3.6.1",
"cross-env": "6.0.3",
"detect-secrets": "1.0.5",
"eslint": "6.8.0",
"fs-extra": "8.1.0",
"get-stdin": "7.0.0",
"kleur": "3.0.3",
"husky": "2.7.0",
"in-publish": "2.0.1",
"jest": "25.5.4",
"jest-environment-node": "25.5.0",
"jest-junit": "9.0.0",
"in-publish": "2.0.0",
"jest": "^24.9.0",
"jest-environment-node": "^24.9.0",
"jest-junit": "^9.0.0",
"lerna": "^3.18.4",
"lint-staged": "8.2.1",
"lockfile-lint": "4.3.7",
"nock": "12.0.3",
"node-mocks-http": "^1.10.1",
"prettier": "2.2.1",
"puppeteer": "5.5.0",
"rimraf": "3.0.2",
"selfsigned": "1.10.8",
"standard-version": "9.1.1",
"supertest": "6.1.1",
"typescript": "4.1.3",
"verdaccio-auth-memory": "10.0.0",
"verdaccio-memory": "10.0.0"
"nock": "^11.7.2",
"prettier": "^1.19.1",
"rimraf": "3.0.0",
"selfsigned": "1.10.7",
"standard-version": "^7.0.1",
"supertest": "^4.0.2",
"typescript": "^3.7.5",
"verdaccio-auth-memory": "^8.5.0",
"verdaccio-memory": "^8.5.0",
"verdaccio": "^4.4.0"
},
"keywords": [
"private",
"package",
"repository",
"registry",
"enterprise",
"modules",
"proxy",
"server",
"verdaccio"
],
"scripts": {
"release": "standard-version -a -s",
"prepublish": "in-publish && yarn run code:build || not-in-publish",
"type-check": "tsc --noEmit",
"type-check:watch": "yarn run type-check -- --watch",
"pretest": "yarn run code:build",
"format": "prettier --single-quote --trailing-comma none --write \"{src,test}/**/*.ts\"",
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\" --debug-check",
"test": "yarn run test:unit",
"test:clean": "npx jest --clearCache",
"test:unit": "cross-env NODE_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2 --passWithNoTests",
"test:functional": "cross-env NODE_ENV=test jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index* --passWithNoTests",
"test:e2e:cli": "cross-env NODE_ENV=test jest --config ./test/e2e-cli/jest.config.e2e.cli.js --passWithNoTests",
"test:e2e": "yarn jest --config ./test/jest.config.e2e.js",
"test:all": "yarn run test && yarn run test:functional && yarn run test:e2e & yarn run test:e2e:pkg",
"pre:ci": "yarn run lint",
"coverage:publish": "codecov",
"lint": "yarn run type-check && yarn run lint:ts",
"lint:ts": "eslint \"**/*.{js,jsx,ts,tsx}\"",
"lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts verdaccio npm yarn",
"start": "yarn babel-node --extensions \".ts,.tsx\" src/lib/cli",
"start:debug": "yarn node debug/bootstrap.js",
"code:build": "yarn babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps inline",
"code:docker-build": "yarn babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\"",
"docker": "docker build -t verdaccio/verdaccio:pr-2122 . --no-cache",
"docker:run": "docker run -it --rm -p 4873:4873 verdaccio/verdaccio:local"
},
"engines": {
"node": ">=12",
"npm": ">=6"
},
"preferGlobal": true,
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -e $GIT_PARAMS"
}
},
"lint-staged": {
"relative": true,
"linters": {
"*": [
"eslint .",
"git add"
]
},
"ignore": [
"*.json"
]
"bootstrap": "lerna bootstrap",
"debug": "node debug/bootstrap.js",
"dev": "cross-env BABEL_ENV=registry babel-node --extensions \".ts,.tsx\" packages/cli/src",
"clean": "lerna run clean",
"build": "lerna run build",
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
"release": "lerna version --conventional-commits",
"version:canary": "lerna version --conventional-commits --conventional-prerelease --no-commit-hooks --exact --no-changelog --yes --preid alpha",
"publish:canary": "lerna publish from-package --canary --yes --no-git-reset --no-git-tag-version --no-push --force-publish --concurrency 1",
"release:from-prerelease": "lerna version --conventional-commits --conventional-graduate",
"release:prerelease": "lerna version --conventional-commits --conventional-prerelease --preid next --registry http://localhost:4873",
"release:publish": "lerna publish from-git",
"release:publish-prerelease": "lerna publish from-git --pre-dist-tag next",
"lint": "eslint \"packages/**/@(src|tests)/**\"",
"test": "lerna run test --concurrency 1",
"test:e2e:cli": "cross-env NODE_ENV=test jest --config ./test/e2e-cli/jest.config.e2e.cli.js --passWithNoTests"
},
"license": "MIT",
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/verdaccio",
"logo": "https://opencollective.com/verdaccio/logo.txt"
}
}

3
packages/api/.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": [["@verdaccio"]]
}

View file

@ -0,0 +1,9 @@
module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
verbose: true,
collectCoverage: true,
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
};

43
packages/api/package.json Normal file
View file

@ -0,0 +1,43 @@
{
"name": "@verdaccio/api",
"version": "5.0.0-alpha.0",
"description": "loaders logic",
"main": "./build/index.js",
"types": "build/index.d.ts",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/verdaccio/verdaccio"
},
"homepage": "https://verdaccio.org",
"scripts": {
"clean": "rimraf ./build",
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
"type-check": "tsc --noEmit",
"build:types": "tsc --emitDeclarationOnly --declaration true",
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
"build": "npm run build:js && npm run build:types"
},
"license": "MIT",
"dependencies": {
"@verdaccio/commons-api": "^9.0.0",
"@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/hooks": "5.0.0-alpha.0",
"@verdaccio/logger": "5.0.0-alpha.0",
"@verdaccio/middleware": "5.0.0-alpha.0",
"@verdaccio/utils": "5.0.0-alpha.0",
"body-parser": "^1.19.0",
"cookies": "^0.8.0",
"express": "4.17.1",
"lodash": "^4.17.15",
"mime": "^2.4.4"
},
"devDependencies": {
"@verdaccio/dev-types": "5.0.0-alpha.0",
"@verdaccio/types": "^9.0.0"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
}

View file

@ -0,0 +1,78 @@
import mime from 'mime';
import _ from 'lodash';
import{ Router } from 'express';
import { media, allow } from '@verdaccio/middleware';
import { API_MESSAGE, HTTP_STATUS, DIST_TAGS } from '@verdaccio/dev-commons';
import { VerdaccioError } from '@verdaccio/commons-api';
import { Package } from '@verdaccio/types';
// @ts-ignore
import{ IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
export default function(route: Router, auth: IAuth, storage: IStorageHandler): void {
const can = allow(auth);
const tag_package_version = function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): $NextFunctionVer {
if (_.isString(req.body) === false) {
return next('route');
}
const tags = {};
tags[req.params.tag] = req.body;
storage.mergeTags(req.params.package, tags, function(err: Error): $NextFunctionVer {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
return next({ ok: API_MESSAGE.TAG_ADDED });
});
};
// tagging a package
route.put('/:package/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
route.post('/-/package/:package/dist-tags/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
route.put('/-/package/:package/dist-tags/:tag', can('publish'), media(mime.getType('json')), tag_package_version);
route.delete('/-/package/:package/dist-tags/:tag', can('publish'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const tags = {};
tags[req.params.tag] = null;
storage.mergeTags(req.params.package, tags, function(err: VerdaccioError): $NextFunctionVer {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_REMOVED,
});
});
});
route.get('/-/package/:package/dist-tags', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.getPackage({
name: req.params.package,
uplinksLook: true,
req,
callback: function(err: VerdaccioError, info: Package): $NextFunctionVer {
if (err) {
return next(err);
}
next(info[DIST_TAGS]);
},
});
});
route.post('/-/package/:package/dist-tags', can('publish'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
storage.mergeTags(req.params.package, req.body, function(err: VerdaccioError): $NextFunctionVer {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TAG_UPDATED,
});
});
});
}

View file

@ -1,31 +1,26 @@
import { Config } from '@verdaccio/types';
import _ from 'lodash';
import express from 'express';
import { match, validateName, validatePackage, encodeScopePackage, antiLoop } from '@verdaccio/middleware';
import { IAuth, IStorageHandler } from '@verdaccio/dev-types';
import { Config } from '@verdaccio/types';
import bodyParser from 'body-parser';
import { IAuth, IStorageHandler } from '../../../types';
import whoami from './api/whoami';
import ping from './api/ping';
import user from './api/user';
import distTags from './api/dist-tags';
import publish from './api/publish';
import search from './api/search';
import pkg from './api/package';
import stars from './api/stars';
import profile from './api/v1/profile';
import token from './api/v1/token';
import v1Search from './api/v1/search';
import whoami from './whoami';
import ping from './ping';
import user from './user';
import distTags from './dist-tags';
import publish from './publish';
import search from './search';
import pkg from './package';
import stars from './stars';
import profile from './v1/profile';
import token from './v1/token';
import v1Search from './api/v1/search'
const {
match,
validateName,
validatePackage,
encodeScopePackage,
antiLoop
} = require('../middleware');
const { match, validateName, validatePackage, encodeScopePackage, antiLoop } = require('../middleware');
export default function (config: Config, auth: IAuth, storage: IStorageHandler) {
export default function(config: Config, auth: IAuth, storage: IStorageHandler) {
/* eslint new-cap:off */
const app = express.Router();
/* eslint new-cap:off */
@ -49,6 +44,7 @@ export default function (config: Config, auth: IAuth, storage: IStorageHandler)
app.use(auth.apiJWTmiddleware());
app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' }));
// @ts-ignore
app.use(antiLoop(config));
// encode / in a scoped package name to be matched as a single parameter in routes
app.use(encodeScopePackage);
@ -64,7 +60,7 @@ export default function (config: Config, auth: IAuth, storage: IStorageHandler)
stars(app, storage);
if (_.get(config, 'experiments.search') === true) {
v1Search(app, auth, storage);
v1Search(app, auth, storage)
}
if (_.get(config, 'experiments.token') === true) {

View file

@ -0,0 +1,74 @@
import _ from 'lodash';
import { Router } from 'express';
import { allow } from '@verdaccio/middleware';
import { convertDistRemoteToLocalTarballUrls, getVersion, ErrorCode } from '@verdaccio/utils';
import { HEADERS, DIST_TAGS, API_ERROR } from '@verdaccio/dev-commons';
import { Config, Package } from '@verdaccio/types';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
const downloadStream = (packageName: string, filename: string, storage: any, req: $RequestExtend, res: $ResponseExtend): void => {
const stream = storage.getTarball(packageName, filename);
stream.on('content-length', function(content): void {
res.header('Content-Length', content);
});
stream.on('error', function(err): void {
return res.report_error(err);
});
res.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM);
stream.pipe(res);
};
export default function(route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
const can = allow(auth);
// TODO: anonymous user?
route.get('/:package/:version?', can('access'), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const getPackageMetaCallback = function(err, metadata: Package): void {
if (err) {
return next(err);
}
metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config.url_prefix);
let queryVersion = req.params.version;
if (_.isNil(queryVersion)) {
return next(metadata);
}
let version = getVersion(metadata, queryVersion);
if (_.isNil(version) === false) {
return next(version);
}
if (_.isNil(metadata[DIST_TAGS]) === false) {
if (_.isNil(metadata[DIST_TAGS][queryVersion]) === false) {
queryVersion = metadata[DIST_TAGS][queryVersion];
version = getVersion(metadata, queryVersion);
if (_.isNil(version) === false) {
return next(version);
}
}
}
return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${req.params.version}`));
};
storage.getPackage({
name: req.params.package,
uplinksLook: true,
req,
callback: getPackageMetaCallback,
});
});
route.get('/:scopedPackage/-/:scope/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
const { scopedPackage, filename } = req.params;
downloadStream(scopedPackage, filename, storage, req, res);
});
route.get('/:package/-/:filename', can('access'), function(req: $RequestExtend, res: $ResponseExtend): void {
downloadStream(req.params.package, req.params.filename, storage, req, res);
});
}

View file

@ -1,10 +1,5 @@
/**
* @prettier
* @flow
*/
import { Router } from 'express';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '../../../../types';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer } from '@verdaccio/dev-types';
export default function (route: Router): void {
route.get(

View file

@ -1,21 +1,18 @@
import Path from 'path';
import _ from 'lodash';
import buildDebug from 'debug';
import Path from 'path';
import mime from 'mime';
import { Router } from 'express';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons';
import {validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation} from '@verdaccio/utils';
import { media, expectJson, allow } from '@verdaccio/middleware';
import { notify } from '@verdaccio/hooks';
import { Config, Callback, MergeTags, Version, Package } from '@verdaccio/types';
import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '../../../lib/constants';
import { validateMetadata, isObject, ErrorCode, hasDiffOneKey, isRelatedToDeprecation } from '../../../lib/utils';
import { media, expectJson, allow } from '../../middleware';
import { notify } from '../../../lib/notify';
import { logger } from '@verdaccio/logger';
import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import { logger } from '../../../lib/logger';
import { isPublishablePackage } from '../../../lib/storage-utils';
import star from './star';
const debug = buildDebug('verdaccio:publish');
import {isPublishablePackage} from "./utils";
export default function publish(router: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
const can = allow(auth);
@ -106,18 +103,20 @@ export default function publish(router: Router, auth: IAuth, storage: IStorageHa
*/
export function publishPackage(storage: IStorageHandler, config: Config, auth: IAuth): any {
const starApi = star(storage);
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
debug('publishing or updating a new version for %o', packageName);
logger.debug({packageName} , `publishing or updating a new version for @{packageName}`);
/**
* Write tarball of stream data from package clients.
*/
const createTarball = function (filename: string, data, cb: Callback): void {
const createTarball = function(filename: string, data, cb: Callback): void {
const stream = storage.addTarball(packageName, filename);
stream.on('error', function (err) {
stream.on('error', function(err) {
cb(err);
});
stream.on('success', function () {
stream.on('success', function() {
cb();
});
// this is dumb and memory-consuming, but what choices do we have?
@ -129,18 +128,18 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
/**
* Add new package version in storage
*/
const createVersion = function (version: string, metadata: Version, cb: Callback): void {
const createVersion = function(version: string, metadata: Version, cb: Callback): void {
storage.addVersion(packageName, version, metadata, null, cb);
};
/**
* Add new tags in storage
*/
const addTags = function (tags: MergeTags, cb: Callback): void {
const addTags = function(tags: MergeTags, cb: Callback): void {
storage.mergeTags(packageName, tags, cb);
};
const afterChange = function (error, okMessage, metadata): void {
const afterChange = function(error, okMessage, metadata): void {
const metadataCopy: Package = { ...metadata };
const { _attachments, versions } = metadataCopy;
@ -161,7 +160,8 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue https://github.com/rlidwka/sinopia/issues/31, dealing with it here:
const isInvalidBodyFormat = isObject(_attachments) === false || hasDiffOneKey(_attachments) || isObject(versions) === false || hasDiffOneKey(versions);
const isInvalidBodyFormat = isObject(_attachments) === false || hasDiffOneKey(_attachments) ||
isObject(versions) === false || hasDiffOneKey(versions);
if (isInvalidBodyFormat) {
// npm is doing something strange again
@ -177,7 +177,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
// at this point document is either created or existed before
const [firstAttachmentKey] = Object.keys(_attachments);
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function (error) {
createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function(error) {
if (error) {
return next(error);
}
@ -187,12 +187,12 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
versionMetadataToPublish.readme = _.isNil(versionMetadataToPublish.readme) === false ? String(versionMetadataToPublish.readme) : '';
createVersion(versionToPublish, versionMetadataToPublish, function (error) {
createVersion(versionToPublish, versionMetadataToPublish, function(error) {
if (error) {
return next(error);
}
addTags(metadataCopy[DIST_TAGS], async function (error) {
addTags(metadataCopy[DIST_TAGS], async function(error) {
if (error) {
return next(error);
}
@ -218,26 +218,27 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
const metadata = validateMetadata(req.body, packageName);
// treating deprecation as updating a package
if (req.params._rev || isRelatedToDeprecation(req.body)) {
debug('updating a new version for %o', packageName);
logger.debug({packageName} , `updating a new version for @{packageName}`);
// we check unpublish permissions, an update is basically remove versions
const remote = req.remote_user;
auth.allow_unpublish({ packageName }, remote, (error) => {
auth.allow_unpublish({packageName}, remote, (error) => {
if (error) {
logger.error({ packageName }, `not allowed to unpublish a version for @{packageName}`);
logger.debug({packageName} , `not allowed to unpublish a version for @{packageName}`);
return next(error);
}
storage.changePackage(packageName, metadata, req.params.revision, function (error) {
storage.changePackage(packageName, metadata, req.params.revision, function(error) {
afterChange(error, API_MESSAGE.PKG_CHANGED, metadata);
});
});
} else {
debug('adding a new version for %o', packageName);
storage.addPackage(packageName, metadata, function (error) {
logger.debug({packageName} , `adding a new version for @{packageName}`);
storage.addPackage(packageName, metadata, function(error) {
afterChange(error, API_MESSAGE.PKG_CREATED, metadata);
});
}
} catch (error) {
logger.error({ packageName }, 'error on publish, bad package data for @{packageName}');
logger.error({packageName}, 'error on publish, bad package data for @{packageName}');
return next(ErrorCode.getBadData(API_ERROR.BAD_PACKAGE_DATA));
}
};
@ -247,10 +248,11 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I
* un-publish a package
*/
export function unPublishPackage(storage: IStorageHandler) {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
debug('unpublishing %o', packageName);
storage.removePackage(packageName, function (err) {
logger.debug({packageName} , `unpublishing @{packageName}`);
storage.removePackage(packageName, function(err) {
if (err) {
return next(err);
}
@ -264,16 +266,18 @@ export function unPublishPackage(storage: IStorageHandler) {
* Delete tarball
*/
export function removeTarball(storage: IStorageHandler) {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
const { filename, revision } = req.params;
debug('removing a tarball for %o-%o-%o', packageName, filename, revision);
storage.removeTarball(packageName, filename, revision, function (err) {
const {filename, revision} = req.params;
logger.debug({packageName, filename, revision} , `removing a tarball for @{packageName}-@{tarballName}-@{revision}`);
storage.removeTarball(packageName, filename, revision, function(err) {
if (err) {
return next(err);
}
res.status(HTTP_STATUS.CREATED);
debug('success remove tarball for %o-%o-%o', packageName, filename, revision);
logger.debug({packageName, filename, revision} , `success remove tarball for @{packageName}-@{tarballName}-@{revision}`);
return next({ ok: API_MESSAGE.TARBALL_REMOVED });
});
};
@ -282,11 +286,11 @@ export function removeTarball(storage: IStorageHandler) {
* Adds a new version
*/
export function addVersion(storage: IStorageHandler) {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const { version, tag } = req.params;
const packageName = req.params.package;
storage.addVersion(packageName, version, req.body, tag, function (error) {
storage.addVersion(packageName, version, req.body, tag, function(error) {
if (error) {
return next(error);
}
@ -303,29 +307,29 @@ export function addVersion(storage: IStorageHandler) {
* uploadPackageTarball
*/
export function uploadPackageTarball(storage: IStorageHandler) {
return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
return function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
const packageName = req.params.package;
const stream = storage.addTarball(packageName, req.params.filename);
req.pipe(stream);
// checking if end event came before closing
let complete = false;
req.on('end', function () {
req.on('end', function() {
complete = true;
stream.done();
});
req.on('close', function () {
req.on('close', function() {
if (!complete) {
stream.abort();
}
});
stream.on('error', function (err) {
return res.locals.report_error(err);
stream.on('error', function(err) {
return res.report_error(err);
});
stream.on('success', function () {
stream.on('success', function() {
res.status(HTTP_STATUS.CREATED);
return next({
ok: API_MESSAGE.TARBALL_UPLOADED,

View file

@ -1,19 +1,15 @@
import { HEADERS } from '../../../lib/constants';
import { HEADERS } from '@verdaccio/dev-commons';
/**
* @prettier
*/
export default function (route, auth, storage): void {
export default function(route, auth, storage): void {
// searching packages
route.get('/-/all(/since)?', function (req, res) {
route.get('/-/all(/since)?', function(req, res) {
let received_end = false;
let response_finished = false;
let processing_pkgs = 0;
let firstPackage = true;
res.status(200);
res.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET);
res.set(HEADERS.CONTENT_TYPE, HEADERS.JSON_CHARSET)
/*
* Offical NPM registry (registry.npmjs.org) no longer return whole database,
@ -37,7 +33,7 @@ export default function (route, auth, storage): void {
if (!respShouldBeArray) {
res.set('Date', 'Mon, 10 Oct 1983 00:12:48 GMT');
}
const check_finish = function (): void {
const check_finish = function(): void {
if (!received_end) {
return;
}
@ -66,7 +62,7 @@ export default function (route, auth, storage): void {
stream.on('data', function each(pkg) {
processing_pkgs++;
auth.allow_access({ packageName: pkg.name }, req.remote_user, function (err, allowed) {
auth.allow_access({ packageName: pkg.name }, req.remote_user, function(err, allowed) {
processing_pkgs--;
if (err) {
@ -94,11 +90,11 @@ export default function (route, auth, storage): void {
});
});
stream.on('error', function () {
stream.on('error', function() {
res.socket.destroy();
});
stream.on('end', function () {
stream.on('end', function() {
received_end = true;
check_finish();
});

View file

@ -1,14 +1,11 @@
// @flow
import { Response } from 'express';
import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
import {Response} from 'express';
import _ from 'lodash';
import buildDebug from 'debug';
import { USERS, HTTP_STATUS } from '../../../lib/constants';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '../../../../types';
import { logger } from '../../../lib/logger';
import { logger } from '@verdaccio/logger';
const debug = buildDebug('verdaccio:star');
export default function (storage: IStorageHandler): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
import {$RequestExtend, $NextFunctionVer, IStorageHandler} from '@verdaccio/dev-types';
export default function(storage: IStorageHandler): (req: $RequestExtend, res: Response, next: $NextFunctionVer) => void {
const validateInputs = (newUsers, localUsers, username, isStar): boolean => {
const isExistlocalUsers = _.isNil(localUsers[username]) === false;
if (isStar && isExistlocalUsers && localUsers[username]) {
@ -23,8 +20,8 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
return (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
const name = req.params.package;
debug('starring a package for %o', name);
const afterChangePackage = function (err?: Error) {
logger.debug({name}, 'starring a package for @{name}');
const afterChangePackage = function(err?: Error) {
if (err) {
return next(err);
}
@ -37,7 +34,7 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
storage.getPackage({
name,
req,
callback: function (err, info) {
callback: function(err, info) {
if (err) {
return next(err);
}
@ -49,22 +46,16 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R
if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) {
return afterChangePackage();
}
const users = isStar
? {
...localStarUsers,
[remoteUsername]: true,
}
: _.reduce(
localStarUsers,
(users, value, key) => {
if (key !== remoteUsername) {
users[key] = value;
}
return users;
},
{}
);
storage.changePackage(name, { ...info, users }, req.body._rev, function (err) {
const users = isStar ? {
...localStarUsers,
[remoteUsername]: true,
} : _.reduce(localStarUsers, (users, value, key) => {
if (key !== remoteUsername) {
users[key] = value;
}
return users;
}, {});
storage.changePackage(name, { ...info, users}, req.body._rev, function(err) {
afterChangePackage(err);
});
},

32
packages/api/src/stars.ts Normal file
View file

@ -0,0 +1,32 @@
import _ from 'lodash';
import { Response, Router } from 'express';
import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons';
import { Package } from '@verdaccio/types';
import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types';
type Packages = Package[];
export default function(route: Router, storage: IStorageHandler): void {
route.get('/-/_view/starredByUser', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {
const remoteUsername = req.remote_user.name;
storage.getLocalDatabase((err, localPackages: Packages) => {
if (err) {
return next(err);
}
const filteredPackages: Packages = localPackages.filter((localPackage: Package) =>
_.keys(localPackage[USERS]).includes(remoteUsername)
);
res.status(HTTP_STATUS.OK);
next({
rows: filteredPackages.map((filteredPackage: Package) => ({
value: filteredPackage.name,
})),
});
});
});
}

View file

@ -1,31 +1,30 @@
import _ from 'lodash';
import Cookies from 'cookies';
import { Response, Router } from 'express';
import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils';
import { logger } from '@verdaccio/logger';
import { Config, RemoteUser } from '@verdaccio/types';
import { Response, Router } from 'express';
import { ErrorCode } from '../../../lib/utils';
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '../../../lib/constants';
import { createRemoteUser, createSessionToken, getApiToken, getAuthenticatedMessage, validatePassword } from '../../../lib/auth-utils';
import { logger } from '../../../lib/logger';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types';
import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '@verdaccio/dev-commons';
import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '../../../../types';
export default function (route: Router, auth: IAuth, config: Config): void {
route.get('/-/user/:org_couchdb_user', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
export default function(route: Router, auth: IAuth, config: Config): void {
route.get('/-/user/:org_couchdb_user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
res.status(HTTP_STATUS.OK);
next({
ok: getAuthenticatedMessage(req.remote_user.name),
});
});
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
const { name, password } = req.body;
const remoteName = req.remote_user.name;
if (_.isNil(remoteName) === false && _.isNil(name) === false && remoteName === name) {
auth.authenticate(name, password, async function callbackAuthenticate(err, user): Promise<void> {
if (err) {
logger.error({ name, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
logger.trace({ name, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.BAD_USERNAME_PASSWORD));
}
@ -45,7 +44,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
return next(ErrorCode.getCode(HTTP_STATUS.BAD_REQUEST, API_ERROR.PASSWORD_SHORT()));
}
auth.add_user(name, password, async function (err, user): Promise<void> {
auth.add_user(name, password, async function(err, user): Promise<void> {
if (err) {
if (err.status >= HTTP_STATUS.BAD_REQUEST && err.status < HTTP_STATUS.INTERNAL_ERROR) {
// With npm registering is the same as logging in,
@ -68,7 +67,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
}
});
route.delete('/-/user/token/*', function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
route.delete('/-/user/token/*', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
res.status(HTTP_STATUS.OK);
next({
ok: API_MESSAGE.LOGGED_OUT,
@ -77,7 +76,7 @@ export default function (route: Router, auth: IAuth, config: Config): void {
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
route.post('/_session', Cookies.express(), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
route.post('/_session', Cookies.express(), function(req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void {
res.cookies.set('AuthSession', String(Math.random()), createSessionToken());
next({

13
packages/api/src/utils.ts Normal file
View file

@ -0,0 +1,13 @@
import {Package} from '@verdaccio/types';
import _ from 'lodash';
/**
* Check whether the package metadta has enough data to be published
* @param pkg metadata
*/
export function isPublishablePackage(pkg: Package): boolean {
const keys: string[] = Object.keys(pkg);
return _.includes(keys, 'versions');
}

View file

@ -0,0 +1,83 @@
import _ from 'lodash';
import { Response, Router } from 'express';
import { API_ERROR, APP_ERROR, HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import { ErrorCode, validatePassword } from '@verdaccio/utils';
import { $NextFunctionVer, $RequestExtend, IAuth } from '@verdaccio/dev-types';
export interface Profile {
tfa: boolean;
name: string;
email: string;
email_verified: boolean;
created: string;
updated: string;
cidr_whitelist: string[] | null;
fullname: string;
}
export default function(route: Router, auth: IAuth): void {
function buildProfile(name: string): Profile {
return {
tfa: false,
name,
email: '',
email_verified: false,
created: '',
updated: '',
cidr_whitelist: null,
fullname: '',
};
}
route.get('/-/npm/v1/user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
if (_.isNil(req.remote_user.name) === false) {
return next(buildProfile(req.remote_user.name));
}
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({
message: API_ERROR.MUST_BE_LOGGED,
});
});
route.post('/-/npm/v1/user', function(req: $RequestExtend, res: Response, next: $NextFunctionVer): void {
if (_.isNil(req.remote_user.name)) {
res.status(HTTP_STATUS.UNAUTHORIZED);
return next({
message: API_ERROR.MUST_BE_LOGGED,
});
}
const { password, tfa } = req.body;
const { name } = req.remote_user;
if (_.isNil(password) === false) {
if (validatePassword(password.new) === false) {
/* eslint new-cap:off */
return next(ErrorCode.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT()));
/* eslint new-cap:off */
}
auth.changePassword(
name,
password.old,
password.new,
(err, isUpdated): $NextFunctionVer => {
if (_.isNull(err) === false) {
return next(ErrorCode.getCode(err.status, err.message) || ErrorCode.getConflict(err.message));
}
if (isUpdated) {
return next(buildProfile(req.remote_user.name));
}
return next(ErrorCode.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
}
);
} else if (_.isNil(tfa) === false) {
return next(ErrorCode.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED));
} else {
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));
}
});
}

View file

@ -0,0 +1,121 @@
import _ from 'lodash';
import { HTTP_STATUS, SUPPORT_ERRORS } from '@verdaccio/dev-commons';
import {ErrorCode, stringToMD5, mask, getApiToken } from '@verdaccio/utils';
import { logger } from '@verdaccio/logger';
import { Response, Router } from 'express';
import {$NextFunctionVer, $RequestExtend, IAuth, IStorageHandler} from '../../../types';
import { Config, RemoteUser, Token } from '@verdaccio/types';
export type NormalizeToken = Token & {
created: string;
};
function normalizeToken(token: Token): NormalizeToken {
return {
...token,
created: new Date(token.created).toISOString(),
};
};
// https://github.com/npm/npm-profile/blob/latest/lib/index.js
export default function(route: Router, auth: IAuth, storage: IStorageHandler, config: Config): void {
route.get('/-/npm/v1/tokens', async function(req: $RequestExtend, res: Response, next: $NextFunctionVer) {
const { name } = req.remote_user;
if (_.isNil(name) === false) {
try {
const tokens = await storage.readTokens({user: name});
const totalTokens = tokens.length;
logger.debug({totalTokens}, 'token list retrieved: @{totalTokens}');
res.status(HTTP_STATUS.OK);
return next({
objects: tokens.map(normalizeToken),
urls: {
next: '', // TODO: pagination?
},
});
} catch (error) {
logger.error({ error: error.msg }, 'token list has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
}
return next(ErrorCode.getUnauthorized());
});
route.post('/-/npm/v1/tokens', function(req: $RequestExtend, res: Response, next: $NextFunctionVer) {
const { password, readonly, cidr_whitelist } = req.body;
const { name } = req.remote_user;
if (!_.isBoolean(readonly) || !_.isArray(cidr_whitelist)) {
return next(ErrorCode.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));
}
auth.authenticate(name, password, async (err, user: RemoteUser) => {
if (err) {
const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;
return next(ErrorCode.getCode(errorCode, err.message));
}
req.remote_user = user;
if (!_.isFunction(storage.saveToken)) {
return next(ErrorCode.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT));
}
try {
const token = await getApiToken(auth, config, user, password);
const key = stringToMD5(token);
// TODO: use a utility here
const maskedToken = mask(token, 5);
const created = new Date().getTime();
/**
* cidr_whitelist: is not being used, we pass it through
* token: we do not store the real token (it is generated once and retrieved to the user), just a mask of it.
*/
const saveToken: Token = {
user: name,
token: maskedToken,
key,
cidr: cidr_whitelist,
readonly,
created,
};
await storage.saveToken(saveToken);
logger.debug({ key, name }, 'token @{key} was created for user @{name}');
return next(normalizeToken({
token,
user: name,
key: saveToken.key,
cidr: cidr_whitelist,
readonly,
created: saveToken.created,
}));
} catch (error) {
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
});
});
route.delete('/-/npm/v1/tokens/token/:tokenKey', async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {
const { params: { tokenKey }} = req;
const { name } = req.remote_user;
if (_.isNil(name) === false) {
logger.debug({name}, '@{name} has requested remove a token');
try {
await storage.deleteToken(name, tokenKey);
logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');
return next({});
} catch(error) {
logger.error({ error: error.msg }, 'token creation has failed: @{error}');
return next(ErrorCode.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));
}
}
return next(ErrorCode.getUnauthorized());
});
}

View file

@ -1,5 +1,5 @@
import { Response, Router } from 'express';
import { $RequestExtend, $NextFunctionVer } from '../../../../types';
import { $RequestExtend, $NextFunctionVer } from '@verdaccio/dev-types';
export default function (route: Router): void {
route.get('/whoami', (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => {

View file

@ -1,15 +1,11 @@
import {
addVersion,
uploadPackageTarball,
removeTarball,
unPublishPackage,
publishPackage
} from '../../../../src/api/endpoint/api/publish';
import { HTTP_STATUS, API_ERROR } from '../../../../src/lib/constants';
import { addVersion, uploadPackageTarball, removeTarball, unPublishPackage, publishPackage } from '../src/publish';
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
const REVISION_MOCK = '15-e53a77096b0ee33e';
require('../../../../src/lib/logger').setup([{ type: 'stdout', format: 'pretty', level: 'info' }]);
require('@verdaccio/logger').setup([
{ type: 'stdout', format: 'pretty', level: 'info' }
]);
describe('Publish endpoints - add a tag', () => {
let req;
@ -21,18 +17,18 @@ describe('Publish endpoints - add a tag', () => {
params: {
version: '1.0.0',
tag: 'tag',
package: 'verdaccio'
package: 'verdaccio',
},
body: ''
body: '',
};
res = {
status: jest.fn()
status: jest.fn(),
};
next = jest.fn();
});
test('should add a version', (done) => {
test('should add a version', done => {
const storage = {
addVersion: (packageName, version, body, tag, cb) => {
expect(packageName).toEqual(req.params.package);
@ -41,7 +37,7 @@ describe('Publish endpoints - add a tag', () => {
expect(tag).toEqual(req.params.tag);
cb();
done();
}
},
};
// @ts-ignore
@ -51,15 +47,15 @@ describe('Publish endpoints - add a tag', () => {
expect(next).toHaveBeenLastCalledWith({ ok: 'package published' });
});
test('when failed to add a version', (done) => {
test('when failed to add a version', done => {
const storage = {
addVersion: (packageName, version, body, tag, cb) => {
const error = {
message: 'failure'
message: 'failure',
};
cb(error);
done();
}
},
};
// @ts-ignore
@ -81,10 +77,10 @@ describe('Publish endpoints - upload package tarball', () => {
req = {
params: {
filename: 'verdaccio.gzip',
package: 'verdaccio'
package: 'verdaccio',
},
pipe: jest.fn(),
on: jest.fn()
on: jest.fn(),
};
res = { status: jest.fn(), report_error: jest.fn() };
next = jest.fn();
@ -94,14 +90,14 @@ describe('Publish endpoints - upload package tarball', () => {
const stream = {
done: jest.fn(),
abort: jest.fn(),
on: jest.fn(() => (status, cb) => cb())
on: jest.fn(() => (status, cb) => cb()),
};
const storage = {
addTarball(packageName, filename) {
expect(packageName).toEqual(req.params.package);
expect(filename).toEqual(req.params.filename);
return stream;
}
},
};
// @ts-ignore
@ -124,14 +120,14 @@ describe('Publish endpoints - delete tarball', () => {
params: {
filename: 'verdaccio.gzip',
package: 'verdaccio',
revision: REVISION_MOCK
}
revision: REVISION_MOCK,
},
};
res = { status: jest.fn() };
next = jest.fn();
});
test('should delete tarball successfully', (done) => {
test('should delete tarball successfully', done => {
const storage = {
removeTarball(packageName, filename, revision, cb) {
expect(packageName).toEqual(req.params.package);
@ -139,7 +135,7 @@ describe('Publish endpoints - delete tarball', () => {
expect(revision).toEqual(req.params.revision);
cb();
done();
}
},
};
// @ts-ignore
@ -148,15 +144,15 @@ describe('Publish endpoints - delete tarball', () => {
expect(next).toHaveBeenCalledWith({ ok: 'tarball removed' });
});
test('failed while deleting the tarball', (done) => {
test('failed while deleting the tarball', done => {
const error = {
message: 'deletion failed'
message: 'deletion failed',
};
const storage = {
removeTarball(packageName, filename, revision, cb) {
cb(error);
done();
}
},
};
// @ts-ignore
@ -176,20 +172,20 @@ describe('Publish endpoints - un-publish package', () => {
beforeEach(() => {
req = {
params: {
package: 'verdaccio'
}
package: 'verdaccio',
},
};
res = { status: jest.fn() };
next = jest.fn();
});
test('should un-publish package successfully', (done) => {
test('should un-publish package successfully', done => {
const storage = {
removePackage(packageName, cb) {
expect(packageName).toEqual(req.params.package);
cb();
done();
}
},
};
// @ts-ignore
@ -198,15 +194,15 @@ describe('Publish endpoints - un-publish package', () => {
expect(next).toHaveBeenCalledWith({ ok: 'package removed' });
});
test('un-publish failed', (done) => {
test('un-publish failed', done => {
const error = {
message: 'un-publish failed'
message: 'un-publish failed',
};
const storage = {
removePackage(packageName, cb) {
cb(error);
done();
}
},
};
// @ts-ignore
@ -226,11 +222,11 @@ describe('Publish endpoints - publish package', () => {
beforeEach(() => {
req = {
body: {
name: 'verdaccio'
name: 'verdaccio',
},
params: {
package: 'verdaccio'
}
package: 'verdaccio',
},
};
res = { status: jest.fn() };
next = jest.fn();
@ -238,7 +234,7 @@ describe('Publish endpoints - publish package', () => {
test('should change the existing package', () => {
const storage = {
changePackage: jest.fn()
changePackage: jest.fn(),
};
req.params._rev = REVISION_MOCK;
@ -250,7 +246,7 @@ describe('Publish endpoints - publish package', () => {
test('should publish a new a new package', () => {
const storage = {
addPackage: jest.fn()
addPackage: jest.fn(),
};
// @ts-ignore
@ -262,7 +258,7 @@ describe('Publish endpoints - publish package', () => {
const storage = {
addPackage() {
throw new Error();
}
},
};
// @ts-ignore
@ -276,23 +272,23 @@ describe('Publish endpoints - publish package', () => {
changePackage: jest.fn(),
getPackage({ callback }) {
callback(null, {
users: {}
users: {},
});
}
},
};
req = {
params: {
package: 'verdaccio'
package: 'verdaccio',
},
body: {
_rev: REVISION_MOCK,
users: {
verdaccio: true
}
verdaccio: true,
},
},
remote_user: {
name: 'verdaccio'
}
name: 'verdaccio',
},
};
// @ts-ignore

View file

@ -12,9 +12,8 @@ import fs from 'fs';
app.param('token', validate_name);
*/
describe('api endpoint app.param()', () => {
const file = '../endpoint/index.ts';
let m;
const requirePath = path.normalize(path.join(__dirname + '/../../../../src/api/web/', file));
const requirePath = path.normalize(path.join(__dirname, '../src/index.ts'));
const source = fs.readFileSync(requirePath, 'utf8');
const very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g;
const appParams = {};

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./build"
},
"include": ["src/**/*", "types/*.d.ts"],
"exclude": ["src/**/*.test.ts"]
}

3
packages/auth/.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": [["@verdaccio"]]
}

View file

@ -0,0 +1,9 @@
module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
verbose: true,
collectCoverage: true,
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
};

View file

@ -0,0 +1,39 @@
{
"name": "@verdaccio/auth",
"version": "5.0.0-alpha.0",
"description": "logger",
"main": "./build/index.js",
"types": "build/index.d.ts",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/verdaccio/verdaccio"
},
"homepage": "https://verdaccio.org",
"scripts": {
"clean": "rimraf ./build",
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
"type-check": "tsc --noEmit",
"build:types": "tsc --emitDeclarationOnly --declaration true",
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
"build": "npm run build:js && npm run build:types"
},
"license": "MIT",
"dependencies": {
"@verdaccio/commons-api": "^9.0.0",
"@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/loaders": "5.0.0-alpha.0",
"@verdaccio/utils": "5.0.0-alpha.0",
"bunyan": "1.8.12",
"express": "^4.17.1",
"lodash": "^4.17.15"
},
"devDependencies": {
"@types/bunyan": "1.8.6",
"@verdaccio/dev-types": "5.0.0-alpha.0"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
}

View file

@ -1,13 +1,10 @@
import _ from 'lodash';
import { VerdaccioError } from '@verdaccio/commons-api';
import buildDebug from 'debug';
import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types';
import { NextFunction } from 'express';
import loadPlugin from '../lib/plugin-loader';
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '../../types';
import { API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER } from './constants';
import { aesEncrypt, signPayload } from './crypto-utils';
import { logger } from './logger';
import { VerdaccioError } from '@verdaccio/commons-api';
import {API_ERROR, SUPPORT_ERRORS, TOKEN_BASIC, TOKEN_BEARER} from '@verdaccio/dev-commons';
import { loadPlugin } from '@verdaccio/loaders';
import { aesEncrypt, signPayload } from '@verdaccio/utils';
import {
getDefaultPlugins,
getMiddlewareCredentials,
@ -16,24 +13,29 @@ import {
isAuthHeaderValid,
getSecurity,
isAESLegacy,
convertPayloadToBase64,
ErrorCode,
parseAuthTokenHeader,
parseBasicPayload,
createRemoteUser,
} from './auth-utils';
import { convertPayloadToBase64, ErrorCode } from './utils';
import { getMatchedPackagesSpec } from './config-utils';
} from '@verdaccio/utils';
const debug = buildDebug('verdaccio:auth');
import { getMatchedPackagesSpec } from '@verdaccio/utils';
import { Config, Logger, Callback, IPluginAuth, RemoteUser, JWTSignOptions, Security, AuthPluginPackage, AllowAccess, PackageAccess } from '@verdaccio/types';
import { $RequestExtend, $ResponseExtend, IAuth, AESPayload } from '@verdaccio/dev-types';
/* eslint-disable @typescript-eslint/no-var-requires */
const LoggerApi = require('@verdaccio/logger');
class Auth implements IAuth {
public config: Config;
public logger: Logger;
public secret: string; // pragma: allowlist secret
public secret: string;
public plugins: IPluginAuth<Config>[];
public constructor(config: Config) {
this.config = config;
this.logger = logger;
this.logger = LoggerApi.logger.child({ sub: 'auth' });
this.secret = config.secret;
this.plugins = this._loadPlugin(config);
this._applyDefaultPlugins();
@ -45,49 +47,56 @@ class Auth implements IAuth {
logger: this.logger,
};
return loadPlugin<IPluginAuth<Config>>(config, config.auth, pluginOptions, (plugin: IPluginAuth<Config>): boolean => {
const { authenticate, allow_access, allow_publish } = plugin;
// @ts-ignore
return authenticate || allow_access || allow_publish;
});
return loadPlugin<IPluginAuth<Config>>(
config,
config.auth,
pluginOptions,
(plugin: IPluginAuth<Config>): boolean => {
const { authenticate, allow_access, allow_publish } = plugin;
// @ts-ignore
return authenticate || allow_access || allow_publish;
}
);
}
private _applyDefaultPlugins(): void {
this.plugins.push(getDefaultPlugins(this.logger));
this.plugins.push(getDefaultPlugins());
}
public changePassword(
username: string,
password: string, // pragma: allowlist secret
newPassword: string, // pragma: allowlist secret
cb: Callback
): void {
const validPlugins = _.filter(this.plugins, (plugin) => _.isFunction(plugin.changePassword));
public changePassword(username: string, password: string, newPassword: string, cb: Callback): void {
const validPlugins = _.filter(this.plugins, plugin => _.isFunction(plugin.changePassword));
if (_.isEmpty(validPlugins)) {
return cb(ErrorCode.getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
}
if (_.isEmpty(validPlugins)) {
return cb(ErrorCode.getInternalError(SUPPORT_ERRORS.PLUGIN_MISSING_INTERFACE));
}
for (const plugin of validPlugins) {
if (_.isNil(plugin) || _.isFunction(plugin.changePassword) === false) {
debug('auth plugin does not implement changePassword, trying next one');
continue;
} else {
debug('updating password for %o', username);
plugin.changePassword!(username, password, newPassword, (err, profile): void => {
if (err) {
this.logger.error(
{ username, err },
`An error has been produced
for (const plugin of validPlugins) {
if (_.isNil(plugin) || _.isFunction(plugin.changePassword) === false) {
this.logger.trace('auth plugin does not implement changePassword, trying next one');
continue;
} else {
this.logger.trace({username}, 'updating password for @{username}');
plugin.changePassword!(
username,
password,
newPassword,
(err, profile): void => {
if (err) {
this.logger.error(
{username, err},
`An error has been produced
updating the password for @{username}. Error: @{err.message}`
);
return cb(err);
}
this.logger.info({ username }, 'updated password for @{username} was successful');
return cb(null, profile);
});
}
}
);
return cb(err);
}
this.logger.trace({username}, 'updated password for @{username} was successful');
return cb(null, profile);
}
);
}
}
}
public authenticate(username: string, password: string, cb: Callback): void {
@ -95,13 +104,15 @@ class Auth implements IAuth {
const self = this;
(function next(): void {
const plugin = plugins.shift() as IPluginAuth<Config>;
if (_.isFunction(plugin.authenticate) === false) {
return next();
}
debug('authenticating %o', username);
plugin.authenticate(username, password, function (err, groups): void {
self.logger.trace({ username }, 'authenticating @{username}');
plugin.authenticate(username, password, function(err, groups): void {
if (err) {
self.logger.error({ username, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
self.logger.trace({ username, err }, 'authenticating for user @{username} failed. Error: @{err.message}');
return cb(err);
}
@ -121,7 +132,8 @@ class Auth implements IAuth {
if (!isGroupValid) {
throw new TypeError(API_ERROR.BAD_FORMAT_USER_GROUP);
}
debug('authentication for user %o was successfully. Groups: %o', username, groups);
self.logger.trace({ username, groups }, 'authentication for user @{username} was successfully. Groups: @{groups}');
return cb(err, createRemoteUser(username, groups));
}
next();
@ -132,7 +144,8 @@ class Auth implements IAuth {
public add_user(user: string, password: string, cb: Callback): void {
const self = this;
const plugins = this.plugins.slice(0);
debug('add user %o', user);
this.logger.trace({ user }, 'add user @{user}');
(function next(): void {
const plugin = plugins.shift() as IPluginAuth<Config>;
let method = 'adduser';
@ -145,13 +158,13 @@ class Auth implements IAuth {
next();
} else {
// p.add_user() execution
plugin[method](user, password, function (err, ok): void {
plugin[method](user, password, function(err, ok): void {
if (err) {
self.logger.error({ user, err: err.message }, 'the user @{user} could not being added. Error: @{err}');
self.logger.trace({ user, err: err.message }, 'the user @{user} could not being added. Error: @{err}');
return cb(err);
}
if (ok) {
self.logger.info({ user }, 'the user @{user} has been added');
self.logger.trace({ user }, 'the user @{user} has been added');
return self.authenticate(user, password, cb);
}
next();
@ -165,10 +178,10 @@ class Auth implements IAuth {
*/
public allow_access({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback): void {
const plugins = this.plugins.slice(0);
const self = this;
const pkgAllowAcces: AllowAccess = { name: packageName, version: packageVersion };
const pkg = Object.assign({}, pkgAllowAcces, getMatchedPackagesSpec(packageName, this.config.packages)) as AllowAccess & PackageAccess;
debug('allow access for %o', packageName);
const self = this;
this.logger.trace({ packageName }, 'allow access for @{packageName}');
(function next(): void {
const plugin: IPluginAuth<Config> = plugins.shift() as IPluginAuth<Config>;
@ -177,14 +190,14 @@ class Auth implements IAuth {
return next();
}
plugin.allow_access!(user, pkg, function (err, ok: boolean): void {
plugin.allow_access!(user, pkg, function(err, ok: boolean): void {
if (err) {
self.logger.error({ packageName, err }, 'forbidden access for @{packageName}. Error: @{err.message}');
self.logger.trace({ packageName, err }, 'forbidden access for @{packageName}. Error: @{err.message}');
return callback(err);
}
if (ok) {
self.logger.info({ packageName }, 'allowed access for @{packageName}');
self.logger.trace({ packageName }, 'allowed access for @{packageName}');
return callback(null, ok);
}
@ -195,30 +208,35 @@ class Auth implements IAuth {
public allow_unpublish({ packageName, packageVersion }: AuthPluginPackage, user: RemoteUser, callback: Callback): void {
const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages));
debug('allow unpublish for %o', packageName);
this.logger.trace({ packageName }, 'allow unpublish for @{packageName}');
for (const plugin of this.plugins) {
if (_.isNil(plugin) || _.isFunction(plugin.allow_unpublish) === false) {
debug('allow unpublish for %o plugin does not implement allow_unpublish', packageName);
this.logger.trace({ packageName }, 'allow unpublish for @{packageName} plugin does not implement allow_unpublish');
continue;
} else {
plugin.allow_unpublish!(user, pkg, (err, ok: boolean): void => {
if (err) {
this.logger.error({ packageName, user: user?.name }, '@{user} forbidden publish for @{packageName}, it will fallback on unpublish permissions');
return callback(err);
}
plugin.allow_unpublish!(
user,
pkg,
(err, ok: boolean): void => {
if (err) {
this.logger.trace({ packageName }, 'forbidden publish for @{packageName}, it will fallback on unpublish permissions');
return callback(err);
}
if (_.isNil(ok) === true) {
debug('we bypass unpublish for %o, publish will handle the access', packageName);
// @ts-ignore
// eslint-disable-next-line
return this.allow_publish(...arguments);
}
if (_.isNil(ok) === true) {
this.logger.trace({ packageName }, 'we bypass unpublish for @{packageName}, publish will handle the access');
// @ts-ignore
// eslint-disable-next-line
return this.allow_publish(...arguments);
}
if (ok) {
this.logger.info({ packageName, user: user?.name }, '@{user} allowed unpublish for @{packageName}');
return callback(null, ok);
if (ok) {
this.logger.trace({ packageName }, 'allowed unpublish for @{packageName}');
return callback(null, ok);
}
}
});
);
}
}
}
@ -230,29 +248,36 @@ class Auth implements IAuth {
const plugins = this.plugins.slice(0);
const self = this;
const pkg = Object.assign({ name: packageName, version: packageVersion }, getMatchedPackagesSpec(packageName, this.config.packages));
debug('allow publish for %o init | plugins: %o', packageName, plugins);
this.logger.trace({ packageName, plugins: this.plugins.length }, 'allow publish for @{packageName} init | plugins: @{plugins}');
(function next(): void {
const plugin = plugins.shift();
if (_.isNil(plugin) || _.isFunction(plugin.allow_publish) === false) {
debug('allow publish for %o plugin does not implement allow_publish', packageName);
self.logger.trace({ packageName }, 'allow publish for @{packageName} plugin does not implement allow_publish');
return next();
}
// @ts-ignore
plugin.allow_publish(user, pkg, (err: VerdaccioError, ok: boolean): void => {
if (_.isNil(err) === false && _.isError(err)) {
self.logger.error({ packageName, user: user?.name }, '@{user} is forbidden publish for @{packageName}');
return callback(err);
}
plugin.allow_publish(
user,
pkg,
// @ts-ignore
(err: VerdaccioError, ok: boolean): void => {
if (_.isNil(err) === false && _.isError(err)) {
self.logger.trace({ packageName }, 'forbidden publish for @{packageName}');
return callback(err);
}
if (ok) {
self.logger.info({ packageName, user: user?.name }, '@{user} is allowed publish for @{packageName}');
return callback(null, ok);
if (ok) {
self.logger.trace({ packageName }, 'allowed publish for @{packageName}');
return callback(null, ok);
}
self.logger.trace({ packageName }, 'allow publish skip validation for @{packageName}');
next(); // cb(null, false) causes next plugin to roll
}
debug('allow publish skip validation for %o', packageName);
next(); // cb(null, false) causes next plugin to roll
});
);
})();
}
@ -268,7 +293,7 @@ class Auth implements IAuth {
return (req: $RequestExtend, res: $ResponseExtend, _next: NextFunction): void => {
req.pause();
const next = function (err: VerdaccioError | void): void {
const next = function(err: VerdaccioError | void): void {
req.resume();
// uncomment this to reject users with bad auth headers
// return _next.apply(null, arguments)
@ -293,7 +318,7 @@ class Auth implements IAuth {
}
if (!isAuthHeaderValid(authorization)) {
debug('api middleware auth heather is not valid');
this.logger.trace('api middleware auth heather is not valid');
return next(ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER));
}
@ -301,10 +326,10 @@ class Auth implements IAuth {
const { secret } = this.config;
if (isAESLegacy(security)) {
debug('api middleware using legacy auth token');
this.logger.trace('api middleware using legacy auth token');
this._handleAESMiddleware(req, security, secret, authorization, next);
} else {
debug('api middleware using JWT auth token');
this.logger.trace('api middleware using JWT auth token');
this._handleJWTAPIMiddleware(req, security, secret, authorization, next);
}
};
@ -316,15 +341,19 @@ class Auth implements IAuth {
// this should happen when client tries to login with an existing user
const credentials = convertPayloadToBase64(token).toString();
const { user, password } = parseBasicPayload(credentials) as AESPayload;
this.authenticate(user, password, (err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
this.authenticate(
user,
password,
(err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
}
}
});
);
} else {
// jwt handler
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
@ -343,15 +372,19 @@ class Auth implements IAuth {
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
if (credentials) {
const { user, password } = credentials;
this.authenticate(user, password, (err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
this.authenticate(
user,
password,
(err, user): void => {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = createAnonymousRemoteUser();
next(err);
}
}
});
);
} else {
// we force npm client to ask again with basic authentication
return next(ErrorCode.getBadRequest(API_ERROR.BAD_AUTH_HEADER));
@ -438,4 +471,4 @@ class Auth implements IAuth {
}
}
export default Auth;
export { Auth };

View file

@ -0,0 +1 @@
export { Auth } from './auth'

View file

@ -1,27 +1,35 @@
import _ from 'lodash';
import Auth from '../../../../src/lib/auth';
import { CHARACTER_ENCODING, TOKEN_BEARER } from '../../../../src/lib/constants';
// $FlowFixMe
import configExample from '../../partials/config';
import AppConfig from '../../../../src/lib/config';
import { setup } from '../../../../src/lib/logger';
import { Auth } from '@verdaccio/auth';
import {CHARACTER_ENCODING, TOKEN_BEARER} from '@verdaccio/dev-commons';
import { configExample } from '@verdaccio/mock';
import {Config as AppConfig } from '@verdaccio/config';
import {setup} from '@verdaccio/logger';
import { buildToken, convertPayloadToBase64, parseConfigFile } from '../../../../src/lib/utils';
import {
buildUserBuffer,
getApiToken,
getAuthenticatedMessage,
getMiddlewareCredentials,
getSecurity
} from '../../../../src/lib/auth-utils';
import { aesDecrypt, verifyPayload } from '../../../../src/lib/crypto-utils';
import { parseConfigurationFile } from '../../__helper';
getSecurity,
aesDecrypt, verifyPayload,
buildToken, convertPayloadToBase64, parseConfigFile
} from '@verdaccio/utils';
import { IAuth } from '../../../../types';
import { Config, Security, RemoteUser } from '@verdaccio/types';
import { IAuth } from '@verdaccio/dev-types';
import {Config, Security, RemoteUser} from '@verdaccio/types';
import path from "path";
setup([]);
const parseConfigurationFile = (conf) => {
const { name, ext } = path.parse(conf);
const format = ext.startsWith('.') ? ext.substring(1) : 'yaml';
return path.join(__dirname, `./partials/config/${format}/security/${name}.${format}`);
};
describe('Auth utilities', () => {
jest.setTimeout(20000);
@ -31,7 +39,7 @@ describe('Auth utilities', () => {
function getConfig(configFileName: string, secret: string) {
const conf = parseConfigFile(parseConfigurationSecurityFile(configFileName));
const secConf = _.merge(configExample(), conf);
const secConf= _.merge(configExample(), conf);
secConf.secret = secret;
const config: Config = new AppConfig(secConf);
@ -44,8 +52,7 @@ describe('Auth utilities', () => {
password: string,
secret = '12345',
methodToSpy: string,
methodNotBeenCalled: string
): Promise<string> {
methodNotBeenCalled: string): Promise<string> {
const config: Config = getConfig(configFileName, secret);
const auth: IAuth = new Auth(config);
// @ts-ignore
@ -74,9 +81,7 @@ describe('Auth utilities', () => {
};
const verifyAES = (token: string, user: string, password: string, secret: string) => {
const payload = aesDecrypt(convertPayloadToBase64(token), secret).toString(
CHARACTER_ENCODING.UTF8
);
const payload = aesDecrypt(convertPayloadToBase64(token), secret).toString(CHARACTER_ENCODING.UTF8);
const content = payload.split(':');
expect(content[0]).toBe(user);
@ -85,98 +90,56 @@ describe('Auth utilities', () => {
describe('getApiToken test', () => {
test('should sign token with aes and security missing', async () => {
const token = await signCredentials(
'security-missing',
'test',
'test',
'1234567',
'aesEncrypt',
'jwtEncrypt'
);
const token = await signCredentials('security-missing',
'test', 'test', '1234567', 'aesEncrypt', 'jwtEncrypt');
verifyAES(token, 'test', 'test', '1234567');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with aes and security empty', async () => {
const token = await signCredentials(
'security-empty',
'test',
'test',
'123456',
'aesEncrypt',
'jwtEncrypt'
);
const token = await signCredentials('security-empty',
'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
verifyAES(token, 'test', 'test', '123456');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with aes', async () => {
const token = await signCredentials(
'security-basic',
'test',
'test',
'123456',
'aesEncrypt',
'jwtEncrypt'
);
const token = await signCredentials('security-basic',
'test', 'test', '123456', 'aesEncrypt', 'jwtEncrypt');
verifyAES(token, 'test', 'test', '123456');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with legacy and jwt disabled', async () => {
const token = await signCredentials(
'security-no-legacy',
'test',
'test',
'x8T#ZCx=2t',
'aesEncrypt',
'jwtEncrypt'
);
const token = await signCredentials('security-no-legacy',
'test', 'test', 'x8T#ZCx=2t', 'aesEncrypt', 'jwtEncrypt');
expect(_.isString(token)).toBeTruthy();
verifyAES(token, 'test', 'test', 'x8T#ZCx=2t');
});
test('should sign token with legacy enabled and jwt enabled', async () => {
const token = await signCredentials(
'security-jwt-legacy-enabled',
'test',
'test',
'secret',
'jwtEncrypt',
'aesEncrypt'
);
const token = await signCredentials('security-jwt-legacy-enabled',
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
verifyJWT(token, 'test', 'test', 'secret');
expect(_.isString(token)).toBeTruthy();
});
test('should sign token with jwt enabled', async () => {
const token = await signCredentials(
'security-jwt',
'test',
'test',
'secret',
'jwtEncrypt',
'aesEncrypt'
);
const token = await signCredentials('security-jwt',
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
expect(_.isString(token)).toBeTruthy();
verifyJWT(token, 'test', 'test', 'secret');
});
test('should sign with jwt whether legacy is disabled', async () => {
const token = await signCredentials(
'security-legacy-disabled',
'test',
'test',
'secret',
'jwtEncrypt',
'aesEncrypt'
);
const token = await signCredentials('security-legacy-disabled',
'test', 'test', 'secret', 'jwtEncrypt', 'aesEncrypt');
expect(_.isString(token)).toBeTruthy();
verifyJWT(token, 'test', 'test', 'secret');
@ -185,7 +148,7 @@ describe('Auth utilities', () => {
describe('getAuthenticatedMessage test', () => {
test('should sign token with jwt enabled', () => {
expect(getAuthenticatedMessage('test')).toBe("you are authenticated as 'test'");
expect(getAuthenticatedMessage('test')).toBe('you are authenticated as \'test\'');
});
});
@ -195,14 +158,8 @@ describe('Auth utilities', () => {
const secret = 'secret';
const user = 'test';
const pass = 'test';
const token = await signCredentials(
'security-legacy',
user,
pass,
secret,
'aesEncrypt',
'jwtEncrypt'
);
const token = await signCredentials('security-legacy',
user, pass, secret, 'aesEncrypt', 'jwtEncrypt');
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(security, secret, `Bearer ${token}`);
@ -230,41 +187,21 @@ describe('Auth utilities', () => {
test.concurrent('should return empty credential wrong secret key', async () => {
const secret = 'secret';
const token = await signCredentials(
'security-legacy',
'test',
'test',
secret,
'aesEncrypt',
'jwtEncrypt'
);
const token = await signCredentials('security-legacy',
'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(
security,
'BAD_SECRET',
buildToken(TOKEN_BEARER, token)
);
const credentials = getMiddlewareCredentials(security, 'BAD_SECRET', buildToken(TOKEN_BEARER, token));
expect(credentials).not.toBeDefined();
});
test.concurrent('should return empty credential wrong scheme', async () => {
const secret = 'secret';
const token = await signCredentials(
'security-legacy',
'test',
'test',
secret,
'aesEncrypt',
'jwtEncrypt'
);
const token = await signCredentials('security-legacy',
'test', 'test', secret, 'aesEncrypt', 'jwtEncrypt');
const config: Config = getConfig('security-legacy', secret);
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(
security,
secret,
buildToken('BAD_SCHEME', token)
);
const credentials = getMiddlewareCredentials(security, secret, buildToken('BAD_SCHEME', token));
expect(credentials).not.toBeDefined();
});
@ -274,11 +211,7 @@ describe('Auth utilities', () => {
const auth: IAuth = new Auth(config);
const token = auth.aesEncrypt(Buffer.from(`corruptedBuffer`)).toString('base64');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(
security,
secret,
buildToken(TOKEN_BEARER, token)
);
const credentials = getMiddlewareCredentials(security, secret, buildToken(TOKEN_BEARER, token));
expect(credentials).not.toBeDefined();
});
});
@ -287,11 +220,7 @@ describe('Auth utilities', () => {
test('should return anonymous whether token is corrupted', () => {
const config: Config = getConfig('security-jwt', '12345');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(
security,
'12345',
buildToken(TOKEN_BEARER, 'fakeToken')
);
const credentials = getMiddlewareCredentials(security, '12345', buildToken(TOKEN_BEARER, 'fakeToken'));
expect(credentials).toBeDefined();
// @ts-ignore
@ -305,11 +234,7 @@ describe('Auth utilities', () => {
test('should return anonymous whether token and scheme are corrupted', () => {
const config: Config = getConfig('security-jwt', '12345');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(
security,
'12345',
buildToken('FakeScheme', 'fakeToken')
);
const credentials = getMiddlewareCredentials(security, '12345', buildToken('FakeScheme', 'fakeToken'));
expect(credentials).not.toBeDefined();
});
@ -318,20 +243,10 @@ describe('Auth utilities', () => {
const secret = 'secret';
const user = 'test';
const config: Config = getConfig('security-jwt', secret);
const token = await signCredentials(
'security-jwt',
user,
'secretTest',
secret,
'jwtEncrypt',
'aesEncrypt'
);
const token = await signCredentials('security-jwt',
user, 'secretTest', secret, 'jwtEncrypt', 'aesEncrypt');
const security: Security = getSecurity(config);
const credentials = getMiddlewareCredentials(
security,
secret,
buildToken(TOKEN_BEARER, token)
);
const credentials = getMiddlewareCredentials(security, secret, buildToken(TOKEN_BEARER, token));
expect(credentials).toBeDefined();
// @ts-ignore
expect(credentials.name).toEqual(user);

View file

@ -0,0 +1,14 @@
import {aesDecrypt, aesEncrypt, convertPayloadToBase64} from "@verdaccio/utils";
describe('test crypto utils', () => {
describe('default encryption', () => {
test('decrypt payload flow', () => {
const payload = 'juan';
const token = aesEncrypt(Buffer.from(payload), '12345').toString('base64');
const data = aesDecrypt(convertPayloadToBase64(token), '12345').toString('utf8');
expect(payload).toEqual(data);
});
});
});

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./build"
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts"]
}

3
packages/cli/.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": [["@verdaccio"]]
}

3
packages/cli/bin/verdaccio Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env node
require('../build');

View file

@ -0,0 +1,9 @@
module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
verbose: true,
collectCoverage: true,
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
};

40
packages/cli/package.json Normal file
View file

@ -0,0 +1,40 @@
{
"name": "@verdaccio/cli",
"version": "5.0.0-alpha.0",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
},
"bin": {
"verdaccio": "./bin/verdaccio",
"verdaccio-cli": "./bin/verdaccio"
},
"repository": {
"type": "git",
"url": "git://github.com/verdaccio/verdaccio"
},
"homepage": "https://verdaccio.org",
"description": "verdaccio CLI",
"license": "MIT",
"main": "./build/index.js",
"types": "build/index.d.ts",
"scripts": {
"clean": "rimraf ./build",
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
"type-check": "tsc --noEmit",
"build:types": "tsc --emitDeclarationOnly --declaration true",
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
"build": "npm run build:js && npm run build:types",
"bundle": "cross-env BABEL_ENV=registry webpack --config scripts/bundle.js --profile --json > compilation-stats.json"
},
"dependencies": {
"@verdaccio/config": "5.0.0-alpha.0",
"@verdaccio/node-api": "5.0.0-alpha.0",
"@verdaccio/utils": "5.0.0-alpha.0",
"commander": "3.0.2",
"envinfo": "7.4.0",
"kleur": "3.0.3",
"semver": "7.1.2"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
}

View file

@ -0,0 +1,5 @@
{
"rules": {
"no-console": 0
}
}

60
packages/cli/src/cli.ts Normal file
View file

@ -0,0 +1,60 @@
/* eslint no-sync:0 */
/* eslint no-empty:0 */
import commander from 'commander';
import { bgYellow, bgRed } from 'kleur';
import { setup, logger } from '@verdaccio/logger';
import infoCommand from "./commands/info";
import initProgram from "./commands/init";
import {isVersionValid, MIN_NODE_VERSION} from "./utils";
const isRootUser = process.getuid && process.getuid() === 0;
if (isRootUser) {
global.console.warn(bgYellow().red('*** WARNING: Verdaccio doesn\'t need superuser privileges. Don\'t run it under root! ***'));
}
if (isVersionValid()) {
global.console.error(bgRed(`Verdaccio requires at least Node.js ${MIN_NODE_VERSION} or higher, please upgrade your Node.js distribution`));
process.exit(1);
}
process.title = 'verdaccio';
// default setup
setup(null, {logStart: false});
const pkgVersion = '5.0.0';
const pkgName = 'verdaccio';
commander
.option('-i, --info', 'prints debugging information about the local environment')
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
.option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)')
.version(pkgVersion)
.parse(process.argv);
const fallbackConfig = commander.args.length == 1 && !commander.config;
const isHelp = commander.args.length !== 0;
if (commander.info) {
infoCommand();
} else if (fallbackConfig) {
// handling "verdaccio [config]" case if "-c" is missing in command line
commander.config = commander.args.pop();
initProgram(commander, pkgVersion, pkgName);
} else if (isHelp) {
commander.help();
} else {
initProgram(commander, pkgVersion, pkgName);
}
process.on('uncaughtException', function(err) {
logger.fatal( {
err: err,
},
'uncaught exception, please report (https://github.com/verdaccio/verdaccio/issues) this: \n@{err.stack}' );
process.exit(255);
});

View file

@ -0,0 +1,18 @@
import envinfo from 'envinfo';
export default function infoCommand() {
// eslint-disable-next-line no-console
console.log('\nEnvironment Info:');
(async () => {
const data = await envinfo.run({
System: ['OS', 'CPU'],
Binaries: ['Node', 'Yarn', 'npm'],
Virtualization: ['Docker'],
Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
npmGlobalPackages: ['verdaccio'],
});
// eslint-disable-next-line no-console
console.log(data);
process.exit(0);
})();
}

View file

@ -0,0 +1,42 @@
import path from "path";
import _ from 'lodash';
import { parseConfigFile} from "@verdaccio/utils";
import { findConfigFile } from "@verdaccio/config";
import { logger } from '@verdaccio/logger';
import {startVerdaccio, listenDefaultCallback} from "@verdaccio/node-api";
export const DEFAULT_PROCESS_NAME: string = 'verdaccio';
export default function initProgram(commander, pkgVersion, pkgName) {
const cliListener = commander.listen;
let configPathLocation;
let verdaccioConfiguration;
try {
configPathLocation = findConfigFile(commander.config);
verdaccioConfiguration = parseConfigFile(configPathLocation);
const { web, https, self_path } = verdaccioConfiguration;
process.title = web && web.title || DEFAULT_PROCESS_NAME;
// note: self_path is only being used by @verdaccio/storage , not really useful and migth be removed soon
if (!self_path) {
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
self_path: path.resolve(configPathLocation)
});
}
if (!https) {
verdaccioConfiguration = _.assign({}, verdaccioConfiguration, {
https: {enable: false}
});
}
logger.warn({file: configPathLocation}, 'config file - @{file}');
startVerdaccio(verdaccioConfiguration, cliListener, configPathLocation, pkgVersion, pkgName, listenDefaultCallback);
} catch (err) {
logger.fatal({file: configPathLocation, err: err}, 'cannot open config file @{file}: @{!err.message}');
process.exit(1);
}
}

View file

@ -0,0 +1 @@
require('./cli');

View file

@ -0,0 +1,5 @@
import semver from "semver";
export const MIN_NODE_VERSION = '6.9.0';
export const isVersionValid = () => semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`) === false

View file

@ -0,0 +1,3 @@
describe('cli test', () => {
test.todo('write some test for this module');
});

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./build"
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts"]
}

View file

@ -0,0 +1,3 @@
{
"presets": [["@verdaccio"]]
}

View file

@ -0,0 +1,25 @@
{
"name": "@verdaccio/dev-commons",
"version": "5.0.0-alpha.0",
"description": "loaders logic",
"main": "./build/index.js",
"types": "build/index.d.ts",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/verdaccio/verdaccio"
},
"homepage": "https://verdaccio.org",
"license": "MIT",
"scripts": {
"clean": "rimraf ./build",
"type-check": "tsc --noEmit",
"build:types": "tsc --emitDeclarationOnly --declaration true",
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
"build": "npm run build:js && npm run build:types"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
}

View file

@ -1,9 +1,3 @@
/**
* @prettier
*/
// @flow
export const DEFAULT_PORT = '4873';
export const DEFAULT_PROTOCOL = 'http';
export const DEFAULT_DOMAIN = 'localhost';

View file

@ -0,0 +1 @@
export * from './constants';

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./build"
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts"]
}

3
packages/config/.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": [["@verdaccio"]]
}

View file

@ -0,0 +1,9 @@
module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
verbose: true,
collectCoverage: true,
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
};

View file

@ -0,0 +1,32 @@
{
"name": "@verdaccio/config",
"version": "5.0.0-alpha.0",
"description": "logger",
"main": "./build/index.js",
"types": "build/index.d.ts",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/verdaccio/verdaccio"
},
"license": "MIT",
"homepage": "https://verdaccio.org",
"scripts": {
"clean": "rimraf ./build",
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
"type-check": "tsc --noEmit",
"build:types": "tsc --emitDeclarationOnly --declaration true",
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
"build": "npm run build:js && npm run build:types"
},
"dependencies": {
"@verdaccio/utils": "5.0.0-alpha.0"
},
"devDependencies": {
"@types/bunyan": "1.8.6"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
}

View file

@ -1,22 +1,23 @@
import fs from 'fs';
import Path from 'path';
import path from 'path';
import _ from 'lodash';
import Path from 'path';
import { logger } from '@verdaccio/logger';
import mkdirp from 'mkdirp';
import { logger } from './logger';
import { folderExists, fileExists } from './utils';
import { CHARACTER_ENCODING } from './constants';
import { folderExists, fileExists } from '@verdaccio/utils';
import { CHARACTER_ENCODING } from '@verdaccio/dev-commons';
const CONFIG_FILE = 'config.yaml';
const XDG = 'xdg';
const WIN = 'win';
const WIN32 = 'win32';
// eslint-disable-next-line
const pkgJSON = require('../../package.json');
const pkgJSON = require('../package.json');
export type SetupDirectory = {
path: string;
type: string;
type: string
};
/**
@ -34,9 +35,7 @@ function findConfigFile(configPath: string): string {
throw new Error('no configuration files can be processed');
}
const primaryConf: any = _.find(configPaths, (configLocation: any) =>
fileExists(configLocation.path)
);
const primaryConf: any = _.find(configPaths, (configLocation: any) => fileExists(configLocation.path));
if (_.isNil(primaryConf) === false) {
return primaryConf.path;
}
@ -54,8 +53,11 @@ function createConfigFile(configLocation: any): SetupDirectory {
return configLocation;
}
function readDefaultConfig(): string {
return fs.readFileSync(require.resolve('../../conf/default.yaml'), 'utf-8');
export function readDefaultConfig(): Buffer {
const pathDefaultConf: string = path.resolve(__dirname, 'conf/default.yaml');
// @ts-ignore
return fs.readFileSync(pathDefaultConf, CHARACTER_ENCODING.UTF8);
}
function createConfigFolder(configLocation): void {
@ -71,8 +73,7 @@ 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');
let dataDir = 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'));
return defaultConfig.replace(/^storage: .\/storage$/m, `storage: ${dataDir}`);
@ -81,17 +82,13 @@ function updateStorageLinks(configLocation, defaultConfig): string {
}
function getConfigPaths(): SetupDirectory[] {
const listPaths: SetupDirectory[] = [
getXDGDirectory(),
getWindowsDirectory(),
getRelativeDefaultDirectory(),
getOldDirectory()
].reduce(function (acc, currentValue: any): SetupDirectory[] {
if (_.isUndefined(currentValue) === false) {
acc.push(currentValue);
}
return acc;
}, [] as SetupDirectory[]);
const listPaths: SetupDirectory[] = [getXDGDirectory(), getWindowsDirectory(), getRelativeDefaultDirectory(), getOldDirectory()].reduce(
function(acc, currentValue: any): SetupDirectory[] {
if (_.isUndefined(currentValue) === false) {
acc.push(currentValue);
}
return acc;
}, [] as SetupDirectory[]);
return listPaths;
}
@ -102,7 +99,7 @@ const getXDGDirectory = (): SetupDirectory | void => {
if (XDGConfig && folderExists(XDGConfig)) {
return {
path: Path.join(XDGConfig, pkgJSON.name, CONFIG_FILE),
type: XDG
type: XDG,
};
}
};
@ -113,7 +110,7 @@ const getWindowsDirectory = (): SetupDirectory | void => {
if (process.platform === WIN32 && process.env.APPDATA && folderExists(process.env.APPDATA)) {
return {
path: Path.resolve(Path.join(process.env.APPDATA, pkgJSON.name, CONFIG_FILE)),
type: WIN
type: WIN,
};
}
};
@ -121,15 +118,15 @@ const getWindowsDirectory = (): SetupDirectory | void => {
const getRelativeDefaultDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', pkgJSON.name, CONFIG_FILE)),
type: 'def'
type: 'def',
};
};
const getOldDirectory = (): SetupDirectory => {
return {
path: Path.resolve(Path.join('.', CONFIG_FILE)),
type: 'old'
type: 'old',
};
};
export default findConfigFile;
export { findConfigFile };

View file

@ -1,19 +1,23 @@
import assert from 'assert';
import _ from 'lodash';
import assert from 'assert';
import { PackageList, Config as AppConfig, Security, Logger } from '@verdaccio/types';
import { MatchedPackage, StartUpConfig } from '../../types';
import { generateRandomHexString } from './crypto-utils';
import {
getMatchedPackagesSpec,
normalisePackageAccess,
sanityCheckUplinksProps,
uplinkSanityCheck
} from './config-utils';
import { getUserAgent, isObject } from './utils';
import { APP_ERROR } from './constants';
uplinkSanityCheck,
generateRandomHexString,
getUserAgent,
isObject
} from '@verdaccio/utils';
import { APP_ERROR } from '@verdaccio/dev-commons';
import { PackageList, Config as AppConfig, Security, Logger } from '@verdaccio/types';
import { MatchedPackage, StartUpConfig } from '@verdaccio/dev-types';
const LoggerApi = require('@verdaccio/logger');
const LoggerApi = require('./logger');
const strategicConfigProps = ['uplinks', 'packages'];
const allowedEnvConfig = ['http_proxy', 'https_proxy', 'no_proxy'];
@ -57,7 +61,7 @@ class Config implements AppConfig {
assert(_.isObject(config), APP_ERROR.CONFIG_NOT_VALID);
// sanity check for strategic config properties
strategicConfigProps.forEach(function (x): void {
strategicConfigProps.forEach(function(x): void {
if (self[x] == null) {
self[x] = {};
}
@ -75,11 +79,13 @@ class Config implements AppConfig {
this.packages = normalisePackageAccess(self.packages);
// loading these from ENV if aren't in config
allowedEnvConfig.forEach((envConf): void => {
if (!(envConf in self)) {
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
allowedEnvConfig.forEach(
(envConf): void => {
if (!(envConf in self)) {
self[envConf] = process.env[envConf] || process.env[envConf.toUpperCase()];
}
}
});
);
// unique identifier of self server (or a cluster), used to avoid loops
// @ts-ignore
@ -110,4 +116,4 @@ class Config implements AppConfig {
}
}
export default Config;
export { Config };

View file

@ -0,0 +1,2 @@
export * from './config'
export * from './config-path'

View file

@ -1,18 +1,20 @@
import path from 'path';
import _ from 'lodash';
import Config from '../../../../src/lib/config';
import { parseConfigFile } from '../../../../src/lib/utils';
import { DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE } from '../../../../src/lib/constants';
import { setup } from '../../../../src/lib/logger';
import { Config, readDefaultConfig } from '@verdaccio/config';
import { setup } from '@verdaccio/logger';
import {DEFAULT_REGISTRY, DEFAULT_UPLINK, ROLES, WEB_TITLE} from '@verdaccio/dev-commons';
import {parseConfigFile} from '@verdaccio/utils';
setup([]);
const resolveConf = (conf) => {
const { name, ext } = path.parse(conf);
return path.join(__dirname, `../../../../conf/${name}${ext.startsWith('.') ? ext : '.yaml'}`);
return path.join(__dirname, `../src/conf/${name}${ext.startsWith('.') ? ext : '.yaml'}`);
};
const checkDefaultUplink = (config) => {
expect(_.isObject(config.uplinks[DEFAULT_UPLINK])).toBeTruthy();
expect(config.uplinks[DEFAULT_UPLINK].url).toMatch(DEFAULT_REGISTRY);
@ -43,7 +45,7 @@ const checkDefaultConfPackages = (config) => {
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['**'].proxy,).toContainEqual(DEFAULT_UPLINK);
// uplinks
expect(config.uplinks[DEFAULT_UPLINK]).toBeDefined();
expect(config.uplinks[DEFAULT_UPLINK].url).toEqual(DEFAULT_REGISTRY);
@ -67,7 +69,7 @@ const checkDefaultConfPackages = (config) => {
};
describe('Config file', () => {
beforeAll(function () {
beforeAll(function() {
/* eslint no-invalid-this: 0 */
// @ts-ignore
this.config = new Config(parseConfigFile(resolveConf('default')));
@ -93,5 +95,9 @@ describe('Config file', () => {
});
});
describe('Config file', () => {});
describe('Config file', () => {
});
});

View file

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./build"
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts"]
}

3
packages/hooks/.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": [["@verdaccio"]]
}

View file

@ -0,0 +1,9 @@
module.exports = {
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
verbose: true,
collectCoverage: true,
coveragePathIgnorePatterns: ['node_modules', 'fixtures'],
};

View file

@ -0,0 +1,36 @@
{
"name": "@verdaccio/hooks",
"version": "5.0.0-alpha.0",
"description": "loaders logic",
"main": "./build/index.js",
"types": "build/index.d.ts",
"author": {
"name": "Juan Picado",
"email": "juanpicado19@gmail.com"
},
"repository": {
"type": "git",
"url": "git://github.com/verdaccio/verdaccio"
},
"license": "MIT",
"homepage": "https://verdaccio.org",
"dependencies": {
"@verdaccio/commons-api": "^9.0.0",
"@verdaccio/logger": "5.0.0-alpha.0",
"handlebars": "^4.5.3",
"request": "2.87.0"
},
"devDependencies": {
"@verdaccio/dev-commons": "5.0.0-alpha.0",
"@verdaccio/types": "^8.5.2"
},
"scripts": {
"clean": "rimraf ./build",
"test": "cross-env NODE_ENV=test BABEL_ENV=test jest",
"type-check": "tsc --noEmit",
"build:types": "tsc --emitDeclarationOnly --declaration true",
"build:js": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps",
"build": "npm run build:js && npm run build:types"
},
"gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982"
}

View file

@ -0,0 +1 @@
export {handleNotify, notify, sendNotification } from './notify';

View file

@ -0,0 +1,25 @@
import isNil from 'lodash/isNil';
import request, { RequiredUriUrl } from 'request';
import { logger } from '@verdaccio/logger';
import { HTTP_STATUS } from '@verdaccio/commons-api';
export function notifyRequest(options: RequiredUriUrl, content): Promise<any | Error> {
return new Promise(
(resolve, reject): void => {
request(options, function(err, response, body): void {
if (err || response.statusCode >= HTTP_STATUS.BAD_REQUEST) {
const errorMessage = isNil(err) ? response.body : err.message;
logger.error({ errorMessage }, 'notify service has thrown an error: @{errorMessage}');
reject(errorMessage);
}
logger.info({ content }, 'A notification has been shipped: @{content}');
if (isNil(body) === false) {
logger.debug({ body }, ' body: @{body}');
resolve(body);
}
reject(Error('body is missing'));
});
}
);
}

View file

@ -1,14 +1,13 @@
import { parseConfigurationFile } from '../../__helper';
import { parseConfigFile } from '../../../../src/lib/utils';
import { notify } from '../../../../src/lib/notify';
import {parseConfigFile} from '@verdaccio/utils';
import { setup } from '@verdaccio/logger';
import { notifyRequest } from '../../../../src/lib/notify/notify-request';
import { setup } from '../../../../src/lib/logger';
import {notify} from '../src';
import {notifyRequest} from '../src/notify-request';
import {parseConfigurationFile} from './__helper';
setup([]);
jest.mock('./../../../../src/lib/notify/notify-request', () => ({
jest.mock('../src/notify-request', () => ({
notifyRequest: jest.fn((options, content) => Promise.resolve([options, content]))
}));
@ -16,32 +15,29 @@ const parseConfigurationNotifyFile = (name) => {
return parseConfigurationFile(`notify/${name}`);
};
const singleNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.notify'));
const singleHeaderNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.header.notify')
);
const packagePatternNotificationConfig = parseConfigFile(
parseConfigurationNotifyFile('single.packagePattern.notify')
);
const singleHeaderNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.header.notify'));
const packagePatternNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.packagePattern.notify'));
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
describe('Notifications:: Notify', () => {
beforeEach(() => {
jest.clearAllMocks();
});
// FUTURE: we should add some sort of health check of all props, (not implemented yet)
test('should not fails if config is not provided', async () => {
test("should not fails if config is not provided", async () => {
// @ts-ignore
await notify({}, {});
expect(notifyRequest).toHaveBeenCalledTimes(0);
});
test('should send notification', async () => {
test("should send notification", async () => {
const name = 'package';
// @ts-ignore
const response = await notify({ name }, singleNotificationConfig, { name: 'foo' }, 'bar');
const response = await notify({name}, singleNotificationConfig, { name: 'foo'}, 'bar');
const [options, content] = response;
expect(options.headers).toBeDefined();
@ -52,36 +48,39 @@ describe('Notifications:: Notify', () => {
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test('should send single header notification', async () => {
test("should send single header notification", async () => {
// @ts-ignore
await notify({}, singleHeaderNotificationConfig, { name: 'foo' }, 'bar');
await notify({}, singleHeaderNotificationConfig, { name: 'foo'}, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test('should send multiple notification', async () => {
test("should send multiple notification", async () => {
// @ts-ignore
await notify({ name }, multiNotificationConfig, { name: 'foo' }, 'bar');
await notify({name}, multiNotificationConfig, { name: 'foo'}, 'bar');
expect(notifyRequest).toHaveBeenCalled();
expect(notifyRequest).toHaveBeenCalledTimes(3);
});
describe('packagePatternFlags', () => {
test('should send single notification with packagePatternFlags', async () => {
test("should send single notification with packagePatternFlags", async () => {
const name = 'package';
// @ts-ignore
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
await notify({name}, packagePatternNotificationConfig, { name: 'foo'}, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(1);
});
test('should not match on send single notification with packagePatternFlags', async () => {
test("should not match on send single notification with packagePatternFlags", async () => {
const name = 'no-mach-name';
// @ts-ignore
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
await notify({name}, packagePatternNotificationConfig, { name: 'foo'}, 'bar');
expect(notifyRequest).toHaveBeenCalledTimes(0);
});
});
})
});

View file

@ -1,4 +1,4 @@
import { HTTP_STATUS, API_ERROR } from '../../../../src/lib/constants';
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
/* eslint-disable @typescript-eslint/no-var-requires */
/**
@ -8,16 +8,16 @@ const logger = {
logger: {
error: jest.fn(),
debug: jest.fn(),
info: jest.fn()
}
info: jest.fn(),
},
};
jest.doMock('../../../../src/lib/logger', () => logger);
jest.doMock('@verdaccio/logger', () => logger);
/**
* Test Data
*/
const options = {
url: 'http://slack-service'
url: 'http://slack-service',
};
const content = 'Verdaccio@x.x.x successfully published';
@ -29,19 +29,16 @@ describe('Notifications:: notifyRequest', () => {
test('when notification service throws error', async () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.BAD_REQUEST
statusCode: HTTP_STATUS.BAD_REQUEST,
};
const error = {
message: API_ERROR.BAD_DATA
message: API_ERROR.BAD_DATA,
};
resolver(error, response);
});
const notification = require('../../../../src/lib/notify/notify-request');
const args = [
{ errorMessage: 'bad data' },
'notify service has thrown an error: @{errorMessage}'
];
const notification = require('../src/notify-request');
const args = [{ errorMessage: 'bad data' }, 'notify service has thrown an error: @{errorMessage}'];
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
expect(logger.logger.error).toHaveBeenCalledWith(...args);
@ -51,17 +48,14 @@ describe('Notifications:: notifyRequest', () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.BAD_REQUEST,
body: API_ERROR.BAD_DATA
body: API_ERROR.BAD_DATA,
};
resolver(null, response);
});
const notification = require('../../../../src/lib/notify/notify-request');
const args = [
{ errorMessage: 'bad data' },
'notify service has thrown an error: @{errorMessage}'
];
const notification = require('../src/notify-request');
const args = [{ errorMessage: 'bad data' }, 'notify service has thrown an error: @{errorMessage}'];
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
expect(logger.logger.error).toHaveBeenCalledWith(...args);
@ -71,19 +65,17 @@ describe('Notifications:: notifyRequest', () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.OK,
body: 'Successfully delivered'
body: 'Successfully delivered',
};
resolver(null, response, response.body);
});
const notification = require('../../../../src/lib/notify/notify-request');
const notification = require('../src/notify-request');
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
const debugArgs = [{ body: 'Successfully delivered' }, ' body: @{body}'];
await expect(notification.notifyRequest(options, content)).resolves.toEqual(
'Successfully delivered'
);
await expect(notification.notifyRequest(options, content)).resolves.toEqual('Successfully delivered');
expect(logger.logger.info).toHaveBeenCalledWith(...infoArgs);
expect(logger.logger.debug).toHaveBeenCalledWith(...debugArgs);
});
@ -91,13 +83,13 @@ describe('Notifications:: notifyRequest', () => {
test('when notification is successfully delivered but body is undefined/null', async () => {
jest.doMock('request', () => (options, resolver) => {
const response = {
statusCode: HTTP_STATUS.OK
statusCode: HTTP_STATUS.OK,
};
resolver(null, response);
});
const notification = require('../../../../src/lib/notify/notify-request');
const notification = require('../src/notify-request');
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
await expect(notification.notifyRequest(options, content)).rejects.toThrow('body is missing');

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