From 3838d3d2126abe9d526d849c8ca6d48821fb6516 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Wed, 19 Aug 2020 20:27:35 +0200 Subject: [PATCH] feat: relocate core packages (#1906) * chore: clean lint warnings * refactor: move core packages --- .vscode/launch.json | 4 +- .vscode/settings.json | 4 +- .../api/__mocks__/@verdaccio/logger/index.js | 28 +- packages/api/package.json | 3 +- packages/api/src/package.ts | 25 +- packages/api/src/publish.ts | 40 +- packages/api/src/star.ts | 12 +- packages/api/src/user.ts | 5 +- packages/api/test/integration/package.spec.ts | 22 +- packages/commons/src/helpers/pkg.ts | 2 +- packages/core/file-locking/.babelrc | 3 + packages/core/file-locking/.eslintignore | 5 + packages/core/file-locking/.gitignore | 1 + packages/core/file-locking/CHANGELOG.md | 290 ++++++++ packages/core/file-locking/LICENSE | 21 + packages/core/file-locking/README.md | 22 + packages/core/file-locking/jest.config.js | 3 + packages/core/file-locking/package.json | 47 ++ packages/core/file-locking/src/index.ts | 3 + packages/core/file-locking/src/lockfile.ts | 29 + packages/core/file-locking/src/readFile.ts | 82 +++ packages/core/file-locking/src/unclock.ts | 10 + packages/core/file-locking/src/utils.ts | 56 ++ .../tests/__snapshots__/lock.spec.ts.snap | 23 + .../file-locking/tests/assets/package.json | 4 + .../file-locking/tests/assets/package2.json | 4 + .../tests/assets/wrong.package.json | 4 + packages/core/file-locking/tests/lock.spec.ts | 117 +++ packages/core/file-locking/tsconfig.json | 9 + .../core/file-locking/types/lockfile.d.ts | 20 + packages/core/htpasswd/.babelrc | 3 + packages/core/htpasswd/.eslintignore | 6 + packages/core/htpasswd/.eslintrc.json | 5 + packages/core/htpasswd/CHANGELOG.md | 328 +++++++++ packages/core/htpasswd/LICENSE | 21 + packages/core/htpasswd/README.md | 65 ++ packages/core/htpasswd/jest.config.js | 3 + packages/core/htpasswd/package.json | 50 ++ packages/core/htpasswd/src/crypt3.ts | 52 ++ packages/core/htpasswd/src/htpasswd.ts | 239 +++++++ packages/core/htpasswd/src/index.ts | 11 + packages/core/htpasswd/src/utils.ts | 174 +++++ .../htpasswd/tests/__fixtures__/config.yaml | 39 + .../core/htpasswd/tests/__fixtures__/htpasswd | 2 + .../core/htpasswd/tests/__mocks__/Config.js | 50 ++ .../core/htpasswd/tests/__mocks__/Logger.ts | 1 + .../tests/__snapshots__/utils.test.ts.snap | 17 + packages/core/htpasswd/tests/crypt3.test.ts | 31 + packages/core/htpasswd/tests/htpasswd.test.ts | 285 ++++++++ packages/core/htpasswd/tests/utils.test.ts | 252 +++++++ packages/core/htpasswd/tsconfig.json | 9 + packages/core/local-storage/.babelrc | 3 + packages/core/local-storage/.eslintrc.json | 5 + packages/core/local-storage/CHANGELOG.md | 467 ++++++++++++ packages/core/local-storage/LICENSE | 21 + packages/core/local-storage/README.md | 49 ++ .../_storage/first-package/package.json | 1 + .../_storage/new-readme-0.0.0.tgz | Bin 0 -> 352 bytes packages/core/local-storage/jest.config.js | 3 + packages/core/local-storage/package.json | 60 ++ packages/core/local-storage/src/index.ts | 5 + .../core/local-storage/src/local-database.ts | 434 ++++++++++++ packages/core/local-storage/src/local-fs.ts | 384 ++++++++++ packages/core/local-storage/src/pkg-utils.ts | 29 + packages/core/local-storage/src/utils.ts | 75 ++ .../__fixtures__/databases/corrupted.json | 6 + .../tests/__fixtures__/databases/empty.json | 1 + .../tests/__fixtures__/databases/ok.json | 20 + .../tests/__fixtures__/findPackages/.gitkeep | 0 .../findPackages/@scoped_second/.gitkeep | 0 .../findPackages/@scoped_second/pkg1/.gitkeep | 0 .../findPackages/@scoped_second/pkg2/.gitkeep | 0 .../findPackages/@scoped_test/.gitkeep | 0 .../findPackages/@scoped_test/pkg1/.gitkeep | 0 .../findPackages/@scoped_test/pkg2/.gitkeep | 0 .../__fixtures__/findPackages/pk3/.gitkeep | 0 .../local-storage/tests/__fixtures__/pkg.js | 50 ++ .../readme-test-corrupt/corrupt.js | 0 .../__fixtures__/readme-test/package.json | 56 ++ .../readme-test/test-readme-0.0.0.tgz | Bin 0 -> 352 bytes .../local-storage/tests/__mocks__/Config.ts | 89 +++ .../local-storage/tests/__mocks__/Logger.ts | 14 + .../tests/local-database.test.ts | 273 +++++++ .../core/local-storage/tests/local-fs.test.ts | 344 +++++++++ .../core/local-storage/tests/utils.test.ts | 74 ++ packages/core/local-storage/tsconfig.json | 9 + packages/core/readme/.babelrc | 3 + packages/core/readme/.gitignore | 1 + packages/core/readme/CHANGELOG.md | 332 +++++++++ packages/core/readme/LICENSE | 21 + packages/core/readme/README.md | 21 + packages/core/readme/jest.config.js | 3 + packages/core/readme/package.json | 52 ++ packages/core/readme/src/index.ts | 17 + .../tests/partials/mixed-html-mk/readme.md | 5 + packages/core/readme/tests/readme.spec.ts | 201 ++++++ packages/core/readme/tsconfig.json | 9 + packages/core/streams/.babelrc | 3 + packages/core/streams/.eslintignore | 6 + packages/core/streams/.eslintrc.json | 5 + packages/core/streams/.gitignore | 1 + packages/core/streams/CHANGELOG.md | 275 ++++++++ packages/core/streams/LICENSE | 21 + packages/core/streams/README.md | 20 + packages/core/streams/jest.config.js | 3 + packages/core/streams/package.json | 40 ++ packages/core/streams/src/index.ts | 83 +++ packages/core/streams/test/mystreams.test.ts | 29 + packages/core/streams/tsconfig.json | 9 + packages/core/types/.eslintrc.json | 9 + packages/core/types/CHANGELOG.md | 665 ++++++++++++++++++ packages/core/types/LICENSE | 21 + packages/core/types/README.md | 48 ++ packages/core/types/package.json | 34 + packages/core/types/src/index.ts | 533 ++++++++++++++ packages/core/types/tsconfig.json | 9 + packages/proxy/package.json | 4 +- packages/store/package.json | 5 +- packages/store/src/local-storage.ts | 36 +- packages/store/src/storage.ts | 36 +- packages/utils/package.json | 2 +- packages/utils/src/utils.ts | 1 - .../test/__snapshots__/utils.spec.ts.snap | 15 +- pnpm-lock.yaml | 140 +++- pnpm-workspace.yaml | 1 + 125 files changed, 7656 insertions(+), 76 deletions(-) create mode 100644 packages/core/file-locking/.babelrc create mode 100644 packages/core/file-locking/.eslintignore create mode 100644 packages/core/file-locking/.gitignore create mode 100644 packages/core/file-locking/CHANGELOG.md create mode 100644 packages/core/file-locking/LICENSE create mode 100644 packages/core/file-locking/README.md create mode 100644 packages/core/file-locking/jest.config.js create mode 100644 packages/core/file-locking/package.json create mode 100644 packages/core/file-locking/src/index.ts create mode 100644 packages/core/file-locking/src/lockfile.ts create mode 100644 packages/core/file-locking/src/readFile.ts create mode 100644 packages/core/file-locking/src/unclock.ts create mode 100644 packages/core/file-locking/src/utils.ts create mode 100644 packages/core/file-locking/tests/__snapshots__/lock.spec.ts.snap create mode 100644 packages/core/file-locking/tests/assets/package.json create mode 100644 packages/core/file-locking/tests/assets/package2.json create mode 100644 packages/core/file-locking/tests/assets/wrong.package.json create mode 100644 packages/core/file-locking/tests/lock.spec.ts create mode 100644 packages/core/file-locking/tsconfig.json create mode 100644 packages/core/file-locking/types/lockfile.d.ts create mode 100644 packages/core/htpasswd/.babelrc create mode 100644 packages/core/htpasswd/.eslintignore create mode 100644 packages/core/htpasswd/.eslintrc.json create mode 100644 packages/core/htpasswd/CHANGELOG.md create mode 100644 packages/core/htpasswd/LICENSE create mode 100644 packages/core/htpasswd/README.md create mode 100644 packages/core/htpasswd/jest.config.js create mode 100644 packages/core/htpasswd/package.json create mode 100644 packages/core/htpasswd/src/crypt3.ts create mode 100644 packages/core/htpasswd/src/htpasswd.ts create mode 100644 packages/core/htpasswd/src/index.ts create mode 100644 packages/core/htpasswd/src/utils.ts create mode 100644 packages/core/htpasswd/tests/__fixtures__/config.yaml create mode 100644 packages/core/htpasswd/tests/__fixtures__/htpasswd create mode 100644 packages/core/htpasswd/tests/__mocks__/Config.js create mode 100644 packages/core/htpasswd/tests/__mocks__/Logger.ts create mode 100644 packages/core/htpasswd/tests/__snapshots__/utils.test.ts.snap create mode 100644 packages/core/htpasswd/tests/crypt3.test.ts create mode 100644 packages/core/htpasswd/tests/htpasswd.test.ts create mode 100644 packages/core/htpasswd/tests/utils.test.ts create mode 100644 packages/core/htpasswd/tsconfig.json create mode 100644 packages/core/local-storage/.babelrc create mode 100644 packages/core/local-storage/.eslintrc.json create mode 100644 packages/core/local-storage/CHANGELOG.md create mode 100644 packages/core/local-storage/LICENSE create mode 100644 packages/core/local-storage/README.md create mode 100644 packages/core/local-storage/_storage/first-package/package.json create mode 100644 packages/core/local-storage/_storage/new-readme-0.0.0.tgz create mode 100644 packages/core/local-storage/jest.config.js create mode 100644 packages/core/local-storage/package.json create mode 100644 packages/core/local-storage/src/index.ts create mode 100644 packages/core/local-storage/src/local-database.ts create mode 100644 packages/core/local-storage/src/local-fs.ts create mode 100644 packages/core/local-storage/src/pkg-utils.ts create mode 100644 packages/core/local-storage/src/utils.ts create mode 100644 packages/core/local-storage/tests/__fixtures__/databases/corrupted.json create mode 100644 packages/core/local-storage/tests/__fixtures__/databases/empty.json create mode 100644 packages/core/local-storage/tests/__fixtures__/databases/ok.json create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg1/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg2/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg1/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg2/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/findPackages/pk3/.gitkeep create mode 100644 packages/core/local-storage/tests/__fixtures__/pkg.js create mode 100644 packages/core/local-storage/tests/__fixtures__/readme-test-corrupt/corrupt.js create mode 100644 packages/core/local-storage/tests/__fixtures__/readme-test/package.json create mode 100644 packages/core/local-storage/tests/__fixtures__/readme-test/test-readme-0.0.0.tgz create mode 100644 packages/core/local-storage/tests/__mocks__/Config.ts create mode 100644 packages/core/local-storage/tests/__mocks__/Logger.ts create mode 100644 packages/core/local-storage/tests/local-database.test.ts create mode 100644 packages/core/local-storage/tests/local-fs.test.ts create mode 100644 packages/core/local-storage/tests/utils.test.ts create mode 100644 packages/core/local-storage/tsconfig.json create mode 100644 packages/core/readme/.babelrc create mode 100644 packages/core/readme/.gitignore create mode 100644 packages/core/readme/CHANGELOG.md create mode 100644 packages/core/readme/LICENSE create mode 100644 packages/core/readme/README.md create mode 100644 packages/core/readme/jest.config.js create mode 100644 packages/core/readme/package.json create mode 100644 packages/core/readme/src/index.ts create mode 100644 packages/core/readme/tests/partials/mixed-html-mk/readme.md create mode 100644 packages/core/readme/tests/readme.spec.ts create mode 100644 packages/core/readme/tsconfig.json create mode 100644 packages/core/streams/.babelrc create mode 100644 packages/core/streams/.eslintignore create mode 100644 packages/core/streams/.eslintrc.json create mode 100644 packages/core/streams/.gitignore create mode 100644 packages/core/streams/CHANGELOG.md create mode 100644 packages/core/streams/LICENSE create mode 100644 packages/core/streams/README.md create mode 100644 packages/core/streams/jest.config.js create mode 100644 packages/core/streams/package.json create mode 100644 packages/core/streams/src/index.ts create mode 100644 packages/core/streams/test/mystreams.test.ts create mode 100644 packages/core/streams/tsconfig.json create mode 100644 packages/core/types/.eslintrc.json create mode 100644 packages/core/types/CHANGELOG.md create mode 100644 packages/core/types/LICENSE create mode 100644 packages/core/types/README.md create mode 100644 packages/core/types/package.json create mode 100644 packages/core/types/src/index.ts create mode 100644 packages/core/types/tsconfig.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 90d61a19a..e664d450c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,7 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { "type": "node", "request": "launch", @@ -21,12 +22,11 @@ "name": "Unit Tests", "type": "node", "request": "launch", - "program": "${workspaceRoot}/node_modules/jest-cli/bin/jest.js", + "program": "${workspaceRoot}/node_modules/bin/jest", "stopOnEntry": false, "args": [ "--debug=true" ], "cwd": "${workspaceRoot}", - "preLaunchTask": "pre-test", "runtimeExecutable": null, "runtimeArgs": [ "--nolazy" diff --git a/.vscode/settings.json b/.vscode/settings.json index dabe4ffe2..5d1bb3a90 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,11 +2,11 @@ { "files.exclude": { "**/.nyc_output": true, - "**/build": true, + "**/build": false, "**/coverage": true, ".idea": true, "storage_default_storage": true, ".yarn": true }, "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file +} diff --git a/packages/api/__mocks__/@verdaccio/logger/index.js b/packages/api/__mocks__/@verdaccio/logger/index.js index c42519fa9..17f25600e 100644 --- a/packages/api/__mocks__/@verdaccio/logger/index.js +++ b/packages/api/__mocks__/@verdaccio/logger/index.js @@ -1,19 +1,21 @@ -const setup = jest.fn(); +const debug = require('debug')('verdaccio:test'); + +const setup = debug; const logger = { child: jest.fn(() => ({ - debug: jest.fn(), - trace: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), + debug, + trace: debug, + warn: debug, + info: debug, + error: debug, + fatal: debug, })), - debug: jest.fn(), - trace: jest.fn(), - warn: jest.fn(), - info: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), + debug: debug, + trace: debug, + warn: debug, + info: debug, + error: debug, + fatal: debug, }; export { setup, logger }; diff --git a/packages/api/package.json b/packages/api/package.json index c858f4fd1..f849b90a3 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -31,6 +31,7 @@ "@verdaccio/middleware": "5.0.0-alpha.0", "@verdaccio/store": "5.0.0-alpha.0", "@verdaccio/utils": "5.0.0-alpha.0", + "debug": "^4.1.1", "cookies": "0.8.0", "express": "4.17.1", "lodash": "4.17.15", @@ -42,7 +43,7 @@ "@verdaccio/dev-types": "5.0.0-alpha.0", "@verdaccio/types": "9.5.0", "body-parser": "1.19.0", - "express": "4.17.1" + "supertest": "next" }, "gitHead": "7c246ede52ff717707fcae66dd63fc4abd536982" } diff --git a/packages/api/src/package.ts b/packages/api/src/package.ts index 08630a8bf..3dc21e491 100644 --- a/packages/api/src/package.ts +++ b/packages/api/src/package.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import { Router } from 'express'; +import buildDebug from 'debug'; import { allow } from '@verdaccio/middleware'; import { convertDistRemoteToLocalTarballUrls, getVersion, ErrorCode } from '@verdaccio/utils'; @@ -7,6 +8,8 @@ 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 debug = buildDebug('verdaccio:api:package'); + const downloadStream = (packageName: string, filename: string, storage: any, req: $RequestExtend, res: $ResponseExtend): void => { const stream = storage.getTarball(packageName, filename); @@ -26,36 +29,52 @@ export default function (route: Router, auth: IAuth, storage: IStorageHandler, c const can = allow(auth); // TODO: anonymous user? route.get('/:package/:version?', can('access'), function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { + debug('init package by version'); + const name = req.params.package; const getPackageMetaCallback = function (err, metadata: Package): void { if (err) { + debug('error on fetch metadata for %o with error %o', name, err.message); return next(err); } - metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config.url_prefix); + debug('convert dist remote to local with prefix %o', config?.url_prefix); + metadata = convertDistRemoteToLocalTarballUrls(metadata, req, config?.url_prefix); let queryVersion = req.params.version; + debug('query by param version: %o', queryVersion); if (_.isNil(queryVersion)) { + debug('param %o version found', queryVersion); return next(metadata); } let version = getVersion(metadata, queryVersion); + debug('query by latest version %o and result %o', queryVersion, version); if (_.isNil(version) === false) { + debug('latest version found %o', version); return next(version); } if (_.isNil(metadata[DIST_TAGS]) === false) { if (_.isNil(metadata[DIST_TAGS][queryVersion]) === false) { queryVersion = metadata[DIST_TAGS][queryVersion]; + debug('dist-tag version found %o', queryVersion); version = getVersion(metadata, queryVersion); if (_.isNil(version) === false) { + debug('dist-tag found %o', version); return next(version); } } + } else { + debug('dist tag not detected'); } - return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${req.params.version}`)); + + debug('package version not found %o', queryVersion); + return next(ErrorCode.getNotFound(`${API_ERROR.VERSION_NOT_EXIST}: ${queryVersion}`)); }; + debug('get package name %o', name); + debug('uplinks look up enabled'); storage.getPackage({ - name: req.params.package, + name, uplinksLook: true, req, callback: getPackageMetaCallback, diff --git a/packages/api/src/publish.ts b/packages/api/src/publish.ts index a972e840e..8c0ea9286 100644 --- a/packages/api/src/publish.ts +++ b/packages/api/src/publish.ts @@ -2,6 +2,7 @@ import Path from 'path'; import _ from 'lodash'; import mime from 'mime'; import { Router } from 'express'; +import buildDebug from 'debug'; import { IAuth, $ResponseExtend, $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types'; import { API_MESSAGE, HEADERS, DIST_TAGS, API_ERROR, HTTP_STATUS } from '@verdaccio/dev-commons'; @@ -14,6 +15,8 @@ import { logger } from '@verdaccio/logger'; import star from './star'; import { isPublishablePackage } from './utils'; +const debug = buildDebug('verdaccio:api:publish'); + export default function publish(router: Router, auth: IAuth, storage: IStorageHandler, config: Config): void { const can = allow(auth); @@ -106,7 +109,7 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { const packageName = req.params.package; - logger.debug({ packageName }, `publishing or updating a new version for @{packageName}`); + debug('publishing or updating a new version for %o', packageName); /** * Write tarball of stream data from package clients. @@ -114,14 +117,16 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I const createTarball = function (filename: string, data, cb: Callback): void { const stream = storage.addTarball(packageName, filename); stream.on('error', function (err) { + debug('error on stream a tarball %o for %o with error %o', filename, packageName, err.message); cb(err); }); stream.on('success', function () { + debug('success on stream a tarball %o for %o', filename, packageName); cb(); }); // this is dumb and memory-consuming, but what choices do we have? // flow: we need first refactor this file before decides which type use here - stream.end(new Buffer(data.data, 'base64')); + stream.end(Buffer.from(data.data, 'base64')); stream.done(); }; @@ -129,6 +134,7 @@ 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 { + debug('add a new package version %o to storage %o', version, metadata); storage.addVersion(packageName, version, metadata, null, cb); }; @@ -136,20 +142,26 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I * Add new tags in storage */ const addTags = function (tags: MergeTags, cb: Callback): void { + debug('add new tag %o to storage', packageName); storage.mergeTags(packageName, tags, cb); }; const afterChange = function (error, okMessage, metadata: Package): void { const metadataCopy: Package = { ...metadata }; + debug('after change metadata %o', metadata); const { _attachments, versions } = metadataCopy; // `npm star` wouldn't have attachments // and `npm deprecate` would have attachments as a empty object, i.e {} if (_.isNil(_attachments) || JSON.stringify(_attachments) === '{}') { + debug('no attachments detected'); if (error) { + debug('no_attachments: after change error with %o', error.message); return next(error); } + + debug('no_attachments: after change success'); res.status(HTTP_STATUS.CREATED); return next({ ok: okMessage, @@ -165,11 +177,13 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I if (isInvalidBodyFormat) { // npm is doing something strange again // if this happens in normal circumstances, report it as a bug + debug('invalid body format'); logger.info({ packageName }, `wrong package format on publish a package @{packageName}`); return next(ErrorCode.getBadRequest(API_ERROR.UNSUPORTED_REGISTRY_CALL)); } if (error && error.status !== HTTP_STATUS.CONFLICT) { + debug('error on change or update a package with %o', error.message); return next(error); } @@ -177,7 +191,9 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I const [firstAttachmentKey] = Object.keys(_attachments); createTarball(Path.basename(firstAttachmentKey), _attachments[firstAttachmentKey], function (error) { + debug('creating a tarball %o', firstAttachmentKey); if (error) { + debug('error on create a tarball for %o with error %o', packageName, error.message); return next(error); } @@ -187,20 +203,24 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I createVersion(versionToPublish, versions[versionToPublish], function (error) { if (error) { + debug('error on create a version for %o with error %o', packageName, error.message); return next(error); } addTags(metadataCopy[DIST_TAGS], async function (error) { if (error) { + debug('error on create a tag for %o with error %o', packageName, error.message); return next(error); } try { await notify(metadataCopy, config, req.remote_user, `${metadataCopy.name}@${versionToPublish}`); } catch (error) { + debug('error on notify add a new tag %o', `${metadataCopy.name}@${versionToPublish}`); logger.error({ error }, 'notify batch service has failed: @{error}'); } + debug('add a tag succesfully for %o', `${metadataCopy.name}@${versionToPublish}`); res.status(HTTP_STATUS.CREATED); return next({ ok: okMessage, success: true }); }); @@ -209,33 +229,40 @@ export function publishPackage(storage: IStorageHandler, config: Config, auth: I }; if (isPublishablePackage(req.body) === false && isObject(req.body.users)) { + debug('starting star a package'); return starApi(req, res, next); } try { + debug('pre validation metadata to publish %o', req.body); const metadata = validateMetadata(req.body, packageName); + debug('post validation metadata to publish %o', metadata); // treating deprecation as updating a package if (req.params._rev || isRelatedToDeprecation(req.body)) { - logger.debug({ packageName }, `updating a new version for @{packageName}`); + debug('updating a new version for %o', packageName); // we check unpublish permissions, an update is basically remove versions const remote = req.remote_user; auth.allow_unpublish({ packageName }, remote, (error) => { + debug('allowed to unpublish a package %o', packageName); if (error) { - logger.debug({ packageName }, `not allowed to unpublish a version for @{packageName}`); + debug('not allowed to unpublish a version for %o', packageName); return next(error); } + debug('update a package'); storage.changePackage(packageName, metadata, req.params.revision, function (error) { afterChange(error, API_MESSAGE.PKG_CHANGED, metadata); }); }); } else { - logger.debug({ packageName }, `adding a new version for @{packageName}`); + debug('adding a new version for the package %o', packageName); storage.addPackage(packageName, metadata, function (error) { + debug('package metadata updated %o', metadata); afterChange(error, API_MESSAGE.PKG_CREATED, metadata); }); } } catch (error) { + debug('error on publish, bad package format %o', packageName); logger.error({ packageName }, 'error on publish, bad package data for @{packageName}'); return next(ErrorCode.getBadData(API_ERROR.BAD_PACKAGE_DATA)); } @@ -287,12 +314,15 @@ export function addVersion(storage: IStorageHandler) { return function (req: $RequestExtend, res: $ResponseExtend, next: $NextFunctionVer): void { const { version, tag } = req.params; const packageName = req.params.package; + debug('add a new version %o and tag %o for %o', version, tag, packageName); storage.addVersion(packageName, version, req.body, tag, function (error) { if (error) { + debug('error on add new version'); return next(error); } + debug('success on add new version'); res.status(HTTP_STATUS.CREATED); return next({ ok: API_MESSAGE.PKG_PUBLISHED, diff --git a/packages/api/src/star.ts b/packages/api/src/star.ts index 672e8f579..ba4062e70 100644 --- a/packages/api/src/star.ts +++ b/packages/api/src/star.ts @@ -2,9 +2,12 @@ import { USERS, HTTP_STATUS } from '@verdaccio/dev-commons'; import { Response } from 'express'; import _ from 'lodash'; import { logger } from '@verdaccio/logger'; +import buildDebug from 'debug'; import { $RequestExtend, $NextFunctionVer, IStorageHandler } from '@verdaccio/dev-types'; +const debug = buildDebug('verdaccio:api:publish:star'); + 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; @@ -20,22 +23,27 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R return (req: $RequestExtend, res: Response, next: $NextFunctionVer): void => { const name = req.params.package; - logger.debug({ name }, 'starring a package for @{name}'); + debug('starring a package for %o', name); const afterChangePackage = function (err?: Error) { if (err) { + debug('error on update package for %o', name); return next(err); } + + debug('succes update package for %o', name); res.status(HTTP_STATUS.OK); next({ success: true, }); }; + debug('get package info package for %o', name); storage.getPackage({ name, req, callback: function (err, info) { if (err) { + debug('error on get package info package for %o', name); return next(err); } const newStarUser = req.body[USERS]; @@ -43,6 +51,7 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R const localStarUsers = info[USERS]; // Check is star or unstar const isStar = Object.keys(newStarUser).includes(remoteUsername); + debug('is start? %o', isStar); if (_.isNil(localStarUsers) === false && validateInputs(newStarUser, localStarUsers, remoteUsername, isStar)) { return afterChangePackage(); } @@ -61,6 +70,7 @@ export default function (storage: IStorageHandler): (req: $RequestExtend, res: R }, {} ); + debug('update package for %o', name); storage.changePackage(name, { ...info, users }, req.body._rev, function (err) { afterChangePackage(err); }); diff --git a/packages/api/src/user.ts b/packages/api/src/user.ts index 7464eeb86..8355e4d24 100644 --- a/packages/api/src/user.ts +++ b/packages/api/src/user.ts @@ -1,13 +1,12 @@ import _ from 'lodash'; -import Cookies from 'cookies'; import { Response, Router } from 'express'; -import { createRemoteUser, createSessionToken, getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils'; +import { createRemoteUser, getAuthenticatedMessage, validatePassword, ErrorCode } from '@verdaccio/utils'; import { getApiToken } from '@verdaccio/auth'; import { logger } from '@verdaccio/logger'; import { Config, RemoteUser } from '@verdaccio/types'; -import { $RequestExtend, $ResponseExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types'; +import { $RequestExtend, $NextFunctionVer, IAuth } from '@verdaccio/dev-types'; import { API_ERROR, API_MESSAGE, HTTP_STATUS } from '@verdaccio/dev-commons'; export default function (route: Router, auth: IAuth, config: Config): void { diff --git a/packages/api/test/integration/package.spec.ts b/packages/api/test/integration/package.spec.ts index 37f5751a7..7d6cd692b 100644 --- a/packages/api/test/integration/package.spec.ts +++ b/packages/api/test/integration/package.spec.ts @@ -15,10 +15,12 @@ jest.mock('@verdaccio/auth', () => ({ apiJWTmiddleware() { return mockApiJWTmiddleware(); } - allow_access(_d, f_, cb) { + allow_access(_d, _f, cb) { + // always allow access cb(null, true); } - allow_publish(_d, f_, cb) { + allow_publish(_d, _f, cb) { + // always allow publish cb(null, true); } }, @@ -26,12 +28,12 @@ jest.mock('@verdaccio/auth', () => ({ describe('package', () => { let app; - beforeAll(async () => { + beforeEach(async () => { app = await initializeServer('package.yaml'); - await publishVersion(app, 'package.yaml', 'foo', '1.0.0'); }); test('should return a package', async (done) => { + await publishVersion(app, 'package.yaml', 'foo', '1.0.0'); return supertest(app) .get('/foo') .set('Accept', HEADERS.JSON) @@ -44,33 +46,35 @@ describe('package', () => { }); test('should return a package by version', async (done) => { + await publishVersion(app, 'package.yaml', 'foo2', '1.0.0'); return supertest(app) - .get('/foo/1.0.0') + .get('/foo2/1.0.0') .set('Accept', HEADERS.JSON) .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) .expect(HTTP_STATUS.OK) .then((response) => { - expect(response.body.name).toEqual('foo'); + expect(response.body.name).toEqual('foo2'); done(); }); }); // TODO: investigate the 404 test.skip('should return a package by dist-tag', async (done) => { + // await publishVersion(app, 'package.yaml', 'foo3', '1.0.0'); await publishVersion(app, 'package.yaml', 'foo-tagged', '1.0.0'); await publishTaggedVersion(app, 'package.yaml', 'foo-tagged', '1.0.1', 'test'); return supertest(app) - .get('/foo-tagged/1.0.0') + .get('/foo-tagged/1.0.1') .set('Accept', HEADERS.JSON) .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) .expect(HTTP_STATUS.CREATED) .then((response) => { - expect(response.body.name).toEqual('foo'); + expect(response.body.name).toEqual('foo3'); done(); }); }); - test('should return 404', async () => { + test.skip('should return 404', async () => { return supertest(app) .get('/404-not-found') .set('Accept', HEADERS.JSON) diff --git a/packages/commons/src/helpers/pkg.ts b/packages/commons/src/helpers/pkg.ts index e1bb2c273..2c58d1e75 100644 --- a/packages/commons/src/helpers/pkg.ts +++ b/packages/commons/src/helpers/pkg.ts @@ -49,7 +49,7 @@ export function generatePackageMetadata(pkgName: string, version = '1.0.0', dist [`${pkgName}-${version}.tgz`]: { content_type: 'application/octet-stream', data: - 'H4sIAAAAAAAAE+2W32vbMBDH85y/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo//79KPeQsnIw5KUDX/9IOvurLuz/DHSjK/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS/pLQe+D+FIv/agIWI6GX66kFuIhT+1gDjrp/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi/IHpU9fz3/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6/f88f/Pu47zomiPk2Lv/dOv8h+P/34/D/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=', + 'H4sIAAAAAAAAE+2W32vbMBDH85y/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo//79KPeQsnIw5KUDX/9IOvurLuz/DHSjK/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS/pLQe+D+FIv/agIWI6GX66kFuIhT+1gDjrp/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi/IHpU9fz3/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6/f88f/Pu47zomiPk2Lv/dOv8h+P/34/D/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=+2W32vbMBDH85y', length: 512, }, }, diff --git a/packages/core/file-locking/.babelrc b/packages/core/file-locking/.babelrc new file mode 100644 index 000000000..851856e59 --- /dev/null +++ b/packages/core/file-locking/.babelrc @@ -0,0 +1,3 @@ +{ + "extends": "../../../.babelrc" +} diff --git a/packages/core/file-locking/.eslintignore b/packages/core/file-locking/.eslintignore new file mode 100644 index 000000000..720d752fa --- /dev/null +++ b/packages/core/file-locking/.eslintignore @@ -0,0 +1,5 @@ +node_modules +coverage/ +lib/ +.nyc_output +tests-report/ diff --git a/packages/core/file-locking/.gitignore b/packages/core/file-locking/.gitignore new file mode 100644 index 000000000..c3af85790 --- /dev/null +++ b/packages/core/file-locking/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/core/file-locking/CHANGELOG.md b/packages/core/file-locking/CHANGELOG.md new file mode 100644 index 000000000..58d268ec3 --- /dev/null +++ b/packages/core/file-locking/CHANGELOG.md @@ -0,0 +1,290 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07) + + +### Features + +* **eslint-config:** enable eslint curly ([#308](https://github.com/verdaccio/monorepo/issues/308)) ([91acb12](https://github.com/verdaccio/monorepo/commit/91acb121847018e737c21b367fcaab8baa918347)) + + + + + +## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08) + + +### Bug Fixes + +* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7)) +* fixed lint errors ([c80e915](https://github.com/verdaccio/monorepo/commit/c80e915)) +* quotes should be single ([ae9aa44](https://github.com/verdaccio/monorepo/commit/ae9aa44)) + + + + + +## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01) + +**Note:** Version bump only for package @verdaccio/file-locking + + + + + +# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01) + + +### Bug Fixes + +* eslint and typescript errors ([8b3f153](https://github.com/verdaccio/monorepo/commit/8b3f153)) +* lint issues ([d195fff](https://github.com/verdaccio/monorepo/commit/d195fff)) + + +### Features + +* remote lodash as dependency ([affb65b](https://github.com/verdaccio/monorepo/commit/affb65b)) + + + + + +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +### [1.0.4](https://github.com/verdaccio/file-locking/compare/v1.0.3...v1.0.4) (2019-07-16) + + +### Build System + +* **deps:** update dependencies ([45b12de](https://github.com/verdaccio/file-locking/commit/45b12de)) +* **deps:** update husky dependency ([bdb7bad](https://github.com/verdaccio/file-locking/commit/bdb7bad)) + + + + +## [1.0.3](https://github.com/verdaccio/file-locking/compare/v1.0.2...v1.0.3) (2019-06-22) + + +### Bug Fixes + +* update build script and remove source map ([ec3db50](https://github.com/verdaccio/file-locking/commit/ec3db50)) + + + + +## [1.0.2](https://github.com/verdaccio/file-locking/compare/v1.0.1...v1.0.2) (2019-06-15) + + + + +## [1.0.1](https://github.com/verdaccio/file-locking/compare/v1.0.0...v1.0.1) (2019-06-15) + + +### Bug Fixes + +* eslint and typescript errors ([3538e7c](https://github.com/verdaccio/file-locking/commit/3538e7c)) diff --git a/packages/core/file-locking/LICENSE b/packages/core/file-locking/LICENSE new file mode 100644 index 000000000..65fb12e2a --- /dev/null +++ b/packages/core/file-locking/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Verdaccio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/file-locking/README.md b/packages/core/file-locking/README.md new file mode 100644 index 000000000..50151bfa5 --- /dev/null +++ b/packages/core/file-locking/README.md @@ -0,0 +1,22 @@ +## Deprecated repository + +**This repository has been moved to a monorepo you can find in [verdaccio/monorepo](https://github.com/verdaccio/monorepo). This package is located in [`core/file-locking` folder](https://github.com/verdaccio/monorepo/tree/master/core/file-locking)** + +--- + +# File Locking + +This an utility to lock and unlock files + +[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/file-locking/latest.svg)](https://www.npmjs.com/package/verdaccio) +[![docker pulls](https://img.shields.io/docker/pulls/verdaccio/verdaccio.svg?maxAge=43200)](https://verdaccio.org/docs/en/docker.html) +[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio) +[![stackshare](https://img.shields.io/badge/Follow%20on-StackShare-blue.svg?logo=stackshare&style=flat)](https://stackshare.io/verdaccio) +[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/) +[![node](https://img.shields.io/node/v/@verdaccio/file-locking/latest.svg)](https://www.npmjs.com/package/verdaccio) +![MIT](https://img.shields.io/github/license/mashape/apistatus.svg) +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/verdaccio/localized.svg)](https://crowdin.com/project/verdaccio) + + +[![Twitter followers](https://img.shields.io/twitter/follow/verdaccio_npm.svg?style=social&label=Follow)](https://twitter.com/verdaccio_npm) +[![Github](https://img.shields.io/github/stars/verdaccio/verdaccio.svg?style=social&label=Stars)](https://github.com/verdaccio/verdaccio/stargazers) diff --git a/packages/core/file-locking/jest.config.js b/packages/core/file-locking/jest.config.js new file mode 100644 index 000000000..1c3fbdb05 --- /dev/null +++ b/packages/core/file-locking/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../../jest/config'); + +module.exports = Object.assign({}, config, {}); diff --git a/packages/core/file-locking/package.json b/packages/core/file-locking/package.json new file mode 100644 index 000000000..378f125b2 --- /dev/null +++ b/packages/core/file-locking/package.json @@ -0,0 +1,47 @@ +{ + "name": "@verdaccio/file-locking", + "version": "10.0.0-beta", + "description": "library that handle file locking", + "keywords": [ + "verdaccio", + "lock", + "fs" + ], + "author": "Juan Picado ", + "license": "MIT", + "homepage": "https://verdaccio.org", + "repository": { + "type": "git", + "url": "https://github.com/verdaccio/monorepo", + "directory": "core/file-locking" + }, + "bugs": { + "url": "https://github.com/verdaccio/monorepo/issues" + }, + "publishConfig": { + "access": "public" + }, + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build" + ], + "dependencies": { + "lockfile": "1.0.4" + }, + "devDependencies": { + "@verdaccio/types": "workspace:*" + }, + "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": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", + "build": "pnpm run build:js && pnpm run build:types" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } +} diff --git a/packages/core/file-locking/src/index.ts b/packages/core/file-locking/src/index.ts new file mode 100644 index 000000000..ad3a6e7c2 --- /dev/null +++ b/packages/core/file-locking/src/index.ts @@ -0,0 +1,3 @@ +export * from './unclock'; +export * from './readFile'; +export * from './lockfile'; diff --git a/packages/core/file-locking/src/lockfile.ts b/packages/core/file-locking/src/lockfile.ts new file mode 100644 index 000000000..4d60a4438 --- /dev/null +++ b/packages/core/file-locking/src/lockfile.ts @@ -0,0 +1,29 @@ +import { Callback } from '@verdaccio/types'; + +import { lockfile, statDir, statfile } from './utils'; + +/** + * locks a file by creating a lock file + * @param name + * @param callback + */ +const lockFile = function (name: string, callback: Callback): void { + Promise.resolve() + .then(() => { + return statDir(name); + }) + .then(() => { + return statfile(name); + }) + .then(() => { + return lockfile(name); + }) + .then(() => { + callback(null); + }) + .catch((err) => { + callback(err); + }); +}; + +export { lockFile }; diff --git a/packages/core/file-locking/src/readFile.ts b/packages/core/file-locking/src/readFile.ts new file mode 100644 index 000000000..a2ec68163 --- /dev/null +++ b/packages/core/file-locking/src/readFile.ts @@ -0,0 +1,82 @@ +import fs from 'fs'; + +import { Callback } from '@verdaccio/types'; + +import { lockFile } from './lockfile'; + +export type ReadFileOptions = { + parse?: boolean; + lock?: boolean; +}; + +/** + * Reads a local file, which involves + * optionally taking a lock + * reading the file contents + * optionally parsing JSON contents + * @param {*} name + * @param {*} options + * @param {*} callback + */ +// eslint-disable-next-line @typescript-eslint/no-empty-function +function readFile(name: string, options: ReadFileOptions = {}, callback: Callback = (): void => {}): void { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + options.lock = options.lock || false; + options.parse = options.parse || false; + + const lock = function (options: ReadFileOptions): Promise { + return new Promise((resolve, reject): void => { + if (!options.lock) { + return resolve(null); + } + + lockFile(name, function (err: NodeJS.ErrnoException | null) { + if (err) { + return reject(err); + } + return resolve(null); + }); + }); + }; + + const read = function (): Promise { + return new Promise((resolve, reject): void => { + fs.readFile(name, 'utf8', function (err, contents) { + if (err) { + return reject(err); + } + + resolve(contents); + }); + }); + }; + + const parseJSON = function (contents: string): Promise { + return new Promise((resolve, reject): void => { + if (!options.parse) { + return resolve(contents); + } + try { + contents = JSON.parse(contents); + return resolve(contents); + } catch (err) { + return reject(err); + } + }); + }; + + Promise.resolve() + .then(() => lock(options)) + .then(() => read()) + .then((content) => parseJSON(content as string)) + .then( + (result) => callback(null, result), + (err) => callback(err) + ); +} + +export { readFile }; diff --git a/packages/core/file-locking/src/unclock.ts b/packages/core/file-locking/src/unclock.ts new file mode 100644 index 000000000..ab1817611 --- /dev/null +++ b/packages/core/file-locking/src/unclock.ts @@ -0,0 +1,10 @@ +import locker from 'lockfile'; +import { Callback } from '@verdaccio/types'; + +// unlocks file by removing existing lock file +export function unlockFile(name: string, next: Callback): void { + const lockFileName = `${name}.lock`; + locker.unlock(lockFileName, function () { + return next(null); + }); +} diff --git a/packages/core/file-locking/src/utils.ts b/packages/core/file-locking/src/utils.ts new file mode 100644 index 000000000..5118a3d50 --- /dev/null +++ b/packages/core/file-locking/src/utils.ts @@ -0,0 +1,56 @@ +import fs from 'fs'; +import path from 'path'; + +import locker from 'lockfile'; + +export const statDir = (name: string): Promise => { + return new Promise((resolve, reject): void => { + // test to see if the directory exists + const dirPath = path.dirname(name); + fs.stat(dirPath, function (err, stats) { + if (err) { + return reject(err); + } else if (!stats.isDirectory()) { + return resolve(new Error(`${path.dirname(name)} is not a directory`)); + } else { + return resolve(null); + } + }); + }); +}; + +export const statfile = (name: string): Promise => { + return new Promise((resolve, reject): void => { + // test to see if the directory exists + fs.stat(name, function (err, stats) { + if (err) { + return reject(err); + } else if (!stats.isFile()) { + return resolve(new Error(`${path.dirname(name)} is not a file`)); + } else { + return resolve(null); + } + }); + }); +}; + +export const lockfile = (name: string): Promise => { + return new Promise((resolve): void => { + const lockOpts = { + // time (ms) to wait when checking for stale locks + wait: 1000, + // how often (ms) to re-check stale locks + pollPeriod: 100, + // locks are considered stale after 5 minutes + stale: 5 * 60 * 1000, + // number of times to attempt to create a lock + retries: 100, + // time (ms) between tries + retryWait: 100, + }; + const lockFileName = `${name}.lock`; + locker.lock(lockFileName, lockOpts, () => { + resolve(); + }); + }); +}; diff --git a/packages/core/file-locking/tests/__snapshots__/lock.spec.ts.snap b/packages/core/file-locking/tests/__snapshots__/lock.spec.ts.snap new file mode 100644 index 000000000..5e04edc73 --- /dev/null +++ b/packages/core/file-locking/tests/__snapshots__/lock.spec.ts.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`testing locking readFile read file with options (parse, lock) should to be found to be read it as object 1`] = ` +Object { + "name": "assets", + "version": "0.0.1", +} +`; + +exports[`testing locking readFile read file with no options should to be found to be read it as object 1`] = ` +Object { + "name": "assets", + "version": "0.0.1", +} +`; + +exports[`testing locking readFile read file with no options should to be found to be read it as string 1`] = ` +"{ + \\"name\\": \\"assets\\", + \\"version\\": \\"0.0.1\\" +} +" +`; diff --git a/packages/core/file-locking/tests/assets/package.json b/packages/core/file-locking/tests/assets/package.json new file mode 100644 index 000000000..c08a4aac4 --- /dev/null +++ b/packages/core/file-locking/tests/assets/package.json @@ -0,0 +1,4 @@ +{ + "name": "assets", + "version": "0.0.1" +} diff --git a/packages/core/file-locking/tests/assets/package2.json b/packages/core/file-locking/tests/assets/package2.json new file mode 100644 index 000000000..c08a4aac4 --- /dev/null +++ b/packages/core/file-locking/tests/assets/package2.json @@ -0,0 +1,4 @@ +{ + "name": "assets", + "version": "0.0.1" +} diff --git a/packages/core/file-locking/tests/assets/wrong.package.json b/packages/core/file-locking/tests/assets/wrong.package.json new file mode 100644 index 000000000..fab05b67d --- /dev/null +++ b/packages/core/file-locking/tests/assets/wrong.package.json @@ -0,0 +1,4 @@ +{ + "name": "assets", + "version": "0.0.1", +} diff --git a/packages/core/file-locking/tests/lock.spec.ts b/packages/core/file-locking/tests/lock.spec.ts new file mode 100644 index 000000000..0531f2bc5 --- /dev/null +++ b/packages/core/file-locking/tests/lock.spec.ts @@ -0,0 +1,117 @@ +import path from 'path'; +import fs from 'fs'; + +import { lockFile, unlockFile, readFile } from '../src/index'; + +interface Error { + message: string; +} + +const getFilePath = (filename: string): string => { + return path.resolve(__dirname, `assets/${filename}`); +}; + +const removeTempFile = (filename: string): void => { + const filepath = getFilePath(filename); + fs.unlink(filepath, (error) => { + if (error) { + throw error; + } + }); +}; + +describe('testing locking', () => { + describe('lockFile', () => { + test('file should be found to be locked', (done) => { + lockFile(getFilePath('package.json'), (error: Error) => { + expect(error).toBeNull(); + removeTempFile('package.json.lock'); + done(); + }); + }); + + test('file should fail to be found to be locked', (done) => { + lockFile(getFilePath('package.fail.json'), (error: Error) => { + expect(error.message).toMatch(/ENOENT: no such file or directory, stat '(.*)package.fail.json'/); + done(); + }); + }); + }); + + describe('unlockFile', () => { + test('file should to be found to be unLock', (done) => { + unlockFile(getFilePath('package.json.lock'), (error: Error) => { + expect(error).toBeNull(); + done(); + }); + }); + }); + + describe('readFile', () => { + test('read file with no options should to be found to be read it as string', (done) => { + readFile(getFilePath('package.json'), {}, (error: Error, data: string) => { + expect(error).toBeNull(); + expect(data).toMatchSnapshot(); + done(); + }); + }); + + test('read file with no options should to be found to be read it as object', (done) => { + const options = { + parse: true, + }; + readFile(getFilePath('package.json'), options, (error: Error, data: string) => { + expect(error).toBeNull(); + expect(data).toMatchSnapshot(); + done(); + }); + }); + + test('read file with options (parse) should to be not found to be read it', (done) => { + const options = { + parse: true, + }; + readFile(getFilePath('package.fail.json'), options, (error: Error) => { + expect(error.message).toMatch(/ENOENT: no such file or directory, open '(.*)package.fail.json'/); + done(); + }); + }); + + test('read file with options should to be found to be read it and fails to be parsed', (done) => { + const options = { + parse: true, + }; + const errorMessage = process.platform === 'win32' ? 'Unexpected token } in JSON at position 47' : 'Unexpected token } in JSON at position 44'; + readFile(getFilePath('wrong.package.json'), options, (error: Error) => { + expect(error.message).toEqual(errorMessage); + done(); + }); + }); + + test('read file with options (parse, lock) should to be found to be read it as object', (done) => { + const options = { + parse: true, + lock: true, + }; + readFile(getFilePath('package2.json'), options, (error: Error, data: string) => { + expect(error).toBeNull(); + expect(data).toMatchSnapshot(); + removeTempFile('package2.json.lock'); + done(); + }); + }); + + test('read file with options (parse, lock) should to be found to be read it and fails to be parsed', (done) => { + const options = { + parse: true, + lock: true, + }; + const errorMessage = process.platform === 'win32' ? 'Unexpected token } in JSON at position 47' : 'Unexpected token } in JSON at position 44'; + readFile(getFilePath('wrong.package.json'), options, (error: Error) => { + expect(error.message).toEqual(errorMessage); + removeTempFile('wrong.package.json.lock'); + done(); + }); + }); + }); +}); diff --git a/packages/core/file-locking/tsconfig.json b/packages/core/file-locking/tsconfig.json new file mode 100644 index 000000000..537522270 --- /dev/null +++ b/packages/core/file-locking/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/file-locking/types/lockfile.d.ts b/packages/core/file-locking/types/lockfile.d.ts new file mode 100644 index 000000000..0ed7246a8 --- /dev/null +++ b/packages/core/file-locking/types/lockfile.d.ts @@ -0,0 +1,20 @@ +declare module 'lockfile' { + type Callback = (err?: Error) => void; + + interface LockOptions { + wait?: number; + pollPeriod?: number; + stale?: number; + retries?: number; + retryWait?: number; + } + + interface LockFileExport { + lock(fileName: string, opts: LockOptions, cb: Callback): void; + unlock(fileName: string, cb: Callback): void; + } + + const lockFileExport: LockFileExport; + + export default lockFileExport; +} diff --git a/packages/core/htpasswd/.babelrc b/packages/core/htpasswd/.babelrc new file mode 100644 index 000000000..851856e59 --- /dev/null +++ b/packages/core/htpasswd/.babelrc @@ -0,0 +1,3 @@ +{ + "extends": "../../../.babelrc" +} diff --git a/packages/core/htpasswd/.eslintignore b/packages/core/htpasswd/.eslintignore new file mode 100644 index 000000000..15e069712 --- /dev/null +++ b/packages/core/htpasswd/.eslintignore @@ -0,0 +1,6 @@ +node_modules +coverage/ +lib/ +.nyc_output +tests-report/ +fixtures/ diff --git a/packages/core/htpasswd/.eslintrc.json b/packages/core/htpasswd/.eslintrc.json new file mode 100644 index 000000000..3e8a7bcd4 --- /dev/null +++ b/packages/core/htpasswd/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "@typescript-eslint/no-use-before-define": "off" + } +} diff --git a/packages/core/htpasswd/CHANGELOG.md b/packages/core/htpasswd/CHANGELOG.md new file mode 100644 index 000000000..03594740b --- /dev/null +++ b/packages/core/htpasswd/CHANGELOG.md @@ -0,0 +1,328 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10) + + +### Bug Fixes + +* update dependencies ([#375](https://github.com/verdaccio/monorepo/issues/375)) ([1e7aeec](https://github.com/verdaccio/monorepo/commit/1e7aeec31b056979285e272793a95b8c75d57c77)) + + + + + +# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [9.4.1](https://github.com/verdaccio/monorepo/compare/v9.4.0...v9.4.1) (2020-04-30) + + +### Bug Fixes + +* **verdaccio-htpasswd:** generate non-constant legacy 2 byte salt ([#357](https://github.com/verdaccio/monorepo/issues/357)) ([d522595](https://github.com/verdaccio/monorepo/commit/d522595122b7deaac8e3bc568f73658041811aaf)) + + + + + +# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08) + + +### Bug Fixes + +* update dependencies ([#332](https://github.com/verdaccio/monorepo/issues/332)) ([b6165ae](https://github.com/verdaccio/monorepo/commit/b6165aea9b7e4012477081eae68bfa7159c58f56)) + + + + + +## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07) + + +### chore + +* update dependencies ([68add74](https://github.com/verdaccio/monorepo/commit/68add743159867f678ddb9168d2bc8391844de47)) + + +### Features + +* **eslint-config:** enable eslint curly ([#308](https://github.com/verdaccio/monorepo/issues/308)) ([91acb12](https://github.com/verdaccio/monorepo/commit/91acb121847018e737c21b367fcaab8baa918347)) + + +### BREAKING CHANGES + +* @verdaccio/eslint-config requires ESLint >=6.8.0 and Prettier >=1.19.1 to fix compatibility with overrides.extends config + + + + + +## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08) + + +### Bug Fixes + +* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7)) + + + + + +## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01) + +**Note:** Version bump only for package verdaccio-htpasswd + + + + + +# Change Log + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [2.0.0](https://github.com/verdaccio/verdaccio-htpasswd/compare/v2.0.0-beta.1...v2.0.0) (2019-04-14) + + +### Features + +* drop node v6 suport ([d1d52e8](https://github.com/verdaccio/verdaccio-htpasswd/commit/d1d52e8)) + + + + +# [2.0.0-beta.1](https://github.com/verdaccio/verdaccio-htpasswd/compare/v2.0.0-beta.0...v2.0.0-beta.1) (2019-02-24) + + +### Bug Fixes + +* package.json to reduce vulnerabilities ([259bdaf](https://github.com/verdaccio/verdaccio-htpasswd/commit/259bdaf)) +* update [@verdaccio](https://github.com/verdaccio)/file-locking@1.0.0 ([ec0bbfd](https://github.com/verdaccio/verdaccio-htpasswd/commit/ec0bbfd)) + + + + +# [2.0.0-beta.0](https://github.com/verdaccio/verdaccio-htpasswd/compare/v1.0.1...v2.0.0-beta.0) (2019-02-03) + + +### Features + +* migrate to typescript ([79f6937](https://github.com/verdaccio/verdaccio-htpasswd/commit/79f6937)) +* remove Node6 from CircleCI ([d3a05ab](https://github.com/verdaccio/verdaccio-htpasswd/commit/d3a05ab)) +* use verdaccio babel preset ([3a63f88](https://github.com/verdaccio/verdaccio-htpasswd/commit/3a63f88)) + + + + +## [1.0.1](https://github.com/verdaccio/verdaccio-htpasswd/compare/v1.0.0...v1.0.1) (2018-09-30) + + +### Bug Fixes + +* password hash & increase coverage ([6420c26](https://github.com/verdaccio/verdaccio-htpasswd/commit/6420c26)) + + + + +# [1.0.0](https://github.com/verdaccio/verdaccio-htpasswd/compare/v0.2.2...v1.0.0) (2018-09-30) + + +### Bug Fixes + +* adds error message for user registration ([0bab945](https://github.com/verdaccio/verdaccio-htpasswd/commit/0bab945)) + + +### Features + +* **change-passwd:** implement change password [#32](https://github.com/verdaccio/verdaccio-htpasswd/issues/32) ([830b143](https://github.com/verdaccio/verdaccio-htpasswd/commit/830b143)) diff --git a/packages/core/htpasswd/LICENSE b/packages/core/htpasswd/LICENSE new file mode 100644 index 000000000..65fb12e2a --- /dev/null +++ b/packages/core/htpasswd/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Verdaccio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/htpasswd/README.md b/packages/core/htpasswd/README.md new file mode 100644 index 000000000..8dc52cd45 --- /dev/null +++ b/packages/core/htpasswd/README.md @@ -0,0 +1,65 @@ + +[![verdaccio (latest)](https://img.shields.io/npm/v/verdaccio-htpasswd/latest.svg)](https://www.npmjs.com/package/verdaccio-htpasswd) +[![Known Vulnerabilities](https://snyk.io/test/github/verdaccio/verdaccio-htpasswd/badge.svg?targetFile=package.json)](https://snyk.io/test/github/verdaccio/verdaccio-htpasswd?targetFile=package.json) +[![CircleCI](https://circleci.com/gh/verdaccio/verdaccio-htpasswd.svg?style=svg)](https://circleci.com/gh/ayusharma/verdaccio-htpasswd) [![codecov](https://codecov.io/gh/ayusharma/verdaccio-htpasswd/branch/master/graph/badge.svg)](https://codecov.io/gh/ayusharma/verdaccio-htpasswd) +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd?ref=badge_shield) +[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio) +[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/) +![MIT](https://img.shields.io/github/license/mashape/apistatus.svg) +[![node](https://img.shields.io/node/v/verdaccio-htpasswd/latest.svg)](https://www.npmjs.com/package/verdaccio-htpasswd) + + +# Verdaccio Module For User Auth Via Htpasswd + +`verdaccio-htpasswd` is a default authentication plugin for the [Verdaccio](https://github.com/verdaccio/verdaccio). + +> This plugin is being used as dependency after `v3.0.0-beta.x`. The `v2.x` still contains this plugin built-in. + +## Install + +As simple as running: + + $ npm install -g verdaccio-htpasswd + +## Configure + + auth: + htpasswd: + file: ./htpasswd + # Maximum amount of users allowed to register, defaults to "+infinity". + # You can set this to -1 to disable registration. + #max_users: 1000 + +## Logging In + +To log in using NPM, run: + +``` + npm adduser --registry https://your.registry.local +``` + +## Generate htpasswd username/password combination + +If you wish to handle access control using htpasswd file, you can generate +username/password combination form +[here](http://www.htaccesstools.com/htpasswd-generator/) and add it to htpasswd +file. + +## How does it work? + +The htpasswd file contains rows corresponding to a pair of username and password +separated with a colon character. The password is encrypted using the UNIX system's +crypt method and may use MD5 or SHA1. + +## Plugin Development in Verdaccio + +There are many ways to extend [Verdaccio](https://github.com/verdaccio/verdaccio), +currently it support authentication plugins, middleware plugins (since v2.7.0) +and storage plugins since (v3.x). +#### Useful Links +- [Plugin Development](http://www.verdaccio.org/docs/en/dev-plugins.html) +- [List of Plugins](http://www.verdaccio.org/docs/en/plugins.html) + + +## License +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fverdaccio%2Fverdaccio-htpasswd?ref=badge_large) diff --git a/packages/core/htpasswd/jest.config.js b/packages/core/htpasswd/jest.config.js new file mode 100644 index 000000000..1c3fbdb05 --- /dev/null +++ b/packages/core/htpasswd/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../../jest/config'); + +module.exports = Object.assign({}, config, {}); diff --git a/packages/core/htpasswd/package.json b/packages/core/htpasswd/package.json new file mode 100644 index 000000000..ed8023e8e --- /dev/null +++ b/packages/core/htpasswd/package.json @@ -0,0 +1,50 @@ +{ + "name": "@verdaccio/htpasswd", + "version": "10.0.0-beta", + "description": "htpasswd auth plugin for Verdaccio", + "keywords": [ + "verdaccio", + "plugin", + "auth", + "htpasswd" + ], + "author": "Ayush Sharma ", + "license": "MIT", + "homepage": "https://verdaccio.org", + "repository": { + "type": "git", + "url": "https://github.com/verdaccio/monorepo", + "directory": "plugins/htpasswd" + }, + "bugs": { + "url": "https://github.com/verdaccio/monorepo/issues" + }, + "main": "./build/index.js", + "types": "./build/index.d.ts", + "files": [ + "build" + ], + "dependencies": { + "@verdaccio/file-locking": "workspace:*", + "apache-md5": "1.1.2", + "bcryptjs": "2.4.3", + "http-errors": "1.8.0", + "unix-crypt-td-js": "1.1.4" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@verdaccio/types": "workspace:*" + }, + "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": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", + "build": "pnpm run build:js && pnpm run build:types" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } +} diff --git a/packages/core/htpasswd/src/crypt3.ts b/packages/core/htpasswd/src/crypt3.ts new file mode 100644 index 000000000..9e34f98c1 --- /dev/null +++ b/packages/core/htpasswd/src/crypt3.ts @@ -0,0 +1,52 @@ +/** Node.js Crypt(3) Library + Inspired by (and intended to be compatible with) sendanor/crypt3 + see https://github.com/sendanor/node-crypt3 + The key difference is the removal of the dependency on the unix crypt(3) function + which is not platform independent, and requires compilation. Instead, a pure + javascript version is used. +*/ + +import crypto from 'crypto'; + +import crypt from 'unix-crypt-td-js'; + +/** + * Create salt + * @param {string} type The type of salt: md5, blowfish (only some linux + * distros), sha256 or sha512. Default is sha512. + * @returns {string} Generated salt string + */ +export function createSalt(type = 'crypt'): string { + switch (type) { + case 'crypt': + // Legacy crypt salt with no prefix (only the first 2 bytes will be used). + return crypto.randomBytes(2).toString('base64'); + + case 'md5': + return '$1$' + crypto.randomBytes(10).toString('base64'); + + case 'blowfish': + return '$2a$' + crypto.randomBytes(10).toString('base64'); + + case 'sha256': + return '$5$' + crypto.randomBytes(10).toString('base64'); + + case 'sha512': + return '$6$' + crypto.randomBytes(10).toString('base64'); + + default: + throw new TypeError(`Unknown salt type at crypt3.createSalt: ${type}`); + } +} + +/** + * Crypt(3) password and data encryption. + * @param {string} key user's typed password + * @param {string} salt Optional salt, for example SHA-512 use "$6$salt$". + * @returns {string} A generated hash in format $id$salt$encrypted + * @see https://en.wikipedia.org/wiki/Crypt_(C) + */ + +export default function crypt3(key: string, salt: string = createSalt()): string { + return crypt(key, salt); +} diff --git a/packages/core/htpasswd/src/htpasswd.ts b/packages/core/htpasswd/src/htpasswd.ts new file mode 100644 index 000000000..fb508365b --- /dev/null +++ b/packages/core/htpasswd/src/htpasswd.ts @@ -0,0 +1,239 @@ +import fs from 'fs'; +import Path from 'path'; + +import { Callback, AuthConf, Config, IPluginAuth } from '@verdaccio/types'; +import { unlockFile } from '@verdaccio/file-locking'; + +import { verifyPassword, lockAndRead, parseHTPasswd, addUserToHTPasswd, changePasswordToHTPasswd, sanityCheck } from './utils'; + +export interface VerdaccioConfigApp extends Config { + file: string; +} + +/** + * HTPasswd - Verdaccio auth class + */ +export default class HTPasswd implements IPluginAuth { + /** + * + * @param {*} config htpasswd file + * @param {object} stuff config.yaml in object from + */ + private users: {}; + private stuff: {}; + private config: {}; + private verdaccioConfig: Config; + private maxUsers: number; + private path: string; + private logger: {}; + private lastTime: any; + // constructor + public constructor(config: AuthConf, stuff: VerdaccioConfigApp) { + this.users = {}; + + // config for this module + this.config = config; + this.stuff = stuff; + + // verdaccio logger + this.logger = stuff.logger; + + // verdaccio main config object + this.verdaccioConfig = stuff.config; + + // all this "verdaccio_config" stuff is for b/w compatibility only + this.maxUsers = config.max_users ? config.max_users : Infinity; + + this.lastTime = null; + + const { file } = config; + + if (!file) { + throw new Error('should specify "file" in config'); + } + + this.path = Path.resolve(Path.dirname(this.verdaccioConfig.self_path), file); + } + + /** + * authenticate - Authenticate user. + * @param {string} user + * @param {string} password + * @param {function} cd + * @returns {function} + */ + public authenticate(user: string, password: string, cb: Callback): void { + this.reload((err) => { + if (err) { + return cb(err.code === 'ENOENT' ? null : err); + } + if (!this.users[user]) { + return cb(null, false); + } + if (!verifyPassword(password, this.users[user])) { + return cb(null, false); + } + + // authentication succeeded! + // return all usergroups this user has access to; + // (this particular package has no concept of usergroups, so just return + // user herself) + return cb(null, [user]); + }); + } + + /** + * Add user + * 1. lock file for writing (other processes can still read) + * 2. reload .htpasswd + * 3. write new data into .htpasswd.tmp + * 4. move .htpasswd.tmp to .htpasswd + * 5. reload .htpasswd + * 6. unlock file + * + * @param {string} user + * @param {string} password + * @param {function} realCb + * @returns {function} + */ + public adduser(user: string, password: string, realCb: Callback): any { + const pathPass = this.path; + let sanity = sanityCheck(user, password, verifyPassword, this.users, this.maxUsers); + + // preliminary checks, just to ensure that file won't be reloaded if it's + // not needed + if (sanity) { + return realCb(sanity, false); + } + + lockAndRead(pathPass, (err, res): void => { + let locked = false; + + // callback that cleans up lock first + const cb = (err): void => { + if (locked) { + unlockFile(pathPass, () => { + // ignore any error from the unlock + realCb(err, !err); + }); + } else { + realCb(err, !err); + } + }; + + if (!err) { + locked = true; + } + + // ignore ENOENT errors, we'll just create .htpasswd in that case + if (err && err.code !== 'ENOENT') { + return cb(err); + } + const body = (res || '').toString('utf8'); + this.users = parseHTPasswd(body); + + // real checks, to prevent race conditions + // parsing users after reading file. + sanity = sanityCheck(user, password, verifyPassword, this.users, this.maxUsers); + + if (sanity) { + return cb(sanity); + } + + try { + this._writeFile(addUserToHTPasswd(body, user, password), cb); + } catch (err) { + return cb(err); + } + }); + } + + /** + * Reload users + * @param {function} callback + */ + public reload(callback: Callback): void { + fs.stat(this.path, (err, stats) => { + if (err) { + return callback(err); + } + if (this.lastTime === stats.mtime) { + return callback(); + } + + this.lastTime = stats.mtime; + + fs.readFile(this.path, 'utf8', (err, buffer) => { + if (err) { + return callback(err); + } + + Object.assign(this.users, parseHTPasswd(buffer)); + callback(); + }); + }); + } + + private _stringToUt8(authentication: string): string { + return (authentication || '').toString(); + } + + private _writeFile(body: string, cb: Callback): void { + fs.writeFile(this.path, body, (err) => { + if (err) { + cb(err); + } else { + this.reload(() => { + cb(null); + }); + } + }); + } + + /** + * changePassword - change password for existing user. + * @param {string} user + * @param {string} password + * @param {function} cd + * @returns {function} + */ + public changePassword(user: string, password: string, newPassword: string, realCb: Callback): void { + lockAndRead(this.path, (err, res) => { + let locked = false; + const pathPassFile = this.path; + + // callback that cleans up lock first + const cb = (err): void => { + if (locked) { + unlockFile(pathPassFile, () => { + // ignore any error from the unlock + realCb(err, !err); + }); + } else { + realCb(err, !err); + } + }; + + if (!err) { + locked = true; + } + + if (err && err.code !== 'ENOENT') { + return cb(err); + } + + const body = this._stringToUt8(res); + this.users = parseHTPasswd(body); + + if (!this.users[user]) { + return cb(new Error('User not found')); + } + + try { + this._writeFile(changePasswordToHTPasswd(body, user, password, newPassword), cb); + } catch (err) { + return cb(err); + } + }); + } +} diff --git a/packages/core/htpasswd/src/index.ts b/packages/core/htpasswd/src/index.ts new file mode 100644 index 000000000..d15898c70 --- /dev/null +++ b/packages/core/htpasswd/src/index.ts @@ -0,0 +1,11 @@ +import HTPasswd from './htpasswd'; + +/** + * A new instance of HTPasswd class. + * @param {object} config + * @param {object} stuff + * @returns {object} + */ +export default function (config, stuff): HTPasswd { + return new HTPasswd(config, stuff); +} diff --git a/packages/core/htpasswd/src/utils.ts b/packages/core/htpasswd/src/utils.ts new file mode 100644 index 000000000..f27a1024a --- /dev/null +++ b/packages/core/htpasswd/src/utils.ts @@ -0,0 +1,174 @@ +import crypto from 'crypto'; + +import md5 from 'apache-md5'; +import bcrypt from 'bcryptjs'; +import createError, { HttpError } from 'http-errors'; +import { readFile } from '@verdaccio/file-locking'; +import { Callback } from '@verdaccio/types'; + +import crypt3 from './crypt3'; + +// this function neither unlocks file nor closes it +// it'll have to be done manually later +export function lockAndRead(name: string, cb: Callback): void { + readFile(name, { lock: true }, (err, res) => { + if (err) { + return cb(err); + } + + return cb(null, res); + }); +} + +/** + * parseHTPasswd - convert htpasswd lines to object. + * @param {string} input + * @returns {object} + */ +export function parseHTPasswd(input: string): Record { + return input.split('\n').reduce((result, line) => { + const args = line.split(':', 3); + if (args.length > 1) { + result[args[0]] = args[1]; + } + return result; + }, {}); +} + +/** + * verifyPassword - matches password and it's hash. + * @param {string} passwd + * @param {string} hash + * @returns {boolean} + */ +export function verifyPassword(passwd: string, hash: string): boolean { + if (hash.match(/^\$2(a|b|y)\$/)) { + return bcrypt.compareSync(passwd, hash); + } else if (hash.indexOf('{PLAIN}') === 0) { + return passwd === hash.substr(7); + } else if (hash.indexOf('{SHA}') === 0) { + return ( + crypto + .createHash('sha1') + // https://nodejs.org/api/crypto.html#crypto_hash_update_data_inputencoding + .update(passwd, 'utf8') + .digest('base64') === hash.substr(5) + ); + } + // for backwards compatibility, first check md5 then check crypt3 + return md5(passwd, hash) === hash || crypt3(passwd, hash) === hash; +} + +/** + * addUserToHTPasswd - Generate a htpasswd format for .htpasswd + * @param {string} body + * @param {string} user + * @param {string} passwd + * @returns {string} + */ +export function addUserToHTPasswd(body: string, user: string, passwd: string): string { + if (user !== encodeURIComponent(user)) { + const err = createError('username should not contain non-uri-safe characters'); + + err.status = 409; + throw err; + } + + if (crypt3) { + passwd = crypt3(passwd); + } else { + passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'utf8').digest('base64'); + } + const comment = 'autocreated ' + new Date().toJSON(); + let newline = `${user}:${passwd}:${comment}\n`; + + if (body.length && body[body.length - 1] !== '\n') { + newline = '\n' + newline; + } + return body + newline; +} + +/** + * Sanity check for a user + * @param {string} user + * @param {object} users + * @param {number} maxUsers + * @returns {object} + */ +export function sanityCheck(user: string, password: string, verifyFn: Callback, users: {}, maxUsers: number): HttpError | null { + let err; + + // check for user or password + if (!user || !password) { + err = Error('username and password is required'); + err.status = 400; + return err; + } + + const hash = users[user]; + + if (maxUsers < 0) { + err = Error('user registration disabled'); + err.status = 409; + return err; + } + + if (hash) { + const auth = verifyFn(password, users[user]); + if (auth) { + err = Error('username is already registered'); + err.status = 409; + return err; + } + err = Error('unauthorized access'); + err.status = 401; + return err; + } else if (Object.keys(users).length >= maxUsers) { + err = Error('maximum amount of users reached'); + err.status = 403; + return err; + } + + return null; +} + +export function getCryptoPassword(password: string): string { + return `{SHA}${crypto.createHash('sha1').update(password, 'utf8').digest('base64')}`; +} + +/** + * changePasswordToHTPasswd - change password for existing user + * @param {string} body + * @param {string} user + * @param {string} passwd + * @param {string} newPasswd + * @returns {string} + */ +export function changePasswordToHTPasswd(body: string, user: string, passwd: string, newPasswd: string): string { + let lines = body.split('\n'); + lines = lines.map((line) => { + const [username, password] = line.split(':', 3); + + if (username === user) { + let _passwd; + let _newPasswd; + if (crypt3) { + _passwd = crypt3(passwd, password); + _newPasswd = crypt3(newPasswd); + } else { + _passwd = getCryptoPassword(passwd); + _newPasswd = getCryptoPassword(newPasswd); + } + + if (password == _passwd) { + // replace old password hash with new password hash + line = line.replace(_passwd, _newPasswd); + } else { + throw new Error('Invalid old Password'); + } + } + return line; + }); + + return lines.join('\n'); +} diff --git a/packages/core/htpasswd/tests/__fixtures__/config.yaml b/packages/core/htpasswd/tests/__fixtures__/config.yaml new file mode 100644 index 000000000..e84148754 --- /dev/null +++ b/packages/core/htpasswd/tests/__fixtures__/config.yaml @@ -0,0 +1,39 @@ +storage: './test-storage' +listen: 'http://localhost:1443/' +auth: + htpasswd: + file: ./htpasswd + # Maximum amount of users allowed to register, defaults to "+inf". + # You can set this to -1 to disable registration. + max_users: 1000 + +# a list of other known repositories we can talk to +uplinks: + npmjs: + url: https://registry.npmjs.org/ + +packages: + '@*/*': + # scoped packages + access: $all + publish: $authenticated + + '*': + # allow all users (including non-authenticated users) to read and + # publish all packages + # + # you can specify usernames/groupnames (depending on your auth plugin) + # and three keywords: "$all", "$anonymous", "$authenticated" + access: $all + + # allow all known users to publish packages + # (anyone can register by default, remember?) + publish: $authenticated + + # if package is not available locally, proxy requests to 'npmjs' registry + proxy: npmjs + +# log settings +logs: + - {type: stdout, format: pretty, level: http} + #- {type: file, path: verdaccio.log, level: info} diff --git a/packages/core/htpasswd/tests/__fixtures__/htpasswd b/packages/core/htpasswd/tests/__fixtures__/htpasswd new file mode 100644 index 000000000..dda54a931 --- /dev/null +++ b/packages/core/htpasswd/tests/__fixtures__/htpasswd @@ -0,0 +1,2 @@ +test:$6FrCaT/v0dwE:autocreated 2018-01-17T03:40:22.958Z +username:$66to3JK5RgZM:autocreated 2018-01-17T03:40:46.315Z diff --git a/packages/core/htpasswd/tests/__mocks__/Config.js b/packages/core/htpasswd/tests/__mocks__/Config.js new file mode 100644 index 000000000..507edb36a --- /dev/null +++ b/packages/core/htpasswd/tests/__mocks__/Config.js @@ -0,0 +1,50 @@ +export default class Config { + constructor() { + this.storage = './test-storage'; + this.listen = 'http://localhost:1443/'; + this.auth = { + htpasswd: { + file: './htpasswd', + max_users: 1000, + }, + }; + this.uplinks = { + npmjs: { + url: 'https://registry.npmjs.org', + cache: true, + }, + }; + this.packages = { + '@*/*': { + access: ['$all'], + publish: ['$authenticated'], + proxy: [], + }, + '*': { + access: ['$all'], + publish: ['$authenticated'], + proxy: ['npmjs'], + }, + '**': { + access: [], + publish: [], + proxy: [], + }, + }; + this.logs = [ + { + type: 'stdout', + format: 'pretty', + level: 35, + }, + ]; + this.self_path = './tests/__fixtures__/config.yaml'; + this.https = { + enable: false, + }; + this.user_agent = 'verdaccio/3.0.0-alpha.7'; + this.users = {}; + this.server_id = '5cf430af30a1'; + this.secret = 'ebde3e3a2a789a0623bf3de58cd127f0b309f573686cc91dc6d0f8fc6214b542'; + } +} diff --git a/packages/core/htpasswd/tests/__mocks__/Logger.ts b/packages/core/htpasswd/tests/__mocks__/Logger.ts new file mode 100644 index 000000000..18e0b8c4a --- /dev/null +++ b/packages/core/htpasswd/tests/__mocks__/Logger.ts @@ -0,0 +1 @@ +export default class Logger {} diff --git a/packages/core/htpasswd/tests/__snapshots__/utils.test.ts.snap b/packages/core/htpasswd/tests/__snapshots__/utils.test.ts.snap new file mode 100644 index 000000000..282edfdaf --- /dev/null +++ b/packages/core/htpasswd/tests/__snapshots__/utils.test.ts.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`addUserToHTPasswd - crypt3 should add new htpasswd to the end 1`] = ` +"username:$66to3JK5RgZM:autocreated 2018-01-14T11:17:40.712Z +" +`; + +exports[`addUserToHTPasswd - crypt3 should add new htpasswd to the end in multiline input 1`] = ` +"test1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z + test2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z +username:$66to3JK5RgZM:autocreated 2018-01-14T11:17:40.712Z +" +`; + +exports[`addUserToHTPasswd - crypt3 should throw an error for incorrect username with space 1`] = `"username should not contain non-uri-safe characters"`; + +exports[`changePasswordToHTPasswd should change the password 1`] = `"root:$6JaJqI5HUf.Q:autocreated 2018-08-20T13:38:12.164Z"`; diff --git a/packages/core/htpasswd/tests/crypt3.test.ts b/packages/core/htpasswd/tests/crypt3.test.ts new file mode 100644 index 000000000..a0239fec2 --- /dev/null +++ b/packages/core/htpasswd/tests/crypt3.test.ts @@ -0,0 +1,31 @@ +import { createSalt } from '../src/crypt3'; + +jest.mock('crypto', () => { + return { + randomBytes: (): { toString: () => string } => { + return { + toString: (): string => '/UEGzD0RxSNDZA==', + }; + }, + }; +}); + +describe('createSalt', () => { + test('should match with the correct salt type', () => { + expect(createSalt('crypt')).toEqual('/UEGzD0RxSNDZA=='); + expect(createSalt('md5')).toEqual('$1$/UEGzD0RxSNDZA=='); + expect(createSalt('blowfish')).toEqual('$2a$/UEGzD0RxSNDZA=='); + expect(createSalt('sha256')).toEqual('$5$/UEGzD0RxSNDZA=='); + expect(createSalt('sha512')).toEqual('$6$/UEGzD0RxSNDZA=='); + }); + + test('should fails on unkwon type', () => { + expect(function () { + createSalt('bad'); + }).toThrow(/Unknown salt type at crypt3.createSalt: bad/); + }); + + test('should generate legacy crypt salt by default', () => { + expect(createSalt()).toEqual(createSalt('crypt')); + }); +}); diff --git a/packages/core/htpasswd/tests/htpasswd.test.ts b/packages/core/htpasswd/tests/htpasswd.test.ts new file mode 100644 index 000000000..c788bb22b --- /dev/null +++ b/packages/core/htpasswd/tests/htpasswd.test.ts @@ -0,0 +1,285 @@ +/* eslint-disable jest/no-mocks-import */ +import crypto from 'crypto'; +// @ts-ignore +import fs from 'fs'; + +import HTPasswd, { VerdaccioConfigApp } from '../src/htpasswd'; + +// FIXME: remove this mocks imports +import Logger from './__mocks__/Logger'; +import Config from './__mocks__/Config'; + +const stuff = { + logger: new Logger(), + config: new Config(), +}; + +const config = { + file: './htpasswd', + max_users: 1000, +}; + +describe('HTPasswd', () => { + let wrapper; + + beforeEach(() => { + wrapper = new HTPasswd(config, (stuff as unknown) as VerdaccioConfigApp); + jest.resetModules(); + + crypto.randomBytes = jest.fn(() => { + return { + toString: (): string => '$6', + }; + }); + }); + + describe('constructor()', () => { + test('should files whether file path does not exist', () => { + expect(function () { + new HTPasswd({}, ({ + config: {}, + } as unknown) as VerdaccioConfigApp); + }).toThrow(/should specify "file" in config/); + }); + }); + + describe('authenticate()', () => { + test('it should authenticate user with given credentials', (done) => { + const callbackTest = (a, b): void => { + expect(a).toBeNull(); + expect(b).toContain('test'); + done(); + }; + const callbackUsername = (a, b): void => { + expect(a).toBeNull(); + expect(b).toContain('username'); + done(); + }; + wrapper.authenticate('test', 'test', callbackTest); + wrapper.authenticate('username', 'password', callbackUsername); + }); + + test('it should not authenticate user with given credentials', (done) => { + const callback = (a, b): void => { + expect(a).toBeNull(); + expect(b).toBeFalsy(); + done(); + }; + wrapper.authenticate('test', 'somerandompassword', callback); + }); + }); + + describe('addUser()', () => { + test('it should not pass sanity check', (done) => { + const callback = (a): void => { + expect(a.message).toEqual('unauthorized access'); + done(); + }; + wrapper.adduser('test', 'somerandompassword', callback); + }); + + test('it should add the user', (done) => { + let dataToWrite; + // @ts-ignore + fs.writeFile = jest.fn((name, data, callback) => { + dataToWrite = data; + callback(); + }); + const callback = (a, b): void => { + expect(a).toBeNull(); + expect(b).toBeTruthy(); + expect(fs.writeFile).toHaveBeenCalled(); + expect(dataToWrite.indexOf('usernotpresent')).not.toEqual(-1); + done(); + }; + wrapper.adduser('usernotpresent', 'somerandompassword', callback); + }); + + describe('addUser() error handling', () => { + test('sanityCheck should return an Error', (done) => { + jest.doMock('../src/utils.ts', () => { + return { + sanityCheck: (): Error => Error('some error'), + }; + }); + + const HTPasswd = require('../src/htpasswd.ts').default; + const wrapper = new HTPasswd(config, stuff); + wrapper.adduser('sanityCheck', 'test', (sanity) => { + expect(sanity.message).toBeDefined(); + expect(sanity.message).toMatch('some error'); + done(); + }); + }); + + test('lockAndRead should return an Error', (done) => { + jest.doMock('../src/utils.ts', () => { + return { + sanityCheck: (): any => null, + lockAndRead: (_a, b): any => b(new Error('lock error')), + }; + }); + + const HTPasswd = require('../src/htpasswd.ts').default; + const wrapper = new HTPasswd(config, stuff); + wrapper.adduser('lockAndRead', 'test', (sanity) => { + expect(sanity.message).toBeDefined(); + expect(sanity.message).toMatch('lock error'); + done(); + }); + }); + + test('addUserToHTPasswd should return an Error', (done) => { + jest.doMock('../src/utils.ts', () => { + return { + sanityCheck: (): any => null, + parseHTPasswd: (): void => {}, + lockAndRead: (_a, b): any => b(null, ''), + unlockFile: (_a, b): any => b(), + }; + }); + + const HTPasswd = require('../src/htpasswd.ts').default; + const wrapper = new HTPasswd(config, stuff); + wrapper.adduser('addUserToHTPasswd', 'test', () => { + done(); + }); + }); + + test('writeFile should return an Error', (done) => { + jest.doMock('../src/utils.ts', () => { + return { + sanityCheck: (): any => null, + parseHTPasswd: (): void => {}, + lockAndRead: (_a, b): any => b(null, ''), + addUserToHTPasswd: (): void => {}, + }; + }); + jest.doMock('fs', () => { + const original = jest.requireActual('fs'); + return { + ...original, + writeFile: jest.fn((_name, _data, callback) => { + callback(new Error('write error')); + }), + }; + }); + + const HTPasswd = require('../src/htpasswd.ts').default; + const wrapper = new HTPasswd(config, stuff); + wrapper.adduser('addUserToHTPasswd', 'test', (err) => { + expect(err).not.toBeNull(); + expect(err.message).toMatch('write error'); + done(); + }); + }); + }); + + describe('reload()', () => { + test('it should read the file and set the users', (done) => { + const output = { test: '$6FrCaT/v0dwE', username: '$66to3JK5RgZM' }; + const callback = (): void => { + expect(wrapper.users).toEqual(output); + done(); + }; + wrapper.reload(callback); + }); + + test('reload should fails on check file', (done) => { + jest.doMock('fs', () => { + return { + stat: (_name, callback): void => { + callback(new Error('stat error'), null); + }, + }; + }); + const callback = (err): void => { + expect(err).not.toBeNull(); + expect(err.message).toMatch('stat error'); + done(); + }; + + const HTPasswd = require('../src/htpasswd.ts').default; + const wrapper = new HTPasswd(config, stuff); + wrapper.reload(callback); + }); + + test('reload times match', (done) => { + jest.doMock('fs', () => { + return { + stat: (_name, callback): void => { + callback(null, { + mtime: null, + }); + }, + }; + }); + const callback = (err): void => { + expect(err).toBeUndefined(); + done(); + }; + + const HTPasswd = require('../src/htpasswd.ts').default; + const wrapper = new HTPasswd(config, stuff); + wrapper.reload(callback); + }); + + test('reload should fails on read file', (done) => { + jest.doMock('fs', () => { + return { + stat: jest.requireActual('fs').stat, + readFile: (_name, _format, callback): void => { + callback(new Error('read error'), null); + }, + }; + }); + const callback = (err): void => { + expect(err).not.toBeNull(); + expect(err.message).toMatch('read error'); + done(); + }; + + const HTPasswd = require('../src/htpasswd.ts').default; + const wrapper = new HTPasswd(config, stuff); + wrapper.reload(callback); + }); + }); + }); + + test('changePassword - it should throw an error for user not found', (done) => { + const callback = (error, isSuccess): void => { + expect(error).not.toBeNull(); + expect(error.message).toBe('User not found'); + expect(isSuccess).toBeFalsy(); + done(); + }; + wrapper.changePassword('usernotpresent', 'oldPassword', 'newPassword', callback); + }); + + test('changePassword - it should throw an error for wrong password', (done) => { + const callback = (error, isSuccess): void => { + expect(error).not.toBeNull(); + expect(error.message).toBe('Invalid old Password'); + expect(isSuccess).toBeFalsy(); + done(); + }; + wrapper.changePassword('username', 'wrongPassword', 'newPassword', callback); + }); + + test('changePassword - it should change password', (done) => { + let dataToWrite; + // @ts-ignore + fs.writeFile = jest.fn((_name, data, callback) => { + dataToWrite = data; + callback(); + }); + const callback = (error, isSuccess): void => { + expect(error).toBeNull(); + expect(isSuccess).toBeTruthy(); + expect(fs.writeFile).toHaveBeenCalled(); + expect(dataToWrite.indexOf('username')).not.toEqual(-1); + done(); + }; + wrapper.changePassword('username', 'password', 'newPassword', callback); + }); +}); diff --git a/packages/core/htpasswd/tests/utils.test.ts b/packages/core/htpasswd/tests/utils.test.ts new file mode 100644 index 000000000..40624ff11 --- /dev/null +++ b/packages/core/htpasswd/tests/utils.test.ts @@ -0,0 +1,252 @@ +import crypto from 'crypto'; + +import { verifyPassword, lockAndRead, parseHTPasswd, addUserToHTPasswd, sanityCheck, changePasswordToHTPasswd, getCryptoPassword } from '../src/utils'; + +const mockReadFile = jest.fn(); +const mockUnlockFile = jest.fn(); + +jest.mock('@verdaccio/file-locking', () => ({ + readFile: () => mockReadFile(), + unlockFile: () => mockUnlockFile(), +})); + +describe('parseHTPasswd', () => { + it('should parse the password for a single line', () => { + const input = 'test:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z'; + const output = { test: '$6b9MlB3WUELU' }; + expect(parseHTPasswd(input)).toEqual(output); + }); + + it('should parse the password for two lines', () => { + const input = `user1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z +user2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z`; + const output = { user1: '$6b9MlB3WUELU', user2: '$6FrCaT/v0dwE' }; + expect(parseHTPasswd(input)).toEqual(output); + }); + + it('should parse the password for multiple lines', () => { + const input = `user1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z +user2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z +user3:$6FrCdfd\v0dwE:autocreated 2017-12-14T13:30:20.838Z +user4:$6FrCasdvppdwE:autocreated 2017-12-14T13:30:20.838Z`; + const output = { + user1: '$6b9MlB3WUELU', + user2: '$6FrCaT/v0dwE', + user3: '$6FrCdfd\v0dwE', + user4: '$6FrCasdvppdwE', + }; + expect(parseHTPasswd(input)).toEqual(output); + }); +}); + +describe('verifyPassword', () => { + it('should verify the MD5/Crypt3 password with true', () => { + const input = ['test', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0']; + expect(verifyPassword(input[0], input[1])).toBeTruthy(); + }); + it('should verify the MD5/Crypt3 password with false', () => { + const input = ['testpasswordchanged', '$apr1$sKXK9.lG$rZ4Iy63Vtn8jF9/USc4BV0']; + expect(verifyPassword(input[0], input[1])).toBeFalsy(); + }); + it('should verify the plain password with true', () => { + const input = ['testpasswordchanged', '{PLAIN}testpasswordchanged']; + expect(verifyPassword(input[0], input[1])).toBeTruthy(); + }); + it('should verify the plain password with false', () => { + const input = ['testpassword', '{PLAIN}testpasswordchanged']; + expect(verifyPassword(input[0], input[1])).toBeFalsy(); + }); + it('should verify the crypto SHA password with true', () => { + const input = ['testpassword', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0=']; + expect(verifyPassword(input[0], input[1])).toBeTruthy(); + }); + it('should verify the crypto SHA password with false', () => { + const input = ['testpasswordchanged', '{SHA}i7YRj4/Wk1rQh2o740pxfTJwj/0=']; + expect(verifyPassword(input[0], input[1])).toBeFalsy(); + }); + it('should verify the bcrypt password with true', () => { + const input = ['testpassword', '$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO']; + expect(verifyPassword(input[0], input[1])).toBeTruthy(); + }); + it('should verify the bcrypt password with false', () => { + const input = ['testpasswordchanged', '$2y$04$Wqed4yN0OktGbiUdxSTwtOva1xfESfkNIZfcS9/vmHLsn3.lkFxJO']; + expect(verifyPassword(input[0], input[1])).toBeFalsy(); + }); +}); + +describe('addUserToHTPasswd - crypt3', () => { + beforeAll(() => { + // @ts-ignore + global.Date = jest.fn(() => { + return { + parse: jest.fn(), + toJSON: (): string => '2018-01-14T11:17:40.712Z', + }; + }); + + crypto.randomBytes = jest.fn(() => { + return { + toString: (): string => '$6', + }; + }); + }); + + it('should add new htpasswd to the end', () => { + const input = ['', 'username', 'password']; + expect(addUserToHTPasswd(input[0], input[1], input[2])).toMatchSnapshot(); + }); + + it('should add new htpasswd to the end in multiline input', () => { + const body = `test1:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z + test2:$6FrCaT/v0dwE:autocreated 2017-12-14T13:30:20.838Z`; + const input = [body, 'username', 'password']; + expect(addUserToHTPasswd(input[0], input[1], input[2])).toMatchSnapshot(); + }); + + it('should throw an error for incorrect username with space', () => { + const [a, b, c] = ['', 'firstname lastname', 'password']; + expect(() => addUserToHTPasswd(a, b, c)).toThrowErrorMatchingSnapshot(); + }); +}); + +// ToDo: mock crypt3 with false +describe('addUserToHTPasswd - crypto', () => { + it('should create password with crypto', () => { + jest.resetModules(); + jest.doMock('../src/crypt3.ts', () => false); + const input = ['', 'username', 'password']; + const utils = require('../src/utils.ts'); + expect(utils.addUserToHTPasswd(input[0], input[1], input[2])).toEqual('username:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=:autocreated 2018-01-14T11:17:40.712Z\n'); + }); +}); + +describe('lockAndRead', () => { + it('should call the readFile method', () => { + // console.log(fileLocking); + // const spy = jest.spyOn(fileLocking, 'readFile'); + const cb = (): void => {}; + lockAndRead('.htpasswd', cb); + expect(mockReadFile).toHaveBeenCalled(); + }); +}); + +describe('sanityCheck', () => { + let users; + + beforeEach(() => { + users = { test: '$6FrCaT/v0dwE' }; + }); + + test('should throw error for user already exists', () => { + const verifyFn = jest.fn(); + const input = sanityCheck('test', users.test, verifyFn, users, Infinity); + expect(input.status).toEqual(401); + expect(input.message).toEqual('unauthorized access'); + expect(verifyFn).toHaveBeenCalled(); + }); + + test('should throw error for registration disabled of users', () => { + const verifyFn = (): void => {}; + const input = sanityCheck('username', users.test, verifyFn, users, -1); + expect(input.status).toEqual(409); + expect(input.message).toEqual('user registration disabled'); + }); + + test('should throw error max number of users', () => { + const verifyFn = (): void => {}; + const input = sanityCheck('username', users.test, verifyFn, users, 1); + expect(input.status).toEqual(403); + expect(input.message).toEqual('maximum amount of users reached'); + }); + + test('should not throw anything and sanity check', () => { + const verifyFn = (): void => {}; + const input = sanityCheck('username', users.test, verifyFn, users, 2); + expect(input).toBeNull(); + }); + + test('should throw error for required username field', () => { + const verifyFn = (): void => {}; + const input = sanityCheck(undefined, users.test, verifyFn, users, 2); + expect(input.message).toEqual('username and password is required'); + expect(input.status).toEqual(400); + }); + + test('should throw error for required password field', () => { + const verifyFn = (): void => {}; + const input = sanityCheck('username', undefined, verifyFn, users, 2); + expect(input.message).toEqual('username and password is required'); + expect(input.status).toEqual(400); + }); + + test('should throw error for required username & password fields', () => { + const verifyFn = (): void => {}; + const input = sanityCheck(undefined, undefined, verifyFn, users, 2); + expect(input.message).toEqual('username and password is required'); + expect(input.status).toEqual(400); + }); + + test('should throw error for existing username and password', () => { + const verifyFn = jest.fn(() => true); + const input = sanityCheck('test', users.test, verifyFn, users, 2); + expect(input.status).toEqual(409); + expect(input.message).toEqual('username is already registered'); + expect(verifyFn).toHaveBeenCalledTimes(1); + }); + + test('should throw error for existing username and password with max number of users reached', () => { + const verifyFn = jest.fn(() => true); + const input = sanityCheck('test', users.test, verifyFn, users, 1); + expect(input.status).toEqual(409); + expect(input.message).toEqual('username is already registered'); + expect(verifyFn).toHaveBeenCalledTimes(1); + }); +}); + +describe('changePasswordToHTPasswd', () => { + test('should throw error for wrong password', () => { + const body = 'test:$6b9MlB3WUELU:autocreated 2017-11-06T18:17:21.957Z'; + + try { + changePasswordToHTPasswd(body, 'test', 'somerandompassword', 'newPassword'); + } catch (error) { + expect(error.message).toEqual('Invalid old Password'); + } + }); + + test('should change the password', () => { + const body = 'root:$6qLTHoPfGLy2:autocreated 2018-08-20T13:38:12.164Z'; + + expect(changePasswordToHTPasswd(body, 'root', 'demo123', 'newPassword')).toMatchSnapshot(); + }); + + test('should generate a different result on salt change', () => { + crypto.randomBytes = jest.fn(() => { + return { + toString: (): string => 'AB', + }; + }); + + const body = 'root:$6qLTHoPfGLy2:autocreated 2018-08-20T13:38:12.164Z'; + + expect(changePasswordToHTPasswd(body, 'root', 'demo123', 'demo123')).toEqual('root:ABfaAAjDKIgfw:autocreated 2018-08-20T13:38:12.164Z'); + }); + + test('should change the password when crypt3 is not available', () => { + jest.resetModules(); + jest.doMock('../src/crypt3.ts', () => false); + const utils = require('../src/utils.ts'); + const body = 'username:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=:autocreated 2018-01-14T11:17:40.712Z'; + expect(utils.changePasswordToHTPasswd(body, 'username', 'password', 'newPassword')).toEqual( + 'username:{SHA}KD1HqTOO0RALX+Klr/LR98eZv9A=:autocreated 2018-01-14T11:17:40.712Z' + ); + }); +}); + +describe('getCryptoPassword', () => { + test('should return the password hash', () => { + const passwordHash = `{SHA}y9vkk2zovmMYTZ8uE/wkkjQ3G5o=`; + + expect(getCryptoPassword('demo123')).toBe(passwordHash); + }); +}); diff --git a/packages/core/htpasswd/tsconfig.json b/packages/core/htpasswd/tsconfig.json new file mode 100644 index 000000000..537522270 --- /dev/null +++ b/packages/core/htpasswd/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/local-storage/.babelrc b/packages/core/local-storage/.babelrc new file mode 100644 index 000000000..851856e59 --- /dev/null +++ b/packages/core/local-storage/.babelrc @@ -0,0 +1,3 @@ +{ + "extends": "../../../.babelrc" +} diff --git a/packages/core/local-storage/.eslintrc.json b/packages/core/local-storage/.eslintrc.json new file mode 100644 index 000000000..3e8a7bcd4 --- /dev/null +++ b/packages/core/local-storage/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "@typescript-eslint/no-use-before-define": "off" + } +} diff --git a/packages/core/local-storage/CHANGELOG.md b/packages/core/local-storage/CHANGELOG.md new file mode 100644 index 000000000..20f0eb69d --- /dev/null +++ b/packages/core/local-storage/CHANGELOG.md @@ -0,0 +1,467 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10) + + +### Bug Fixes + +* update dependencies ([#375](https://github.com/verdaccio/monorepo/issues/375)) ([1e7aeec](https://github.com/verdaccio/monorepo/commit/1e7aeec31b056979285e272793a95b8c75d57c77)) + + + + + +# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [9.5.1](https://github.com/verdaccio/monorepo/compare/v9.5.0...v9.5.1) (2020-06-03) + + +### Bug Fixes + +* restore Node v8 support ([#361](https://github.com/verdaccio/monorepo/issues/361)) ([9be55a1](https://github.com/verdaccio/monorepo/commit/9be55a1deebe954e8eef9edc59af9fd16e29daed)) + + + + + +# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [9.3.4](https://github.com/verdaccio/monorepo/compare/v9.3.3...v9.3.4) (2020-03-11) + + +### Bug Fixes + +* update mkdirp@1.0.3 ([#341](https://github.com/verdaccio/monorepo/issues/341)) ([96db337](https://github.com/verdaccio/monorepo/commit/96db3378a4f2334ec89cfb113af95e9a3a6eb050)) + + + + + +## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08) + + +### Bug Fixes + +* update dependencies ([#332](https://github.com/verdaccio/monorepo/issues/332)) ([b6165ae](https://github.com/verdaccio/monorepo/commit/b6165aea9b7e4012477081eae68bfa7159c58f56)) + + + + + +## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07) + + +### Bug Fixes + +* prevent circular structure exception ([#312](https://github.com/verdaccio/monorepo/issues/312)) ([f565461](https://github.com/verdaccio/monorepo/commit/f565461f5bb2873467eeb4372a12fbf4a4974d17)) + + + + + +## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08) + + +### Bug Fixes + +* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7)) + + + + + +## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18) + +**Note:** Version bump only for package @verdaccio/local-storage + + + + + +# [8.0.0-next.3](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.2...v8.0.0-next.3) (2019-08-16) + + +### Bug Fixes + +* restore closure ([32b9d7e](https://github.com/verdaccio/monorepo/commit/32b9d7e)) +* **build:** error on types for fs callback ([cc35acb](https://github.com/verdaccio/monorepo/commit/cc35acb)) +* Add DATE and VERSION in search result ([e352b75](https://github.com/verdaccio/monorepo/commit/e352b75)) +* avoid open write stream if resource exist [#1191](https://github.com/verdaccio/monorepo/issues/1191) ([f041d3f](https://github.com/verdaccio/monorepo/commit/f041d3f)) +* bug fixing integration ([6c75ac8](https://github.com/verdaccio/monorepo/commit/6c75ac8)) +* build before publish ([cd6c7ff](https://github.com/verdaccio/monorepo/commit/cd6c7ff)) +* check whether path exist before return result ([a4d2af1](https://github.com/verdaccio/monorepo/commit/a4d2af1)) +* flow issues ([f42a284](https://github.com/verdaccio/monorepo/commit/f42a284)) +* ignore flow on this one, we need it ([c8e0b2b](https://github.com/verdaccio/monorepo/commit/c8e0b2b)) +* local storage requires package.json file for read, save and create all the time ([33c847b](https://github.com/verdaccio/monorepo/commit/33c847b)) +* migration from main repository merge [#306](https://github.com/verdaccio/monorepo/issues/306) ([8fbe86e](https://github.com/verdaccio/monorepo/commit/8fbe86e)) +* missing callback ([abfc422](https://github.com/verdaccio/monorepo/commit/abfc422)) +* missing error code ([7121939](https://github.com/verdaccio/monorepo/commit/7121939)) +* move to local storage the fs location handler ([3b12083](https://github.com/verdaccio/monorepo/commit/3b12083)) +* mtimeMs is not backward compatible ([c6f74eb](https://github.com/verdaccio/monorepo/commit/c6f74eb)) +* remove temp file whether is emtpy and fails ([655102f](https://github.com/verdaccio/monorepo/commit/655102f)) +* remove uncessary async ([3e3e3a6](https://github.com/verdaccio/monorepo/commit/3e3e3a6)) +* remove unused parameters ([554e301](https://github.com/verdaccio/monorepo/commit/554e301)) +* restore build path ([4902042](https://github.com/verdaccio/monorepo/commit/4902042)) +* return time as milliseconds ([15467ba](https://github.com/verdaccio/monorepo/commit/15467ba)) +* sync after set secret ([2abae4f](https://github.com/verdaccio/monorepo/commit/2abae4f)) +* temp files are written into the storage ([89a1dc8](https://github.com/verdaccio/monorepo/commit/89a1dc8)) +* unit test ([995a27c](https://github.com/verdaccio/monorepo/commit/995a27c)) +* update @verdaccio/file-locking@1.0.0 ([9bd36f0](https://github.com/verdaccio/monorepo/commit/9bd36f0)) +* update lodash types ([184466c](https://github.com/verdaccio/monorepo/commit/184466c)) + + +### Features + +* token support with level.js ([#168](https://github.com/verdaccio/monorepo/issues/168)) ([ca877ff](https://github.com/verdaccio/monorepo/commit/ca877ff)) +* **build:** standardize build ([33fe090](https://github.com/verdaccio/monorepo/commit/33fe090)) +* change new db name to verdaccio ([#83](https://github.com/verdaccio/monorepo/issues/83)) ([edfca9f](https://github.com/verdaccio/monorepo/commit/edfca9f)) +* drop node v6 support ([664f288](https://github.com/verdaccio/monorepo/commit/664f288)) +* implement search ([2e2bb32](https://github.com/verdaccio/monorepo/commit/2e2bb32)) +* migrate to typescript ([c439d25](https://github.com/verdaccio/monorepo/commit/c439d25)) +* update database method with callbacks ([ef202a9](https://github.com/verdaccio/monorepo/commit/ef202a9)) +* update minor dependencies ([007b026](https://github.com/verdaccio/monorepo/commit/007b026)) + + + + + +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [2.3.0](https://github.com/verdaccio/local-storage/compare/v2.2.1...v2.3.0) (2019-08-13) + + +### Bug Fixes + +* restore closure ([8ec27f2](https://github.com/verdaccio/local-storage/commit/8ec27f2)) + + +### Features + +* add logging for each method ([3c915c7](https://github.com/verdaccio/local-storage/commit/3c915c7)) +* token support with level.js ([#168](https://github.com/verdaccio/local-storage/issues/168)) ([16727bd](https://github.com/verdaccio/local-storage/commit/16727bd)) + +### [2.2.1](https://github.com/verdaccio/local-storage/compare/v2.2.0...v2.2.1) (2019-06-30) + + +### Bug Fixes + +* **build:** error on types for fs callback ([774d808](https://github.com/verdaccio/local-storage/commit/774d808)) + + + +## [2.2.0](https://github.com/verdaccio/local-storage/compare/v2.1.0...v2.2.0) (2019-06-30) + + +### Features + +* **build:** standardize build ([eba832e](https://github.com/verdaccio/local-storage/commit/eba832e)) + + + +# [2.1.0](https://github.com/verdaccio/local-storage/compare/v2.0.0...v2.1.0) (2019-03-29) + + +### Bug Fixes + +* remove uncessary async ([23a09f3](https://github.com/verdaccio/local-storage/commit/23a09f3)) + + +### Features + +* drop node v6 support ([ef548e0](https://github.com/verdaccio/local-storage/commit/ef548e0)) + + + +# [2.0.0](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.3...v2.0.0) (2019-03-29) + + + + +# [2.0.0-beta.3](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.2...v2.0.0-beta.3) (2019-02-24) + + +### Bug Fixes + +* update [@verdaccio](https://github.com/verdaccio)/file-locking@1.0.0 ([587245d](https://github.com/verdaccio/local-storage/commit/587245d)) + + + + +# [2.0.0-beta.2](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.1...v2.0.0-beta.2) (2019-02-24) + + +### Bug Fixes + +* avoid open write stream if resource exist [#1191](https://github.com/verdaccio/local-storage/issues/1191) ([b13904a](https://github.com/verdaccio/local-storage/commit/b13904a)) +* package.json to reduce vulnerabilities ([97e9dc3](https://github.com/verdaccio/local-storage/commit/97e9dc3)) + + + + +# [2.0.0-beta.1](https://github.com/verdaccio/local-storage/compare/v2.0.0-beta.0...v2.0.0-beta.1) (2019-02-03) + + + + +# [2.0.0-beta.0](https://github.com/verdaccio/local-storage/compare/v1.2.0...v2.0.0-beta.0) (2019-02-01) + + +### Bug Fixes + +* **deps:** update dependency lodash to v4.17.11 ([682616a](https://github.com/verdaccio/local-storage/commit/682616a)) + + +### Features + +* custom storage location ([b1423cd](https://github.com/verdaccio/local-storage/commit/b1423cd)) +* migrate to typescript ([fe8344b](https://github.com/verdaccio/local-storage/commit/fe8344b)) + + +### BREAKING CHANGES + +* we change from boolean value to string within the config file + + + + +# [1.2.0](https://github.com/verdaccio/local-storage/compare/v1.1.3...v1.2.0) (2018-08-25) + + +### Features + +* change new db name to verdaccio ([#83](https://github.com/verdaccio/local-storage/issues/83)) ([143977d](https://github.com/verdaccio/local-storage/commit/143977d)) + + + + +## [1.1.3](https://github.com/verdaccio/local-storage/compare/v1.1.2...v1.1.3) (2018-07-15) + + +### Bug Fixes + +* remove unused parameters ([3ce374a](https://github.com/verdaccio/local-storage/commit/3ce374a)) + + + + +## [1.1.2](https://github.com/verdaccio/local-storage/compare/v1.1.1...v1.1.2) (2018-06-09) + + +### Bug Fixes + +* return time as milliseconds ([c98be85](https://github.com/verdaccio/local-storage/commit/c98be85)) + + + + +## [1.1.1](https://github.com/verdaccio/local-storage/compare/v1.1.0...v1.1.1) (2018-06-08) + + +### Bug Fixes + +* check whether path exist before return result ([cb5d4ef](https://github.com/verdaccio/local-storage/commit/cb5d4ef)) + + + + +# [1.1.0](https://github.com/verdaccio/local-storage/compare/v1.0.3...v1.1.0) (2018-06-08) + + +### Bug Fixes + +* **deps:** update dependency async to v2.6.1 ([487b095](https://github.com/verdaccio/local-storage/commit/487b095)) + + +### Features + +* implement search ([f884a24](https://github.com/verdaccio/local-storage/commit/f884a24)) + + + + +# [0.2.0](https://github.com/verdaccio/local-storage/compare/v0.1.4...v0.2.0) (2018-01-17) + + +### Features + +* update minor dependencies ([92daa81](https://github.com/verdaccio/local-storage/commit/92daa81)) + + + + +## [0.1.4](https://github.com/verdaccio/local-storage/compare/v0.1.3...v0.1.4) (2018-01-17) + + +### Bug Fixes + +* remove temp file whether is emtpy and fails ([593e162](https://github.com/verdaccio/local-storage/commit/593e162)) +* unit test ([2573f30](https://github.com/verdaccio/local-storage/commit/2573f30)) diff --git a/packages/core/local-storage/LICENSE b/packages/core/local-storage/LICENSE new file mode 100644 index 000000000..65fb12e2a --- /dev/null +++ b/packages/core/local-storage/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Verdaccio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/local-storage/README.md b/packages/core/local-storage/README.md new file mode 100644 index 000000000..242c7ee02 --- /dev/null +++ b/packages/core/local-storage/README.md @@ -0,0 +1,49 @@ +# @verdaccio/local-storage + +📦 File system storage plugin for verdaccio + +[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/local-storage/latest.svg)](https://www.npmjs.com/package/@verdaccio/local-storage) +[![CircleCI](https://circleci.com/gh/verdaccio/local-storage/tree/master.svg?style=svg)](https://circleci.com/gh/verdaccio/local-storage/tree/master) +[![Known Vulnerabilities](https://snyk.io/test/github/verdaccio/local-storage/badge.svg?targetFile=package.json)](https://snyk.io/test/github/verdaccio/local-storage?targetFile=package.json) +[![codecov](https://codecov.io/gh/verdaccio/local-storage/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/local-storage) +[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio) +[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/) +![MIT](https://img.shields.io/github/license/mashape/apistatus.svg) +[![node](https://img.shields.io/node/v/@verdaccio/local-storage/latest.svg)](https://www.npmjs.com/package/@verdaccio/local-storage) + +> This package is already built-in in verdaccio + +``` +npm install @verdaccio/local-storage +``` + +### API + +### LocalDatabase + +The main object that handle a JSON database the private packages. + +#### Constructor + +``` +new LocalDatabase(config, logger); +``` + +* **config**: A verdaccio configuration instance. +* **logger**: A logger instance + +### LocalFS + +A class that handle an package instance in the File System + +``` +new LocalFS(packageStoragePath, logger); +``` + + + +## License +Verdaccio is [MIT licensed](https://github.com/verdaccio/local-storage/blob/master/LICENSE). + + +[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fverdaccio%2Flocal-storage.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fverdaccio%2Flocal-storage?ref=badge_large) diff --git a/packages/core/local-storage/_storage/first-package/package.json b/packages/core/local-storage/_storage/first-package/package.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/packages/core/local-storage/_storage/first-package/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/core/local-storage/_storage/new-readme-0.0.0.tgz b/packages/core/local-storage/_storage/new-readme-0.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..cb18086fac7e59f8601cc740fd7caf2add8f81c7 GIT binary patch literal 352 zcmV-m0iXUKiwFP!000001MQQsZo)7ShBNyqP9B&jbrK+w*Q!J3t}Iq?gA1`O+q59W zyZ7wS7PUxKRaAtOKf|5Qr~l4YJ}c^_XymQ!;^^5Mot;^XvFUgW81rhvj$=|;l#t~` zSrp}@tfqO!@{$!fo}YV>Q(F5V9IocgYhEWxX&kqxa4k$3Yv2b?{c! z>PgA@jKW|5a-#y|bU&m{TH{qPZks%|Hm=vGfZl7jies@!ti!^%ZikhsrS`Gw;qi{{ zFa4xfCHsZ{t!P*0p78_!MOB^Pzv%h@4@LSpe1o4H(C3iICr8|Kpp-=HIlNO(4$N}` y$qHM3!#w#UJ~<%q91wWS5%@H(!#$-3+BqQXgHSF1OP52?8T3;+Pl6|^M) literal 0 HcmV?d00001 diff --git a/packages/core/local-storage/jest.config.js b/packages/core/local-storage/jest.config.js new file mode 100644 index 000000000..1c3fbdb05 --- /dev/null +++ b/packages/core/local-storage/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../../jest/config'); + +module.exports = Object.assign({}, config, {}); diff --git a/packages/core/local-storage/package.json b/packages/core/local-storage/package.json new file mode 100644 index 000000000..ed691f68e --- /dev/null +++ b/packages/core/local-storage/package.json @@ -0,0 +1,60 @@ +{ + "name": "@verdaccio/local-storage", + "version": "10.0.0-beta", + "description": "Local storage implementation", + "keywords": [ + "plugin", + "verdaccio", + "storage", + "local-storage" + ], + "author": "Juan Picado ", + "license": "MIT", + "homepage": "https://verdaccio.org", + "repository": { + "type": "git", + "url": "https://github.com/verdaccio/monorepo", + "directory": "plugins/local-storage" + }, + "bugs": { + "url": "https://github.com/verdaccio/monorepo/issues" + }, + "publishConfig": { + "access": "public" + }, + "main": "./build/index.js", + "types": "./build/index.d.ts", + "files": [ + "build/" + ], + "engines": { + "node": ">=8" + }, + "dependencies": { + "@verdaccio/commons-api": "^9.7.1", + "@verdaccio/file-locking": "workspace:*", + "@verdaccio/streams": "workspace:*", + "async": "^3.2.0", + "level": "5.0.1", + "lodash": "^4.17.19", + "mkdirp": "^0.5.5" + }, + "devDependencies": { + "@types/minimatch": "^3.0.3", + "@verdaccio/types": "workspace:*", + "minimatch": "^3.0.4", + "rmdir-sync": "^1.0.1" + }, + "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": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", + "build": "pnpm run build:js && pnpm run build:types" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } +} diff --git a/packages/core/local-storage/src/index.ts b/packages/core/local-storage/src/index.ts new file mode 100644 index 000000000..0b1dc5d37 --- /dev/null +++ b/packages/core/local-storage/src/index.ts @@ -0,0 +1,5 @@ +import LocalDatabase from './local-database'; + +export { LocalDatabase }; + +export default LocalDatabase; diff --git a/packages/core/local-storage/src/local-database.ts b/packages/core/local-storage/src/local-database.ts new file mode 100644 index 000000000..73f809512 --- /dev/null +++ b/packages/core/local-storage/src/local-database.ts @@ -0,0 +1,434 @@ +import fs from 'fs'; +import Path from 'path'; +import stream from 'stream'; + +import _ from 'lodash'; +import async from 'async'; +import mkdirp from 'mkdirp'; +import { Callback, Config, IPackageStorage, IPluginStorage, LocalStorage, Logger, StorageList, Token, TokenFilter } from '@verdaccio/types'; +import level from 'level'; +import { getInternalError } from '@verdaccio/commons-api/lib'; + +import LocalDriver, { noSuchFile } from './local-fs'; +import { loadPrivatePackages } from './pkg-utils'; + +const DEPRECATED_DB_NAME = '.sinopia-db.json'; +const DB_NAME = '.verdaccio-db.json'; +const TOKEN_DB_NAME = '.token-db'; + +interface Level { + put(key: string, token, fn?: Function): void; + + get(key: string, fn?: Function): void; + + del(key: string, fn?: Function): void; + + createReadStream(options?: object): stream.Readable; +} + +/** + * Handle local database. + */ +class LocalDatabase implements IPluginStorage<{}> { + public path: string; + public logger: Logger; + public data: LocalStorage; + public config: Config; + public locked: boolean; + public tokenDb; + + /** + * Load an parse the local json database. + * @param {*} path the database path + */ + public constructor(config: Config, logger: Logger) { + this.config = config; + this.path = this._buildStoragePath(config); + this.logger = logger; + this.locked = false; + this.data = this._fetchLocalPackages(); + + this.logger.trace({ config: this.config }, '[local-storage]: configuration: @{config}'); + + this._sync(); + } + + public getSecret(): Promise { + return Promise.resolve(this.data.secret); + } + + public setSecret(secret: string): Promise { + return new Promise((resolve): void => { + this.data.secret = secret; + + resolve(this._sync()); + }); + } + + /** + * Add a new element. + * @param {*} name + * @return {Error|*} + */ + public add(name: string, cb: Callback): void { + if (this.data.list.indexOf(name) === -1) { + this.data.list.push(name); + + this.logger.debug({ name }, '[local-storage]: the private package @{name} has been added'); + cb(this._sync()); + } else { + cb(null); + } + } + + public search(onPackage: Callback, onEnd: Callback, validateName: (name: string) => boolean): void { + const storages = this._getCustomPackageLocalStorages(); + this.logger.trace(`local-storage: [search]: ${JSON.stringify(storages)}`); + const base = Path.dirname(this.config.self_path); + const self = this; + const storageKeys = Object.keys(storages); + this.logger.trace(`local-storage: [search] base: ${base} keys ${storageKeys}`); + + async.eachSeries( + storageKeys, + function (storage, cb) { + const position = storageKeys.indexOf(storage); + const base2 = Path.join(position !== 0 ? storageKeys[0] : ''); + const storagePath: string = Path.resolve(base, base2, storage); + self.logger.trace({ storagePath, storage }, 'local-storage: [search] search path: @{storagePath} : @{storage}'); + fs.readdir(storagePath, (err, files) => { + if (err) { + return cb(err); + } + + async.eachSeries( + files, + function (file, cb) { + self.logger.trace({ file }, 'local-storage: [search] search file path: @{file}'); + if (storageKeys.includes(file)) { + return cb(); + } + + if (file.match(/^@/)) { + // scoped + const fileLocation = Path.resolve(base, storage, file); + self.logger.trace({ fileLocation }, 'local-storage: [search] search scoped file location: @{fileLocation}'); + fs.readdir(fileLocation, function (err, files) { + if (err) { + return cb(err); + } + + async.eachSeries( + files, + (file2, cb) => { + if (validateName(file2)) { + const packagePath = Path.resolve(base, storage, file, file2); + + fs.stat(packagePath, (err, stats) => { + if (_.isNil(err) === false) { + return cb(err); + } + const item = { + name: `${file}/${file2}`, + path: packagePath, + time: stats.mtime.getTime(), + }; + onPackage(item, cb); + }); + } else { + cb(); + } + }, + cb + ); + }); + } else if (validateName(file)) { + const base2 = Path.join(position !== 0 ? storageKeys[0] : ''); + const packagePath = Path.resolve(base, base2, storage, file); + self.logger.trace({ packagePath }, 'local-storage: [search] search file location: @{packagePath}'); + fs.stat(packagePath, (err, stats) => { + if (_.isNil(err) === false) { + return cb(err); + } + onPackage( + { + name: file, + path: packagePath, + time: self._getTime(stats.mtime.getTime(), stats.mtime), + }, + cb + ); + }); + } else { + cb(); + } + }, + cb + ); + }); + }, + // @ts-ignore + onEnd + ); + } + + /** + * Remove an element from the database. + * @param {*} name + * @return {Error|*} + */ + public remove(name: string, cb: Callback): void { + this.get((err, data) => { + if (err) { + cb(getInternalError('error remove private package')); + this.logger.error({ err }, '[local-storage/remove]: remove the private package has failed @{err}'); + } + + const pkgName = data.indexOf(name); + if (pkgName !== -1) { + this.data.list.splice(pkgName, 1); + + this.logger.trace({ name }, 'local-storage: [remove] package @{name} has been removed'); + } + + cb(this._sync()); + }); + } + + /** + * Return all database elements. + * @return {Array} + */ + public get(cb: Callback): void { + const list = this.data.list; + const totalItems = this.data.list.length; + + cb(null, list); + + this.logger.trace({ totalItems }, 'local-storage: [get] full list of packages (@{totalItems}) has been fetched'); + } + + public getPackageStorage(packageName: string): IPackageStorage { + const packageAccess = this.config.getMatchedPackagesSpec(packageName); + + const packagePath: string = this._getLocalStoragePath(packageAccess ? packageAccess.storage : undefined); + this.logger.trace({ packagePath }, '[local-storage/getPackageStorage]: storage selected: @{packagePath}'); + + if (_.isString(packagePath) === false) { + this.logger.debug({ name: packageName }, 'this package has no storage defined: @{name}'); + return; + } + + const packageStoragePath: string = Path.join(Path.resolve(Path.dirname(this.config.self_path || ''), packagePath), packageName); + + this.logger.trace({ packageStoragePath }, '[local-storage/getPackageStorage]: storage path: @{packageStoragePath}'); + + return new LocalDriver(packageStoragePath, this.logger); + } + + public clean(): void { + this._sync(); + } + + public saveToken(token: Token): Promise { + const key = this._getTokenKey(token); + const db = this.getTokenDb(); + + return new Promise((resolve, reject): void => { + db.put(key, token, (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + } + + public deleteToken(user: string, tokenKey: string): Promise { + const key = this._compoundTokenKey(user, tokenKey); + const db = this.getTokenDb(); + return new Promise((resolve, reject): void => { + db.del(key, (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + } + + public readTokens(filter: TokenFilter): Promise { + return new Promise((resolve, reject): void => { + const tokens: Token[] = []; + const key = filter.user + ':'; + const db = this.getTokenDb(); + const stream = db.createReadStream({ + gte: key, + lte: String.fromCharCode(key.charCodeAt(0) + 1), + }); + + stream.on('data', (data) => { + tokens.push(data.value); + }); + + stream.once('end', () => resolve(tokens)); + + stream.once('error', (err) => reject(err)); + }); + } + + private _getTime(time: number, mtime: Date): number | Date { + return time ? time : mtime; + } + + private _getCustomPackageLocalStorages(): object { + const storages = {}; + + // add custom storage if exist + if (this.config.storage) { + storages[this.config.storage] = true; + } + + const { packages } = this.config; + + if (packages) { + const listPackagesConf = Object.keys(packages || {}); + + listPackagesConf.map((pkg) => { + const storage = packages[pkg].storage; + if (storage) { + storages[storage] = false; + } + }); + } + + return storages; + } + + /** + * Syncronize {create} database whether does not exist. + * @return {Error|*} + */ + private _sync(): Error | null { + this.logger.debug('[local-storage/_sync]: init sync database'); + + if (this.locked) { + this.logger.error('Database is locked, please check error message printed during startup to prevent data loss.'); + return new Error('Verdaccio database is locked, please contact your administrator to checkout logs during verdaccio startup.'); + } + // Uses sync to prevent ugly race condition + try { + // https://www.npmjs.com/package/mkdirp#mkdirpsyncdir-opts + const folderName = Path.dirname(this.path); + mkdirp.sync(folderName); + this.logger.debug({ folderName }, '[local-storage/_sync]: folder @{folderName} created succeed'); + } catch (err) { + // perhaps a logger instance? + this.logger.debug({ err }, '[local-storage/_sync/mkdirp.sync]: sync failed @{err}'); + + return null; + } + + try { + fs.writeFileSync(this.path, JSON.stringify(this.data)); + this.logger.debug('[local-storage/_sync/writeFileSync]: sync write succeed'); + + return null; + } catch (err) { + this.logger.debug({ err }, '[local-storage/_sync/writeFileSync]: sync failed @{err}'); + + return err; + } + } + + /** + * Verify the right local storage location. + * @param {String} path + * @return {String} + * @private + */ + private _getLocalStoragePath(storage: string | void): string { + const globalConfigStorage = this.config ? this.config.storage : undefined; + if (_.isNil(globalConfigStorage)) { + throw new Error('global storage is required for this plugin'); + } else { + if (_.isNil(storage) === false && _.isString(storage)) { + return Path.join(globalConfigStorage as string, storage as string); + } + + return globalConfigStorage as string; + } + } + + /** + * Build the local database path. + * @param {Object} config + * @return {string|String|*} + * @private + */ + private _buildStoragePath(config: Config): string { + const sinopiadbPath: string = this._dbGenPath(DEPRECATED_DB_NAME, config); + try { + fs.accessSync(sinopiadbPath, fs.constants.F_OK); + return sinopiadbPath; + } catch (err) { + if (err.code === noSuchFile) { + return this._dbGenPath(DB_NAME, config); + } + + throw err; + } + } + + private _dbGenPath(dbName: string, config: Config): string { + return Path.join(Path.resolve(Path.dirname(config.self_path || ''), config.storage as string, dbName)); + } + + /** + * Fetch local packages. + * @private + * @return {Object} + */ + private _fetchLocalPackages(): LocalStorage { + const list: StorageList = []; + const emptyDatabase = { list, secret: '' }; + + try { + const db = loadPrivatePackages(this.path, this.logger); + + return db; + } catch (err) { + // readFileSync is platform specific, macOS, Linux and Windows thrown an error + // Only recreate if file not found to prevent data loss + if (err.code !== noSuchFile) { + this.locked = true; + this.logger.error('Failed to read package database file, please check the error printed below:\n', `File Path: ${this.path}\n\n ${err.message}`); + } + + return emptyDatabase; + } + } + + private getTokenDb(): Level { + if (!this.tokenDb) { + this.tokenDb = level(this._dbGenPath(TOKEN_DB_NAME, this.config), { + valueEncoding: 'json', + }); + } + + return this.tokenDb; + } + + private _getTokenKey(token: Token): string { + const { user, key } = token; + return this._compoundTokenKey(user, key); + } + + private _compoundTokenKey(user: string, key: string): string { + return `${user}:${key}`; + } +} + +export default LocalDatabase; diff --git a/packages/core/local-storage/src/local-fs.ts b/packages/core/local-storage/src/local-fs.ts new file mode 100644 index 000000000..62b741aaf --- /dev/null +++ b/packages/core/local-storage/src/local-fs.ts @@ -0,0 +1,384 @@ +import fs from 'fs'; +import path from 'path'; + +import _ from 'lodash'; +import mkdirp from 'mkdirp'; +import { UploadTarball, ReadTarball } from '@verdaccio/streams'; +import { unlockFile, readFile } from '@verdaccio/file-locking'; +import { Callback, Logger, Package, ILocalPackageManager, IUploadTarball } from '@verdaccio/types'; +import { getCode, getInternalError, getNotFound, VerdaccioError } from '@verdaccio/commons-api/lib'; + +export const fileExist = 'EEXISTS'; +export const noSuchFile = 'ENOENT'; +export const resourceNotAvailable = 'EAGAIN'; +export const pkgFileName = 'package.json'; + +export const fSError = function (message: string, code = 409): VerdaccioError { + const err: VerdaccioError = getCode(code, message); + // FIXME: we should return http-status codes here instead, future improvement + // @ts-ignore + err.code = message; + + return err; +}; + +const tempFile = function (str): string { + return `${str}.tmp${String(Math.random()).substr(2)}`; +}; + +const renameTmp = function (src, dst, _cb): void { + const cb = (err): void => { + if (err) { + fs.unlink(src, () => {}); + } + _cb(err); + }; + + if (process.platform !== 'win32') { + return fs.rename(src, dst, cb); + } + + // windows can't remove opened file, + // but it seem to be able to rename it + const tmp = tempFile(dst); + fs.rename(dst, tmp, function (err) { + fs.rename(src, dst, cb); + if (!err) { + fs.unlink(tmp, () => {}); + } + }); +}; + +export type ILocalFSPackageManager = ILocalPackageManager & { path: string }; + +export default class LocalFS implements ILocalFSPackageManager { + public path: string; + public logger: Logger; + + public constructor(path: string, logger: Logger) { + this.path = path; + this.logger = logger; + } + + /** + * This function allows to update the package thread-safely + Algorithm: + 1. lock package.json for writing + 2. read package.json + 3. updateFn(pkg, cb), and wait for cb + 4. write package.json.tmp + 5. move package.json.tmp package.json + 6. callback(err?) + * @param {*} name + * @param {*} updateHandler + * @param {*} onWrite + * @param {*} transformPackage + * @param {*} onEnd + */ + public updatePackage(name: string, updateHandler: Callback, onWrite: Callback, transformPackage: Function, onEnd: Callback): void { + this._lockAndReadJSON(pkgFileName, (err, json) => { + let locked = false; + const self = this; + // callback that cleans up lock first + const unLockCallback = function (lockError: Error): void { + // eslint-disable-next-line prefer-rest-params + const _args = arguments; + + if (locked) { + self._unlockJSON(pkgFileName, () => { + // ignore any error from the unlock + if (lockError !== null) { + self.logger.trace( + { + name, + lockError, + }, + '[local-storage/updatePackage/unLockCallback] file: @{name} lock has failed lockError: @{lockError}' + ); + } + + onEnd.apply(lockError, _args); + }); + } else { + self.logger.trace({ name }, '[local-storage/updatePackage/unLockCallback] file: @{name} has been updated'); + + onEnd(..._args); + } + }; + + if (!err) { + locked = true; + this.logger.trace( + { + name, + }, + '[local-storage/updatePackage] file: @{name} has been locked' + ); + } + + if (_.isNil(err) === false) { + if (err.code === resourceNotAvailable) { + return unLockCallback(getInternalError('resource temporarily unavailable')); + } else if (err.code === noSuchFile) { + return unLockCallback(getNotFound()); + } else { + return unLockCallback(err); + } + } + + updateHandler(json, (err) => { + if (err) { + return unLockCallback(err); + } + + onWrite(name, transformPackage(json), unLockCallback); + }); + }); + } + + public deletePackage(packageName: string, callback: (err: NodeJS.ErrnoException | null) => void): void { + this.logger.debug({ packageName }, '[local-storage/deletePackage] delete a package @{packageName}'); + + return fs.unlink(this._getStorage(packageName), callback); + } + + public removePackage(callback: (err: NodeJS.ErrnoException | null) => void): void { + this.logger.debug({ packageName: this.path }, '[local-storage/removePackage] remove a package: @{packageName}'); + + fs.rmdir(this._getStorage('.'), callback); + } + + public createPackage(name: string, value: Package, cb: Callback): void { + this.logger.debug({ packageName: name }, '[local-storage/createPackage] create a package: @{packageName}'); + + this._createFile(this._getStorage(pkgFileName), this._convertToString(value), cb); + } + + public savePackage(name: string, value: Package, cb: Callback): void { + this.logger.debug({ packageName: name }, '[local-storage/savePackage] save a package: @{packageName}'); + + this._writeFile(this._getStorage(pkgFileName), this._convertToString(value), cb); + } + + public readPackage(name: string, cb: Callback): void { + this.logger.debug({ packageName: name }, '[local-storage/readPackage] read a package: @{packageName}'); + + this._readStorageFile(this._getStorage(pkgFileName)).then( + (res) => { + try { + const data: any = JSON.parse(res.toString('utf8')); + + this.logger.trace({ packageName: name }, '[local-storage/readPackage/_readStorageFile] read a package succeed: @{packageName}'); + cb(null, data); + } catch (err) { + this.logger.trace({ err }, '[local-storage/readPackage/_readStorageFile] error on read a package: @{err}'); + cb(err); + } + }, + (err) => { + this.logger.trace({ err }, '[local-storage/readPackage/_readStorageFile] error on read a package: @{err}'); + + return cb(err); + } + ); + } + + public writeTarball(name: string): IUploadTarball { + const uploadStream = new UploadTarball({}); + this.logger.debug({ packageName: name }, '[local-storage/writeTarball] write a tarball for package: @{packageName}'); + + let _ended = 0; + uploadStream.on('end', function () { + _ended = 1; + }); + + const pathName: string = this._getStorage(name); + + fs.access(pathName, (fileNotFound) => { + const exists = !fileNotFound; + if (exists) { + uploadStream.emit('error', fSError(fileExist)); + } else { + const temporalName = path.join(this.path, `${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`); + const file = fs.createWriteStream(temporalName); + const removeTempFile = (): void => fs.unlink(temporalName, () => {}); + let opened = false; + uploadStream.pipe(file); + + uploadStream.done = function (): void { + const onend = function (): void { + file.on('close', function () { + renameTmp(temporalName, pathName, function (err) { + if (err) { + uploadStream.emit('error', err); + } else { + uploadStream.emit('success'); + } + }); + }); + file.end(); + }; + if (_ended) { + onend(); + } else { + uploadStream.on('end', onend); + } + }; + + uploadStream.abort = function (): void { + if (opened) { + opened = false; + file.on('close', function () { + removeTempFile(); + }); + } else { + // if the file does not recieve any byte never is opened and has to be removed anyway. + removeTempFile(); + } + file.end(); + }; + + file.on('open', function () { + opened = true; + // re-emitting open because it's handled in storage.js + uploadStream.emit('open'); + }); + + file.on('error', function (err) { + uploadStream.emit('error', err); + }); + } + }); + + return uploadStream; + } + + public readTarball(name: string): ReadTarball { + const pathName: string = this._getStorage(name); + this.logger.debug({ packageName: name }, '[local-storage/readTarball] read a tarball for package: @{packageName}'); + + const readTarballStream = new ReadTarball({}); + + const readStream = fs.createReadStream(pathName); + + readStream.on('error', function (err) { + readTarballStream.emit('error', err); + }); + + readStream.on('open', function (fd) { + fs.fstat(fd, function (err, stats) { + if (_.isNil(err) === false) { + return readTarballStream.emit('error', err); + } + readTarballStream.emit('content-length', stats.size); + readTarballStream.emit('open'); + readStream.pipe(readTarballStream); + }); + }); + + readTarballStream.abort = function (): void { + readStream.close(); + }; + + return readTarballStream; + } + + private _createFile(name: string, contents: any, callback: Function): void { + this.logger.trace({ name }, '[local-storage/_createFile] create a new file: @{name}'); + + fs.open(name, 'wx', (err) => { + if (err) { + // native EEXIST used here to check exception on fs.open + if (err.code === 'EEXIST') { + this.logger.trace({ name }, '[local-storage/_createFile] file cannot be created, it already exists: @{name}'); + return callback(fSError(fileExist)); + } + } + + this._writeFile(name, contents, callback); + }); + } + + private _readStorageFile(name: string): Promise { + return new Promise((resolve, reject): void => { + this.logger.trace({ name }, '[local-storage/_readStorageFile] read a file: @{name}'); + + fs.readFile(name, (err, data) => { + if (err) { + this.logger.trace({ err }, '[local-storage/_readStorageFile] error on read the file: @{name}'); + reject(err); + } else { + this.logger.trace({ name }, '[local-storage/_readStorageFile] read file succeed: @{name}'); + + resolve(data); + } + }); + }); + } + + private _convertToString(value: Package): string { + return JSON.stringify(value, null, '\t'); + } + + private _getStorage(fileName = ''): string { + const storagePath: string = path.join(this.path, fileName); + + return storagePath; + } + + private _writeFile(dest: string, data: string, cb: Callback): void { + const createTempFile = (cb): void => { + const tempFilePath = tempFile(dest); + + fs.writeFile(tempFilePath, data, (err) => { + if (err) { + this.logger.trace({ name: dest }, '[local-storage/_writeFile] new file: @{name} has been created'); + + return cb(err); + } + + this.logger.trace({ name: dest }, '[local-storage/_writeFile] creating a new file: @{name}'); + renameTmp(tempFilePath, dest, cb); + }); + }; + + createTempFile((err) => { + if (err && err.code === noSuchFile) { + mkdirp(path.dirname(dest), function (err) { + if (err) { + return cb(err); + } + createTempFile(cb); + }); + } else { + cb(err); + } + }); + } + + private _lockAndReadJSON(name: string, cb: Function): void { + const fileName: string = this._getStorage(name); + + readFile( + fileName, + { + lock: true, + parse: true, + }, + (err, res) => { + if (err) { + this.logger.trace({ name }, '[local-storage/_lockAndReadJSON] read new file: @{name} has failed'); + + return cb(err); + } + + this.logger.trace({ name }, '[local-storage/_lockAndReadJSON] file: @{name} read'); + return cb(null, res); + } + ); + } + + private _unlockJSON(name: string, cb: Function): void { + unlockFile(this._getStorage(name), cb); + } +} diff --git a/packages/core/local-storage/src/pkg-utils.ts b/packages/core/local-storage/src/pkg-utils.ts new file mode 100644 index 000000000..8198f63c5 --- /dev/null +++ b/packages/core/local-storage/src/pkg-utils.ts @@ -0,0 +1,29 @@ +import fs from 'fs'; + +import _ from 'lodash'; +import { LocalStorage, StorageList, Logger } from '@verdaccio/types'; + +export function loadPrivatePackages(path: string, logger: Logger): LocalStorage { + const list: StorageList = []; + const emptyDatabase = { list, secret: '' }; + const data = fs.readFileSync(path, 'utf8'); + + if (_.isNil(data)) { + // readFileSync is platform specific, FreeBSD might return null + return emptyDatabase; + } + + let db; + try { + db = JSON.parse(data); + } catch (err) { + logger.error(`Package database file corrupted (invalid JSON), please check the error printed below.\nFile Path: ${path}`, err); + throw Error('Package database file corrupted (invalid JSON)'); + } + + if (_.isEmpty(db)) { + return emptyDatabase; + } + + return db; +} diff --git a/packages/core/local-storage/src/utils.ts b/packages/core/local-storage/src/utils.ts new file mode 100644 index 000000000..7a24f0e82 --- /dev/null +++ b/packages/core/local-storage/src/utils.ts @@ -0,0 +1,75 @@ +import fs from 'fs'; +import path from 'path'; + +import _ from 'lodash'; + +export function getFileStats(packagePath: string): Promise { + return new Promise((resolve, reject): void => { + fs.stat(packagePath, (err, stats) => { + if (_.isNil(err) === false) { + return reject(err); + } + resolve(stats); + }); + }); +} + +export function readDirectory(packagePath: string): Promise { + return new Promise((resolve, reject): void => { + fs.readdir(packagePath, (err, scopedPackages) => { + if (_.isNil(err) === false) { + return reject(err); + } + + resolve(scopedPackages); + }); + }); +} + +function hasScope(file: string): boolean { + return file.match(/^@/) !== null; +} + +/* eslint-disable no-async-promise-executor */ +export async function findPackages(storagePath: string, validationHandler: Function): Promise<{ name: string; path: string }[]> { + const listPackages: { name: string; path: string }[] = []; + return new Promise(async (resolve, reject) => { + try { + const scopePath = path.resolve(storagePath); + const storageDirs = await readDirectory(scopePath); + for (const directory of storageDirs) { + // we check whether has 2nd level + if (hasScope(directory)) { + // we read directory multiple + const scopeDirectory = path.resolve(storagePath, directory); + const scopedPackages = await readDirectory(scopeDirectory); + for (const scopedDirName of scopedPackages) { + if (validationHandler(scopedDirName)) { + // we build the complete scope path + const scopePath = path.resolve(storagePath, directory, scopedDirName); + // list content of such directory + listPackages.push({ + name: `${directory}/${scopedDirName}`, + path: scopePath, + }); + } + } + } else { + // otherwise we read as single level + if (validationHandler(directory)) { + const scopePath = path.resolve(storagePath, directory); + listPackages.push({ + name: directory, + path: scopePath, + }); + } + } + } + } catch (error) { + reject(error); + } + + resolve(listPackages); + }); +} +/* eslint-enable no-async-promise-executor */ diff --git a/packages/core/local-storage/tests/__fixtures__/databases/corrupted.json b/packages/core/local-storage/tests/__fixtures__/databases/corrupted.json new file mode 100644 index 000000000..9bb84ded5 --- /dev/null +++ b/packages/core/local-storage/tests/__fixtures__/databases/corrupted.json @@ -0,0 +1,6 @@ +{ + "list": [ + "webpack", + ], + "secret": "8aabe5a0174be5cb3d5a92c01869101f11864d631a8ec21d0bf16e37ad657e92" +} diff --git a/packages/core/local-storage/tests/__fixtures__/databases/empty.json b/packages/core/local-storage/tests/__fixtures__/databases/empty.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/packages/core/local-storage/tests/__fixtures__/databases/empty.json @@ -0,0 +1 @@ +{} diff --git a/packages/core/local-storage/tests/__fixtures__/databases/ok.json b/packages/core/local-storage/tests/__fixtures__/databases/ok.json new file mode 100644 index 000000000..34a7ee408 --- /dev/null +++ b/packages/core/local-storage/tests/__fixtures__/databases/ok.json @@ -0,0 +1,20 @@ +{ + "list": [ + "webpack", + "pnpm", + "yarn", + "react-redux", + "verdaccio", + "tslint", + "@test/pkg1", + "@verdaccio/types", + "@test/view", + "@my-co/my-package", + "verdaccio-authentication-plugin-sample", + "@verdaccio/streams", + "@verdaccio/file-locking", + "bar", + "foo" + ], + "secret": "8aabe5a0174be5cb3d5a92c01869101f11864d631a8ec21d0bf16e37ad657e92" +} diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg1/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg1/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg2/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_second/pkg2/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg1/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg1/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg2/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/@scoped_test/pkg2/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/findPackages/pk3/.gitkeep b/packages/core/local-storage/tests/__fixtures__/findPackages/pk3/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/pkg.js b/packages/core/local-storage/tests/__fixtures__/pkg.js new file mode 100644 index 000000000..044a38cc2 --- /dev/null +++ b/packages/core/local-storage/tests/__fixtures__/pkg.js @@ -0,0 +1,50 @@ +const json = { + _id: '@scope/pk1-test', + name: '@scope/pk1-test', + description: '', + 'dist-tags': { + latest: '1.0.6', + }, + versions: { + '1.0.6': { + name: '@scope/pk1-test', + version: '1.0.6', + description: '', + main: 'index.js', + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + keywords: [], + author: { + name: 'Juan Picado', + email: 'juan@jotadeveloper.com', + }, + license: 'ISC', + dependencies: { + verdaccio: '^2.7.2', + }, + readme: '# test', + readmeFilename: 'README.md', + _id: '@scope/pk1-test@1.0.6', + _npmVersion: '5.5.1', + _nodeVersion: '8.7.0', + _npmUser: {}, + dist: { + integrity: 'sha512-6gHiERpiDgtb3hjqpQH5/i7zRmvYi9pmCjQf2ZMy3QEa9wVk9RgdZaPWUt7ZOnWUPFjcr9cmE6dUBf+XoPoH4g==', + shasum: '2c03764f651a9f016ca0b7620421457b619151b9', + tarball: 'http://localhost:5555/@scope/pk1-test/-/@scope/pk1-test-1.0.6.tgz', + }, + }, + }, + readme: '# test', + _attachments: { + '@scope/pk1-test-1.0.6.tgz': { + content_type: 'application/octet-stream', + data: + 'H4sIAAAAAAAAE+2W32vbMBDH85y/QnjQp9qxLEeBMsbGlocNBmN7bFdQ5WuqxJaEpGQdo//79KPeQsnIw5KUDX/9IOvurLuz/DHSjK/YAiY6jcXSKjk6sMqypHWNdtmD6hlBI0wqQmo8nVbVqMR4OsNoVB66kF1aW8eML+Vv10m9oF/jP6IfY4QyyTrILlD2eqkcm+gVzpdrJrPz4NuAsULJ4MZFWdBkbcByI7R79CRjx0ScCdnAvf+SkjUFWu8IubzBgXUhDPidQlfZ3BhlLpBUKDiQ1cDFrYDmKkNnZwjuhUM4808+xNVW8P2bMk1Y7vJrtLC1u1MmLPjBF40+Cc4ahV6GDmI/DWygVRpMwVX3KtXUCg7Sxp7ff3nbt6TBFy65gK1iffsN41yoEHtdFbOiisWMH8bPvXUH0SP3k+KG3UBr+DFy7OGfEJr4x5iWVeS/pLQe+D+FIv/agIWI6GX66kFuIhT+1gDjrp/4d7WAvAwEJPh0u14IufWkM0zaW2W6nLfM2lybgJ4LTJ0/jWiAK8OcMjt8MW3OlfQppcuhhQ6k+2OgkK2Q8DssFPi/IHpU9fz3/+xj5NjDf8QFE39VmE4JDfzPCBn4P4X6/f88f/Pu47zomiPk2Lv/dOv8h+P/34/D/p9CL+Kp67mrGDRo0KBBp9ZPsETQegASAAA=', + length: 512, + }, + }, +}; + +export default json; diff --git a/packages/core/local-storage/tests/__fixtures__/readme-test-corrupt/corrupt.js b/packages/core/local-storage/tests/__fixtures__/readme-test-corrupt/corrupt.js new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/local-storage/tests/__fixtures__/readme-test/package.json b/packages/core/local-storage/tests/__fixtures__/readme-test/package.json new file mode 100644 index 000000000..dd2ea17a6 --- /dev/null +++ b/packages/core/local-storage/tests/__fixtures__/readme-test/package.json @@ -0,0 +1,56 @@ +{ + "name": "readme-test", + "versions": { + "0.0.0": { + "name": "test-readme", + "version": "0.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "" + }, + "author": "", + "license": "ISC", + "_id": "test-readme@0.0.0", + "dist": { + "shasum": "8ee7331cbc641581b1a8cecd9d38d744a8feb863", + "tarball": "http://localhost:1234/test-readme/-/test-readme-0.0.0.tgz" + }, + "_from": ".", + "_npmVersion": "1.3.1", + "_npmUser": { + "name": "alex", + "email": "alex@kocharin.ru" + }, + "maintainers": [ + { + "name": "juan", + "email": "juanpicado19@gmail.com" + } + ] + } + }, + "dist-tags": { + "foo": "0.0.0", + "latest": "0.0.0" + }, + "time": { + "modified": "2017-10-06T20:30:38.721Z", + "created": "2017-10-06T20:30:38.721Z", + "0.0.0": "2017-10-06T20:30:38.721Z" + }, + "_distfiles": {}, + "_attachments": { + "test-readme-0.0.0.tgz": { + "shasum": "8ee7331cbc641581b1a8cecd9d38d744a8feb863", + "version": "0.0.0" + } + }, + "_uplinks": {}, + "_rev": "5-d647003b88ff08a0", + "readme": "this is a readme" +} diff --git a/packages/core/local-storage/tests/__fixtures__/readme-test/test-readme-0.0.0.tgz b/packages/core/local-storage/tests/__fixtures__/readme-test/test-readme-0.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..cb18086fac7e59f8601cc740fd7caf2add8f81c7 GIT binary patch literal 352 zcmV-m0iXUKiwFP!000001MQQsZo)7ShBNyqP9B&jbrK+w*Q!J3t}Iq?gA1`O+q59W zyZ7wS7PUxKRaAtOKf|5Qr~l4YJ}c^_XymQ!;^^5Mot;^XvFUgW81rhvj$=|;l#t~` zSrp}@tfqO!@{$!fo}YV>Q(F5V9IocgYhEWxX&kqxa4k$3Yv2b?{c! z>PgA@jKW|5a-#y|bU&m{TH{qPZks%|Hm=vGfZl7jies@!ti!^%ZikhsrS`Gw;qi{{ zFa4xfCHsZ{t!P*0p78_!MOB^Pzv%h@4@LSpe1o4H(C3iICr8|Kpp-=HIlNO(4$N}` y$qHM3!#w#UJ~<%q91wWS5%@H(!#$-3+BqQXgHSF1OP52?8T3;+Pl6|^M) literal 0 HcmV?d00001 diff --git a/packages/core/local-storage/tests/__mocks__/Config.ts b/packages/core/local-storage/tests/__mocks__/Config.ts new file mode 100644 index 000000000..7db13d9c9 --- /dev/null +++ b/packages/core/local-storage/tests/__mocks__/Config.ts @@ -0,0 +1,89 @@ +import minimatch from 'minimatch'; + +// FUTURE: we should use the same is on verdaccio +export function getMatchedPackagesSpec(pkgName: string, packages: object): object | undefined { + for (const i in packages) { + if (minimatch.makeRe(i).exec(pkgName)) { + return packages[i]; + } + } + return; +} + +export default class Config { + public constructor() { + this.storage = './test-storage'; + + this.listen = 'http://localhost:1443/'; + + this.auth = { + htpasswd: { + file: './htpasswd', + max_users: 1000, + }, + }; + + this.uplinks = { + npmjs: { + url: 'https://registry.npmjs.org', + cache: true, + }, + }; + + this.packages = { + '@*/*': { + access: ['$all'], + publish: ['$authenticated'], + proxy: [], + }, + 'local-private-custom-storage': { + access: ['$all'], + publish: ['$authenticated'], + storage: 'private_folder', + }, + '*': { + access: ['$all'], + publish: ['$authenticated'], + proxy: ['npmjs'], + }, + '**': { + access: [], + publish: [], + proxy: [], + }, + }; + + this.logs = [ + { + type: 'stdout', + format: 'pretty', + level: 35, + }, + ]; + + this.self_path = './tests/__fixtures__/config.yaml'; + + this.https = { + enable: false, + }; + + this.user_agent = 'verdaccio/3.0.0-alpha.7'; + + this.users = {}; + + this.server_id = 'severMockId'; + + this.getMatchedPackagesSpec = (pkgName) => getMatchedPackagesSpec(pkgName, this.packages); + + this.checkSecretKey = (secret) => { + if (!secret) { + const newSecret = 'superNewSecret'; + + this.secret = newSecret; + + return newSecret; + } + return secret; + }; + } +} diff --git a/packages/core/local-storage/tests/__mocks__/Logger.ts b/packages/core/local-storage/tests/__mocks__/Logger.ts new file mode 100644 index 000000000..14822a7de --- /dev/null +++ b/packages/core/local-storage/tests/__mocks__/Logger.ts @@ -0,0 +1,14 @@ +import { Logger } from '@verdaccio/types'; + +const logger: Logger = { + warn: jest.fn(), + error: jest.fn(), + // fatal: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + child: jest.fn(), + http: jest.fn(), + trace: jest.fn(), +}; + +export default logger; diff --git a/packages/core/local-storage/tests/local-database.test.ts b/packages/core/local-storage/tests/local-database.test.ts new file mode 100644 index 000000000..f78bbe236 --- /dev/null +++ b/packages/core/local-storage/tests/local-database.test.ts @@ -0,0 +1,273 @@ +/* eslint-disable jest/no-mocks-import */ +import fs from 'fs'; +import path from 'path'; + +import { assign } from 'lodash'; +import { ILocalData, PluginOptions, Token } from '@verdaccio/types'; + +import LocalDatabase from '../src/local-database'; +import { ILocalFSPackageManager } from '../src/local-fs'; +import * as pkgUtils from '../src/pkg-utils'; + +// FIXME: remove this mocks imports +import Config from './__mocks__/Config'; +import logger from './__mocks__/Logger'; + +const optionsPlugin: PluginOptions<{}> = { + logger, + config: new Config(), +}; + +let locaDatabase: ILocalData<{}>; +let loadPrivatePackages; + +describe('Local Database', () => { + beforeEach(() => { + const writeMock = jest.spyOn(fs, 'writeFileSync').mockImplementation(); + loadPrivatePackages = jest.spyOn(pkgUtils, 'loadPrivatePackages').mockReturnValue({ list: [], secret: '' }); + locaDatabase = new LocalDatabase(optionsPlugin.config, optionsPlugin.logger); + (locaDatabase as LocalDatabase).clean(); + writeMock.mockClear(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should create an instance', () => { + expect(optionsPlugin.logger.error).not.toHaveBeenCalled(); + expect(locaDatabase).toBeDefined(); + }); + + test('should display log error if fails on load database', () => { + loadPrivatePackages.mockImplementation(() => { + throw Error(); + }); + + new LocalDatabase(optionsPlugin.config, optionsPlugin.logger); + + expect(optionsPlugin.logger.error).toHaveBeenCalled(); + expect(optionsPlugin.logger.error).toHaveBeenCalledTimes(2); + }); + + describe('should create set secret', () => { + test('should create get secret', async () => { + const secretKey = await locaDatabase.getSecret(); + + expect(secretKey).toBeDefined(); + expect(typeof secretKey === 'string').toBeTruthy(); + }); + + test('should create set secret', async () => { + await locaDatabase.setSecret(optionsPlugin.config.checkSecretKey('')); + + expect(optionsPlugin.config.secret).toBeDefined(); + expect(typeof optionsPlugin.config.secret === 'string').toBeTruthy(); + + const fetchedSecretKey = await locaDatabase.getSecret(); + expect(optionsPlugin.config.secret).toBe(fetchedSecretKey); + }); + }); + + describe('getPackageStorage', () => { + test('should get default storage', () => { + const pkgName = 'someRandomePackage'; + const storage = locaDatabase.getPackageStorage(pkgName); + expect(storage).toBeDefined(); + + if (storage) { + const storagePath = path.normalize((storage as ILocalFSPackageManager).path).toLowerCase(); + expect(storagePath).toBe(path.normalize(path.join(__dirname, '__fixtures__', optionsPlugin.config.storage || '', pkgName)).toLowerCase()); + } + }); + + test('should use custom storage', () => { + const pkgName = 'local-private-custom-storage'; + const storage = locaDatabase.getPackageStorage(pkgName); + + expect(storage).toBeDefined(); + + if (storage) { + const storagePath = path.normalize((storage as ILocalFSPackageManager).path).toLowerCase(); + expect(storagePath).toBe( + path.normalize(path.join(__dirname, '__fixtures__', optionsPlugin.config.storage || '', 'private_folder', pkgName)).toLowerCase() + ); + } + }); + }); + + describe('Database CRUD', () => { + test('should add an item to database', (done) => { + const pgkName = 'jquery'; + locaDatabase.get((err, data) => { + expect(err).toBeNull(); + expect(data).toHaveLength(0); + + locaDatabase.add(pgkName, (err) => { + expect(err).toBeNull(); + locaDatabase.get((err, data) => { + expect(err).toBeNull(); + expect(data).toHaveLength(1); + done(); + }); + }); + }); + }); + + test('should remove an item to database', (done) => { + const pgkName = 'jquery'; + locaDatabase.get((err, data) => { + expect(err).toBeNull(); + expect(data).toHaveLength(0); + locaDatabase.add(pgkName, (err) => { + expect(err).toBeNull(); + locaDatabase.remove(pgkName, (err) => { + expect(err).toBeNull(); + locaDatabase.get((err, data) => { + expect(err).toBeNull(); + expect(data).toHaveLength(0); + done(); + }); + }); + }); + }); + }); + }); + + describe('search', () => { + const onPackageMock = jest.fn((item, cb) => cb()); + const validatorMock = jest.fn(() => true); + const callSearch = (db, numberTimesCalled, cb): void => { + db.search( + onPackageMock, + function onEnd() { + expect(onPackageMock).toHaveBeenCalledTimes(numberTimesCalled); + expect(validatorMock).toHaveBeenCalledTimes(numberTimesCalled); + cb(); + }, + validatorMock + ); + }; + + test('should find scoped packages', (done) => { + const scopedPackages = ['@pkg1/test']; + const stats = { mtime: new Date() }; + jest.spyOn(fs, 'stat').mockImplementation((_, cb) => cb(null, stats as fs.Stats)); + jest.spyOn(fs, 'readdir').mockImplementation((storePath, cb) => cb(null, storePath.match('test-storage') ? scopedPackages : [])); + + callSearch(locaDatabase, 1, done); + }); + + test('should find non scoped packages', (done) => { + const nonScopedPackages = ['pkg1', 'pkg2']; + const stats = { mtime: new Date() }; + jest.spyOn(fs, 'stat').mockImplementation((_, cb) => cb(null, stats as fs.Stats)); + jest.spyOn(fs, 'readdir').mockImplementation((storePath, cb) => cb(null, storePath.match('test-storage') ? nonScopedPackages : [])); + + const db = new LocalDatabase( + assign({}, optionsPlugin.config, { + // clean up this, it creates noise + packages: {}, + }), + optionsPlugin.logger + ); + + callSearch(db, 2, done); + }); + + test('should fails on read the storage', (done) => { + const spyInstance = jest.spyOn(fs, 'readdir').mockImplementation((_, cb) => cb(Error('fails'), null)); + + const db = new LocalDatabase( + assign({}, optionsPlugin.config, { + // clean up this, it creates noise + packages: {}, + }), + optionsPlugin.logger + ); + + callSearch(db, 0, done); + spyInstance.mockRestore(); + }); + }); + + describe('token', () => { + let token: Token; + + beforeEach(() => { + (locaDatabase as LocalDatabase).tokenDb = { + put: jest.fn().mockImplementation((key, value, cb) => cb()), + del: jest.fn().mockImplementation((key, cb) => cb()), + createReadStream: jest.fn(), + }; + + token = { + user: 'someUser', + viewToken: 'viewToken', + key: 'someHash', + readonly: true, + createdTimestamp: new Date().getTime(), + }; + }); + + test('should save token', async (done) => { + const db = (locaDatabase as LocalDatabase).tokenDb; + + await locaDatabase.saveToken(token); + + expect(db.put).toHaveBeenCalledWith('someUser:someHash', token, expect.anything()); + done(); + }); + + test('should delete token', async (done) => { + const db = (locaDatabase as LocalDatabase).tokenDb; + + await locaDatabase.deleteToken('someUser', 'someHash'); + + expect(db.del).toHaveBeenCalledWith('someUser:someHash', expect.anything()); + done(); + }); + + test('should get tokens', async () => { + const db = (locaDatabase as LocalDatabase).tokenDb; + const events = { on: {}, once: {} }; + const stream = { + on: (event, cb): void => { + events.on[event] = cb; + }, + once: (event, cb): void => { + events.once[event] = cb; + }, + }; + db.createReadStream.mockImplementation(() => stream); + setTimeout(() => events.on['data']({ value: token })); + setTimeout(() => events.once['end']()); + + const tokens = await locaDatabase.readTokens({ user: 'someUser' }); + + expect(db.createReadStream).toHaveBeenCalledWith({ + gte: 'someUser:', + lte: 't', + }); + expect(tokens).toHaveLength(1); + expect(tokens[0]).toBe(token); + }); + + test('should fail getting tokens if something goes wrong', async () => { + const db = (locaDatabase as LocalDatabase).tokenDb; + const events = { on: {}, once: {} }; + const stream = { + on: (event, cb): void => { + events.on[event] = cb; + }, + once: (event, cb): void => { + events.once[event] = cb; + }, + }; + db.createReadStream.mockImplementation(() => stream); + setTimeout(() => events.once['error'](new Error('Unexpected error!'))); + + await expect(locaDatabase.readTokens({ user: 'someUser' })).rejects.toThrow('Unexpected error!'); + }); + }); +}); diff --git a/packages/core/local-storage/tests/local-fs.test.ts b/packages/core/local-storage/tests/local-fs.test.ts new file mode 100644 index 000000000..2d1b848d1 --- /dev/null +++ b/packages/core/local-storage/tests/local-fs.test.ts @@ -0,0 +1,344 @@ +import path from 'path'; +import fs from 'fs'; + +import mkdirp from 'mkdirp'; +import rm from 'rmdir-sync'; +import { Logger, ILocalPackageManager, Package } from '@verdaccio/types'; + +import LocalDriver, { fileExist, fSError, noSuchFile, resourceNotAvailable } from '../src/local-fs'; + +import pkg from './__fixtures__/pkg'; + +let localTempStorage: string; +const pkgFileName = 'package.json'; + +const logger: Logger = { + error: () => jest.fn(), + info: () => jest.fn(), + debug: () => jest.fn(), + warn: () => jest.fn(), + child: () => jest.fn(), + http: () => jest.fn(), + trace: () => jest.fn(), +}; + +beforeAll(() => { + localTempStorage = path.join('./_storage'); + rm(localTempStorage); +}); + +describe('Local FS test', () => { + describe('savePackage() group', () => { + test('savePackage()', (done) => { + const data = {}; + const localFs = new LocalDriver(path.join(localTempStorage, 'first-package'), logger); + + localFs.savePackage('pkg.1.0.0.tar.gz', data as Package, (err) => { + expect(err).toBeNull(); + done(); + }); + }); + }); + + describe('readPackage() group', () => { + test('readPackage() success', (done) => { + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger); + + localFs.readPackage(pkgFileName, (err) => { + expect(err).toBeNull(); + done(); + }); + }); + + test('readPackage() fails', (done) => { + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-testt'), logger); + + localFs.readPackage(pkgFileName, (err) => { + expect(err).toBeTruthy(); + done(); + }); + }); + + test('readPackage() fails corrupt', (done) => { + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test-corrupt'), logger); + + localFs.readPackage('corrupt.js', (err) => { + expect(err).toBeTruthy(); + done(); + }); + }); + }); + + describe('createPackage() group', () => { + test('createPackage()', (done) => { + const localFs = new LocalDriver(path.join(localTempStorage, 'createPackage'), logger); + + localFs.createPackage(path.join(localTempStorage, 'package5'), pkg, (err) => { + expect(err).toBeNull(); + done(); + }); + }); + + test('createPackage() fails by fileExist', (done) => { + const localFs = new LocalDriver(path.join(localTempStorage, 'createPackage'), logger); + + localFs.createPackage(path.join(localTempStorage, 'package5'), pkg, (err) => { + expect(err).not.toBeNull(); + expect(err.code).toBe(fileExist); + done(); + }); + }); + + describe('deletePackage() group', () => { + test('deletePackage()', (done) => { + const localFs = new LocalDriver(path.join(localTempStorage, 'createPackage'), logger); + + // verdaccio removes the package.json instead the package name + localFs.deletePackage('package.json', (err) => { + expect(err).toBeNull(); + done(); + }); + }); + }); + }); + + describe('removePackage() group', () => { + beforeEach(() => { + mkdirp.sync(path.join(localTempStorage, '_toDelete')); + }); + + test('removePackage() success', (done) => { + const localFs: ILocalPackageManager = new LocalDriver(path.join(localTempStorage, '_toDelete'), logger); + localFs.removePackage((error) => { + expect(error).toBeNull(); + done(); + }); + }); + + test('removePackage() fails', (done) => { + const localFs: ILocalPackageManager = new LocalDriver(path.join(localTempStorage, '_toDelete_fake'), logger); + localFs.removePackage((error) => { + expect(error).toBeTruthy(); + expect(error.code).toBe('ENOENT'); + done(); + }); + }); + }); + + describe('readTarball() group', () => { + test('readTarball() success', (done) => { + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger); + const readTarballStream = localFs.readTarball('test-readme-0.0.0.tgz'); + + readTarballStream.on('error', function (err) { + expect(err).toBeNull(); + }); + + readTarballStream.on('content-length', function (content) { + expect(content).toBe(352); + }); + + readTarballStream.on('end', function () { + done(); + }); + + readTarballStream.on('data', function (data) { + expect(data).toBeDefined(); + }); + }); + + test('readTarball() fails', (done) => { + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger); + const readTarballStream = localFs.readTarball('file-does-not-exist-0.0.0.tgz'); + + readTarballStream.on('error', function (err) { + expect(err).toBeTruthy(); + done(); + }); + }); + }); + + describe('writeTarball() group', () => { + beforeEach(() => { + const writeTarballFolder: string = path.join(localTempStorage, '_writeTarball'); + rm(writeTarballFolder); + mkdirp.sync(writeTarballFolder); + }); + + test('writeTarball() success', (done) => { + const newFileName = 'new-readme-0.0.0.tgz'; + const readmeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger); + const writeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '../_storage'), logger); + const readTarballStream = readmeStorage.readTarball('test-readme-0.0.0.tgz'); + const writeTarballStream = writeStorage.writeTarball(newFileName); + + writeTarballStream.on('error', function (err) { + expect(err).toBeNull(); + done(); + }); + + writeTarballStream.on('success', function () { + const fileLocation: string = path.join(__dirname, '../_storage', newFileName); + + expect(fs.existsSync(fileLocation)).toBe(true); + done(); + }); + + readTarballStream.on('end', function () { + writeTarballStream.done(); + }); + + writeTarballStream.on('end', function () { + done(); + }); + + writeTarballStream.on('data', function (data) { + expect(data).toBeDefined(); + }); + + readTarballStream.on('error', function (err) { + expect(err).toBeNull(); + done(); + }); + + readTarballStream.pipe(writeTarballStream); + }); + + test('writeTarball() abort', (done) => { + const newFileLocationFolder: string = path.join(localTempStorage, '_writeTarball'); + const newFileName = 'new-readme-abort-0.0.0.tgz'; + const readmeStorage: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/readme-test'), logger); + const writeStorage: ILocalPackageManager = new LocalDriver(newFileLocationFolder, logger); + const readTarballStream = readmeStorage.readTarball('test-readme-0.0.0.tgz'); + const writeTarballStream = writeStorage.writeTarball(newFileName); + + writeTarballStream.on('error', function (err) { + expect(err).toBeTruthy(); + done(); + }); + + writeTarballStream.on('data', function (data) { + expect(data).toBeDefined(); + writeTarballStream.abort(); + }); + + readTarballStream.pipe(writeTarballStream); + }); + }); + + describe('updatePackage() group', () => { + const updateHandler = jest.fn((name, cb) => { + cb(); + }); + const onWrite = jest.fn((name, data, cb) => { + cb(); + }); + const transform = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + test('updatePackage() success', (done) => { + jest.doMock('@verdaccio/file-locking', () => { + return { + readFile: (name, _options, cb): any => cb(null, { name }), + unlockFile: (_something, cb): any => cb(null), + }; + }); + + const LocalDriver = require('../src/local-fs').default; + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger); + + localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, () => { + expect(transform).toHaveBeenCalledTimes(1); + expect(updateHandler).toHaveBeenCalledTimes(1); + expect(onWrite).toHaveBeenCalledTimes(1); + done(); + }); + }); + + describe('updatePackage() failures handler', () => { + test('updatePackage() whether locking fails', (done) => { + jest.doMock('@verdaccio/file-locking', () => { + return { + readFile: (name, _options, cb): any => cb(Error('whateverError'), { name }), + unlockFile: (_something, cb): any => cb(null), + }; + }); + require('../src/local-fs').default; + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger); + + localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => { + expect(err).not.toBeNull(); + expect(transform).toHaveBeenCalledTimes(0); + expect(updateHandler).toHaveBeenCalledTimes(0); + expect(onWrite).toHaveBeenCalledTimes(0); + done(); + }); + }); + + test('updatePackage() unlock a missing package', (done) => { + jest.doMock('@verdaccio/file-locking', () => { + return { + readFile: (name, _options, cb): any => cb(fSError(noSuchFile, 404), { name }), + unlockFile: (_something, cb): any => cb(null), + }; + }); + const LocalDriver = require('../src/local-fs').default; + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger); + + localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => { + expect(err).not.toBeNull(); + expect(transform).toHaveBeenCalledTimes(0); + expect(updateHandler).toHaveBeenCalledTimes(0); + expect(onWrite).toHaveBeenCalledTimes(0); + done(); + }); + }); + + test('updatePackage() unlock a resource non available', (done) => { + jest.doMock('@verdaccio/file-locking', () => { + return { + readFile: (name, _options, cb): any => cb(fSError(resourceNotAvailable, 503), { name }), + unlockFile: (_something, cb): any => cb(null), + }; + }); + const LocalDriver = require('../src/local-fs').default; + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger); + + localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => { + expect(err).not.toBeNull(); + expect(transform).toHaveBeenCalledTimes(0); + expect(updateHandler).toHaveBeenCalledTimes(0); + expect(onWrite).toHaveBeenCalledTimes(0); + done(); + }); + }); + + test('updatePackage() if updateHandler fails', (done) => { + jest.doMock('@verdaccio/file-locking', () => { + return { + readFile: (name, _options, cb): any => cb(null, { name }), + unlockFile: (_something, cb): any => cb(null), + }; + }); + + const LocalDriver = require('../src/local-fs').default; + const localFs: ILocalPackageManager = new LocalDriver(path.join(__dirname, '__fixtures__/update-package'), logger); + const updateHandler = jest.fn((_name, cb) => { + cb(fSError('something wrong', 500)); + }); + + localFs.updatePackage('updatePackage', updateHandler, onWrite, transform, (err) => { + expect(err).not.toBeNull(); + expect(transform).toHaveBeenCalledTimes(0); + expect(updateHandler).toHaveBeenCalledTimes(1); + expect(onWrite).toHaveBeenCalledTimes(0); + done(); + }); + }); + }); + }); +}); diff --git a/packages/core/local-storage/tests/utils.test.ts b/packages/core/local-storage/tests/utils.test.ts new file mode 100644 index 000000000..646ea646e --- /dev/null +++ b/packages/core/local-storage/tests/utils.test.ts @@ -0,0 +1,74 @@ +import path from 'path'; +import fs from 'fs'; + +import { findPackages } from '../src/utils'; +import { loadPrivatePackages } from '../src/pkg-utils'; +import { noSuchFile } from '../src/local-fs'; + +// FIXME: remove this mocks imports +// eslint-disable-next-line jest/no-mocks-import +import logger from './__mocks__/Logger'; + +describe('Utitlies', () => { + const loadDb = (name): string => path.join(__dirname, '__fixtures__/databases', `${name}.json`); + + beforeEach(() => { + jest.resetModules(); + }); + + test('should load private packages', () => { + const database = loadDb('ok'); + const db = loadPrivatePackages(database, logger); + + expect(db.list).toHaveLength(15); + }); + + test('should load and empty private packages if database file is valid and empty', () => { + const database = loadDb('empty'); + const db = loadPrivatePackages(database, logger); + + expect(db.list).toHaveLength(0); + }); + + test('should fails on load private packages', () => { + const database = loadDb('corrupted'); + + expect(() => { + loadPrivatePackages(database, logger); + }).toThrow(); + }); + + test('should handle null read values and return empty database', () => { + const spy = jest.spyOn(fs, 'readFileSync'); + spy.mockReturnValue(null); + + const database = loadDb('ok'); + const db = loadPrivatePackages(database, logger); + + expect(db.list).toHaveLength(0); + + spy.mockRestore(); + }); + + describe('find packages', () => { + test('should fails on wrong storage path', async () => { + try { + await findPackages( + './no_such_folder_fake', + jest.fn(() => true) + ); + } catch (e) { + expect(e.code).toEqual(noSuchFile); + } + }); + + test('should fetch all packages from valid storage', async () => { + const storage = path.join(__dirname, '__fixtures__/findPackages'); + const validator = jest.fn((file) => file.indexOf('.') === -1); + const pkgs = await findPackages(storage, validator); + // the result is 7 due number of packages on "findPackages" directory + expect(pkgs).toHaveLength(5); + expect(validator).toHaveBeenCalledTimes(8); + }); + }); +}); diff --git a/packages/core/local-storage/tsconfig.json b/packages/core/local-storage/tsconfig.json new file mode 100644 index 000000000..537522270 --- /dev/null +++ b/packages/core/local-storage/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/readme/.babelrc b/packages/core/readme/.babelrc new file mode 100644 index 000000000..851856e59 --- /dev/null +++ b/packages/core/readme/.babelrc @@ -0,0 +1,3 @@ +{ + "extends": "../../../.babelrc" +} diff --git a/packages/core/readme/.gitignore b/packages/core/readme/.gitignore new file mode 100644 index 000000000..c3af85790 --- /dev/null +++ b/packages/core/readme/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/core/readme/CHANGELOG.md b/packages/core/readme/CHANGELOG.md new file mode 100644 index 000000000..5fb327cdd --- /dev/null +++ b/packages/core/readme/CHANGELOG.md @@ -0,0 +1,332 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [9.7.3](https://github.com/verdaccio/monorepo/compare/v9.7.2...v9.7.3) (2020-07-30) + + +### Bug Fixes + +* update marked / request security vulnerability ([#378](https://github.com/verdaccio/monorepo/issues/378)) ([4188e08](https://github.com/verdaccio/monorepo/commit/4188e088f42d0f6e090c948b869312ba1f30cd79)) + + + + + +## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [9.5.1](https://github.com/verdaccio/monorepo/compare/v9.5.0...v9.5.1) (2020-06-03) + + +### Bug Fixes + +* restore Node v8 support ([#361](https://github.com/verdaccio/monorepo/issues/361)) ([9be55a1](https://github.com/verdaccio/monorepo/commit/9be55a1deebe954e8eef9edc59af9fd16e29daed)) + + + + + +# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [9.3.3](https://github.com/verdaccio/monorepo/compare/v9.3.2...v9.3.3) (2020-03-11) + + +### Bug Fixes + +* update jsdom@16.2.1 ([#340](https://github.com/verdaccio/monorepo/issues/340)) ([6060769](https://github.com/verdaccio/monorepo/commit/6060769d52f796337dda9f1a54f149c5fb22ca17)) + + + + + +## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08) + + +### Bug Fixes + +* security dependency jsdom@16.2.0 update ([#338](https://github.com/verdaccio/monorepo/issues/338)) ([0599f3e](https://github.com/verdaccio/monorepo/commit/0599f3e16fd1de993494943e2e7464d10b62d6be)) +* update dependencies ([#332](https://github.com/verdaccio/monorepo/issues/332)) ([b6165ae](https://github.com/verdaccio/monorepo/commit/b6165aea9b7e4012477081eae68bfa7159c58f56)) + + + + + +## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23) + + +### Bug Fixes + +* core/readme/package.json to reduce vulnerabilities ([#216](https://github.com/verdaccio/monorepo/issues/216)) ([40299ab](https://github.com/verdaccio/monorepo/commit/40299ab)) + + + + + +# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08) + + +### Bug Fixes + +* fixed lint errors ([5e677f7](https://github.com/verdaccio/monorepo/commit/5e677f7)) +* fixed lint errors ([c80e915](https://github.com/verdaccio/monorepo/commit/c80e915)) +* quotes should be single ([ae9aa44](https://github.com/verdaccio/monorepo/commit/ae9aa44)) + + + + + +## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29) + + +### Bug Fixes + +* **readme:** security vulnerabilities in marked dep ([ee604b1](https://github.com/verdaccio/monorepo/commit/ee604b1)) + + + + + +## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26) + + +### Bug Fixes + +* **security:** Cross-site Scripting (XSS) for readme ([7b53e1b](https://github.com/verdaccio/monorepo/commit/7b53e1b)) + + + + + +# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01) + +**Note:** Version bump only for package @verdaccio/readme + + + + + +# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01) + + +### Features + +* **readme:** import readme package ([f4bbf3a](https://github.com/verdaccio/monorepo/commit/f4bbf3a)) +* **readme:** modernize project ([0d8f963](https://github.com/verdaccio/monorepo/commit/0d8f963)) + + + + + +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +### [1.0.4](https://github.com/verdaccio/readme/compare/v1.0.3...v1.0.4) (2019-06-22) + + +### Bug Fixes + +* update dependencies ([3316ccf](https://github.com/verdaccio/readme/commit/3316ccf)) + + + +### [1.0.3](https://github.com/verdaccio/readme/compare/v1.0.2...v1.0.3) (2019-05-15) + + +### Bug Fixes + +* **build:** lib folder as main ([e1ac882](https://github.com/verdaccio/readme/commit/e1ac882)) + + + +### [1.0.2](https://github.com/verdaccio/readme/compare/v1.0.1...v1.0.2) (2019-05-15) + + +### Bug Fixes + +* **build:** remove publish script ([9b36d5f](https://github.com/verdaccio/readme/commit/9b36d5f)) + + + +### 1.0.1 (2019-05-15) + + +### Tests + +* add basic test ([774a54d](https://github.com/verdaccio/readme/commit/774a54d)) +* add image test ([8c4639e](https://github.com/verdaccio/readme/commit/8c4639e)) +* add xss scenarios ([81e43e8](https://github.com/verdaccio/readme/commit/81e43e8)) +* add xss scenarios ([b211b97](https://github.com/verdaccio/readme/commit/b211b97)) diff --git a/packages/core/readme/LICENSE b/packages/core/readme/LICENSE new file mode 100644 index 000000000..65fb12e2a --- /dev/null +++ b/packages/core/readme/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Verdaccio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/readme/README.md b/packages/core/readme/README.md new file mode 100644 index 000000000..69501b016 --- /dev/null +++ b/packages/core/readme/README.md @@ -0,0 +1,21 @@ +# @verdaccio/readme + +📃 Readme markdown parser + +[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/readme/latest.svg)](https://www.npmjs.com/package/@verdaccio/readme) +[![CircleCI](https://circleci.com/gh/verdaccio/readme/tree/master.svg?style=svg)](https://circleci.com/gh/verdaccio/readme/tree/master) +[![Known Vulnerabilities](https://snyk.io/test/github/verdaccio/readme/badge.svg?targetFile=package.json)](https://snyk.io/test/github/verdaccio/readme?targetFile=package.json) +[![codecov](https://codecov.io/gh/verdaccio/readme/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/readme) +[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio) +[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/) +![MIT](https://img.shields.io/github/license/mashape/apistatus.svg) +[![node](https://img.shields.io/node/v/@verdaccio/readme/latest.svg)](https://www.npmjs.com/package/@verdaccio/readme) + +> This package is already built-in in verdaccio + +``` +npm install @verdaccio/readme +``` + +## License +Verdaccio is [MIT licensed](https://github.com/verdaccio/readme/blob/master/LICENSE). diff --git a/packages/core/readme/jest.config.js b/packages/core/readme/jest.config.js new file mode 100644 index 000000000..1c3fbdb05 --- /dev/null +++ b/packages/core/readme/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../../jest/config'); + +module.exports = Object.assign({}, config, {}); diff --git a/packages/core/readme/package.json b/packages/core/readme/package.json new file mode 100644 index 000000000..65f5e0a4f --- /dev/null +++ b/packages/core/readme/package.json @@ -0,0 +1,52 @@ +{ + "name": "@verdaccio/readme", + "version": "10.0.0-beta", + "description": "Readme markdown parser", + "keywords": [ + "verdaccio", + "readme", + "markdown" + ], + "author": { + "name": "Juan Picado", + "email": "juanpicado19@gmail.com" + }, + "license": "MIT", + "homepage": "https://verdaccio.org", + "repository": { + "type": "git", + "url": "https://github.com/verdaccio/monorepo", + "directory": "core/readme" + }, + "bugs": { + "url": "https://github.com/verdaccio/monorepo/issues" + }, + "publishConfig": { + "access": "public" + }, + "main": "./build/index.js", + "types": "./build/index.d.ts", + "files": [ + "build" + ], + "dependencies": { + "dompurify": "2.0.8", + "jsdom": "15.2.1", + "marked": "1.1.1" + }, + "devDependencies": { + "@verdaccio/types": "workspace:*" + }, + "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": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", + "build": "pnpm run build:js && pnpm run build:types" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } +} diff --git a/packages/core/readme/src/index.ts b/packages/core/readme/src/index.ts new file mode 100644 index 000000000..e88a690c5 --- /dev/null +++ b/packages/core/readme/src/index.ts @@ -0,0 +1,17 @@ +import marked from 'marked'; +import createDOMPurify from 'dompurify'; +import { JSDOM } from 'jsdom'; + +const DOMPurify = createDOMPurify(new JSDOM('').window); + +export default function parseReadme(readme: string): string | void { + if (readme) { + return DOMPurify.sanitize( + marked(readme, { + sanitize: false, + }).trim() + ); + } + + return; +} diff --git a/packages/core/readme/tests/partials/mixed-html-mk/readme.md b/packages/core/readme/tests/partials/mixed-html-mk/readme.md new file mode 100644 index 000000000..f8a9add6f --- /dev/null +++ b/packages/core/readme/tests/partials/mixed-html-mk/readme.md @@ -0,0 +1,5 @@ +# mix html and XSS markdown + +[Basic](javascript:alert('Basic')) + + diff --git a/packages/core/readme/tests/readme.spec.ts b/packages/core/readme/tests/readme.spec.ts new file mode 100644 index 000000000..b10332684 --- /dev/null +++ b/packages/core/readme/tests/readme.spec.ts @@ -0,0 +1,201 @@ +import fs from 'fs'; +import path from 'path'; + +import parseReadme from '../src'; + +function readReadme(project: string, fileName = 'readme.md'): Promise { + return new Promise((resolve, reject): void => { + fs.readFile(path.join(__dirname, 'partials', project, fileName), 'utf8', (err, data) => { + if (err) { + return reject(err); + } + + return resolve(data.toString()); + }); + }); +} + +function clean(text: string): string { + return text.replace(/\n|\r/g, '').trim(); +} + +describe('readme', () => { + test('should handle empty readme', () => { + expect(parseReadme('')).toBeUndefined(); + }); + + test('should handle single string readme', () => { + expect(parseReadme('this is a readme')).toEqual('

this is a readme

'); + }); + + test('should handle wrong text', () => { + expect(parseReadme(undefined)).toBeUndefined(); + }); + + describe('basic parsing', () => { + test('should parse basic', () => { + expect(parseReadme('# hi')).toEqual(`

hi

`); + }); + + test('should parse basic / js alert', () => { + expect(parseReadme("[Basic](javascript:alert('Basic'))")).toEqual('

Basic

'); + }); + + test('should parse basic / local storage', () => { + expect(parseReadme('[Local Storage](javascript:alert(JSON.stringify(localStorage)))')).toEqual('

Local Storage

'); + }); + + test('should parse basic / case insensitive', () => { + expect(parseReadme("[CaseInsensitive](JaVaScRiPt:alert('CaseInsensitive'))")).toEqual('

CaseInsensitive

'); + }); + + test('should parse basic / url', () => { + expect(parseReadme("[URL](javascript://www.google.com%0Aalert('URL'))")).toEqual('

URL

'); + }); + + test('should parse basic / in quotes', () => { + expect(parseReadme('[In Quotes](\'javascript:alert("InQuotes")\')')).toEqual('

In Quotes

'); + }); + }); + + describe('should parse images', () => { + test('in quotes', () => { + expect(parseReadme('![Escape SRC - onload](https://www.example.com/image.png"onload="alert(\'ImageOnLoad\'))')).toEqual( + '

Escape SRC - onload

' + ); + }); + + test('in image error', () => { + expect(parseReadme('![Escape SRC - onerror]("onerror="alert(\'ImageOnError\'))')).toEqual( + '

Escape SRC - onerror

' + ); + }); + }); + + describe('should test fuzzing', () => { + test('xss / document cookie', () => { + expect(parseReadme('[XSS](javascript:prompt(document.cookie))')).toEqual('

XSS

'); + }); + + test('xss / white space cookie', () => { + expect(parseReadme('[XSS](j a v a s c r i p t:prompt(document.cookie))')).toEqual( + '

[XSS](j a v a s c r i p t:prompt(document.cookie))

' + ); + }); + + test('xss / data test/html', () => { + expect(parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')).toEqual('

XSS

'); + }); + + test('xss / data test/html encoded', () => { + expect(parseReadme('[XSS](javascript:alert('XSS'))')).toEqual( + '

XSS

' + ); + }); + + test('xss / js prompt', () => { + expect(parseReadme('[XSS]: (javascript:prompt(document.cookie))')).toEqual(''); + }); + + test('xss / js window error alert', () => { + expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual('

XSS

'); + }); + + test('xss / js window encoded prompt', () => { + expect(parseReadme('[XSS](javascript://%0d%0aprompt(1))')).toEqual('

XSS

'); + }); + + test('xss / js window encoded prompt multiple statement', () => { + expect(parseReadme('[XSS](javascript://%0d%0aprompt(1);com)')).toEqual('

XSS

'); + }); + + test('xss / js window encoded window error alert multiple statement', () => { + expect(parseReadme('[XSS](javascript:window.onerror=alert;throw%20document.cookie)')).toEqual('

XSS

'); + }); + + test('xss / js window encoded window error alert throw error', () => { + expect(parseReadme('[XSS](javascript://%0d%0awindow.onerror=alert;throw%20document.cookie)')).toEqual('

XSS

'); + }); + + test('xss / js window encoded data text/html base 64', () => { + expect(parseReadme('[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)')).toEqual('

XSS

'); + }); + + test('xss / js vbscript alert', () => { + expect(parseReadme('[XSS](vbscript:alert(document.domain))')).toEqual('

XSS

'); + }); + + describe('xss / js alert this', () => { + test('xss / js case #1', () => { + expect(parseReadme('[XSS](javascript:this;alert(1))')).toEqual('

XSS

'); + }); + + test('xss / js case #2', () => { + expect(parseReadme('[XSS](javascript:this;alert(1))')).toEqual('

XSS

'); + }); + + test('xss / js case #3', () => { + expect(parseReadme('[XSS](javascript:this;alert(1))')).toEqual('

XSS

'); + }); + + test('xss / js case #4', () => { + expect(parseReadme('[XSS](Javascript:alert(1))')).toEqual('

XSS

'); + }); + + test('xss / js case #5', () => { + expect(parseReadme('[XSS](Javas%26%2399;ript:alert(1))')).toEqual('

XSS

'); + }); + + test('xss / js case #6', () => { + expect(parseReadme('[XSS](javascript:alert￾(1))')).toEqual('

XSS

'); + }); + }); + + test('xss / js confirm', () => { + expect(parseReadme('[XSS](javascript:confirm(1)')).toEqual('

XSS

'); + }); + + describe('xss / js url', () => { + test('xss / case #1', () => { + expect(parseReadme('[XSS](javascript://www.google.com%0Aprompt(1))')).toEqual('

XSS

'); + }); + + test('xss / case #2', () => { + expect(parseReadme('[XSS](javascript://%0d%0aconfirm(1);com)')).toEqual('

XSS

'); + }); + + test('xss / case #3', () => { + expect(parseReadme('[XSS](javascript:window.onerror=confirm;throw%201)')).toEqual('

XSS

'); + }); + + test('xss / case #4', () => { + expect(parseReadme('[XSS](�javascript:alert(document.domain))')).toEqual('

XSS

'); + }); + + test('xss / case #5', () => { + expect(parseReadme('![XSS](javascript:prompt(document.cookie))\\')).toEqual('

XSS\\

'); + }); + + test('xss / case #6', () => { + expect(parseReadme('![XSS](data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K)\\')).toEqual( + '

XSS\\

' + ); + }); + + // FIXME: requires proper parsing + test.skip('xss / case #7', () => { + expect(parseReadme(`![XSS'"\`onerror=prompt(document.cookie)](x)\\`)).toEqual('

![XSS\'\\"`onerror=prompt(document.cookie)](x)\\\\

'); + }); + }); + }); + + describe('mix readmes / markdown', () => { + test('should parse marked', async () => { + const readme: string = await readReadme('mixed-html-mk'); + + expect(clean(parseReadme(readme) as string)).toEqual( + `

mix html and XSS markdown

Basic

` + ); + }); + }); +}); diff --git a/packages/core/readme/tsconfig.json b/packages/core/readme/tsconfig.json new file mode 100644 index 000000000..537522270 --- /dev/null +++ b/packages/core/readme/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/streams/.babelrc b/packages/core/streams/.babelrc new file mode 100644 index 000000000..851856e59 --- /dev/null +++ b/packages/core/streams/.babelrc @@ -0,0 +1,3 @@ +{ + "extends": "../../../.babelrc" +} diff --git a/packages/core/streams/.eslintignore b/packages/core/streams/.eslintignore new file mode 100644 index 000000000..acf8e826f --- /dev/null +++ b/packages/core/streams/.eslintignore @@ -0,0 +1,6 @@ +node_modules +coverage/ +lib/ +.nyc_output +tests-report/ +build/ diff --git a/packages/core/streams/.eslintrc.json b/packages/core/streams/.eslintrc.json new file mode 100644 index 000000000..3e8a7bcd4 --- /dev/null +++ b/packages/core/streams/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "@typescript-eslint/no-use-before-define": "off" + } +} diff --git a/packages/core/streams/.gitignore b/packages/core/streams/.gitignore new file mode 100644 index 000000000..c3af85790 --- /dev/null +++ b/packages/core/streams/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/core/streams/CHANGELOG.md b/packages/core/streams/CHANGELOG.md new file mode 100644 index 000000000..5faef014b --- /dev/null +++ b/packages/core/streams/CHANGELOG.md @@ -0,0 +1,275 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [9.7.1](https://github.com/verdaccio/monorepo/compare/v9.7.0...v9.7.1) (2020-07-10) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [9.6.1](https://github.com/verdaccio/monorepo/compare/v9.6.0...v9.6.1) (2020-06-07) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [9.4.0](https://github.com/verdaccio/monorepo/compare/v9.3.4...v9.4.0) (2020-03-21) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [9.3.2](https://github.com/verdaccio/monorepo/compare/v9.3.1...v9.3.2) (2020-03-08) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [9.3.1](https://github.com/verdaccio/monorepo/compare/v9.3.0...v9.3.1) (2020-02-23) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.2.0](https://github.com/verdaccio/monorepo/compare/v8.2.0-next.0...v8.2.0) (2019-10-23) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.2.0-next.0](https://github.com/verdaccio/monorepo/compare/v8.1.4...v8.2.0-next.0) (2019-10-08) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.1.2](https://github.com/verdaccio/monorepo/compare/v8.1.1...v8.1.2) (2019-09-29) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.1.1](https://github.com/verdaccio/monorepo/compare/v8.1.0...v8.1.1) (2019-09-26) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01) + +**Note:** Version bump only for package @verdaccio/streams + + + + + +# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01) + + +### Bug Fixes + +* add es6 imports ([932a22d](https://github.com/verdaccio/monorepo/commit/932a22d)) +* lint warnings ([444a99e](https://github.com/verdaccio/monorepo/commit/444a99e)) + + +### Features + +* drop node v6 support ([bb319c4](https://github.com/verdaccio/monorepo/commit/bb319c4)) +* **build:** use typescript, jest 24 and babel 7 as stack BREAKING CHANGE: typescript build system requires a major release to avoid issues with old installations ([4743a9a](https://github.com/verdaccio/monorepo/commit/4743a9a)) +* add stream library ([434628f](https://github.com/verdaccio/monorepo/commit/434628f)) +* migration to typescript ([748ca92](https://github.com/verdaccio/monorepo/commit/748ca92)) + + + + + +# Change Log + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +# [2.0.0](https://github.com/verdaccio/streams/compare/v2.0.0-beta.0...v2.0.0) (2019-03-29) + + +### Features + +* drop node v6 support ([5771eed](https://github.com/verdaccio/streams/commit/5771eed)) + + + + +# [2.0.0-beta.0](https://github.com/verdaccio/streams/compare/v1.0.0...v2.0.0-beta.0) (2019-01-27) + + +### Features + +* migration to typescript ([4e1e959](https://github.com/verdaccio/streams/commit/4e1e959)) +* **build:** use typescript, jest 24 and babel 7 as stack ([c93a980](https://github.com/verdaccio/streams/commit/c93a980)) + + +### BREAKING CHANGES + +* **build:** typescript build system requires a major release to avoid issues with old installations diff --git a/packages/core/streams/LICENSE b/packages/core/streams/LICENSE new file mode 100644 index 000000000..65fb12e2a --- /dev/null +++ b/packages/core/streams/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Verdaccio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/streams/README.md b/packages/core/streams/README.md new file mode 100644 index 000000000..dc50f65f4 --- /dev/null +++ b/packages/core/streams/README.md @@ -0,0 +1,20 @@ +# Streams + +[![CircleCI](https://circleci.com/gh/verdaccio/streams.svg?style=svg)](https://circleci.com/gh/ayusharma/@verdaccio/streams) +[![codecov](https://codecov.io/gh/verdaccio/streams/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/streams) +[![verdaccio (latest)](https://img.shields.io/npm/v/@verdaccio/streams/latest.svg)](https://www.npmjs.com/package/@verdaccio/streams) +[![backers](https://opencollective.com/verdaccio/tiers/backer/badge.svg?label=Backer&color=brightgreen)](https://opencollective.com/verdaccio) +[![discord](https://img.shields.io/discord/388674437219745793.svg)](http://chat.verdaccio.org/) +![MIT](https://img.shields.io/github/license/mashape/apistatus.svg) +[![node](https://img.shields.io/node/v/@verdaccio/streams/latest.svg)](https://www.npmjs.com/package/@verdaccio/streams) + + +This project provides an extension of `PassThrough` stream. + +## Detail + +It provides 2 additional methods `abort()` and `done()`. Those implementations are widely use in the verdaccio core for handle `tarballs`. + +## License + +MIT (http://www.opensource.org/licenses/mit-license.php) diff --git a/packages/core/streams/jest.config.js b/packages/core/streams/jest.config.js new file mode 100644 index 000000000..1c3fbdb05 --- /dev/null +++ b/packages/core/streams/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../../jest/config'); + +module.exports = Object.assign({}, config, {}); diff --git a/packages/core/streams/package.json b/packages/core/streams/package.json new file mode 100644 index 000000000..1acb75e74 --- /dev/null +++ b/packages/core/streams/package.json @@ -0,0 +1,40 @@ +{ + "name": "@verdaccio/streams", + "version": "10.0.0-beta", + "description": "Stream extension for Verdaccio", + "keywords": [ + "verdaccio", + "streams" + ], + "main": "./build/index.js", + "types": "./build/index.d.ts", + "author": "Juan Picado ", + "license": "MIT", + "homepage": "https://verdaccio.org", + "repository": { + "type": "git", + "url": "https://github.com/verdaccio/monorepo", + "directory": "core/streams" + }, + "bugs": { + "url": "https://github.com/verdaccio/monorepo/issues" + }, + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@verdaccio/types": "^9.7.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": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", + "build": "pnpm run build:js && pnpm run build:types" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } +} diff --git a/packages/core/streams/src/index.ts b/packages/core/streams/src/index.ts new file mode 100644 index 000000000..425c980a1 --- /dev/null +++ b/packages/core/streams/src/index.ts @@ -0,0 +1,83 @@ +import { PassThrough, TransformOptions } from 'stream'; + +export interface IReadTarball { + abort?: () => void; +} + +export interface IUploadTarball { + done?: () => void; + abort?: () => void; +} + +/** + * This stream is used to read tarballs from repository. + * @param {*} options + * @return {Stream} + */ +class ReadTarball extends PassThrough implements IReadTarball { + /** + * + * @param {Object} options + */ + public constructor(options: TransformOptions) { + super(options); + // called when data is not needed anymore + addAbstractMethods(this, 'abort'); + } + + public abort(): void {} +} + +/** + * This stream is used to upload tarballs to a repository. + * @param {*} options + * @return {Stream} + */ +class UploadTarball extends PassThrough implements IUploadTarball { + /** + * + * @param {Object} options + */ + public constructor(options: any) { + super(options); + // called when user closes connection before upload finishes + addAbstractMethods(this, 'abort'); + + // called when upload finishes successfully + addAbstractMethods(this, 'done'); + } + + public abort(): void {} + public done(): void {} +} + +/** + * This function intercepts abstract calls and replays them allowing. + * us to attach those functions after we are ready to do so + * @param {*} self + * @param {*} name + */ +// Perhaps someone knows a better way to write this +function addAbstractMethods(self: any, name: any): void { + self._called_methods = self._called_methods || {}; + + self.__defineGetter__(name, function () { + return function (): void { + self._called_methods[name] = true; + }; + }); + + self.__defineSetter__(name, function (fn: any) { + delete self[name]; + + self[name] = fn; + + if (self._called_methods && self._called_methods[name]) { + delete self._called_methods[name]; + + self[name](); + } + }); +} + +export { ReadTarball, UploadTarball }; diff --git a/packages/core/streams/test/mystreams.test.ts b/packages/core/streams/test/mystreams.test.ts new file mode 100644 index 000000000..cc5f0a22c --- /dev/null +++ b/packages/core/streams/test/mystreams.test.ts @@ -0,0 +1,29 @@ +import { ReadTarball, UploadTarball } from '../src/index'; + +describe('mystreams', () => { + test('should delay events on ReadTarball abort', (cb) => { + const readTballStream = new ReadTarball({}); + readTballStream.abort(); + setTimeout(function () { + readTballStream.abort = function (): void { + cb(); + }; + readTballStream.abort = function (): never { + throw Error('fail'); + }; + }, 10); + }); + + test('should delay events on UploadTarball abort', (cb) => { + const uploadTballStream = new UploadTarball({}); + uploadTballStream.abort(); + setTimeout(function () { + uploadTballStream.abort = function (): void { + cb(); + }; + uploadTballStream.abort = function (): never { + throw Error('fail'); + }; + }, 10); + }); +}); diff --git a/packages/core/streams/tsconfig.json b/packages/core/streams/tsconfig.json new file mode 100644 index 000000000..537522270 --- /dev/null +++ b/packages/core/streams/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/types/.eslintrc.json b/packages/core/types/.eslintrc.json new file mode 100644 index 000000000..20672e236 --- /dev/null +++ b/packages/core/types/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "@typescript-eslint/adjacent-overload-signatures": "off", + "@typescript-eslint/explicit-member-accessibility": "off", + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unused-vars": "off" + } +} diff --git a/packages/core/types/CHANGELOG.md b/packages/core/types/CHANGELOG.md new file mode 100644 index 000000000..9ec2e5673 --- /dev/null +++ b/packages/core/types/CHANGELOG.md @@ -0,0 +1,665 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [9.7.2](https://github.com/verdaccio/monorepo/compare/v9.7.1...v9.7.2) (2020-07-20) + + +### Bug Fixes + +* incorrect AuthAccessCallback and AuthCallback ([#374](https://github.com/verdaccio/monorepo/issues/374)) ([97538f8](https://github.com/verdaccio/monorepo/commit/97538f886271ccdbea7862957f65c4a17c4cd831)), closes [/github.com/verdaccio/verdaccio/blob/master/src/lib/auth.ts#L264](https://github.com//github.com/verdaccio/verdaccio/blob/master/src/lib/auth.ts/issues/L264) [/github.com/verdaccio/verdaccio/blob/master/src/lib/auth.ts#L114](https://github.com//github.com/verdaccio/verdaccio/blob/master/src/lib/auth.ts/issues/L114) + + + + + +# [9.7.0](https://github.com/verdaccio/monorepo/compare/v9.6.1...v9.7.0) (2020-06-24) + + +### Features + +* types for https config ([#368](https://github.com/verdaccio/monorepo/issues/368)) ([aa4aa83](https://github.com/verdaccio/monorepo/commit/aa4aa83e8a2f6a29ebe7c0b43ccc560a37fe2da9)) + + + + + +# [9.5.0](https://github.com/verdaccio/monorepo/compare/v9.4.1...v9.5.0) (2020-05-02) + + +### Features + +* **types:** custom favicon ([#356](https://github.com/verdaccio/monorepo/issues/356)) ([bd78861](https://github.com/verdaccio/monorepo/commit/bd78861f46cd5189808b6689d2018a7bac6755f7)) + + + + + +# [9.3.0](https://github.com/verdaccio/monorepo/compare/v9.2.0...v9.3.0) (2020-01-29) + + +### Features + +* **types:** adding tag type for auth plugins ([#318](https://github.com/verdaccio/monorepo/issues/318)) ([7f07c94](https://github.com/verdaccio/monorepo/commit/7f07c94d9dba5ac45b35aef3bd1ffd3080fb35db)) + + + + + +# [9.0.0](https://github.com/verdaccio/monorepo/compare/v8.5.3...v9.0.0) (2020-01-07) + +**Note:** Version bump only for package @verdaccio/types + + + + + +## [8.5.2](https://github.com/verdaccio/monorepo/compare/v8.5.1...v8.5.2) (2019-12-25) + + +### Bug Fixes + +* add types for storage handler ([#307](https://github.com/verdaccio/monorepo/issues/307)) ([c35746e](https://github.com/verdaccio/monorepo/commit/c35746ebba071900db172608dedff66a7d27c23d)) + + + + + +## [8.5.1](https://github.com/verdaccio/monorepo/compare/v8.5.0...v8.5.1) (2019-12-24) + + +### Bug Fixes + +* add new types for local storage ([#306](https://github.com/verdaccio/monorepo/issues/306)) ([e715e24](https://github.com/verdaccio/monorepo/commit/e715e24ec7b7e7b3dca31a3321714ebccadf2a8d)) + + + + + +# [8.5.0](https://github.com/verdaccio/monorepo/compare/v8.4.2...v8.5.0) (2019-12-22) + + +### Bug Fixes + +* **types:** add allow_unpublish generic ([#305](https://github.com/verdaccio/monorepo/issues/305)) ([aeaf64c](https://github.com/verdaccio/monorepo/commit/aeaf64c67cafb9ec16fa5a66aad9c4912f2a3710)) + + + + + +## [8.4.2](https://github.com/verdaccio/monorepo/compare/v8.4.1...v8.4.2) (2019-11-23) + +**Note:** Version bump only for package @verdaccio/types + + + + + +## [8.4.1](https://github.com/verdaccio/monorepo/compare/v8.4.0...v8.4.1) (2019-11-22) + +**Note:** Version bump only for package @verdaccio/types + + + + + +# [8.4.0](https://github.com/verdaccio/monorepo/compare/v8.3.0...v8.4.0) (2019-11-22) + + +### Bug Fixes + +* adds sort_packages in WebConf Interface ([#227](https://github.com/verdaccio/monorepo/issues/227)) ([5b60ade](https://github.com/verdaccio/monorepo/commit/5b60adef5da49d7d1b62aa9f484b27c9fa319bdd)) + + + + + +# [8.3.0](https://github.com/verdaccio/monorepo/compare/v8.2.0...v8.3.0) (2019-10-27) + + +### Features + +* improve auth callback TS types ([#225](https://github.com/verdaccio/monorepo/issues/225)) ([ee442a0](https://github.com/verdaccio/monorepo/commit/ee442a0)) + + + + + +# [8.1.0](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.1...v8.1.0) (2019-09-07) + +**Note:** Version bump only for package @verdaccio/types + + + + + +## [8.0.1-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.1-next.0...v8.0.1-next.1) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/types + + + + + +## [8.0.1-next.0](https://github.com/verdaccio/monorepo/compare/v8.0.0...v8.0.1-next.0) (2019-08-29) + +**Note:** Version bump only for package @verdaccio/types + + + + + +# [8.0.0](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.4...v8.0.0) (2019-08-22) + +**Note:** Version bump only for package @verdaccio/types + + + + + +# [8.0.0-next.4](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.3...v8.0.0-next.4) (2019-08-18) + +**Note:** Version bump only for package @verdaccio/types + + + + + +# [8.0.0-next.2](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.1...v8.0.0-next.2) (2019-08-03) + + +### Bug Fixes + +* update types for tokens ([9734fa8](https://github.com/verdaccio/monorepo/commit/9734fa8)) + + + + + +# [8.0.0-next.1](https://github.com/verdaccio/monorepo/compare/v8.0.0-next.0...v8.0.0-next.1) (2019-08-01) + +**Note:** Version bump only for package @verdaccio/types + + + + + +# [8.0.0-next.0](https://github.com/verdaccio/monorepo/compare/v2.0.0...v8.0.0-next.0) (2019-08-01) + + +### Bug Fixes + +* add _autogenerated to UpLinkConf ([436bd91](https://github.com/verdaccio/monorepo/commit/436bd91)) +* add config prop to IBasicAuth ([2481d6f](https://github.com/verdaccio/monorepo/commit/2481d6f)) +* add missing adduser method ([22cdb4e](https://github.com/verdaccio/monorepo/commit/22cdb4e)) +* add missing properties ([973c5e4](https://github.com/verdaccio/monorepo/commit/973c5e4)) +* allow extend config ([0aea94f](https://github.com/verdaccio/monorepo/commit/0aea94f)) +* allow sub types on allow auth methods ([7325f74](https://github.com/verdaccio/monorepo/commit/7325f74)) +* deprecated methods are optional ([b77155a](https://github.com/verdaccio/monorepo/commit/b77155a)) +* entry point [#14](https://github.com/verdaccio/monorepo/issues/14) ([7575e75](https://github.com/verdaccio/monorepo/commit/7575e75)) +* export Author type ([bf7115b](https://github.com/verdaccio/monorepo/commit/bf7115b)) +* fix/token i local package manager ([#61](https://github.com/verdaccio/monorepo/issues/61)) ([a7e0fc8](https://github.com/verdaccio/monorepo/commit/a7e0fc8)) +* fixes for storage plugin types per code review ([#59](https://github.com/verdaccio/monorepo/issues/59)) ([04fccb8](https://github.com/verdaccio/monorepo/commit/04fccb8)) +* getPackageStorage allowed to return undefined ([8a859d0](https://github.com/verdaccio/monorepo/commit/8a859d0)) +* improvements config interface ([1dac321](https://github.com/verdaccio/monorepo/commit/1dac321)) +* methods return Stream ([22e0672](https://github.com/verdaccio/monorepo/commit/22e0672)) +* remove options from get package metadata ([2bfc048](https://github.com/verdaccio/monorepo/commit/2bfc048)) +* remove wrong definition ([acba624](https://github.com/verdaccio/monorepo/commit/acba624)) +* remove wrong imports ([c82f51c](https://github.com/verdaccio/monorepo/commit/c82f51c)) +* restore missing type on RemoteUser ([b596896](https://github.com/verdaccio/monorepo/commit/b596896)) +* storage types ([1285675](https://github.com/verdaccio/monorepo/commit/1285675)) +* tokens are accesible also in local-storage ([08b342d](https://github.com/verdaccio/monorepo/commit/08b342d)) +* update https ([c93c3fc](https://github.com/verdaccio/monorepo/commit/c93c3fc)) +* update readTarball with right parameters ([8cbc7d1](https://github.com/verdaccio/monorepo/commit/8cbc7d1)) +* update streams type ([7fa7be5](https://github.com/verdaccio/monorepo/commit/7fa7be5)) +* update types for local data ([6706770](https://github.com/verdaccio/monorepo/commit/6706770)) +* update utils types ([7c37133](https://github.com/verdaccio/monorepo/commit/7c37133)) +* wrong signature for auth plugin ([e3e2508](https://github.com/verdaccio/monorepo/commit/e3e2508)) + + +### Features + +* add AuthPluginPackage type ([f0e1cea](https://github.com/verdaccio/monorepo/commit/f0e1cea)) +* add callback to database methods ([d0d55e9](https://github.com/verdaccio/monorepo/commit/d0d55e9)) +* add config file types ([188a3e5](https://github.com/verdaccio/monorepo/commit/188a3e5)) +* add gravatar prop for web config ([b3ac873](https://github.com/verdaccio/monorepo/commit/b3ac873)) +* add interface for middleware and storage plugin ([2b18e22](https://github.com/verdaccio/monorepo/commit/2b18e22)) +* add IStorageManager for middleware plugin ([0ac1cc4](https://github.com/verdaccio/monorepo/commit/0ac1cc4)) +* Add locking library on typings ([7f7ab67](https://github.com/verdaccio/monorepo/commit/7f7ab67)) +* add RemoteUser type ([7d11892](https://github.com/verdaccio/monorepo/commit/7d11892)) +* add search method BREAKING CHANGE: search method must be implemented to allow search functionality ([b6d94e6](https://github.com/verdaccio/monorepo/commit/b6d94e6)) +* add secret gateway methods ([5300147](https://github.com/verdaccio/monorepo/commit/5300147)) +* add Security configuration ([0cdc0dd](https://github.com/verdaccio/monorepo/commit/0cdc0dd)) +* add types for auth plugin ([6378186](https://github.com/verdaccio/monorepo/commit/6378186)) +* add types for PackageUsers ([ad5f917](https://github.com/verdaccio/monorepo/commit/ad5f917)) +* add types for search class ([e23782d](https://github.com/verdaccio/monorepo/commit/e23782d)) +* callback does not return ([fd78bfc](https://github.com/verdaccio/monorepo/commit/fd78bfc)) +* merge changes from 5.x ([5f61009](https://github.com/verdaccio/monorepo/commit/5f61009)) +* package access props are not optional ([61708e2](https://github.com/verdaccio/monorepo/commit/61708e2)) +* remove flow [#70](https://github.com/verdaccio/monorepo/issues/70) ([2218b74](https://github.com/verdaccio/monorepo/commit/2218b74)) +* remove sync method ([f60f81c](https://github.com/verdaccio/monorepo/commit/f60f81c)) +* secret methods are async ([d5eacf5](https://github.com/verdaccio/monorepo/commit/d5eacf5)) +* support for an IPluginStorageFilter ([#58](https://github.com/verdaccio/monorepo/issues/58)) ([eab219e](https://github.com/verdaccio/monorepo/commit/eab219e)) +* token types ([#60](https://github.com/verdaccio/monorepo/issues/60)) @Eomm ([6e74da6](https://github.com/verdaccio/monorepo/commit/6e74da6)) +* **auth:** add method to update password ([e257c3a](https://github.com/verdaccio/monorepo/commit/e257c3a)) +* **storage:** path is not mandatory ([2c42931](https://github.com/verdaccio/monorepo/commit/2c42931)) + + +### BREAKING CHANGES + +* remove flow definitions +* storage needs to add new methods + +* add: token types + +* add: typescripts types +* **auth:** it will affect all auth plugins + + + + + +# Changelog + +All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. + +## [7.0.0](https://github.com/verdaccio/flow-types/compare/v6.2.0...v7.0.0) (2019-07-25) + + +### Bug Fixes + +* add _autogenerated to UpLinkConf ([971e52e](https://github.com/verdaccio/flow-types/commit/971e52e)) +* **IPluginAuth:** adduser, changePassword optional ([40d4e7a](https://github.com/verdaccio/flow-types/commit/40d4e7a)), closes [/github.com/verdaccio/verdaccio/blob/66f4197236d9d71af149314aae15102b336f45e1/src/lib/auth.ts#L67-L71](https://github.com/verdaccio/flow-types/issues/L67-L71) [/github.com/verdaccio/verdaccio/blob/66f4197236d9d71af149314aae15102b336f45e1/src/lib/auth.ts#L138-L166](https://github.com/verdaccio/flow-types/issues/L138-L166) +* **IPluginAuth:** remove `login_url` ([1663c07](https://github.com/verdaccio/flow-types/commit/1663c07)), closes [#69](https://github.com/verdaccio/flow-types/issues/69) + + +### Features + +* merge changes from 5.x ([7d2d526](https://github.com/verdaccio/flow-types/commit/7d2d526)) +* remove flow [#70](https://github.com/verdaccio/flow-types/issues/70) ([826d5fe](https://github.com/verdaccio/flow-types/commit/826d5fe)) + + +### BREAKING CHANGES + +* remove flow definitions + + + + +# [6.2.0](https://github.com/verdaccio/flow-types/compare/v6.1.0...v6.2.0) (2019-05-28) + + +### Features + +* add types for Version ([900861d](https://github.com/verdaccio/flow-types/commit/900861d)) + + + + +# [6.1.0](https://github.com/verdaccio/flow-types/compare/v6.0.2...v6.1.0) (2019-05-14) + + +### Bug Fixes + +* revert token signature ([59a03e7](https://github.com/verdaccio/flow-types/commit/59a03e7)) + + +### Features + +* moved token api signature ([e51991c](https://github.com/verdaccio/flow-types/commit/e51991c)) + + + + +## [6.0.2](https://github.com/verdaccio/flow-types/compare/v6.0.1...v6.0.2) (2019-05-06) + + +### Bug Fixes + +* fix/token i local package manager ([#61](https://github.com/verdaccio/flow-types/issues/61)) ([14adfb2](https://github.com/verdaccio/flow-types/commit/14adfb2)) + + + + +## [6.0.1](https://github.com/verdaccio/flow-types/compare/v6.0.0...v6.0.1) (2019-05-04) + + +### Bug Fixes + +* tokens are accesible also in local-storage ([56551cf](https://github.com/verdaccio/flow-types/commit/56551cf)) + + + + +# [6.0.0](https://github.com/verdaccio/flow-types/compare/v5.0.2...v6.0.0) (2019-04-30) + + +### Bug Fixes + +* remove wrong imports ([a75476a](https://github.com/verdaccio/flow-types/commit/a75476a)) + + +### Features + +* token types ([#60](https://github.com/verdaccio/flow-types/issues/60)) @Eomm ([7b74982](https://github.com/verdaccio/flow-types/commit/7b74982)) + + +### BREAKING CHANGES + +* storage needs to add new methods + +* add: token types + +* add: typescripts types + + + + +## [5.0.2](https://github.com/verdaccio/flow-types/compare/v5.0.1...v5.0.2) (2019-04-22) + + +### Bug Fixes + +* fixes for storage plugin types per code review ([#59](https://github.com/verdaccio/flow-types/issues/59)) ([c2ea90b](https://github.com/verdaccio/flow-types/commit/c2ea90b)) + + + + +## [5.0.1](https://github.com/verdaccio/flow-types/compare/v5.0.0...v5.0.1) (2019-04-18) + + + + +# [5.0.0](https://github.com/verdaccio/flow-types/compare/v5.0.0-beta.4...v5.0.0) (2019-04-18) + + +### Features + +* support for an IPluginStorageFilter ([#58](https://github.com/verdaccio/flow-types/issues/58)) ([e67559f](https://github.com/verdaccio/flow-types/commit/e67559f)) + + + + +# [5.0.0-beta.4](https://github.com/verdaccio/flow-types/compare/v5.0.0-beta.3...v5.0.0-beta.4) (2019-03-29) + + +### Features + +* **storage:** path is not mandatory ([784f1bb](https://github.com/verdaccio/flow-types/commit/784f1bb)) + + + + +# [5.0.0-beta.3](https://github.com/verdaccio/flow-types/compare/v5.0.0-beta.2...v5.0.0-beta.3) (2019-03-09) + + +### Features + +* add types for PackageUsers ([9bb3c26](https://github.com/verdaccio/flow-types/commit/9bb3c26)) + + + + +# [5.0.0-beta.2](https://github.com/verdaccio/flow-types/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2019-02-03) + + +### Features + +* allow_access and allow_publish are optional for auth plugin ([0d5a53c](https://github.com/verdaccio/flow-types/commit/0d5a53c)) + + + + +# [5.0.0-beta.1](https://github.com/verdaccio/flow-types/compare/v5.0.0-beta.0...v5.0.0-beta.1) (2019-02-01) + + + + +# [5.0.0-beta.0](https://github.com/verdaccio/flow-types/compare/v4.3.0...v5.0.0-beta.0) (2019-01-27) + + + + +# [4.3.0](https://github.com/verdaccio/flow-types/compare/v4.2.0...v4.3.0) (2019-01-12) + + +### Features + +* add gravatar prop for web config ([99ceae9](https://github.com/verdaccio/flow-types/commit/99ceae9)) + + + + +# [4.2.0](https://github.com/verdaccio/flow-types/compare/v4.1.2...v4.2.0) (2019-01-12) + + +### Features + +* add AuthPluginPackage type ([0e46b04](https://github.com/verdaccio/flow-types/commit/0e46b04)) + + + + +## [4.1.2](https://github.com/verdaccio/flow-types/compare/v4.1.1...v4.1.2) (2018-11-11) + + +### Bug Fixes + +* remove wrong definition ([9bc53fc](https://github.com/verdaccio/flow-types/commit/9bc53fc)) + + + + +## [4.1.1](https://github.com/verdaccio/flow-types/compare/v4.1.0...v4.1.1) (2018-10-06) + + +### Bug Fixes + +* deprecated methods are optional ([4c96e89](https://github.com/verdaccio/flow-types/commit/4c96e89)) + + + + +# [4.1.0](https://github.com/verdaccio/flow-types/compare/v4.0.0...v4.1.0) (2018-10-06) + + +### Features + +* package access props are not optional ([afabaf1](https://github.com/verdaccio/flow-types/commit/afabaf1)) + + + + +# [4.0.0](https://github.com/verdaccio/flow-types/compare/v3.7.2...v4.0.0) (2018-09-30) + + +### Features + +* **auth:** add method to update password ([21fc43f](https://github.com/verdaccio/flow-types/commit/21fc43f)) + + +### BREAKING CHANGES + +* **auth:** it will affect all auth plugins + + + + +## [3.7.2](https://github.com/verdaccio/flow-types/compare/v3.7.1...v3.7.2) (2018-09-27) + + +### Bug Fixes + +* entry point [#14](https://github.com/verdaccio/flow-types/issues/14) ([f7b8982](https://github.com/verdaccio/flow-types/commit/f7b8982)) +* export Author type ([7869dde](https://github.com/verdaccio/flow-types/commit/7869dde)) + + + + +## [3.7.1](https://github.com/verdaccio/flow-types/compare/v3.7.0...v3.7.1) (2018-08-11) + + +### Bug Fixes + +* restore missing type on RemoteUser ([88d809e](https://github.com/verdaccio/flow-types/commit/88d809e)) + + + + +# [3.7.0](https://github.com/verdaccio/flow-types/compare/v3.6.0...v3.7.0) (2018-08-05) + + +### Features + +* add Security configuration ([0d9aece](https://github.com/verdaccio/flow-types/commit/0d9aece)) + + + + +# [3.6.0](https://github.com/verdaccio/flow-types/compare/v3.5.1...v3.6.0) (2018-07-30) + + +### Features + +* changes max_users type to number ([1fa6e73](https://github.com/verdaccio/flow-types/commit/1fa6e73)) + + + + +## [3.5.1](https://github.com/verdaccio/flow-types/compare/v3.5.0...v3.5.1) (2018-07-21) + + +### Bug Fixes + +* login_url should be an optional property ([0fcfb9c](https://github.com/verdaccio/flow-types/commit/0fcfb9c)) + + + + +# [3.5.0](https://github.com/verdaccio/flow-types/compare/v3.4.3...v3.5.0) (2018-07-21) + + +### Features + +* add `login_url` to verdaccio$IPluginAuth ([6e03209](https://github.com/verdaccio/flow-types/commit/6e03209)), closes [verdaccio/verdaccio#834](https://github.com/verdaccio/verdaccio/issues/834) + + + + +## [3.4.3](https://github.com/verdaccio/flow-types/compare/v3.4.2...v3.4.3) (2018-07-19) + + +### Bug Fixes + +* allow extend config ([06e810f](https://github.com/verdaccio/flow-types/commit/06e810f)) + + + + +## [3.4.2](https://github.com/verdaccio/flow-types/compare/v3.4.1...v3.4.2) (2018-07-17) + + + + +## [3.4.1](https://github.com/verdaccio/flow-types/compare/v3.4.0...v3.4.1) (2018-07-16) + + +### Bug Fixes + +* allow sub types on allow auth methods ([fa8125b](https://github.com/verdaccio/flow-types/commit/fa8125b)) + + + + +# [3.4.0](https://github.com/verdaccio/flow-types/compare/v3.3.3...v3.4.0) (2018-07-16) + + +### Features + +* add RemoteUser type ([aa83839](https://github.com/verdaccio/flow-types/commit/aa83839)) + + + + +## [3.3.3](https://github.com/verdaccio/flow-types/compare/v3.3.2...v3.3.3) (2018-07-16) + + +### Bug Fixes + +* wrong signature for auth plugin ([11a0ce6](https://github.com/verdaccio/flow-types/commit/11a0ce6)) + + + + +## [3.3.2](https://github.com/verdaccio/flow-types/compare/v3.3.1...v3.3.2) (2018-07-16) + + +### Bug Fixes + +* add missing adduser method ([0b54fe7](https://github.com/verdaccio/flow-types/commit/0b54fe7)) + + + + +## [3.3.1](https://github.com/verdaccio/flow-types/compare/v3.3.0...v3.3.1) (2018-07-15) + + +### Bug Fixes + +* add config prop to IBasicAuth ([0714316](https://github.com/verdaccio/flow-types/commit/0714316)) + + + + +# [3.3.0](https://github.com/verdaccio/flow-types/compare/v3.2.0...v3.3.0) (2018-07-15) + + +### Features + +* add IStorageManager for middleware plugin ([d473b4c](https://github.com/verdaccio/flow-types/commit/d473b4c)) + + + + +# [3.2.0](https://github.com/verdaccio/flow-types/compare/v3.1.0...v3.2.0) (2018-07-15) + + +### Features + +* add interface for middleware and storage plugin ([0028085](https://github.com/verdaccio/flow-types/commit/0028085)) + + + + +# [3.1.0](https://github.com/verdaccio/flow-types/compare/v3.0.1...v3.1.0) (2018-07-14) + + +### Features + +* add types for auth plugin ([a9b7bc9](https://github.com/verdaccio/flow-types/commit/a9b7bc9)) + + + + +## [3.0.1](https://github.com/verdaccio/flow-types/compare/v3.0.0...v3.0.1) (2018-07-02) + + +### Bug Fixes + +* improvements config interface ([8ea6276](https://github.com/verdaccio/flow-types/commit/8ea6276)) + + + + +# [3.0.0](https://github.com/verdaccio/flow-types/compare/v2.2.2...v3.0.0) (2018-06-08) + + +### Features + +* add search method ([2cf3ce9](https://github.com/verdaccio/flow-types/commit/2cf3ce9)) + + +### BREAKING CHANGES + +* search method must be implemented to allow search functionality diff --git a/packages/core/types/LICENSE b/packages/core/types/LICENSE new file mode 100644 index 000000000..65fb12e2a --- /dev/null +++ b/packages/core/types/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Verdaccio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/types/README.md b/packages/core/types/README.md new file mode 100644 index 000000000..21b7141e3 --- /dev/null +++ b/packages/core/types/README.md @@ -0,0 +1,48 @@ +# Typescript types for Verdaccio + +Typescript definitions for verdaccio plugins and internal code + +# Typescript +For usage with the library, the `tsconfig.json` should looks like this. + +``` +//tsconfig.json +{ + "compilerOptions": { + "target": "esnext", + "module": "commonjs", + "declaration": true, + "noImplicitAny": false, + "strict": true, + "outDir": "lib", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "typeRoots": [ + "./node_modules/@verdaccio/types/lib/verdaccio", + "./node_modules/@types" + ] + }, + "include": [ + "src/*.ts", + "types/*.d.ts" + ] +} +``` + +### Imports + +``` +import type {ILocalData, LocalStorage, Logger, Config} from '@verdaccio/types'; + + class LocalData implements ILocalData { + + path: string; + logger: Logger; + data: LocalStorage; + config: Config; + locked: boolean; + ... +} +``` + + diff --git a/packages/core/types/package.json b/packages/core/types/package.json new file mode 100644 index 000000000..8e9ec54c0 --- /dev/null +++ b/packages/core/types/package.json @@ -0,0 +1,34 @@ +{ + "name": "@verdaccio/types", + "version": "10.0.0-beta", + "description": "verdaccio types definitions", + "keywords": [ + "verdaccio", + "typescript", + "types" + ], + "author": "Juan Picado ", + "license": "MIT", + "homepage": "https://verdaccio.org", + "repository": { + "type": "git", + "url": "https://github.com/verdaccio/monorepo", + "directory": "core/types" + }, + "bugs": { + "url": "https://github.com/verdaccio/monorepo/issues" + }, + "publishConfig": { + "access": "public" + }, + "main": "lib/index.js", + "typings": "lib/index.d.ts", + "scripts": { + "test": "tsc --noEmit", + "build": "rimraf lib tsconfig.tsbuildinfo && tsc --build" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio" + } +} diff --git a/packages/core/types/src/index.ts b/packages/core/types/src/index.ts new file mode 100644 index 000000000..698b29a94 --- /dev/null +++ b/packages/core/types/src/index.ts @@ -0,0 +1,533 @@ +import { PassThrough } from 'stream'; + +type StringValue = string | void | null; + +type StorageList = string[]; +type Callback = Function; +// FIXME: err should be something flexible enough for any implementation +type CallbackAction = (err: any | null) => void; +type CallbackError = (err: NodeJS.ErrnoException) => void; +interface Author { + name: string; + email?: string; + url?: string; +} + +interface Dist { + integrity?: string; + shasum: string; + tarball: string; +} + +interface RemoteUser { + real_groups: string[]; + groups: string[]; + name: string | void; + error?: string; +} + +interface LocalStorage { + list: any; + secret: string; +} + +interface Version { + name: string; + version: string; + devDependencies?: string; + directories?: any; + dist: Dist; + author: string | Author; + main: string; + homemage?: string; + license?: string; + readme: string; + readmeFileName?: string; + readmeFilename?: string; + description: string; + bin?: string; + bugs?: any; + files?: string[]; + gitHead?: string; + maintainers?: Author[]; + contributors?: Author[]; + repository?: string | any; + scripts?: any; + homepage?: string; + etag?: string; + dependencies: any; + keywords?: string | string[]; + nodeVersion?: string; + _id: string; + _npmVersion?: string; + _npmUser: Author; + _hasShrinkwrap?: boolean; + deprecated?: string; +} + +interface Logger { + child: (conf: any) => any; + debug: (conf: any, template?: string) => void; + error: (conf: any, template?: string) => void; + http: (conf: any, template?: string) => void; + trace: (conf: any, template?: string) => void; + warn: (conf: any, template?: string) => void; + info: (conf: any, template?: string) => void; +} + +interface Versions { + [key: string]: Version; +} + +interface DistFile { + url: string; + sha: string; + registry?: string; +} + +interface MergeTags { + [key: string]: string; +} + +interface DistFiles { + [key: string]: DistFile; +} + +interface AttachMents { + [key: string]: AttachMentsItem; +} + +interface AttachMentsItem { + content_type?: string; + data?: string; + length?: number; + shasum?: string; + version?: string; +} + +interface GenericBody { + [key: string]: string; +} + +interface UpLinkMetadata { + etag: string; + fetched: number; +} + +interface UpLinks { + [key: string]: UpLinkMetadata; +} + +interface Tags { + [key: string]: Version; +} + +interface Headers { + [key: string]: string; +} + +interface PackageUsers { + [key: string]: boolean; +} + +interface Package { + _id?: string; + name: string; + versions: Versions; + 'dist-tags': GenericBody; + time?: GenericBody; + readme?: string; + users?: PackageUsers; + _distfiles: DistFiles; + _attachments: AttachMents; + _uplinks: UpLinks; + _rev: string; +} + +interface IUploadTarball extends PassThrough { + abort(): void; + done(): void; +} + +interface IReadTarball extends PassThrough { + abort(): void; +} + +interface UpLinkTokenConf { + type: 'Bearer' | 'Basic'; + token?: string; + token_env?: boolean | string; +} + +interface UpLinkConf { + url: string; + ca?: string; + cache?: boolean; + timeout?: string | void; + maxage?: string | void; + max_fails?: number | void; + fail_timeout?: string | void; + headers?: Headers; + auth?: UpLinkTokenConf; + strict_ssl?: boolean | void; + _autogenerated?: boolean; +} + +interface AuthPluginPackage { + packageName: string; + packageVersion?: string; + tag?: string; +} + +interface PackageAccess { + storage?: string; + publish?: string[]; + proxy?: string[]; + access?: string[]; +} + +interface PackageList { + [key: string]: PackageAccess; +} + +interface UpLinksConfList { + [key: string]: UpLinkConf; +} + +type LoggerType = 'stdout' | 'stderr' | 'file'; +type LoggerFormat = 'pretty' | 'pretty-timestamped' | 'file'; +type LoggerLevel = 'http' | 'fatal' | 'warn' | 'info' | 'debug' | 'trace'; + +interface LoggerConfItem { + type: LoggerType; + format: LoggerFormat; + level: LoggerLevel; +} + +interface PublishOptions { + allow_offline: boolean; +} + +type AuthConf = any | AuthHtpasswd; + +interface AuthHtpasswd { + file: string; + max_users: number; +} + +interface Notifications { + method: string; + packagePattern: RegExp; + packagePatternFlags: string; + endpoint: string; + content: string; + headers: Headers; +} + +interface ConfigFile { + storage: string; + plugins: string; + self_path: string; + packages: PackageList; + uplinks: UpLinksConfList; + logs: LoggerConf[]; + web: WebConf; + auth: AuthConf; + publish?: PublishOptions; + url_prefix?: string; + listen?: ListenAddress; + https?: HttpsConf; + http_proxy?: string; + https_proxy?: string; + no_proxy?: string; + max_body_size?: string; + notifications: Notifications; +} + +interface Token { + user: string; + token: string; + key: string; + cidr?: string[]; + readonly: boolean; + created: number | string; + updated?: number | string; +} + +interface TokenFilter { + user: string; +} + +type SyncReturn = Error | void; +type IPackageStorage = ILocalPackageManager | void; +type IPackageStorageManager = ILocalPackageManager; +type IPluginStorage = ILocalData; + +interface AuthHtpasswd { + file: string; + max_users: number; +} + +interface ILocalStorage { + add(name: string): void; + remove(name: string): void; + get(): StorageList; + sync(): void; +} + +interface LoggerConf { + [key: string]: LoggerConfItem; +} + +interface ListenAddress { + [key: string]: string; +} + +interface WebConf { + enable?: boolean; + title?: string; + logo?: string; + favicon?: string; + gravatar?: boolean; + sort_packages?: string; +} + +interface HttpsConfKeyCert { + key: string; + cert: string; + ca?: string; +} + +interface HttpsConfPfx { + pfx: string; + passphrase?: string; +} + +type HttpsConf = HttpsConfKeyCert | HttpsConfPfx; + +interface JWTOptions { + sign: JWTSignOptions; + verify: JWTVerifyOptions; +} + +interface JWTVerifyOptions { + algorithm?: string; + expiresIn?: string; + notBefore?: string | number; + ignoreExpiration?: boolean; + maxAge?: string | number; + clockTimestamp?: number; +} + +interface JWTSignOptions { + algorithm?: string; + expiresIn?: string; + notBefore?: string; + ignoreExpiration?: boolean; + maxAge?: string | number; + clockTimestamp?: number; +} + +interface APITokenOptions { + legacy: boolean; + jwt?: JWTOptions; +} + +interface Security { + web: JWTOptions; + api: APITokenOptions; +} + +interface Config { + user_agent: string; + server_id: any; + _debug?: boolean; + storage?: string | void; + plugins?: string | void; + secret: string; + self_path: string; + packages: PackageList; + uplinks: UpLinksConfList; + logs?: LoggerConf[]; + web?: WebConf; + auth?: AuthConf; + security: Security; + publish?: PublishOptions; + url_prefix?: string; + store?: any; + listen?: ListenAddress; + https?: HttpsConf; + http_proxy?: string; + https_proxy?: string; + no_proxy?: string; + max_body_size?: string; + notifications?: Notifications; + middlewares?: any; + filters?: any; + checkSecretKey(token: string): string; + getMatchedPackagesSpec(storage: string): PackageAccess | void; + [key: string]: any; +} + +interface ConfigWithHttps extends Config { + https: HttpsConf; +} + +interface ITokenActions { + saveToken(token: Token): Promise; + deleteToken(user: string, tokenKey: string): Promise; + readTokens(filter: TokenFilter): Promise; +} + +/** + * This method expect return a Package object + * eg: + * { + * name: string; + * time: number; + * ... and other props + * } + * + * The `cb` callback object will be executed if: + * - it might return object (truly) + * - it might reutrn null + */ +type onSearchPackage = (item: Package, cb: CallbackAction) => void; +// FIXME: error should be export type `VerdaccioError = HttpError & { code: number };` +// but this type is on @verdaccio/commons-api and cannot be used here yet +type onEndSearchPackage = (error?: any) => void; +type onValidatePackage = (name: string) => boolean; + +interface ILocalData extends IPlugin, ITokenActions { + logger: Logger; + config: T & Config; + add(name: string, callback: Callback): void; + remove(name: string, callback: Callback): void; + get(callback: Callback): void; + getSecret(): Promise; + setSecret(secret: string): Promise; + getPackageStorage(packageInfo: string): IPackageStorage; + search(onPackage: onSearchPackage, onEnd: onEndSearchPackage, validateName: onValidatePackage): void; +} + +type StorageUpdateCallback = (data: Package, cb: CallbackAction) => void; +type StorageUpdateHandler = (name: string, cb: StorageUpdateCallback) => void; +type StorageWriteCallback = (name: string, json: Package, callback: Callback) => void; +type PackageTransformer = (pkg: Package) => Package; +type ReadPackageCallback = (err: any | null, data?: Package) => void; + +interface ILocalPackageManager { + logger: Logger; + writeTarball(pkgName: string): IUploadTarball; + readTarball(pkgName: string): IReadTarball; + readPackage(fileName: string, callback: ReadPackageCallback): void; + createPackage(pkgName: string, value: Package, cb: CallbackAction): void; + deletePackage(fileName: string, callback: CallbackAction): void; + removePackage(callback: CallbackAction): void; + updatePackage( + pkgFileName: string, + updateHandler: StorageUpdateCallback, + onWrite: StorageWriteCallback, + transformPackage: PackageTransformer, + onEnd: CallbackAction + ): void; + savePackage(fileName: string, json: Package, callback: CallbackAction): void; +} + +interface TarballActions { + addTarball(name: string, filename: string): IUploadTarball; + getTarball(name: string, filename: string): IReadTarball; + removeTarball(name: string, filename: string, revision: string, callback: Callback): void; +} + +interface StoragePackageActions extends TarballActions { + addVersion(name: string, version: string, metadata: Version, tag: StringValue, callback: Callback): void; + mergeTags(name: string, tags: MergeTags, callback: Callback): void; + removePackage(name: string, callback: Callback): void; + changePackage(name: string, metadata: Package, revision: string, callback: Callback): void; +} + +interface IStorageManager extends StoragePackageActions { + config: T & Config; + logger: Logger; + init(config: T & Config, filters: any): Promise; + addPackage(name: string, metadata: any, callback: Callback): Promise; + getPackage(options: any): void; + search(startkey: string, options: any): IReadTarball; + getLocalDatabase(callback: Callback): void; +} + +interface IBasicStorage extends StoragePackageActions { + addPackage(name: string, info: Package, callback: Callback): void; + updateVersions(name: string, packageInfo: Package, callback: Callback): void; + getPackageMetadata(name: string, callback: Callback): void; + search(startKey: string, options: any): IReadTarball; + getSecret(config: T & Config): Promise; +} + +interface IBasicAuth { + config: T & Config; + aesEncrypt(buf: Buffer): Buffer; + authenticate(user: string, password: string, cb: Callback): void; + changePassword(user: string, password: string, newPassword: string, cb: Callback): void; + allow_access(pkg: AuthPluginPackage, user: RemoteUser, callback: Callback): void; + add_user(user: string, password: string, cb: Callback): any; +} + +export interface Plugin { + new (config: T, options: PluginOptions): T; +} + +interface IPlugin { + version?: string; + // In case a plugin needs to be cleaned up/removed + close?(): void; +} + +interface PluginOptions { + config: T & Config; + logger: Logger; +} + +interface AllowAccess { + name: string; + version?: string; + tag?: string; +} + +// FIXME: error should be export type `VerdaccioError = HttpError & { code: number };` instead of AuthError +// but this type is on @verdaccio/commons-api and cannot be used here yet (I don't know why) +interface HttpError extends Error { + status: number; + statusCode: number; + expose: boolean; + headers?: { + [key: string]: string; + }; + [key: string]: any; +} + +type AuthError = HttpError & { code: number }; +type AuthAccessCallback = (error: AuthError | null, access: boolean) => void; +type AuthCallback = (error: AuthError | null, groups: string[] | false) => void; + +interface IPluginAuth extends IPlugin { + authenticate(user: string, password: string, cb: AuthCallback): void; + adduser?(user: string, password: string, cb: AuthCallback): void; + changePassword?(user: string, password: string, newPassword: string, cb: AuthCallback): void; + allow_publish?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void; + allow_access?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void; + allow_unpublish?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void; + allow_publish?(user: RemoteUser, pkg: AllowAccess & PackageAccess, cb: AuthAccessCallback): void; + allow_access?(user: RemoteUser, pkg: AllowAccess & PackageAccess, cb: AuthAccessCallback): void; + allow_unpublish?(user: RemoteUser, pkg: AllowAccess & PackageAccess, cb: AuthAccessCallback): void; + apiJWTmiddleware?(helpers: any): Function; +} + +interface IPluginMiddleware extends IPlugin { + register_middlewares(app: any, auth: IBasicAuth, storage: IStorageManager): void; +} + +interface IPluginStorageFilter extends IPlugin { + filter_metadata(packageInfo: Package): Promise; +} diff --git a/packages/core/types/tsconfig.json b/packages/core/types/tsconfig.json new file mode 100644 index 000000000..537522270 --- /dev/null +++ b/packages/core/types/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/proxy/package.json b/packages/proxy/package.json index b6e8522d0..7b320d0da 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -24,9 +24,9 @@ }, "dependencies": { "@verdaccio/dev-commons": "5.0.0-alpha.0", - "@verdaccio/local-storage": "9.6.1", "@verdaccio/logger": "5.0.0-alpha.0", - "@verdaccio/streams": "9.6.1", + "@verdaccio/local-storage": "workspace:*", + "@verdaccio/streams": "workspace:*", "@verdaccio/utils": "5.0.0-alpha.0", "JSONStream": "1.3.5", "request": "2.87.0" diff --git a/packages/store/package.json b/packages/store/package.json index c9a8402e8..3b7a47972 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -19,7 +19,7 @@ "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:js": "cross-env babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", "build": "pnpm run build:js && pnpm run build:types" }, "dependencies": { @@ -29,11 +29,12 @@ "@verdaccio/local-storage": "9.6.1", "@verdaccio/logger": "5.0.0-alpha.0", "@verdaccio/proxy": "5.0.0-alpha.0", - "@verdaccio/streams": "9.6.1", + "@verdaccio/streams": "workspace:*", "@verdaccio/utils": "5.0.0-alpha.0", "async": "3.1.1", "lunr-mutable-indexes": "^2.3.2", "lodash": "4.17.15", + "debug": "^4.1.1", "semver": "7.1.2" }, "devDependencies": { diff --git a/packages/store/src/local-storage.ts b/packages/store/src/local-storage.ts index d1edc281c..36362a4ac 100644 --- a/packages/store/src/local-storage.ts +++ b/packages/store/src/local-storage.ts @@ -1,6 +1,8 @@ import assert from 'assert'; import UrlNode from 'url'; import _ from 'lodash'; +import buildDebug from 'debug'; + import { ErrorCode, isObject, getLatestVersion, tagVersion, validateName } from '@verdaccio/utils'; import { API_ERROR, DIST_TAGS, HTTP_STATUS, STORAGE, SUPPORT_ERRORS, USERS } from '@verdaccio/dev-commons'; import { createTarballHash } from '@verdaccio/utils'; @@ -40,6 +42,8 @@ import { normalizeContributors, } from './storage-utils'; +const debug = buildDebug('verdaccio:storage:local'); + /** * Implements Storage interface (same for storage.js, local-storage.js, up-storage.js). */ @@ -49,30 +53,34 @@ class LocalStorage implements IStorage { public logger: Logger; public constructor(config: Config, logger: Logger) { + debug('local storage created'); this.logger = logger.child({ sub: 'fs' }); this.config = config; this.storagePlugin = this._loadStorage(config, logger); } public addPackage(name: string, pkg: Package, callback: Callback): void { + debug(`creating a package for`, name); const storage: any = this._getLocalStorage(name); if (_.isNil(storage)) { + debug(`storage is missing for %o package cannot be added`, name); return callback(ErrorCode.getNotFound('this package cannot be added')); } storage.createPackage(name, generatePackageTemplate(name), (err) => { - // FIXME: it will be fixed here https://github.com/verdaccio/verdaccio/pull/1360 - // @ts-ignore if (_.isNull(err) === false && (err.code === STORAGE.FILE_EXIST_ERROR || err.code === HTTP_STATUS.CONFLICT)) { + debug(`error on creating a package for %o with error %o`, name, err.message); return callback(ErrorCode.getConflict()); } const latest = getLatestVersion(pkg); if (_.isNil(latest) === false && pkg.versions[latest]) { + debug('latest version found %o for %o', latest, name); return callback(null, pkg.versions[latest]); } + debug('no latest version found for %o', name); return callback(); }); } @@ -85,7 +93,7 @@ class LocalStorage implements IStorage { */ public removePackage(name: string, callback: Callback): void { const storage: any = this._getLocalStorage(name); - this.logger.debug({ name }, `[storage] removing package @{name}`); + debug(`removing package for %o`, name); if (_.isNil(storage)) { return callback(ErrorCode.getNotFound()); @@ -94,6 +102,7 @@ class LocalStorage implements IStorage { storage.readPackage(name, (err, data: Package): void => { if (_.isNil(err) === false) { if (err.code === STORAGE.NO_SUCH_FILE_ERROR || err.code === HTTP_STATUS.NOT_FOUND) { + debug(`error on not found %o with error %o`, name, err.message); return callback(ErrorCode.getNotFound()); } return callback(err); @@ -104,13 +113,14 @@ class LocalStorage implements IStorage { this.storagePlugin.remove(name, (removeFailed: Error): void => { if (removeFailed) { // This will happen when database is locked - this.logger.debug({ name }, `[storage/removePackage] the database is locked, removed has failed for @{name}`); + debug(`the database is locked, removed has failed for %o`, name); return callback(ErrorCode.getBadData(removeFailed.message)); } storage.deletePackage(STORAGE.PACKAGE_FILE_NAME, (err): void => { if (err) { + debug(`error on delete a package %o with error %o`, name, err.message); return callback(err); } const attachments = Object.keys(data._attachments); @@ -128,6 +138,7 @@ class LocalStorage implements IStorage { * @param {*} callback */ public updateVersions(name: string, packageInfo: Package, callback: Callback): void { + debug(`updating versions for package %o`, name); this._readCreatePackage(name, (err, packageLocalJson): void => { if (err) { return callback(err); @@ -139,6 +150,8 @@ class LocalStorage implements IStorage { if (packageInfo.readme !== packageLocalJson.readme) { change = true; } + + debug('update versions'); for (const versionId in packageInfo.versions) { if (_.isNil(packageLocalJson.versions[versionId])) { let version = packageInfo.versions[versionId]; @@ -146,12 +159,13 @@ class LocalStorage implements IStorage { // we don't keep readme for package versions, // only one readme per package version = cleanUpReadme(version); + debug('clean up readme for %o', versionId); version.contributors = normalizeContributors(version.contributors as Author[]); change = true; packageLocalJson.versions[versionId] = version; - if (version.dist && version.dist.tarball) { + if (version?.dist?.tarball) { const urlObject: any = UrlNode.parse(version.dist.tarball); const filename = urlObject.pathname.replace(/^.*\//, ''); @@ -161,8 +175,6 @@ class LocalStorage implements IStorage { url: version.dist.tarball, sha: version.dist.shasum, }); - /* eslint spaced-comment: 0 */ - // $FlowFixMe const upLink: string = version[Symbol.for('__verdaccio_uplink')]; if (_.isNil(upLink) === false) { @@ -173,6 +185,7 @@ class LocalStorage implements IStorage { } } + debug('update dist-tags'); for (const tag in packageInfo[DIST_TAGS]) { if (!packageLocalJson[DIST_TAGS][tag] || packageLocalJson[DIST_TAGS][tag] !== packageInfo[DIST_TAGS][tag]) { change = true; @@ -194,6 +207,7 @@ class LocalStorage implements IStorage { } } + debug('update time'); if ('time' in packageInfo && !_.isEqual(packageLocalJson.time, packageInfo.time)) { packageLocalJson.time = packageInfo.time; change = true; @@ -219,6 +233,7 @@ class LocalStorage implements IStorage { * @param {*} callback */ public addVersion(name: string, version: string, metadata: Version, tag: StringValue, callback: CallbackAction): void { + debug(`add version package for`, name); this._updatePackage( name, (data, cb: Callback): void => { @@ -286,6 +301,7 @@ class LocalStorage implements IStorage { * @param {*} callback */ public mergeTags(pkgName: string, tags: MergeTags, callback: CallbackAction): void { + debug(`merge tags for`, pkgName); this._updatePackage( pkgName, (data, cb): void => { @@ -337,12 +353,13 @@ class LocalStorage implements IStorage { * @return {Function} */ public changePackage(name: string, incomingPkg: Package, revision: string | void, callback: Callback): void { + debug(`change package tags for %o revision`, name); if (!isObject(incomingPkg.versions) || !isObject(incomingPkg[DIST_TAGS])) { - this.logger.debug({ name }, `changePackage bad data for @{name}`); + debug(`change package bad data for %o`, name); return callback(ErrorCode.getBadData()); } - this.logger.debug({ name }, `changePackage udapting package for @{name}`); + debug(`change package udapting package for %o`, name); this._updatePackage( name, (localData: Package, cb: CallbackAction): void => { @@ -427,6 +444,7 @@ class LocalStorage implements IStorage { * @return {Stream} */ public addTarball(name: string, filename: string): IUploadTarball { + debug(`add a tarball for %o`, name); assert(validateName(filename)); let length = 0; diff --git a/packages/store/src/storage.ts b/packages/store/src/storage.ts index 084f9f223..7d587b2b6 100644 --- a/packages/store/src/storage.ts +++ b/packages/store/src/storage.ts @@ -2,6 +2,8 @@ import assert from 'assert'; import Stream from 'stream'; import async, { AsyncResultArrayCallback } from 'async'; import _ from 'lodash'; +import buildDebug from 'debug'; + import { ProxyStorage } from '@verdaccio/proxy'; import { API_ERROR, HTTP_STATUS, DIST_TAGS } from '@verdaccio/dev-commons'; import { ReadTarball } from '@verdaccio/streams'; @@ -18,6 +20,8 @@ import { LocalStorage } from './local-storage'; import { mergeVersions } from './metadata-utils'; import { checkPackageLocal, publishPackage, checkPackageRemote, cleanUpLinksRef, mergeUplinkTimeIntoLocal, generatePackageTemplate } from './storage-utils'; +const debug = buildDebug('verdaccio:storage'); + class Storage implements IStorageHandler { public localStorage: IStorage; public config: Config; @@ -28,6 +32,7 @@ class Storage implements IStorageHandler { public constructor(config: Config) { this.config = config; this.uplinks = setupUpLinks(config); + debug('uplinks available %o', this.uplinks); this.logger = logger.child({ module: 'storage' }); this.filters = []; // @ts-ignore @@ -36,6 +41,7 @@ class Storage implements IStorageHandler { public init(config: Config, filters: IPluginFilters = []): Promise { this.filters = filters; + debug('filters available %o', filters); this.localStorage = new LocalStorage(this.config, logger); return this.localStorage.getSecret(config); @@ -49,11 +55,15 @@ class Storage implements IStorageHandler { */ public async addPackage(name: string, metadata: any, callback: Function): Promise { try { + debug('add package for %o', name); await checkPackageLocal(name, this.localStorage); + debug('look up remote for %o', name); await checkPackageRemote(name, this._isAllowPublishOffline(), this._syncUplinksMetadata.bind(this)); + debug('publishing a package for %o', name); await publishPackage(name, metadata, this.localStorage as IStorage); callback(); } catch (err) { + debug('error on add a package for %o with error %o', name, err?.error); callback(err); } } @@ -79,6 +89,7 @@ class Storage implements IStorageHandler { Used storages: local (write) */ public addVersion(name: string, version: string, metadata: Version, tag: StringValue, callback: Callback): void { + debug('add the version %o for package %o', version, name); this.localStorage.addVersion(name, version, metadata, tag, callback); } @@ -87,6 +98,7 @@ class Storage implements IStorageHandler { Used storages: local (write) */ public mergeTags(name: string, tagHash: MergeTags, callback: Callback): void { + debug('merge tags for package %o tags %o', name, tagHash); this.localStorage.mergeTags(name, tagHash, callback); } @@ -96,6 +108,7 @@ class Storage implements IStorageHandler { Used storages: local (write) */ public changePackage(name: string, metadata: Package, revision: string, callback: Callback): void { + debug('change existing package for package %o revision %o', name, revision); this.localStorage.changePackage(name, metadata, revision, callback); } @@ -105,6 +118,7 @@ class Storage implements IStorageHandler { Used storages: local (write) */ public removePackage(name: string, callback: Callback): void { + debug('remove packagefor package %o', name); this.localStorage.removePackage(name, callback); // update the indexer SearchInstance.remove(name); @@ -127,6 +141,7 @@ class Storage implements IStorageHandler { Used storages: local (write) */ public addTarball(name: string, filename: string): IUploadTarball { + debug('add tarball for package %o', name); return this.localStorage.addTarball(name, filename); } @@ -138,6 +153,7 @@ class Storage implements IStorageHandler { Used storages: local || uplink (just one) */ public getTarball(name: string, filename: string): IReadTarball { + debug('get tarball for package %o filename %o', name, filename); const readStream = new ReadTarball({}); readStream.abort = function () {}; @@ -250,7 +266,7 @@ class Storage implements IStorageHandler { }); savestream.on('error', function (err): void { - self.logger.warn({ err: err, fileName: file }, 'error saving file @{fileName}: @{err.message}\n@{err.stack}'); + self.logger.warn({ err: err, fileName: file }, 'error saving file @{fileName}: @{err?.message}\n@{err.stack}'); if (savestream) { savestream.abort(); } @@ -277,18 +293,23 @@ class Storage implements IStorageHandler { * @property {function} options.callback Callback for receive data */ public getPackage(options: IGetPackageOptions): void { - this.localStorage.getPackageMetadata(options.name, (err, data): void => { + const { name } = options; + debug('get package for %o', name); + this.localStorage.getPackageMetadata(name, (err, data): void => { if (err && (!err.status || err.status >= HTTP_STATUS.INTERNAL_ERROR)) { // report internal errors right away + debug('error on get package for %o with error %o', name, err?.message); return options.callback(err); } - this._syncUplinksMetadata(options.name, data, { req: options.req, uplinksLook: options.uplinksLook }, function getPackageSynUpLinksCallback( + debug('sync uplinks for %o', name); + this._syncUplinksMetadata(name, data, { req: options.req, uplinksLook: options.uplinksLook }, function getPackageSynUpLinksCallback( err, result: Package, uplinkErrors ): void { if (err) { + debug('error on sync package for %o with error %o', name, err?.message); return options.callback(err); } @@ -297,6 +318,7 @@ class Storage implements IStorageHandler { // npm can throw if this field doesn't exist result._attachments = {}; + debug('sync uplinks errors %o', uplinkErrors); options.callback(null, result, uplinkErrors); }); }); @@ -331,7 +353,7 @@ class Storage implements IStorageHandler { // join streams lstream.pipe(stream, { end: false }); lstream.on('error', function (err): void { - self.logger.error({ err: err }, 'uplink error: @{err.message}'); + self.logger.error({ err: err }, 'uplink error: @{err?.message}'); cb(); cb = function (): void {}; }); @@ -357,7 +379,7 @@ class Storage implements IStorageHandler { }; lstream.pipe(stream, { end: true }); lstream.on('error', function (err: VerdaccioError): void { - self.logger.error({ err: err }, 'search error: @{err.message}'); + self.logger.error({ err: err }, 'search error: @{err?.message}'); stream.end(); }); } @@ -470,7 +492,7 @@ class Storage implements IStorageHandler { sub: 'out', err: err, }, - 'package.json validating error @{!err.message}\n@{err.stack}' + 'package.json validating error @{!err?.message}\n@{err.stack}' ); return cb(null, [err]); } @@ -492,7 +514,7 @@ class Storage implements IStorageHandler { sub: 'out', err: err, }, - 'package.json parsing error @{!err.message}\n@{err.stack}' + 'package.json parsing error @{!err?.message}\n@{err.stack}' ); return cb(null, [err]); } diff --git a/packages/utils/package.json b/packages/utils/package.json index ee2763198..b8d493691 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -17,7 +17,7 @@ "dependencies": { "@verdaccio/commons-api": "^9.7.1", "@verdaccio/dev-commons": "workspace:5.0.0-alpha.0", - "@verdaccio/readme": "9.6.1", + "@verdaccio/readme": "workspace:*", "js-yaml": "3.13.1", "jsonwebtoken": "8.5.1", "minimatch": "3.0.4", diff --git a/packages/utils/src/utils.ts b/packages/utils/src/utils.ts index 8d50fafc3..8f8c894bc 100644 --- a/packages/utils/src/utils.ts +++ b/packages/utils/src/utils.ts @@ -217,7 +217,6 @@ export function getVersion(pkg: Package, version: any): Version | void { try { version = semver.parse(version, true); for (const versionItem in pkg.versions) { - // $FlowFixMe if (version.compare(semver.parse(versionItem, true)) === 0) { return pkg.versions[versionItem]; } diff --git a/packages/utils/test/__snapshots__/utils.spec.ts.snap b/packages/utils/test/__snapshots__/utils.spec.ts.snap index 4dd089d8a..a39958cf3 100644 --- a/packages/utils/test/__snapshots__/utils.spec.ts.snap +++ b/packages/utils/test/__snapshots__/utils.spec.ts.snap @@ -7,18 +7,23 @@ exports[`Utilities parseReadme should parse makrdown text to html template 1`] =

These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.

Prerequisites

What things you need to install the software and how to install them

-
Give examples

Installing

+
Give examples
+

Installing

A step by step series of examples that tell you how to get a development env running

Say what the step will be

-
Give the example

And repeat

-
until finished

End with an example of getting some data out of the system or using it for a little demo

+
Give the example
+

And repeat

+
until finished
+

End with an example of getting some data out of the system or using it for a little demo

Running the tests

Explain how to run the automated tests for this system

Break down into end to end tests

Explain what these tests test and why

-
Give an example

And coding style tests

+
Give an example
+

And coding style tests

Explain what these tests test and why

-
Give an example

Deployment

+
Give an example
+

Deployment

Add additional notes about how to deploy this on a live system

Built With

    diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9656714b..6891e7434 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -173,6 +173,7 @@ importers: '@verdaccio/store': 'link:../store' '@verdaccio/utils': 'link:../utils' cookies: 0.8.0 + debug: 4.1.1 express: 4.17.1 lodash: 4.17.15 mime: 2.4.4 @@ -182,6 +183,7 @@ importers: '@verdaccio/server': 'link:../server' '@verdaccio/types': 9.5.0 body-parser: 1.19.0 + supertest: 5.0.0-0 specifiers: '@verdaccio/auth': 5.0.0-alpha.0 '@verdaccio/commons-api': 9.6.1 @@ -197,9 +199,11 @@ importers: '@verdaccio/utils': 5.0.0-alpha.0 body-parser: 1.19.0 cookies: 0.8.0 + debug: ^4.1.1 express: 4.17.1 lodash: 4.17.15 mime: 2.4.4 + supertest: next packages/auth: dependencies: '@verdaccio/commons-api': 9.6.1 @@ -261,6 +265,77 @@ importers: '@verdaccio/logger': 'workspace:5.0.0-alpha.0' '@verdaccio/utils': 'workspace:5.0.0-alpha.0' mkdirp: 0.5.5 + packages/core/file-locking: + dependencies: + lockfile: 1.0.4 + devDependencies: + '@verdaccio/types': 'link:../types' + specifiers: + '@verdaccio/types': 'workspace:*' + lockfile: 1.0.4 + packages/core/htpasswd: + dependencies: + '@verdaccio/file-locking': 'link:../file-locking' + apache-md5: 1.1.2 + bcryptjs: 2.4.3 + http-errors: 1.8.0 + unix-crypt-td-js: 1.1.4 + devDependencies: + '@types/bcryptjs': 2.4.2 + '@verdaccio/types': 'link:../types' + specifiers: + '@types/bcryptjs': ^2.4.2 + '@verdaccio/file-locking': 'workspace:*' + '@verdaccio/types': 'workspace:*' + apache-md5: 1.1.2 + bcryptjs: 2.4.3 + http-errors: 1.8.0 + unix-crypt-td-js: 1.1.4 + packages/core/local-storage: + dependencies: + '@verdaccio/commons-api': 9.7.1 + '@verdaccio/file-locking': 'link:../file-locking' + '@verdaccio/streams': 'link:../streams' + async: 3.2.0 + level: 5.0.1 + lodash: 4.17.19 + mkdirp: 0.5.5 + devDependencies: + '@types/minimatch': 3.0.3 + '@verdaccio/types': 'link:../types' + minimatch: 3.0.4 + rmdir-sync: 1.0.1 + specifiers: + '@types/minimatch': ^3.0.3 + '@verdaccio/commons-api': ^9.7.1 + '@verdaccio/file-locking': 'workspace:*' + '@verdaccio/streams': 'workspace:*' + '@verdaccio/types': 'workspace:*' + async: ^3.2.0 + level: 5.0.1 + lodash: ^4.17.19 + minimatch: ^3.0.4 + mkdirp: ^0.5.5 + rmdir-sync: ^1.0.1 + packages/core/readme: + dependencies: + dompurify: 2.0.8 + jsdom: 15.2.1 + marked: 1.1.1 + devDependencies: + '@verdaccio/types': 'link:../types' + specifiers: + '@verdaccio/types': 'workspace:*' + dompurify: 2.0.8 + jsdom: 15.2.1 + marked: 1.1.1 + packages/core/streams: + devDependencies: + '@verdaccio/types': 9.7.2 + specifiers: + '@verdaccio/types': ^9.7.2 + packages/core/types: + specifiers: {} packages/hooks: dependencies: '@verdaccio/commons-api': 9.6.1 @@ -406,9 +481,9 @@ importers: packages/proxy: dependencies: '@verdaccio/dev-commons': 'link:../commons' - '@verdaccio/local-storage': 9.6.1 + '@verdaccio/local-storage': 'link:../core/local-storage' '@verdaccio/logger': 'link:../logger' - '@verdaccio/streams': 9.6.1 + '@verdaccio/streams': 'link:../core/streams' '@verdaccio/utils': 'link:../utils' JSONStream: 1.3.5 request: 2.87.0 @@ -418,9 +493,9 @@ importers: specifiers: '@verdaccio/dev-commons': 5.0.0-alpha.0 '@verdaccio/dev-types': 5.0.0-alpha.0 - '@verdaccio/local-storage': 9.6.1 + '@verdaccio/local-storage': 'workspace:*' '@verdaccio/logger': 5.0.0-alpha.0 - '@verdaccio/streams': 9.6.1 + '@verdaccio/streams': 'workspace:*' '@verdaccio/types': 9.5.0 '@verdaccio/utils': 5.0.0-alpha.0 JSONStream: 1.3.5 @@ -479,9 +554,10 @@ importers: '@verdaccio/local-storage': 9.6.1 '@verdaccio/logger': 'link:../logger' '@verdaccio/proxy': 'link:../proxy' - '@verdaccio/streams': 9.6.1 + '@verdaccio/streams': 'link:../core/streams' '@verdaccio/utils': 'link:../utils' async: 3.1.1 + debug: 4.1.1 lodash: 4.17.15 lunr-mutable-indexes: 2.3.2 semver: 7.1.2 @@ -500,10 +576,11 @@ importers: '@verdaccio/logger': 5.0.0-alpha.0 '@verdaccio/mock': 5.0.0-alpha.0 '@verdaccio/proxy': 5.0.0-alpha.0 - '@verdaccio/streams': 9.6.1 + '@verdaccio/streams': 'workspace:*' '@verdaccio/types': 9.5.0 '@verdaccio/utils': 5.0.0-alpha.0 async: 3.1.1 + debug: ^4.1.1 lodash: 4.17.15 lunr-mutable-indexes: ^2.3.2 semver: 7.1.2 @@ -516,7 +593,7 @@ importers: dependencies: '@verdaccio/commons-api': 9.7.1 '@verdaccio/dev-commons': 'link:../commons' - '@verdaccio/readme': 9.6.1 + '@verdaccio/readme': 'link:../core/readme' js-yaml: 3.13.1 jsonwebtoken: 8.5.1 minimatch: 3.0.4 @@ -532,7 +609,7 @@ importers: '@verdaccio/dev-commons': 'workspace:5.0.0-alpha.0' '@verdaccio/dev-types': 'workspace:5.0.0-alpha.0' '@verdaccio/logger': 'workspace:5.0.0-alpha.0' - '@verdaccio/readme': 9.6.1 + '@verdaccio/readme': 'workspace:*' js-yaml: 3.13.1 jsonwebtoken: 8.5.1 lodash: ^4.17.19 @@ -2328,6 +2405,10 @@ packages: dev: true resolution: integrity: sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ== + /@types/bcryptjs/2.4.2: + dev: true + resolution: + integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ== /@types/body-parser/1.19.0: dependencies: '@types/connect': 3.4.33 @@ -2765,6 +2846,10 @@ packages: dev: true resolution: integrity: sha512-QeijEgVBVRTGyOteIl+RD4aiEbiUM1SkN7UAjZxhwns0o2YebdO+Z2iphmx0EZdo4STnWLuGSRlJexTaU/+lDQ== + /@verdaccio/types/9.7.2: + dev: true + resolution: + integrity: sha512-zv8sMrghtrzkxfo+IOojZYk/j4D5kmF/DMwXS9GnmObTM2nAOTsBzlOxHHBdHaiOK+cntw2YRYQJAebMG5J5sA== /@verdaccio/ui-theme/0.3.13: dev: true engines: @@ -4275,7 +4360,6 @@ packages: /debug/4.1.1: dependencies: ms: 2.1.2 - dev: true resolution: integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== /decamelize-keys/1.1.0: @@ -7563,6 +7647,13 @@ packages: hasBin: true resolution: integrity: sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== + /marked/1.1.1: + dev: false + engines: + node: '>= 8.16.2' + hasBin: true + resolution: + integrity: sha512-mJzT8D2yPxoPh7h0UXkB+dBj4FykPJ2OIfxAWeIHrvoHDkFxukV/29QxoFQoPM6RLEwhIFdJpmKBlqVM3s2ZIw== /matcher/1.1.1: dependencies: escape-string-regexp: 1.0.5 @@ -9184,6 +9275,10 @@ packages: hasBin: true resolution: integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + /rmdir-sync/1.0.1: + dev: true + resolution: + integrity: sha1-8thlTaNcOGzC/zoPE7L6m+vwCQU= /rsvp/4.8.5: dev: true engines: @@ -9852,6 +9947,24 @@ packages: node: '>= 4.0' resolution: integrity: sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== + /superagent/5.1.0: + dependencies: + component-emitter: 1.3.0 + cookiejar: 2.1.2 + debug: 4.1.1 + fast-safe-stringify: 2.0.7 + form-data: 2.5.1 + formidable: 1.2.2 + methods: 1.1.2 + mime: 2.4.6 + qs: 6.9.4 + readable-stream: 3.6.0 + semver: 6.3.0 + dev: true + engines: + node: '>= 6.4.0' + resolution: + integrity: sha512-7V6JVx5N+eTL1MMqRBX0v0bG04UjrjAvvZJTF/VDH/SH2GjSLqlrcYepFlpTrXpm37aSY6h3GGVWGxXl/98TKA== /supertest/4.0.2: dependencies: methods: 1.1.2 @@ -9860,6 +9973,15 @@ packages: node: '>=6.0.0' resolution: integrity: sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ== + /supertest/5.0.0-0: + dependencies: + methods: 1.1.2 + superagent: 5.1.0 + dev: true + engines: + node: '>=6.0.0' + resolution: + integrity: sha512-+XblQKVMblt7kf4BRtK1vezM+Xxq+CWlksy4kmLyqDsN1y89YrDIJ0j/H2CGLMMNk+8k1/bCcGUw8XLs1NYxpg== /supports-color/2.0.0: dev: true engines: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c0687101c..7663483ea 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - packages/* + - packages/core/* - "!**/test/**"