From a3a209b5e2eceb5cd8d9d2f2db046bc6d647e869 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Thu, 18 Aug 2022 21:39:34 +0200 Subject: [PATCH] feat: upgrade to pino 8 with async logging (#3308) --- .changeset/clever-pugs-warn.md | 12 + .gitignore | 3 +- packages/cli/src/commands/init.ts | 3 +- packages/core/core/src/warning-utils.ts | 3 - packages/core/types/README.md | 34 +- packages/core/types/index.d.ts | 694 ------------------ packages/core/types/package.json | 19 +- packages/core/types/src/commons.ts | 37 + packages/core/types/src/configuration.ts | 296 ++++++++ packages/core/types/src/manifest.ts | 217 ++++++ packages/core/types/src/plugins/auth.ts | 22 + packages/core/types/src/plugins/commons.ts | 15 + packages/core/types/src/plugins/filter.ts | 6 + packages/core/types/src/plugins/index.ts | 31 + packages/core/types/src/plugins/middleware.ts | 8 + packages/core/types/src/plugins/storage.ts | 95 +++ packages/core/types/src/plugins/theme.ts | 0 packages/core/types/src/search.ts | 28 + packages/core/types/src/types.ts | 5 + packages/core/types/tsconfig.build.json | 9 + packages/core/types/tsconfig.json | 9 + packages/core/types/typedoc.json | 5 + packages/logger-prettify/jest.config.js | 9 +- packages/logger-prettify/package.json | 10 +- packages/logger-prettify/src/formatter.ts | 18 +- packages/logger-prettify/src/index.ts | 22 +- packages/logger-prettify/src/levels.ts | 1 + packages/logger-prettify/src/prettify.ts | 118 +++ packages/logger-prettify/src/types.ts | 1 + packages/logger-prettify/src/utils.ts | 2 +- .../test/__snapshots__/formatter.spec.ts.snap | 20 +- .../test/__snapshots__/index.spec.ts.snap | 6 - .../logger-prettify/test/formatter.spec.ts | 19 +- packages/logger-prettify/test/index.spec.ts | 38 +- packages/logger-prettify/test/utils.test.ts | 20 + packages/logger/package.json | 7 +- packages/logger/src/index.ts | 2 +- packages/logger/src/logger.ts | 149 ++-- packages/logger/test/createLogger.spec.ts | 20 + packages/logger/test/logger.spec.ts | 144 +++- packages/plugins/audit/src/audit.ts | 2 +- packages/tools/eslint/src/index.js | 1 + packages/tools/mock/package.json | 2 +- pnpm-lock.yaml | 275 +++---- 44 files changed, 1334 insertions(+), 1103 deletions(-) create mode 100644 .changeset/clever-pugs-warn.md delete mode 100644 packages/core/types/index.d.ts create mode 100644 packages/core/types/src/commons.ts create mode 100644 packages/core/types/src/configuration.ts create mode 100644 packages/core/types/src/manifest.ts create mode 100644 packages/core/types/src/plugins/auth.ts create mode 100644 packages/core/types/src/plugins/commons.ts create mode 100644 packages/core/types/src/plugins/filter.ts create mode 100644 packages/core/types/src/plugins/index.ts create mode 100644 packages/core/types/src/plugins/middleware.ts create mode 100644 packages/core/types/src/plugins/storage.ts create mode 100644 packages/core/types/src/plugins/theme.ts create mode 100644 packages/core/types/src/search.ts create mode 100644 packages/core/types/src/types.ts create mode 100644 packages/core/types/tsconfig.build.json create mode 100644 packages/core/types/tsconfig.json create mode 100644 packages/core/types/typedoc.json create mode 100644 packages/logger-prettify/src/prettify.ts delete mode 100644 packages/logger-prettify/test/__snapshots__/index.spec.ts.snap create mode 100644 packages/logger-prettify/test/utils.test.ts create mode 100644 packages/logger/test/createLogger.spec.ts diff --git a/.changeset/clever-pugs-warn.md b/.changeset/clever-pugs-warn.md new file mode 100644 index 000000000..bf590ac28 --- /dev/null +++ b/.changeset/clever-pugs-warn.md @@ -0,0 +1,12 @@ +--- +'@verdaccio/cli': major +'@verdaccio/core': major +'@verdaccio/types': major +'@verdaccio/logger': major +'@verdaccio/logger-prettify': major +'verdaccio-audit': major +'@verdaccio/eslint-config': major +'@verdaccio/mock': major +--- + +feat: migrate to pino.js 8 diff --git a/.gitignore b/.gitignore index c8c6c7338..1019f5a64 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,8 @@ api-results.json hyper-results.json hyper-results*.json api-results*.json +.clinic/ #docs ./api -packages/core/core/docs +**/docs/** diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 46d257430..3c5042cd1 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -2,9 +2,8 @@ import { Command, Option } from 'clipanion'; import { findConfigFile, parseConfigFile } from '@verdaccio/config'; import { logger, setup } from '@verdaccio/logger'; -import { LoggerConfigItem } from '@verdaccio/logger'; import { initServer } from '@verdaccio/node-api'; -import { ConfigYaml } from '@verdaccio/types'; +import { ConfigYaml, LoggerConfigItem } from '@verdaccio/types'; export const DEFAULT_PROCESS_NAME: string = 'verdaccio'; diff --git a/packages/core/core/src/warning-utils.ts b/packages/core/core/src/warning-utils.ts index ad2f7f483..c91f78be8 100644 --- a/packages/core/core/src/warning-utils.ts +++ b/packages/core/core/src/warning-utils.ts @@ -6,7 +6,6 @@ const verdaccioDeprecation = 'VerdaccioDeprecation'; export enum Codes { VERWAR001 = 'VERWAR001', - VERWAR002 = 'VERWAR002', VERWAR003 = 'VERWAR003', VERWAR004 = 'VERWAR004', // deprecation warnings @@ -19,8 +18,6 @@ warningInstance.create( `Verdaccio doesn't need superuser privileges. don't run it under root` ); -warningInstance.create(verdaccioWarning, Codes.VERWAR002, 'logger is not defined'); - warningInstance.create( verdaccioWarning, Codes.VERWAR003, diff --git a/packages/core/types/README.md b/packages/core/types/README.md index cf4bc25cb..ced51f640 100644 --- a/packages/core/types/README.md +++ b/packages/core/types/README.md @@ -1,30 +1,8 @@ -# TypeScript types for Verdaccio +# @verdaccio/types TypeScript definitions for Verdaccio plugins and internal code. -# TypeScript - -For usage with the library, the `tsconfig.json` should looks like this. - -```json5 -// 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 +### Usage ```ts import type {ILocalData, LocalStorage, Logger, Config} from '@verdaccio/types'; @@ -38,3 +16,11 @@ class LocalData implements ILocalData { ... } ``` + +### Run docs + +Generate the package types documentation at `./docs` folder. + +```bash + pnpm build:docs +``` diff --git a/packages/core/types/index.d.ts b/packages/core/types/index.d.ts deleted file mode 100644 index 216e5b1f6..000000000 --- a/packages/core/types/index.d.ts +++ /dev/null @@ -1,694 +0,0 @@ -/// -import { PassThrough, PipelinePromise, Readable, Stream, Writable } from 'stream'; - -declare module '@verdaccio/types' { - 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; - interface Author { - username?: string; - name: string; - email?: string; - url?: string; - } - - type PackageManagers = 'pnpm' | 'yarn' | 'npm'; - - // FUTURE: WebConf and TemplateUIOptions should be merged . - type CommonWebConf = { - title?: string; - logo?: string; - favicon?: string; - gravatar?: boolean; - sort_packages?: string; - darkMode?: boolean; - url_prefix?: string; - language?: string; - login?: boolean; - scope?: string; - pkgManagers?: PackageManagers[]; - }; - - /** - * Options are passed to the index.html - */ - export type TemplateUIOptions = { - uri?: string; - darkMode?: boolean; - protocol?: string; - host?: string; - // deprecated - basename?: string; - scope?: string; - showInfo?: boolean; - showSettings?: boolean; - showSearch?: boolean; - showFooter?: boolean; - showThemeSwitch?: boolean; - showDownloadTarball?: boolean; - showRaw?: boolean; - base: string; - primaryColor?: string; - version?: string; - logoURI?: string; - flags: FlagsConfig; - } & CommonWebConf; - - /** - * Options on config.yaml for web - */ - type WebConf = { - // FIXME: rename to primaryColor and move it to CommonWebConf - primary_color?: string; - enable?: boolean; - scriptsHead?: string[]; - scriptsBodyAfter?: string[]; - metaScripts?: string[]; - bodyBefore?: string[]; - bodyAfter?: string[]; - } & CommonWebConf; - - interface Signatures { - keyid: string; - sig: string; - } - - interface Dist { - 'npm-signature'?: string; - signatures?: Signatures[]; - fileCount?: number; - integrity?: string; - shasum: string; - unpackedSize?: number; - 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 { - data?: string; - content_type?: 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; - } - - /** - * @deprecated use Manifest instead - */ - 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 PublishManifest { - /** - * The `_attachments` object has different usages: - * - * - When a package is published, it contains the tarball as an string, this string is used to be - * converted as a tarball, usually attached to the package but not stored in the database. - * - If user runs `npm star` the _attachments will be at the manifest body but empty. - * - * It has also an internal usage: - * - * - Used as a cache for the tarball, quick access to the tarball shasum, etc. Instead - * iterate versions and find the right one, just using the tarball as a key which is what - * the package manager sends to the registry. - * - * - A `_attachments` object is added every time a private tarball is published, upstream cached tarballs are - * not being part of this object, only for published private packages. - * - * Note: This field is removed when the package is accesed through the web user interface. - * */ - _attachments: AttachMents; - } - - /** - * Represents upstream manifest from another registry - */ - interface FullRemoteManifest { - _id?: string; - _rev?: string; - name: string; - description?: string; - 'dist-tags': GenericBody; - time: GenericBody; - versions: Versions; - maintainers?: Author[]; - /** store the latest readme **/ - readme?: string; - /** store star assigned to this packages by users */ - users?: PackageUsers; - // TODO: not clear what access exactly means - access?: any; - bugs?: { url: string }; - license?: string; - homepage?: string; - repository?: string | { type?: string; url: string; directory?: string }; - keywords?: string[]; - } - - interface Manifest extends FullRemoteManifest, PublishManifest { - // private fields only used by verdaccio - /** - * store fast access to the dist url of an specific tarball, instead search version - * by id, just the tarball id is faster. - * - * The _distfiles is created only when a package is being sync from an upstream. - * also used to fetch tarballs from upstream, the private publish tarballs are not stored in - * this object because they are not published in the upstream registry. - */ - _distfiles: DistFiles; - /** - * Store access cache metadata, to avoid to fetch the same metadata multiple times. - * - * The key represents the uplink id which is composed of a etag and a fetched timestamp. - * - * The fetched timestamp is the time when the metadata was fetched, used to avoid to fetch the - * same metadata until the metadata is older than the last fetch. - */ - _uplinks: UpLinks; - /** - * store the revision of the manifest - */ - _rev: string; - } - - interface 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[]; - unpublish: string[]; - } - - interface PackageAccessYaml { - storage?: string; - publish?: string; - proxy?: string; - access?: string; - unpublish?: string; - } - - // info passed to the auth plugin when a package is package is being published - interface AllowAccess { - name: string; - version?: string; - tag?: string; - } - - interface AuthPackageAllow extends PackageAccess, AllowAccess {} - - interface PackageList { - [key: string]: PackageAccess; - } - - interface PackageListYaml { - [key: string]: PackageAccessYaml; - } - interface UpLinksConfList { - [key: string]: UpLinkConf; - } - - type LoggerType = 'stdout' | 'stderr' | 'file'; - type LoggerFormat = 'pretty' | 'pretty-timestamped' | 'file' | 'json'; - 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; - } - - // FUTURE: rename to Notification - interface Notifications { - method: string; - packagePattern: RegExp; - packagePatternFlags: string; - endpoint: string; - content: string; - headers: Headers; - } - - type Notification = Notifications; - - interface Token { - user: string; - token: string; - key: string; - cidr?: string[]; - readonly: boolean; - created: number | string; - updated?: number | string; - } - - interface TokenFilter { - user: string; - } - - type IPackageStorage = ILocalPackageManager | undefined; - 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 ListenAddress { - [key: string]: 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; - } - - export type FlagsConfig = { - searchRemote?: boolean; - changePassword?: boolean; - }; - - export type RateLimit = { - windowMs: number; - max: number; - }; - - export type ServerSettingsConf = { - // express-rate-limit settings - rateLimit: RateLimit; - keepAliveTimeout?: number; - // force http2 if https is defined - http2?: boolean; - }; - - type URLPrefix = { - // if is false, it would be relative by default - absolute: boolean; - // base path - // eg: absolute: true, https://somedomain.com/xxx/ - // eg: absolute: false, /xxx/ (default) if url_prefix is an string instead an object - basePath: string; - }; - - /** - * YAML configuration file available options. - */ - interface ConfigYaml { - _debug?: boolean; - storage?: string | void; - packages?: PackageListYaml; - uplinks: UpLinksConfList; - // FUTURE: log should be mandatory - log?: LoggerConfItem; - web?: WebConf; - auth?: AuthConf; - security?: Security; - publish?: PublishOptions; - store?: any; - listen?: ListenAddress; - https?: HttpsConf; - http_proxy?: string; - plugins?: string | void; - https_proxy?: string; - no_proxy?: string; - max_body_size?: string; - notifications?: Notifications; - notify?: Notifications | Notifications[]; - middlewares?: any; - filters?: any; - url_prefix?: string; - server?: ServerSettingsConf; - flags?: FlagsConfig; - // internal objects, added by internal yaml to JS config parser - // @deprecated use configPath instead - config_path?: string; - // save the configuration file path - configPath?: string; - } - - /** - * Configuration object with additonal methods for configuration, includes yaml and internal medatada. - * @interface Config - * @extends {ConfigYaml} - */ - interface Config extends Omit { - user_agent: string; - server_id: string; - secret: string; - // save the configuration file path, it's fails without thi configPath - configPath: string; - // packages from yaml file looks different from packages inside the config file - packages: PackageList; - // security object defaults is added by the config file but optional in the yaml file - security: Security; - // @deprecated (pending adding the replacement) - checkSecretKey(token: string): string; - getMatchedPackagesSpec(storage: string): PackageAccess | void; - // TODO: verify how to handle this in the future - [key: string]: any; - } - - type PublisherMaintainer = { - username: string; - email: string; - }; - - type SearchPackageBody = { - name: string; - scope: string; - description: string; - author: string | PublisherMaintainer; - version: string; - keywords: string | string[] | undefined; - date: string; - links?: { - npm: string; // only include placeholder for URL eg: {url}/{packageName} - homepage?: string; - repository?: string; - bugs?: string; - }; - publisher?: any; - maintainers?: PublisherMaintainer[]; - }; - - interface ConfigWithHttps extends Config { - https: HttpsConf; - } - - export interface ITokenActions { - saveToken(token: Token): Promise; - deleteToken(user: string, tokenKey: string): Promise; - readTokens(filter: TokenFilter): Promise; - } - - /** - * @deprecated use @verdaccio/core pluginUtils instead - */ - interface ILocalData extends IPlugin, ITokenActions { - logger: Logger; - config: T & Config; - add(name: string): Promise; - remove(name: string): Promise; - get(): Promise; - init(): Promise; - getSecret(): Promise; - setSecret(secret: string): Promise; - getPackageStorage(packageInfo: string): IPackageStorage; - } - - interface ILocalPackageManager { - logger: Logger; - deletePackage(fileName: string): Promise; - removePackage(): Promise; - // next packages migration (this list is meant to replace the callback parent functions) - updatePackage( - packageName: string, - handleUpdate: (manifest: Manifest) => Promise - ): Promise; - readPackage(name: string): Promise; - savePackage(pkgName: string, value: Manifest): Promise; - readTarball(pkgName: string, { signal }): Promise; - createPackage(name: string, manifest: Manifest): Promise; - writeTarball(tarballName: string, { signal }): Promise; - // verify if tarball exist in the storage - hasTarball(fileName: string): Promise; - // verify if package exist in the storage - hasPackage(): Promise; - } - - // @deprecated use IBasicAuth from @verdaccio/auth - interface IBasicAuth { - config: T & Config; - aesEncrypt(buf: string): string; - 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; - } - - // FIXME: error should be export type `VerdaccioError = HttpError & { code: number };` - // instead of AuthError - // but this type is on @verdaccio/core 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 & AuthPackageAllow, cb: AuthAccessCallback): void; - allow_access?(user: RemoteUser, pkg: T & PackageAccess, cb: AuthAccessCallback): void; - allow_unpublish?(user: RemoteUser, pkg: T & AuthPackageAllow, 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; - } - - // @deprecated use @verdaccio/server - interface IPluginMiddleware extends IPlugin { - register_middlewares(app: any, auth: IBasicAuth, storage: any): void; - } - - interface IPluginStorageFilter extends IPlugin { - filter_metadata(packageInfo: Manifest): Promise; - } - - export type SearchResultWeb = { - name: string; - version: string; - description: string; - }; -} diff --git a/packages/core/types/package.json b/packages/core/types/package.json index 157dc48f7..7b9e62375 100644 --- a/packages/core/types/package.json +++ b/packages/core/types/package.json @@ -31,15 +31,24 @@ "publishConfig": { "access": "public" }, - "main": "index.d.ts", - "types": "index.d.ts", + "main": "build/types.d.ts", + "types": "build/types.d.ts", "scripts": { - "test": "exit 0", - "build": "exit 0" + "clean": "rimraf ./build", + "test": "pnpm type-check", + "build:docs": "typedoc --options ./typedoc.json --excludeExternals --tsconfig tsconfig.build.json", + "type-check": "tsc --noEmit -p tsconfig.build.json", + "build": "tsc --emitDeclarationOnly -p tsconfig.build.json" }, "devDependencies": { "@types/node": "16.11.47", - "tsd": "0.22.0" + "typedoc": "beta", + "typedoc-plugin-missing-exports": "1.0.0" + }, + "typedoc": { + "entryPoint": "./src/types.ts", + "readmeFile": "./README.md", + "displayName": "@verdaccio/types" }, "funding": { "type": "opencollective", diff --git a/packages/core/types/src/commons.ts b/packages/core/types/src/commons.ts new file mode 100644 index 000000000..988f70bdd --- /dev/null +++ b/packages/core/types/src/commons.ts @@ -0,0 +1,37 @@ +// @deprecated add proper type fot the callback +export type Callback = Function; +// FIXME: err should be something flexible enough for any implementation +export type CallbackAction = (err: any | null) => void; +// eslint-disable-next-line no-undef +export type CallbackError = (err: NodeJS.ErrnoException) => void; + +export interface RemoteUser { + real_groups: string[]; + groups: string[]; + name: string | void; + error?: string; +} + +export type StringValue = string | void | null; + +// FIXME: error should be export type `VerdaccioError = HttpError & { code: number };` +// instead of AuthError +// but this type is on @verdaccio/core and cannot be used here yet (I don't know why) +export interface HttpError extends Error { + status: number; + statusCode: number; + expose: boolean; + headers?: { + [key: string]: string; + }; + [key: string]: any; +} + +export type URLPrefix = { + // if is false, it would be relative by default + absolute: boolean; + // base path + // eg: absolute: true, https://somedomain.com/xxx/ + // eg: absolute: false, /xxx/ (default) if url_prefix is an string instead an object + basePath: string; +}; diff --git a/packages/core/types/src/configuration.ts b/packages/core/types/src/configuration.ts new file mode 100644 index 000000000..3ccfb34a3 --- /dev/null +++ b/packages/core/types/src/configuration.ts @@ -0,0 +1,296 @@ +import { PackageAccess, PackageList } from './manifest'; + +export type TypeToken = 'Bearer' | 'Basic'; + +export 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; +} + +export type LoggerType = 'stdout' | 'file'; +export type LoggerFormat = 'pretty' | 'pretty-timestamped' | 'json'; +export type LoggerLevel = 'http' | 'fatal' | 'warn' | 'info' | 'debug' | 'trace'; + +export type LoggerConfigItem = { + type?: LoggerType; + /** + * The format + */ + format?: LoggerFormat; + path?: string; + level?: string; + colors?: boolean; + async?: boolean; +}; + +export interface ConfigWithHttps extends Config { + https: HttpsConf; +} + +export interface PackageAccessYaml { + storage?: string; + publish?: string; + proxy?: string; + access?: string; + unpublish?: string; +} + +export interface LoggerConfItem { + type: LoggerType; + format: LoggerFormat; + level: LoggerLevel; +} + +export interface Headers { + [key: string]: string; +} + +export interface UpLinkTokenConf { + type: TypeToken; + token?: string; + token_env?: boolean | string; +} + +export 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; +} + +export type RateLimit = { + windowMs?: number; + max?: number; +}; + +// export interface WebConf { +// enable?: boolean; +// title?: string; +// logo?: string; +// favicon?: string; +// gravatar?: boolean; +// sort_packages?: string; +// rateLimit?: RateLimit; +// } + +export type FlagsConfig = { + searchRemote?: boolean; + changePassword?: boolean; +}; + +export type PackageManagers = 'pnpm' | 'yarn' | 'npm'; + +// FUTURE: WebConf and TemplateUIOptions should be merged . +export type CommonWebConf = { + title?: string; + logo?: string; + favicon?: string; + gravatar?: boolean; + sort_packages?: string; + darkMode?: boolean; + url_prefix?: string; + language?: string; + login?: boolean; + scope?: string; + pkgManagers?: PackageManagers[]; +}; + +/** + * Options are passed to the index.html + */ +export type TemplateUIOptions = { + uri?: string; + darkMode?: boolean; + protocol?: string; + host?: string; + // deprecated + basename?: string; + scope?: string; + showInfo?: boolean; + showSettings?: boolean; + showSearch?: boolean; + showFooter?: boolean; + showThemeSwitch?: boolean; + showDownloadTarball?: boolean; + showRaw?: boolean; + base: string; + primaryColor?: string; + version?: string; + logoURI?: string; + flags: FlagsConfig; +} & CommonWebConf; + +/** + * Options on config.yaml for web + */ +export type WebConf = { + // FIXME: rename to primaryColor and move it to CommonWebConf + primary_color?: string; + enable?: boolean; + scriptsHead?: string[]; + scriptsBodyAfter?: string[]; + metaScripts?: string[]; + bodyBefore?: string[]; + bodyAfter?: string[]; +} & CommonWebConf; + +export interface UpLinksConfList { + [key: string]: UpLinkConf; +} + +export interface AuthHtpasswd { + file: string; + max_users: number; +} + +export type AuthConf = any | AuthHtpasswd; + +export interface JWTOptions { + sign: JWTSignOptions; + verify: JWTVerifyOptions; +} + +export interface JWTSignOptions { + algorithm?: string; + expiresIn?: string; + notBefore?: string; + ignoreExpiration?: boolean; + maxAge?: string | number; + clockTimestamp?: number; +} + +export interface JWTVerifyOptions { + algorithm?: string; + expiresIn?: string; + notBefore?: string | number; + ignoreExpiration?: boolean; + maxAge?: string | number; + clockTimestamp?: number; +} + +export interface APITokenOptions { + legacy: boolean; + jwt?: JWTOptions; +} + +export interface Security { + web: JWTOptions; + api: APITokenOptions; +} + +export interface PublishOptions { + allow_offline: boolean; +} + +export interface ListenAddress { + [key: string]: string; +} + +export interface HttpsConfKeyCert { + key: string; + cert: string; + ca?: string; +} + +export interface HttpsConfPfx { + pfx: string; + passphrase?: string; +} + +export type HttpsConf = HttpsConfKeyCert | HttpsConfPfx; + +export interface Notifications { + method: string; + packagePattern: RegExp; + packagePatternFlags: string; + endpoint: string; + content: string; + headers: Headers; +} + +export type Notification = Notifications; + +export type ServerSettingsConf = { + // express-rate-limit settings + rateLimit: RateLimit; + keepAliveTimeout?: number; +}; + +/** + * YAML configuration file available options. + */ +export interface ConfigYaml { + _debug?: boolean; + storage?: string | void; + packages: PackageList; + uplinks: UpLinksConfList; + log?: LoggerConfItem; + web?: WebConf; + auth?: AuthConf; + security: Security; + publish?: PublishOptions; + store?: any; + listen?: ListenAddress; + https?: HttpsConf; + http_proxy?: string; + plugins?: string | void; + https_proxy?: string; + no_proxy?: string; + max_body_size?: string; + notifications?: Notifications; + notify?: Notifications | Notifications[]; + middlewares?: any; + filters?: any; + url_prefix?: string; + server?: ServerSettingsConf; + flags?: FlagsConfig; + // internal objects, added by internal yaml to JS config parser + // @deprecated use configPath instead + config_path?: string; + // save the configuration file path + configPath?: string; +} + +/** + * Configuration object with additonal methods for configuration, includes yaml and internal medatada. + * @interface Config + * @extends {ConfigYaml} + */ +export interface Config extends Omit { + user_agent: string; + server_id: string; + secret: string; + // save the configuration file path, it's fails without thi configPath + configPath: string; + // packages from yaml file looks different from packages inside the config file + packages: PackageList; + // security object defaults is added by the config file but optional in the yaml file + security: Security; + // @deprecated (pending adding the replacement) + checkSecretKey(token: string): string; + getMatchedPackagesSpec(storage: string): PackageAccess | void; + // TODO: verify how to handle this in the future + [key: string]: any; +} + +export interface AllowAccess { + name: string; + version?: string; + tag?: string; +} + +// info passed to the auth plugin when a package is package is being published + +export interface AuthPackageAllow extends PackageAccess, AllowAccess {} diff --git a/packages/core/types/src/manifest.ts b/packages/core/types/src/manifest.ts new file mode 100644 index 000000000..cc72daa6d --- /dev/null +++ b/packages/core/types/src/manifest.ts @@ -0,0 +1,217 @@ +export interface PackageAccess { + storage?: string; + publish?: string[]; + proxy?: string[]; + access?: string[]; + unpublish: string[]; +} + +export interface PackageList { + [key: string]: PackageAccess; +} + +export interface MergeTags { + [key: string]: string; +} + +export interface DistFile { + url: string; + sha: string; + registry?: string; +} + +export interface DistFiles { + [key: string]: DistFile; +} + +export interface Token { + user: string; + token: string; + key: string; + cidr?: string[]; + readonly: boolean; + created: number | string; + updated?: number | string; +} + +export interface AttachMents { + [key: string]: AttachMentsItem; +} + +export interface AttachMentsItem { + content_type?: string; + data?: string; + length?: number; + shasum?: string; + version?: string; +} + +export interface GenericBody { + [key: string]: string; +} + +export interface UpLinkMetadata { + etag: string; + fetched: number; +} + +export interface UpLinks { + [key: string]: UpLinkMetadata; +} + +export interface Signatures { + keyid: string; + sig: string; +} + +export interface Dist { + 'npm-signature'?: string; + signatures?: Signatures[]; + fileCount?: number; + integrity?: string; + shasum: string; + unpackedSize?: number; + tarball: string; +} + +export interface Author { + username?: string; + name: string; + email?: string; + url?: string; +} + +export interface PackageUsers { + [key: string]: boolean; +} + +export interface Tags { + [key: string]: Version; +} + +export 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; +} + +export interface Versions { + [key: string]: Version; +} + +/** + * @deprecated use Manifest instead + */ +export 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; +} + +/** + * Represents upstream manifest from another registry + */ +export interface FullRemoteManifest { + _id?: string; + _rev?: string; + name: string; + description?: string; + 'dist-tags': GenericBody; + time: GenericBody; + versions: Versions; + maintainers?: Author[]; + /** store the latest readme **/ + readme?: string; + /** store star assigned to this packages by users */ + users?: PackageUsers; + // TODO: not clear what access exactly means + access?: any; + bugs?: { url: string }; + license?: string; + homepage?: string; + repository?: string | { type?: string; url: string; directory?: string }; + keywords?: string[]; +} + +export interface Manifest extends FullRemoteManifest, PublishManifest { + // private fields only used by verdaccio + /** + * store fast access to the dist url of an specific tarball, instead search version + * by id, just the tarball id is faster. + * + * The _distfiles is created only when a package is being sync from an upstream. + * also used to fetch tarballs from upstream, the private publish tarballs are not stored in + * this object because they are not published in the upstream registry. + */ + _distfiles: DistFiles; + /** + * Store access cache metadata, to avoid to fetch the same metadata multiple times. + * + * The key represents the uplink id which is composed of a etag and a fetched timestamp. + * + * The fetched timestamp is the time when the metadata was fetched, used to avoid to fetch the + * same metadata until the metadata is older than the last fetch. + */ + _uplinks: UpLinks; + /** + * store the revision of the manifest + */ + _rev: string; +} +export interface PublishManifest { + /** + * The `_attachments` object has different usages: + * + * - When a package is published, it contains the tarball as an string, this string is used to be + * converted as a tarball, usually attached to the package but not stored in the database. + * - If user runs `npm star` the _attachments will be at the manifest body but empty. + * + * It has also an internal usage: + * + * - Used as a cache for the tarball, quick access to the tarball shasum, etc. Instead + * iterate versions and find the right one, just using the tarball as a key which is what + * the package manager sends to the registry. + * + * - A `_attachments` object is added every time a private tarball is published, upstream cached tarballs are + * not being part of this object, only for published private packages. + * + * Note: This field is removed when the package is accesed through the web user interface. + * */ + _attachments: AttachMents; +} diff --git a/packages/core/types/src/plugins/auth.ts b/packages/core/types/src/plugins/auth.ts new file mode 100644 index 000000000..93060384d --- /dev/null +++ b/packages/core/types/src/plugins/auth.ts @@ -0,0 +1,22 @@ +import { Callback, HttpError, RemoteUser } from '../commons'; +import { Config } from '../configuration'; + +export interface AuthPluginPackage { + packageName: string; + packageVersion?: string; + tag?: string; +} + +export type AuthError = HttpError & { code: number }; +export type AuthAccessCallback = (error: AuthError | null, access: boolean) => void; +export type AuthCallback = (error: AuthError | null, groups: string[] | false) => void; + +// @deprecated use IBasicAuth from @verdaccio/auth +export 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; +} diff --git a/packages/core/types/src/plugins/commons.ts b/packages/core/types/src/plugins/commons.ts new file mode 100644 index 000000000..6d415ce2a --- /dev/null +++ b/packages/core/types/src/plugins/commons.ts @@ -0,0 +1,15 @@ +import { Config, Logger } from '../configuration'; + +export class Plugin { + public constructor(config: T, options: PluginOptions) {} +} + +export interface IPlugin { + // TODO: not used on core yet + version?: string; +} + +export interface PluginOptions { + config: T & Config; + logger: Logger; +} diff --git a/packages/core/types/src/plugins/filter.ts b/packages/core/types/src/plugins/filter.ts new file mode 100644 index 000000000..2c8bc1315 --- /dev/null +++ b/packages/core/types/src/plugins/filter.ts @@ -0,0 +1,6 @@ +import { Manifest } from '../manifest'; +import { IPlugin } from './commons'; + +export interface IPluginStorageFilter extends IPlugin { + filter_metadata(packageInfo: Manifest): Promise; +} diff --git a/packages/core/types/src/plugins/index.ts b/packages/core/types/src/plugins/index.ts new file mode 100644 index 000000000..a94492efb --- /dev/null +++ b/packages/core/types/src/plugins/index.ts @@ -0,0 +1,31 @@ +import { RemoteUser } from '../commons'; +import { AllowAccess } from '../configuration'; +import { PackageAccess } from '../manifest'; +import { AuthAccessCallback, AuthCallback } from './auth'; +import { IPlugin } from './commons'; + +export interface IPluginAuth extends IPlugin { + /** + * @param props user from Application component + */ + 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; +} + +export * from './auth'; +export * from './storage'; +export * from './middleware'; +export * from './commons'; +export * from './filter'; diff --git a/packages/core/types/src/plugins/middleware.ts b/packages/core/types/src/plugins/middleware.ts new file mode 100644 index 000000000..fb71c76a7 --- /dev/null +++ b/packages/core/types/src/plugins/middleware.ts @@ -0,0 +1,8 @@ +import { Config } from '../configuration'; +import { IBasicAuth } from './auth'; +import { IPlugin } from './commons'; + +// TODO: convert to generic storage should come from implementation +export interface IPluginMiddleware extends IPlugin { + register_middlewares(app: any, auth: IBasicAuth, storage: K): void; +} diff --git a/packages/core/types/src/plugins/storage.ts b/packages/core/types/src/plugins/storage.ts new file mode 100644 index 000000000..595e587c6 --- /dev/null +++ b/packages/core/types/src/plugins/storage.ts @@ -0,0 +1,95 @@ +import { PassThrough, PipelinePromise, Readable, Stream, Writable } from 'stream'; + +import { Callback, CallbackAction, StringValue } from '../commons'; +import { Config, Logger } from '../configuration'; +import { Manifest, MergeTags, Token, Version } from '../manifest'; +import { IPlugin } from './commons'; + +export type StorageList = string[]; + +export interface LocalStorage { + list: any; + secret: string; +} + +export interface ILocalStorage { + add(name: string): void; + remove(name: string): void; + get(): StorageList; + sync(): void; +} + +export interface TokenFilter { + user: string; +} + +export 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 + */ +export type onSearchPackage = (item: Manifest, 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 +export type onEndSearchPackage = (error?: any) => void; +export type onValidatePackage = (name: string) => boolean; + +export type StorageUpdateCallback = (data: Manifest, cb: CallbackAction) => void; + +export type StorageWriteCallback = (name: string, json: Manifest, callback: Callback) => void; +export type PackageTransformer = (pkg: Manifest) => Manifest; +export type ReadPackageCallback = (err: any | null, data?: Manifest) => void; + +export interface ILocalPackageManager { + logger: Logger; + deletePackage(fileName: string): Promise; + removePackage(): Promise; + // next packages migration (this list is meant to replace the callback parent functions) + updatePackage( + packageName: string, + handleUpdate: (manifest: Manifest) => Promise + ): Promise; + readPackage(name: string): Promise; + savePackage(pkgName: string, value: Manifest): Promise; + readTarball(pkgName: string, { signal }): Promise; + createPackage(name: string, manifest: Manifest): Promise; + writeTarball(tarballName: string, { signal }): Promise; + // verify if tarball exist in the storage + hasTarball(fileName: string): Promise; + // verify if package exist in the storage + hasPackage(): Promise; +} + +export type IPackageStorage = ILocalPackageManager | void; + +export type IPluginStorage = ILocalData; +export type IPackageStorageManager = ILocalPackageManager; + +/** + * @deprecated use @verdaccio/core pluginUtils instead + */ +interface ILocalData extends IPlugin, ITokenActions { + logger: Logger; + config: T & Config; + add(name: string): Promise; + remove(name: string): Promise; + get(): Promise; + init(): Promise; + getSecret(): Promise; + setSecret(secret: string): Promise; + getPackageStorage(packageInfo: string): IPackageStorage; +} diff --git a/packages/core/types/src/plugins/theme.ts b/packages/core/types/src/plugins/theme.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/types/src/search.ts b/packages/core/types/src/search.ts new file mode 100644 index 000000000..d1e04af2e --- /dev/null +++ b/packages/core/types/src/search.ts @@ -0,0 +1,28 @@ +export type PublisherMaintainer = { + username: string; + email: string; +}; + +export type SearchPackageBody = { + name: string; + scope: string; + description: string; + author: string | PublisherMaintainer; + version: string; + keywords: string | string[] | undefined; + date: string; + links?: { + npm: string; // only include placeholder for URL eg: {url}/{packageName} + homepage?: string; + repository?: string; + bugs?: string; + }; + publisher?: any; + maintainers?: PublisherMaintainer[]; +}; + +export type SearchResultWeb = { + name: string; + version: string; + description: string; +}; diff --git a/packages/core/types/src/types.ts b/packages/core/types/src/types.ts new file mode 100644 index 000000000..20d526d5e --- /dev/null +++ b/packages/core/types/src/types.ts @@ -0,0 +1,5 @@ +export * from './plugins'; +export * from './manifest'; +export * from './search'; +export * from './commons'; +export * from './configuration'; diff --git a/packages/core/types/tsconfig.build.json b/packages/core/types/tsconfig.build.json new file mode 100644 index 000000000..79f1f81e0 --- /dev/null +++ b/packages/core/types/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/types/tsconfig.json b/packages/core/types/tsconfig.json new file mode 100644 index 000000000..35b3cee58 --- /dev/null +++ b/packages/core/types/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.reference.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build" + }, + "include": ["src/**/*"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/core/types/typedoc.json b/packages/core/types/typedoc.json new file mode 100644 index 000000000..3e320f069 --- /dev/null +++ b/packages/core/types/typedoc.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["src/types.ts"], + "sort": ["source-order"] +} diff --git a/packages/logger-prettify/jest.config.js b/packages/logger-prettify/jest.config.js index 7da7d2da8..44aa5663a 100644 --- a/packages/logger-prettify/jest.config.js +++ b/packages/logger-prettify/jest.config.js @@ -1,3 +1,10 @@ const config = require('../../jest/config'); -module.exports = Object.assign({}, config, {}); +module.exports = Object.assign({}, config, { + coverageThreshold: { + global: { + // FIXME: increase to 90 + lines: 60, + }, + }, +}); diff --git a/packages/logger-prettify/package.json b/packages/logger-prettify/package.json index 5c838f45b..590e07699 100644 --- a/packages/logger-prettify/package.json +++ b/packages/logger-prettify/package.json @@ -31,7 +31,7 @@ }, "scripts": { "clean": "rimraf ./build", - "test": "jest", + "test": "cross-env TZ=utc jest", "type-check": "tsc --noEmit -p tsconfig.build.json", "build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json", "build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", @@ -40,13 +40,13 @@ }, "dependencies": { "dayjs": "1.11.4", - "fast-safe-stringify": "2.1.1", - "kleur": "3.0.3", + "pino-abstract-transport": "1.0.0", + "colorette": "2.0.7", "lodash": "4.17.21", - "prettier-bytes": "1.0.4" + "sonic-boom": "3.2.0" }, "devDependencies": { - "pino": "7.11.0" + "pino": "8.4.1" }, "funding": { "type": "opencollective", diff --git a/packages/logger-prettify/src/formatter.ts b/packages/logger-prettify/src/formatter.ts index ff1a5d8d1..601cd8c08 100644 --- a/packages/logger-prettify/src/formatter.ts +++ b/packages/logger-prettify/src/formatter.ts @@ -1,4 +1,4 @@ -import { green, red, white } from 'kleur'; +import { green, red, white } from 'colorette'; import { inspect } from 'util'; import { LevelCode, calculateLevel, levelsColors, subSystemLevels } from './levels'; @@ -19,7 +19,11 @@ export interface ObjectTemplate { [key: string]: string | number | object | null | void; } -export function fillInMsgTemplate(msg, templateOptions: ObjectTemplate, colors): string { +export function fillInMsgTemplate( + msg: string, + templateOptions: ObjectTemplate, + colors: boolean +): string { const templateRegex = /@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g; return msg.replace(templateRegex, (_, name): string => { @@ -53,10 +57,12 @@ export function fillInMsgTemplate(msg, templateOptions: ObjectTemplate, colors): }); } -function getMessage(debugLevel, msg, sub, templateObjects, hasColors) { +function getMessage(debugLevel, msg, sub, templateObjects, hasColors: boolean) { const finalMessage = fillInMsgTemplate(msg, templateObjects, hasColors); - const subSystemType = subSystemLevels.color[sub ?? 'default']; + const subSystemType = hasColors + ? subSystemLevels.color[sub ?? 'default'] + : subSystemLevels.white[sub ?? 'default']; if (hasColors) { const logString = `${levelsColors[debugLevel](padRight(debugLevel, LEVEL_VALUE_MAX))}${white( `${subSystemType} ${finalMessage}` @@ -71,8 +77,8 @@ function getMessage(debugLevel, msg, sub, templateObjects, hasColors) { export function printMessage( templateObjects: ObjectTemplate, - options: PrettyOptionsExtended, - hasColors = true + options: Pick, + hasColors: boolean ): string { const { prettyStamp } = options; const { level, msg, sub } = templateObjects; diff --git a/packages/logger-prettify/src/index.ts b/packages/logger-prettify/src/index.ts index efbc0ec09..2e02f5dfe 100644 --- a/packages/logger-prettify/src/index.ts +++ b/packages/logger-prettify/src/index.ts @@ -1,20 +1,2 @@ -import { fillInMsgTemplate, printMessage } from './formatter'; -import { PrettyOptionsExtended } from './types'; - -export { fillInMsgTemplate }; - -export type PrettyFactory = (param) => string; - -/* - options eg: - { messageKey: 'msg', levelFirst: true, prettyStamp: false } - */ - -export default function prettyFactory(options: PrettyOptionsExtended): PrettyFactory { - // the break line must happens in the prettify component - const breakLike = '\n'; - return (inputData): string => { - // FIXME: review colors by default is true - return printMessage(inputData, options, true) + breakLike; - }; -} +export { default, buildPretty } from './prettify'; +export { fillInMsgTemplate } from './formatter'; diff --git a/packages/logger-prettify/src/levels.ts b/packages/logger-prettify/src/levels.ts index c6b6f2ef5..03a2514c8 100644 --- a/packages/logger-prettify/src/levels.ts +++ b/packages/logger-prettify/src/levels.ts @@ -53,6 +53,7 @@ export const subSystemLevels = { white: { in: ARROWS.LEFT, out: ARROWS.RIGHT, + auth: ARROWS.NEUTRAL, fs: ARROWS.EQUAL, default: ARROWS.NEUTRAL, }, diff --git a/packages/logger-prettify/src/prettify.ts b/packages/logger-prettify/src/prettify.ts new file mode 100644 index 000000000..cb444c585 --- /dev/null +++ b/packages/logger-prettify/src/prettify.ts @@ -0,0 +1,118 @@ +import { isColorSupported } from 'colorette'; +import { WriteStream } from 'fs'; +import build from 'pino-abstract-transport'; +import SonicBoom, { SonicBoomOpts } from 'sonic-boom'; +import { Transform, pipeline } from 'stream'; +import { isMainThread } from 'worker_threads'; + +import { fillInMsgTemplate, printMessage } from './formatter'; +import { PrettyOptionsExtended } from './types'; + +export { fillInMsgTemplate }; + +function noop() {} + +/** + * Creates a safe SonicBoom instance + * + * @param {object} opts Options for SonicBoom + * + * @returns {object} A new SonicBoom stream + */ +function buildSafeSonicBoom(opts: SonicBoomOpts) { + const stream = new SonicBoom(opts); + stream.on('error', filterBrokenPipe); + if (!opts.sync && isMainThread) { + setupOnExit(stream); + } + return stream; + + function filterBrokenPipe(err) { + if (err.code === 'EPIPE') { + // @ts-ignore + stream.write = noop; + stream.end = noop; + stream.flushSync = noop; + stream.destroy = noop; + return; + } + stream.removeListener('error', filterBrokenPipe); + } +} + +function setupOnExit(stream) { + /* istanbul ignore next */ + if (global.WeakRef && global.WeakMap && global.FinalizationRegistry) { + // This is leak free, it does not leave event handlers + const onExit = require('on-exit-leak-free'); + + onExit.register(stream, autoEnd); + + stream.on('close', function () { + onExit.unregister(stream); + }); + } +} + +/* istanbul ignore next */ +function autoEnd(stream, eventName) { + // This check is needed only on some platforms + + if (stream.destroyed) { + return; + } + + if (eventName === 'beforeExit') { + // We still have an event loop, let's use it + stream.flush(); + stream.on('drain', function () { + stream.end(); + }); + } else { + // We do not have an event loop, so flush synchronously + stream.flushSync(); + } +} + +export function hasColors(colors: boolean | undefined) { + if (colors) { + return isColorSupported; + } + return typeof colors === 'undefined' ? true : colors; +} + +export function buildPretty(opts: PrettyOptionsExtended) { + return (chunk) => { + const colors = hasColors(opts.colors); + return printMessage(chunk, { prettyStamp: opts.prettyStamp }, colors); + }; +} + +export default function (opts) { + const pretty = buildPretty(opts); + // @ts-ignore + return build(function (source) { + const stream = new Transform({ + objectMode: true, + autoDestroy: true, + transform(chunk, enc, cb) { + const line = pretty(chunk) + '\n'; + cb(null, line); + }, + }); + const destination = buildSafeSonicBoom({ + dest: opts.destination || 1, + sync: opts.sync || true, + }) as unknown as WriteStream; + + source.on('unknown', function (line) { + destination.write(line + '\n'); + }); + + pipeline(source, stream, destination, (err) => { + // eslint-disable-next-line no-console + console.error('prettify pipeline error ', err); + }); + return stream; + }); +} diff --git a/packages/logger-prettify/src/types.ts b/packages/logger-prettify/src/types.ts index e41d4b765..cbf1a0e5e 100644 --- a/packages/logger-prettify/src/types.ts +++ b/packages/logger-prettify/src/types.ts @@ -2,4 +2,5 @@ import { PrettyOptions } from 'pino'; export interface PrettyOptionsExtended extends PrettyOptions { prettyStamp: boolean; + colors?: boolean; } diff --git a/packages/logger-prettify/src/utils.ts b/packages/logger-prettify/src/utils.ts index 0fe1460da..16b1e6d31 100644 --- a/packages/logger-prettify/src/utils.ts +++ b/packages/logger-prettify/src/utils.ts @@ -16,7 +16,7 @@ export function padRight(message: string, max = message.length + CUSTOM_PAD_LENG return message.padEnd(max, ' '); } -export function formatLoggingDate(time: number, message): string { +export function formatLoggingDate(time: number, message: string): string { const timeFormatted = dayjs(time).format(FORMAT_DATE); return `[${timeFormatted}]${message}`; diff --git a/packages/logger-prettify/test/__snapshots__/formatter.spec.ts.snap b/packages/logger-prettify/test/__snapshots__/formatter.spec.ts.snap index 6d335d769..5fdc83f08 100644 --- a/packages/logger-prettify/test/__snapshots__/formatter.spec.ts.snap +++ b/packages/logger-prettify/test/__snapshots__/formatter.spec.ts.snap @@ -1,21 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`formatter printMessage should display a bytes request 1`] = `" fatal<-- 200, user: null(127.0.0.1), req: 'GET /verdaccio', bytes: 0/150186"`; +exports[`formatter printMessage should display a bytes request 1`] = `"fatal<-- 200, user: null(127.0.0.1), req: 'GET /verdaccio', bytes: 0/150186 "`; -exports[`formatter printMessage should display a resource request 1`] = `"info <-- 127.0.0.1 requested 'GET /verdaccio' "`; +exports[`formatter printMessage should display a resource request 1`] = `"info <-- 127.0.0.1 requested 'GET /verdaccio' "`; -exports[`formatter printMessage should display a streaming request 1`] = `" fatal--> 304, req: 'GET https://registry.npmjs.org/verdaccio' (streaming)"`; +exports[`formatter printMessage should display a streaming request 1`] = `"fatal--> 304, req: 'GET https://registry.npmjs.org/verdaccio' (streaming) "`; -exports[`formatter printMessage should display an error request 1`] = `" fatal--> ERR, req: 'GET https://registry.fake.org/aaa', error: getaddrinfo ENOTFOUND registry.fake.org"`; +exports[`formatter printMessage should display an error request 1`] = `"fatal--> ERR, req: 'GET https://registry.fake.org/aaa', error: getaddrinfo ENOTFOUND registry.fake.org "`; -exports[`formatter printMessage should display an fatal request 1`] = `" fatal--> ERR, req: 'GET https://registry.fake.org/aaa', error: fatal error"`; +exports[`formatter printMessage should display an fatal request 1`] = `"fatal--> ERR, req: 'GET https://registry.fake.org/aaa', error: fatal error "`; -exports[`formatter printMessage should display config file 1`] = `" warn --- config file - /Users/user/.config/verdaccio/config/config.yaml"`; +exports[`formatter printMessage should display config file 1`] = `"warn --- config file - /Users/user/.config/verdaccio/config/config.yaml "`; -exports[`formatter printMessage should display custom log message 1`] = `" fatal--- custom - foo - undefined"`; +exports[`formatter printMessage should display custom log message 1`] = `"fatal--- custom - foo - undefined "`; -exports[`formatter printMessage should display trace level 1`] = `" trace--- [trace] - foo"`; +exports[`formatter printMessage should display trace level 1`] = `"trace--- [trace] - foo "`; -exports[`formatter printMessage should display trace level with pretty stamp 1`] = `"[formatted-date] trace--- [trace] - foo"`; +exports[`formatter printMessage should display trace level with pretty stamp 1`] = `"[formatted-date]trace--- [trace] - foo "`; -exports[`formatter printMessage should display version and http address 1`] = `" warn --- http address - http://localhost:4873/ - verdaccio/5.0.0"`; +exports[`formatter printMessage should display version and http address 1`] = `"warn --- http address - http://localhost:4873/ - verdaccio/5.0.0 "`; diff --git a/packages/logger-prettify/test/__snapshots__/index.spec.ts.snap b/packages/logger-prettify/test/__snapshots__/index.spec.ts.snap deleted file mode 100644 index 8ef4256c0..000000000 --- a/packages/logger-prettify/test/__snapshots__/index.spec.ts.snap +++ /dev/null @@ -1,6 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`prettyFactory should return a function with options 1`] = ` -" trace--- [trace] - foo -" -`; diff --git a/packages/logger-prettify/test/formatter.spec.ts b/packages/logger-prettify/test/formatter.spec.ts index a22eba88c..1f4d011d7 100644 --- a/packages/logger-prettify/test/formatter.spec.ts +++ b/packages/logger-prettify/test/formatter.spec.ts @@ -22,7 +22,7 @@ describe('formatter', () => { msg: 'config file - @{file}', }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display trace level', () => { @@ -32,7 +32,7 @@ describe('formatter', () => { msg: '[trace] - @{foo}', }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display trace level with pretty stamp', () => { @@ -48,7 +48,8 @@ describe('formatter', () => { log, Object.assign({}, prettyfierOptions, { prettyStamp: true, - }) + }), + false ) ).toMatchSnapshot(); }); @@ -72,7 +73,7 @@ describe('formatter', () => { 'bytes: @{bytes.in}/@{bytes.out}', }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display an error request', () => { @@ -101,7 +102,7 @@ describe('formatter', () => { msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}", }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display an fatal request', () => { @@ -126,7 +127,7 @@ describe('formatter', () => { msg: "@{!status}, req: '@{request.method} @{request.url}', error: @{!error}", }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display a streaming request', () => { @@ -142,7 +143,7 @@ describe('formatter', () => { msg: "@{!status}, req: '@{request.method} @{request.url}' (streaming)", }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display version and http address', () => { @@ -157,7 +158,7 @@ describe('formatter', () => { msg: 'http address - @{addr} - @{version}', }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display custom log message', () => { @@ -168,7 +169,7 @@ describe('formatter', () => { msg: 'custom - @{something} - @{missingParam}', }; - expect(printMessage(log, prettyfierOptions)).toMatchSnapshot(); + expect(printMessage(log, prettyfierOptions, false)).toMatchSnapshot(); }); test('should display a resource request', () => { diff --git a/packages/logger-prettify/test/index.spec.ts b/packages/logger-prettify/test/index.spec.ts index 3f7c388f1..898777b8b 100644 --- a/packages/logger-prettify/test/index.spec.ts +++ b/packages/logger-prettify/test/index.spec.ts @@ -1,18 +1,28 @@ -import * as factory from '../src'; +import pino from 'pino'; +import { Writable } from 'stream'; + +import { buildPretty } from '../src'; describe('prettyFactory', () => { - const prettyfierOptions = { messageKey: 'msg', levelFirst: true, prettyStamp: false }; - test('should return a function', () => { - expect(typeof factory['default']({})).toEqual('function'); - }); - - test('should return a function with options', () => { - const log = { - level: 10, - foo: 'foo', - msg: '[trace] - @{foo}', - }; - - expect(factory['default'](prettyfierOptions)(log)).toMatchSnapshot(); + const prettyfierOptions = { + messageKey: 'msg', + levelFirst: true, + prettyStamp: false, + colors: false, + }; + test('should return a function', (done) => { + const pretty = buildPretty(prettyfierOptions); + const log = pino( + new Writable({ + objectMode: true, + write(chunk, enc, cb) { + const formatted = pretty(JSON.parse(chunk)); + expect(formatted).toBe('info --- test message '); + cb(); + done(); + }, + }) + ); + log.info({ test: 'test' }, '@{test} message'); }); }); diff --git a/packages/logger-prettify/test/utils.test.ts b/packages/logger-prettify/test/utils.test.ts new file mode 100644 index 000000000..c12ba8265 --- /dev/null +++ b/packages/logger-prettify/test/utils.test.ts @@ -0,0 +1,20 @@ +import { formatLoggingDate, padLeft, padRight } from '../src/utils'; + +describe('formatLoggingDate', () => { + test('basic', () => { + expect(formatLoggingDate(1585411248203, ' message')).toEqual('[2020-03-28 16:00:48] message'); + }); +}); + +describe('pad', () => { + test('pad right with custom length', () => { + expect(padRight('message right', 20)).toEqual('message right '); + }); + + test('pad right 2', () => { + expect(padRight('message right')).toEqual('message right '); + }); + test('pad left', () => { + expect(padLeft('message left')).toEqual(' message left'); + }); +}); diff --git a/packages/logger/package.json b/packages/logger/package.json index dd4646a85..0a417fb7e 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -31,7 +31,7 @@ }, "scripts": { "clean": "rimraf ./build", - "test": "jest", + "test": "cross-env TZ=utc jest", "type-check": "tsc --noEmit -p tsconfig.build.json", "build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json", "build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", @@ -41,10 +41,9 @@ "dependencies": { "@verdaccio/core": "workspace:6.0.0-6-next.5", "@verdaccio/logger-prettify": "workspace:6.0.0-6-next.6", - "pino-pretty": "7.6.1", "debug": "4.3.4", - "lodash": "4.17.21", - "pino": "7.11.0" + "colorette": "2.0.7", + "pino": "8.4.1" }, "devDependencies": { "@verdaccio/types": "workspace:11.0.0-6-next.12" diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index 43fa3a9d0..45f5e8114 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -1 +1 @@ -export { setup, createLogger, logger, LoggerConfigItem } from './logger'; +export { setup, createLogger, logger, prepareSetup } from './logger'; diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts index 3b8b59dba..f57e82eec 100644 --- a/packages/logger/src/logger.ts +++ b/packages/logger/src/logger.ts @@ -1,18 +1,24 @@ +// +import { isColorSupported } from 'colorette'; import buildDebug from 'debug'; -import _ from 'lodash'; -import pino from 'pino'; +import pino, { Logger } from 'pino'; -import { warningUtils } from '@verdaccio/core'; -import prettifier, { fillInMsgTemplate } from '@verdaccio/logger-prettify'; +import { fillInMsgTemplate } from '@verdaccio/logger-prettify'; +import { LoggerConfigItem, LoggerFormat } from '@verdaccio/types'; const debug = buildDebug('verdaccio:logger'); -export let logger; - function isProd() { return process.env.NODE_ENV === 'production'; } +function hasColors(colors: boolean | undefined) { + if (colors) { + return isColorSupported; + } + return typeof colors === 'undefined' ? true : colors; +} + const DEFAULT_LOG_FORMAT = isProd() ? 'json' : 'pretty'; export type LogPlugin = { @@ -20,28 +26,19 @@ export type LogPlugin = { options?: any[]; }; -export type LogType = 'file' | 'stdout'; -export type LogFormat = 'json' | 'pretty-timestamped' | 'pretty'; +type LoggerOptions = { level?: string; path?: string; colors?: boolean; sync?: boolean }; export function createLogger( - options = { level: 'http' }, - destination = pino.destination(1), - prettyPrintOptions = { - // we hide warning since the prettifier should not be used in production - // https://getpino.io/#/docs/pretty?id=prettifier-api - suppressFlushSyncWarning: true, - }, - format: LogFormat = DEFAULT_LOG_FORMAT + options: LoggerOptions = { level: 'http' }, + // eslint-disable-next-line no-undef + destination: NodeJS.WritableStream = pino.destination(1), + format: LoggerFormat = DEFAULT_LOG_FORMAT ) { - if (_.isNil(format)) { - format = DEFAULT_LOG_FORMAT; - } - + debug('setup logger'); let pinoConfig = { customLevels: { http: 25, }, - ...options, level: options.level, serializers: { err: pino.stdSerializers.err, @@ -52,23 +49,32 @@ export function createLogger( debug('has prettifier? %o', !isProd()); // pretty logs are not allowed in production for performance reasons - if ((format === DEFAULT_LOG_FORMAT || format !== 'json') && isProd() === false) { + if (['pretty-timestamped', 'pretty'].includes(format) && isProd() === false) { pinoConfig = Object.assign({}, pinoConfig, { - prettifier, - // more info - // https://github.com/pinojs/pino-pretty/issues/37 - prettyPrint: { - levelFirst: true, - prettyStamp: format === 'pretty-timestamped', - ...prettyPrintOptions, + transport: { + target: '@verdaccio/logger-prettify', + options: { + // string or 1 (file descriptor for process.stdout) + destination: options.path || 1, + colors: hasColors(options.colors), + prettyStamp: format === 'pretty-timestamped', + }, }, }); } else { - pinoConfig = Object.assign({}, pinoConfig, { + pinoConfig = { + ...pinoConfig, // more info // https://github.com/pinojs/pino/blob/v7.1.0/docs/api.md#hooks-object + // TODO: improve typings here + // @ts-ignore hooks: { - logMethod(inputArgs, method) { + logMethod( + inputArgs: [any, any, ...any[]], + method: { + apply: (arg0: { logMethod: (inputArgs: any, method: any) => any }, arg1: any[]) => any; + } + ) { const [templateObject, message, ...otherArgs] = inputArgs; const templateVars = !!templateObject && typeof templateObject === 'object' @@ -79,10 +85,12 @@ export function createLogger( return method.apply(this, [templateObject, hydratedMessage, ...otherArgs]); }, }, - }); + }; } const logger = pino(pinoConfig, destination); + /* eslint-disable */ + /* istanbul ignore next */ if (process.env.DEBUG) { logger.on('level-change', (lvl, val, prevLvl, prevVal) => { debug('%s (%d) was changed to %s (%d)', lvl, val, prevLvl, prevVal); @@ -92,34 +100,20 @@ export function createLogger( return logger; } -export function getLogger() { - if (_.isNil(logger)) { - // FIXME: not sure about display here a warning - warningUtils.emit(warningUtils.Codes.VERWAR002); - return; - } - - return logger; -} - const DEFAULT_LOGGER_CONF: LoggerConfigItem = { type: 'stdout', format: 'pretty', level: 'http', }; -export type LoggerConfigItem = { - type?: LogType; - plugin?: LogPlugin; - format?: LogFormat; - path?: string; - level?: string; -}; - export type LoggerConfig = LoggerConfigItem; -export function setup(options: LoggerConfigItem = DEFAULT_LOGGER_CONF) { - debug('setup logger'); +export function prepareSetup(options: LoggerConfigItem = DEFAULT_LOGGER_CONF) { + let logger: Logger<{ + customLevels: { http: number }; + level: string | undefined; + serializers: { err: any; req: any; res: any }; + }>; let loggerConfig = options; if (!loggerConfig?.level) { loggerConfig = Object.assign( @@ -134,40 +128,35 @@ export function setup(options: LoggerConfigItem = DEFAULT_LOGGER_CONF) { if (loggerConfig.type === 'file') { debug('logging file enabled'); const destination = pino.destination(loggerConfig.path); + /* eslint-disable */ + /* istanbul ignore next */ process.on('SIGUSR2', () => destination.reopen()); // @ts-ignore - logger = createLogger(pinoConfig, destination, loggerConfig.format); - // @ts-ignore - } else if (loggerConfig.type === 'rotating-file') { - warningUtils.emit(warningUtils.Codes.VERWAR003); - debug('logging stdout enabled'); - // @ts-ignore - logger = createLogger(pinoConfig, pino.destination(1), loggerConfig.format); + logger = createLogger( + { level: loggerConfig.level, path: loggerConfig.path, colors: loggerConfig.colors }, + destination, + loggerConfig.format + ); + return logger; } else { debug('logging stdout enabled'); // @ts-ignore - logger = createLogger(pinoConfig, pino.destination(1), loggerConfig.format); - } - - if (isProd()) { - // why only on prod? https://github.com/pinojs/pino/issues/920#issuecomment-710807667 - const finalHandler = pino.final(logger, (err, finalLogger, event) => { - finalLogger.info(`${event} caught`); - if (err) { - finalLogger.error(err, 'error caused exit'); - } - process.exit(err ? 1 : 0); - }); - - process.on('uncaughtException', (err) => finalHandler(err, 'uncaughtException')); - process.on('unhandledRejection', (err) => finalHandler(err as Error, 'unhandledRejection')); - process.on('beforeExit', () => finalHandler(null, 'beforeExit')); - process.on('exit', () => finalHandler(null, 'exit')); - process.on('uncaughtException', (err) => finalHandler(err, 'uncaughtException')); - process.on('SIGINT', () => finalHandler(null, 'SIGINT')); - process.on('SIGQUIT', () => finalHandler(null, 'SIGQUIT')); - process.on('SIGTERM', () => finalHandler(null, 'SIGTERM')); + logger = createLogger( + { level: loggerConfig.level, colors: loggerConfig.colors }, + pino.destination(1), + loggerConfig.format + ); + return logger; + } +} + +export let logger: Logger; + +export function setup(options: LoggerConfigItem) { + if (typeof logger !== 'undefined') { + return logger; } + logger = prepareSetup(options); return logger; } diff --git a/packages/logger/test/createLogger.spec.ts b/packages/logger/test/createLogger.spec.ts new file mode 100644 index 000000000..24cdc5670 --- /dev/null +++ b/packages/logger/test/createLogger.spec.ts @@ -0,0 +1,20 @@ +import { Writable } from 'stream'; + +import { createLogger } from '../src'; + +describe('logger test', () => { + describe('json format', () => { + test('should write json to a stream', () => { + const stream = new Writable({ + write(chunk, encoding, callback) { + expect(JSON.parse(chunk.toString())).toEqual( + expect.objectContaining({ level: 30, msg: 'test' }) + ); + callback(); + }, + }); + const logger = createLogger({ level: 'http' }, stream, 'json'); + logger.info('test'); + }); + }); +}); diff --git a/packages/logger/test/logger.spec.ts b/packages/logger/test/logger.spec.ts index 094c92d4d..f4d3bfeaa 100644 --- a/packages/logger/test/logger.spec.ts +++ b/packages/logger/test/logger.spec.ts @@ -1,51 +1,115 @@ -import { logger, setup } from '../src'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { setTimeout } from 'timers/promises'; -const mockWarningUtils = jest.fn(); +import { fileUtils } from '@verdaccio/core'; -jest.mock('@verdaccio/core', () => { - const original = jest.requireActual('@verdaccio/core'); - return { - warningUtils: { - ...original.warningUtils, - emit: (...args) => { - mockWarningUtils(...args); - }, - }, - }; -}); +import { prepareSetup } from '../src'; -describe('logger', () => { - beforeEach(() => { - jest.clearAllMocks(); +async function readLogFile(path: string) { + await setTimeout(1000, 'resolved'); + return readFile(path, 'utf8'); +} + +async function createLogFile() { + const folder = await fileUtils.createTempFolder('logger-1'); + const file = join(folder, 'logger.log'); + return file; +} + +const defaultOptions = { + format: 'json', + level: 'http', + colors: false, +}; + +describe('logger test', () => { + describe('basic', () => { + test('should include default level', async () => { + const file = await createLogFile(); + const logger = prepareSetup({ type: 'file', path: file, colors: false }); + logger.info({ packageName: 'test' }, `testing @{packageName}`); + // Note: this should not be logged + logger.debug(`this should not be logged`); + // Note: this should not be logged + logger.trace(`this should not be logged`); + logger.error(`this should logged`); + const content = await readLogFile(file); + expect(content).toBe('info --- testing test \nerror--- this should logged \n'); + }); + + test('should include all logging level', async () => { + const file = await createLogFile(); + const logger = prepareSetup({ type: 'file', level: 'trace', path: file, colors: false }); + logger.info({ packageName: 'test' }, `testing @{packageName}`); + logger.debug(`this should not be logged`); + logger.trace(`this should not be logged`); + logger.error(`this should logged`); + const content = await readLogFile(file); + expect(content).toBe( + 'info --- testing test \ndebug--- this should not be logged \ntrace--- this should not be logged \nerror--- this should logged \n' + ); + }); }); - test.skip('should write message logger', () => { - jest.spyOn(process.stdout, 'write'); - setup([ - { + describe('json format', () => { + test('should log into a file with json format', async () => { + const file = await createLogFile(); + const logger = prepareSetup({ + ...defaultOptions, + format: 'json', + type: 'file', + path: file, level: 'info', - }, - ]); - - logger.info({ packageName: 'test' }, `publishing or updating a new version for @{packageName}`); - // FIXME: check expect - // expect(spyOn).toHaveBeenCalledTimes(2); + }); + logger.info( + { packageName: 'test' }, + `publishing or updating a new version for @{packageName}` + ); + const content = await readLogFile(file); + expect(JSON.parse(content)).toEqual( + expect.objectContaining({ + level: 30, + msg: 'publishing or updating a new version for test', + }) + ); + }); }); - test.skip('throw deprecation warning if multiple loggers configured', () => { - setup([ - { - level: 'info', - }, - { - level: 'http', - }, - ]); - // expect(mockWarningUtils).toHaveBeenCalledWith(warningUtils.Codes.VERDEP002); - }); + describe('pretty format', () => { + test('should log into a file with pretty', async () => { + const file = await createLogFile(); + const logger = prepareSetup({ + format: 'pretty', + type: 'file', + path: file, + level: 'trace', + colors: false, + }); + logger.info( + { packageName: 'test' }, + `publishing or updating a new version for @{packageName}` + ); + const content = await readLogFile(file); + expect(content).toEqual('info --- publishing or updating a new version for test \n'); + }); - test('regression: do not throw deprecation warning if no logger config is provided', () => { - setup(); - expect(mockWarningUtils).not.toHaveBeenCalled(); + test('should log into a file with pretty-timestamped', async () => { + const file = await createLogFile(); + const logger = prepareSetup({ + format: 'pretty-timestamped', + type: 'file', + path: file, + level: 'trace', + colors: false, + }); + logger.info( + { packageName: 'test' }, + `publishing or updating a new version for @{packageName}` + ); + const content = await readLogFile(file); + // TODO: we might want mock time for testing + expect(content).toMatch('info --- publishing or updating a new version for test \n'); + }); }); }); diff --git a/packages/plugins/audit/src/audit.ts b/packages/plugins/audit/src/audit.ts index 21b367897..a27072e3a 100644 --- a/packages/plugins/audit/src/audit.ts +++ b/packages/plugins/audit/src/audit.ts @@ -11,7 +11,7 @@ import { ConfigAudit } from './types'; // FUTURE: we should be able to overwrite this export const REGISTRY_DOMAIN = 'https://registry.npmjs.org'; -export default class ProxyAudit implements IPluginMiddleware<{}> { +export default class ProxyAudit implements IPluginMiddleware<{}, {}> { public enabled: boolean; public logger: Logger; public strict_ssl: boolean; diff --git a/packages/tools/eslint/src/index.js b/packages/tools/eslint/src/index.js index 942b8612c..e9679ff32 100644 --- a/packages/tools/eslint/src/index.js +++ b/packages/tools/eslint/src/index.js @@ -6,6 +6,7 @@ module.exports = { }, globals: { __APP_VERSION__: true, + NodeJS: true, }, parserOptions: { allowImportExportEverywhere: true, diff --git a/packages/tools/mock/package.json b/packages/tools/mock/package.json index f39067923..a2a732339 100644 --- a/packages/tools/mock/package.json +++ b/packages/tools/mock/package.json @@ -32,7 +32,7 @@ "types": "build/index.d.ts", "scripts": { "clean": "rimraf ./build", - "test": "jest", + "test": "echo 0", "type-check": "tsc --noEmit -p tsconfig.build.json", "build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json", "build:js": "babel src/ --out-dir build/ --copy-files --extensions \".ts,.tsx\" --source-maps", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d40cc157..243ebf884 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -381,10 +381,12 @@ importers: packages/core/types: specifiers: '@types/node': 16.11.47 - tsd: 0.22.0 + typedoc: beta + typedoc-plugin-missing-exports: 1.0.0 devDependencies: '@types/node': 16.11.47 - tsd: 0.22.0 + typedoc: 0.23.0-beta.7_typescript@4.7.4 + typedoc-plugin-missing-exports: 1.0.0_typedoc@0.23.0-beta.7 packages/core/url: specifiers: @@ -452,36 +454,34 @@ importers: '@verdaccio/core': workspace:6.0.0-6-next.5 '@verdaccio/logger-prettify': workspace:6.0.0-6-next.6 '@verdaccio/types': workspace:11.0.0-6-next.12 + colorette: 2.0.7 debug: 4.3.4 - lodash: 4.17.21 - pino: 7.11.0 - pino-pretty: 7.6.1 + pino: 8.4.1 dependencies: '@verdaccio/core': link:../core/core '@verdaccio/logger-prettify': link:../logger-prettify + colorette: 2.0.7 debug: 4.3.4 - lodash: 4.17.21 - pino: 7.11.0 - pino-pretty: 7.6.1 + pino: 8.4.1 devDependencies: '@verdaccio/types': link:../core/types packages/logger-prettify: specifiers: + colorette: 2.0.7 dayjs: 1.11.4 - fast-safe-stringify: 2.1.1 - kleur: 3.0.3 lodash: 4.17.21 - pino: 7.11.0 - prettier-bytes: 1.0.4 + pino: 8.4.1 + pino-abstract-transport: 1.0.0 + sonic-boom: 3.2.0 dependencies: + colorette: 2.0.7 dayjs: 1.11.4 - fast-safe-stringify: 2.1.1 - kleur: 3.0.3 lodash: 4.17.21 - prettier-bytes: 1.0.4 + pino-abstract-transport: 1.0.0 + sonic-boom: 3.2.0 devDependencies: - pino: 7.11.0 + pino: 8.4.1 packages/middleware: specifiers: @@ -1291,7 +1291,7 @@ importers: npm: next-8 dependencies: '@verdaccio/test-cli-commons': link:../cli-commons - npm: 8.16.0 + npm: 8.18.0 test/cli/e2e-pnpm6: specifiers: @@ -1307,7 +1307,7 @@ importers: pnpm: next-7 dependencies: '@verdaccio/test-cli-commons': link:../cli-commons - pnpm: 7.9.0 + pnpm: 7.9.4-0 test/cli/e2e-yarn1: specifiers: @@ -7188,11 +7188,6 @@ packages: /@tsconfig/node16/1.0.2: resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==} - /@tsd/typescript/4.7.4: - resolution: {integrity: sha512-jbtC+RgKZ9Kk65zuRZbKLTACf+tvFW4Rfq0JEMXrlmV3P3yme+Hm+pnb5fJRyt61SjIitcrC810wj7+1tgsEmg==} - hasBin: true - dev: true - /@types/aria-query/4.2.0: resolution: {integrity: sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==} dev: true @@ -8587,16 +8582,6 @@ packages: /argparse/2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - /args/5.0.1: - resolution: {integrity: sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==} - engines: {node: '>= 6.0.0'} - dependencies: - camelcase: 5.0.0 - chalk: 2.4.2 - leven: 2.1.0 - mri: 1.1.4 - dev: false - /argv/0.0.2: resolution: {integrity: sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=} engines: {node: '>=0.6.10'} @@ -9352,7 +9337,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: false /braces/2.3.2: resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} @@ -9558,11 +9542,6 @@ packages: engines: {node: '>=4'} dev: true - /camelcase/5.0.0: - resolution: {integrity: sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==} - engines: {node: '>=6'} - dev: false - /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -9992,6 +9971,10 @@ packages: /colorette/2.0.16: resolution: {integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==} + /colorette/2.0.7: + resolution: {integrity: sha512-wSXeeDPxoi5xKvjvOGxyYlyqB3J+tbowaSsFm1rEsDsDE942aTLftbOE3XMqf3XaYC7QUtcd/3qadNAIEIsAYw==} + dev: false + /combine-promises/1.1.0: resolution: {integrity: sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg==} engines: {node: '>=10'} @@ -10962,10 +10945,6 @@ packages: engines: {node: '>=0.11'} dev: true - /dateformat/4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - dev: false - /dayjs/1.11.3: resolution: {integrity: sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==} dev: false @@ -11586,14 +11565,6 @@ packages: /duplexer3/0.1.4: resolution: {integrity: sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=} - /duplexify/4.1.2: - resolution: {integrity: sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==} - dependencies: - end-of-stream: 1.4.4 - inherits: 2.0.4 - readable-stream: 3.6.0 - stream-shift: 1.0.1 - /eastasianwidth/0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -12202,20 +12173,6 @@ packages: eslint: 8.21.0 dev: false - /eslint-formatter-pretty/4.1.0: - resolution: {integrity: sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==} - engines: {node: '>=10'} - dependencies: - '@types/eslint': 7.28.2 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - eslint-rule-docs: 1.1.231 - log-symbols: 4.1.0 - plur: 4.0.0 - string-width: 4.2.3 - supports-hyperlinks: 2.2.0 - dev: true - /eslint-import-resolver-node/0.3.6: resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} dependencies: @@ -12405,10 +12362,6 @@ packages: engines: {node: '>=4.0.0'} dev: false - /eslint-rule-docs/1.1.231: - resolution: {integrity: sha512-egHz9A1WG7b8CS0x1P6P/Rj5FqZOjray/VjpJa14tMZalfRKvpE2ONJ3plCM7+PcinmU4tcmbPLv0VtwzSdLVA==} - dev: true - /eslint-scope/5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -14640,11 +14593,6 @@ packages: resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==} engines: {node: '>= 10'} - /irregular-plurals/3.3.0: - resolution: {integrity: sha512-MVBLKUTangM3EfRPFROhmWQQKRDsrgI83J8GS3jXy+OwYqiR2/aoWndYQ5416jLE3uaGgLH7ncme3X9y09gZ3g==} - engines: {node: '>=8'} - dev: true - /is-absolute-url/3.0.3: resolution: {integrity: sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==} engines: {node: '>=8'} @@ -15607,11 +15555,6 @@ packages: engines: {node: '>=10'} dev: true - /joycon/3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - dev: false - /js-base64/3.7.2: resolution: {integrity: sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ==} dev: true @@ -15832,6 +15775,10 @@ packages: engines: {node: '>=6'} hasBin: true + /jsonc-parser/3.1.0: + resolution: {integrity: sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==} + dev: true + /jsonfile/4.0.0: resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=} optionalDependencies: @@ -16124,11 +16071,6 @@ packages: dependencies: package-json: 6.5.0 - /leven/2.1.0: - resolution: {integrity: sha1-wuep93IJTe6dNCAq6KzORoeHVYA=} - engines: {node: '>=0.10.0'} - dev: false - /leven/3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -16506,7 +16448,6 @@ packages: /lunr/2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} - dev: false /lz-string/1.4.4: resolution: {integrity: sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=} @@ -16582,7 +16523,6 @@ packages: resolution: {integrity: sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==} engines: {node: '>= 12'} hasBin: true - dev: false /mathml-tag-names/2.1.3: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} @@ -17270,7 +17210,6 @@ packages: engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 - dev: false /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -17331,11 +17270,6 @@ packages: resolution: {integrity: sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==} dev: false - /mri/1.1.4: - resolution: {integrity: sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==} - engines: {node: '>=4'} - dev: false - /mri/1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -17921,8 +17855,8 @@ packages: - which - write-file-atomic - /npm/8.16.0: - resolution: {integrity: sha512-UfLT/hCbcpV9uiTEBthyrOlQxwk8LG5tAGn283g7f7pRx41KcwFiHV7HYgYm2y2GabfnPtf897ptrXRQwxJWzQ==} + /npm/8.18.0: + resolution: {integrity: sha512-G07/yKvNUwhwxYhk8BxcuDPB/4s+y755i6CnH3lf9LQBHP5siUx66WbuNGWEnN3xaBER4+IR3OWApKX7eBO5Dw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16} hasBin: true dev: false @@ -18130,9 +18064,6 @@ packages: /obuf/1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - /on-exit-leak-free/0.2.0: - resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} - /on-exit-leak-free/2.1.0: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} @@ -18560,47 +18491,22 @@ packages: resolution: {integrity: sha1-clVrgM+g1IqXToDnckjoDtT3+HA=} engines: {node: '>=0.10.0'} - /pino-abstract-transport/0.5.0: - resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} - dependencies: - duplexify: 4.1.2 - split2: 4.1.0 - /pino-abstract-transport/1.0.0: resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} dependencies: readable-stream: 4.0.0 split2: 4.1.0 - /pino-pretty/7.6.1: - resolution: {integrity: sha512-H7N6ZYkiyrfwBGW9CSjx0uyO9Q2Lyt73881+OTYk8v3TiTdgN92QHrWlEq/LeWw5XtDP64jeSk3mnc6T+xX9/w==} - hasBin: true - dependencies: - args: 5.0.1 - colorette: 2.0.16 - dateformat: 4.6.3 - fast-safe-stringify: 2.1.1 - joycon: 3.1.1 - on-exit-leak-free: 0.2.0 - pino-abstract-transport: 0.5.0 - pump: 3.0.0 - readable-stream: 3.6.0 - rfdc: 1.3.0 - secure-json-parse: 2.4.0 - sonic-boom: 2.2.3 - strip-json-comments: 3.1.1 - dev: false - /pino-std-serializers/3.2.0: resolution: {integrity: sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==} dev: false - /pino-std-serializers/4.0.0: - resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==} - /pino-std-serializers/5.6.0: resolution: {integrity: sha512-VdUXCw8gO+xhir7sFuoYSjTnzB+TMDGxhAC/ph3YS3sdHnXNdsK0wMtADNUltfeGkn2KDxEM21fnjF3RwXyC8A==} + /pino-std-serializers/6.0.0: + resolution: {integrity: sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==} + /pino/6.14.0: resolution: {integrity: sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==} hasBin: true @@ -18614,22 +18520,6 @@ packages: sonic-boom: 1.1.0 dev: false - /pino/7.11.0: - resolution: {integrity: sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==} - hasBin: true - dependencies: - atomic-sleep: 1.0.0 - fast-redact: 3.1.1 - on-exit-leak-free: 0.2.0 - pino-abstract-transport: 0.5.0 - pino-std-serializers: 4.0.0 - process-warning: 1.0.0 - quick-format-unescaped: 4.0.3 - real-require: 0.1.0 - safe-stable-stringify: 2.3.1 - sonic-boom: 2.2.3 - thread-stream: 0.15.2 - /pino/8.1.0: resolution: {integrity: sha512-53jlxs+02UNTtF1XwVWfa0dHipBiM5GK73XhkHn8M2hUl9y3L94dNwB8BwQhpd5WdHjBkyJiO7v0LRt4SGgsPg==} hasBin: true @@ -18646,6 +18536,22 @@ packages: sonic-boom: 3.0.0 thread-stream: 1.0.1 + /pino/8.4.1: + resolution: {integrity: sha512-rZnbTUNFiYBH1H2OfYYVNBEFRDhN2nRaEmBD2+MDmGXSGyps+YPJ55LzWZP90zg7zyWKy0ZMqGlk47AO6OaBqQ==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.1 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pino-std-serializers: 6.0.0 + process-warning: 2.0.0 + quick-format-unescaped: 4.0.3 + real-require: 0.2.0 + safe-stable-stringify: 2.3.1 + sonic-boom: 3.2.0 + thread-stream: 2.0.1 + /pirates/4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} @@ -18679,21 +18585,14 @@ packages: semver-compare: 1.0.0 dev: true - /plur/4.0.0: - resolution: {integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==} - engines: {node: '>=10'} - dependencies: - irregular-plurals: 3.3.0 - dev: true - /pnpm/6.34.0: resolution: {integrity: sha512-K79DAT5WtGjrUyLtxAyehtN8uy5cXkzT1v+7RIJ3gygCffoLaLlBoG5gcfjfnX7oZH5rxznv/v8tlMIMfZyzTA==} engines: {node: '>=12.17'} hasBin: true dev: false - /pnpm/7.9.0: - resolution: {integrity: sha512-xkIVw73yJm/h5M4VvFIS5Q+gQCRDrp3r92g58PtcCK86aZCa7EQ6q6ivdfTAz0KsAVgloA6Anub28n6wju5v3w==} + /pnpm/7.9.4-0: + resolution: {integrity: sha512-qTilkdOOxwKLXcH7eqReARfMetXZqfWoObZkJCYgPsKD+pJQS+IGuYf1S2w+J+/0Y5518icXnc+nZ6c0CX24Ew==} engines: {node: '>=14.6'} hasBin: true dev: false @@ -20383,6 +20282,10 @@ packages: resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} engines: {node: '>= 12.13.0'} + /real-require/0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + /rechoir/0.6.2: resolution: {integrity: sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=} engines: {node: '>= 0.10'} @@ -21382,6 +21285,14 @@ packages: interpret: 1.4.0 rechoir: 0.6.2 + /shiki/0.10.1: + resolution: {integrity: sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==} + dependencies: + jsonc-parser: 3.1.0 + vscode-oniguruma: 1.6.2 + vscode-textmate: 5.2.0 + dev: true + /side-channel/1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -21555,13 +21466,13 @@ packages: flatstr: 1.0.12 dev: false - /sonic-boom/2.2.3: - resolution: {integrity: sha512-dm32bzlBchhXoJZe0yLY/kdYsHtXhZphidIcCzJib1aEjfciZyvHJ3NjA1zh6jJCO/OBLfdjc5iw6jLS/Go2fg==} + /sonic-boom/3.0.0: + resolution: {integrity: sha512-p5DiZOZHbJ2ZO5MADczp5qrfOd3W5Vr2vHxfCpe7G4AzPwVOweIjbfgku8wSQUuk+Y5Yuo8W7JqRe6XKmKistg==} dependencies: atomic-sleep: 1.0.0 - /sonic-boom/3.0.0: - resolution: {integrity: sha512-p5DiZOZHbJ2ZO5MADczp5qrfOd3W5Vr2vHxfCpe7G4AzPwVOweIjbfgku8wSQUuk+Y5Yuo8W7JqRe6XKmKistg==} + /sonic-boom/3.2.0: + resolution: {integrity: sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==} dependencies: atomic-sleep: 1.0.0 @@ -21799,9 +21710,6 @@ packages: stubs: 3.0.0 dev: true - /stream-shift/1.0.1: - resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} - /stream-transform/2.0.4: resolution: {integrity: sha512-LQXH1pUksoef5Ijo6+2ihnjLLZtZuoqu1vhut6a7xZ77nrLA/shbbx2FAzVC/nkb6wwrPzOO98700mv4HDQcWg==} dependencies: @@ -22461,16 +22369,16 @@ packages: /text-table/0.2.0: resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} - /thread-stream/0.15.2: - resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} - dependencies: - real-require: 0.1.0 - /thread-stream/1.0.1: resolution: {integrity: sha512-JuZyfzx81e5MBk8uIr8ZH76bXyjEQvbRDEkSdlV1JFBdq/rbby2RuvzBYlTBd/xCljxy6lPxrTLXzB9Jl1bNrw==} dependencies: real-require: 0.1.0 + /thread-stream/2.0.1: + resolution: {integrity: sha512-X7vWOdsHLkBq0si20ruEE2ttpS7WOVyD52xKu+TOjrRP9Qi9uB9ynHYpzZUbBptArBSuKYUn4mH+jEBnO2CRGg==} + dependencies: + real-require: 0.2.0 + /throat/6.0.1: resolution: {integrity: sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==} @@ -22682,19 +22590,6 @@ packages: strip-bom: 3.0.0 dev: false - /tsd/0.22.0: - resolution: {integrity: sha512-NH+tfEDQ0Ze8gH7TorB6IxYybD+M68EYawe45YNVrbQcydNBfdQHP9IiD0QbnqmwNXrv+l9GAiULT68mo4q/xA==} - engines: {node: '>=14.16'} - hasBin: true - dependencies: - '@tsd/typescript': 4.7.4 - eslint-formatter-pretty: 4.1.0 - globby: 11.1.0 - meow: 9.0.0 - path-exists: 4.0.0 - read-pkg-up: 7.0.1 - dev: true - /tslib/1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -22823,6 +22718,28 @@ packages: dependencies: is-typedarray: 1.0.0 + /typedoc-plugin-missing-exports/1.0.0_typedoc@0.23.0-beta.7: + resolution: {integrity: sha512-7s6znXnuAj1eD9KYPyzVzR1lBF5nwAY8IKccP5sdoO9crG4lpd16RoFpLsh2PccJM+I2NASpr0+/NMka6ThwVA==} + peerDependencies: + typedoc: 0.22.x || 0.23.x + dependencies: + typedoc: 0.23.0-beta.7_typescript@4.7.4 + dev: true + + /typedoc/0.23.0-beta.7_typescript@4.7.4: + resolution: {integrity: sha512-k8GKXXY4z3HIs7HdSu92jcze1nl71cLiHR329ZflhiO4Ta1rJc4F5uEtwUjCSChXFQaVZUI2l3VaF9nWm1/p7A==} + engines: {node: '>= 14.14'} + hasBin: true + peerDependencies: + typescript: 4.6.x || 4.7.x + dependencies: + lunr: 2.3.9 + marked: 4.0.18 + minimatch: 5.1.0 + shiki: 0.10.1 + typescript: 4.7.4 + dev: true + /typescript/4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} @@ -23567,6 +23484,14 @@ packages: engines: {node: '>=0.10.0'} dev: true + /vscode-oniguruma/1.6.2: + resolution: {integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==} + dev: true + + /vscode-textmate/5.2.0: + resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==} + dev: true + /w3c-hr-time/1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} dependencies: