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

feat: upgrade to pino 8 with async logging (#3308)

This commit is contained in:
Juan Picado 2022-08-18 21:39:34 +02:00 committed by GitHub
parent 9bff9045f8
commit a3a209b5e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1334 additions and 1103 deletions

View file

@ -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

3
.gitignore vendored
View file

@ -46,7 +46,8 @@ api-results.json
hyper-results.json
hyper-results*.json
api-results*.json
.clinic/
#docs
./api
packages/core/core/docs
**/docs/**

View file

@ -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';

View file

@ -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,

View file

@ -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
```

View file

@ -1,694 +0,0 @@
/// <reference types="node" />
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<T> = ILocalData<T>;
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<ConfigYaml, 'packages' | 'security' | 'configPath'> {
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<any>;
deleteToken(user: string, tokenKey: string): Promise<any>;
readTokens(filter: TokenFilter): Promise<Token[]>;
}
/**
* @deprecated use @verdaccio/core pluginUtils instead
*/
interface ILocalData<T> extends IPlugin<T>, ITokenActions {
logger: Logger;
config: T & Config;
add(name: string): Promise<void>;
remove(name: string): Promise<void>;
get(): Promise<any>;
init(): Promise<void>;
getSecret(): Promise<string>;
setSecret(secret: string): Promise<any>;
getPackageStorage(packageInfo: string): IPackageStorage;
}
interface ILocalPackageManager {
logger: Logger;
deletePackage(fileName: string): Promise<void>;
removePackage(): Promise<void>;
// next packages migration (this list is meant to replace the callback parent functions)
updatePackage(
packageName: string,
handleUpdate: (manifest: Manifest) => Promise<Manifest>
): Promise<Manifest>;
readPackage(name: string): Promise<Manifest>;
savePackage(pkgName: string, value: Manifest): Promise<void>;
readTarball(pkgName: string, { signal }): Promise<Readable>;
createPackage(name: string, manifest: Manifest): Promise<void>;
writeTarball(tarballName: string, { signal }): Promise<Writable>;
// verify if tarball exist in the storage
hasTarball(fileName: string): Promise<boolean>;
// verify if package exist in the storage
hasPackage(): Promise<boolean>;
}
// @deprecated use IBasicAuth from @verdaccio/auth
interface IBasicAuth<T> {
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<T> {
new (config: T, options: PluginOptions<T>): T;
}
interface IPlugin<T> {
version?: string;
// In case a plugin needs to be cleaned up/removed
close?(): void;
}
interface PluginOptions<T> {
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<T> extends IPlugin<T> {
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<T> extends IPlugin<T> {
register_middlewares(app: any, auth: IBasicAuth<T>, storage: any): void;
}
interface IPluginStorageFilter<T> extends IPlugin<T> {
filter_metadata(packageInfo: Manifest): Promise<Package>;
}
export type SearchResultWeb = {
name: string;
version: string;
description: string;
};
}

View file

@ -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",

View file

@ -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;
};

View file

@ -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<ConfigYaml, 'packages' | 'security' | 'configPath'> {
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 {}

View file

@ -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;
}

View file

@ -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<T> {
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;
}

View file

@ -0,0 +1,15 @@
import { Config, Logger } from '../configuration';
export class Plugin<T> {
public constructor(config: T, options: PluginOptions<T>) {}
}
export interface IPlugin<T> {
// TODO: not used on core yet
version?: string;
}
export interface PluginOptions<T> {
config: T & Config;
logger: Logger;
}

View file

@ -0,0 +1,6 @@
import { Manifest } from '../manifest';
import { IPlugin } from './commons';
export interface IPluginStorageFilter<T> extends IPlugin<T> {
filter_metadata(packageInfo: Manifest): Promise<Manifest>;
}

View file

@ -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<T> extends IPlugin<T> {
/**
* @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';

View file

@ -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<T, K> extends IPlugin<T> {
register_middlewares(app: any, auth: IBasicAuth<T>, storage: K): void;
}

View file

@ -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<any>;
deleteToken(user: string, tokenKey: string): Promise<any>;
readTokens(filter: TokenFilter): Promise<Token[]>;
}
/**
* 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<void>;
removePackage(): Promise<void>;
// next packages migration (this list is meant to replace the callback parent functions)
updatePackage(
packageName: string,
handleUpdate: (manifest: Manifest) => Promise<Manifest>
): Promise<Manifest>;
readPackage(name: string): Promise<Manifest>;
savePackage(pkgName: string, value: Manifest): Promise<void>;
readTarball(pkgName: string, { signal }): Promise<Readable>;
createPackage(name: string, manifest: Manifest): Promise<void>;
writeTarball(tarballName: string, { signal }): Promise<Writable>;
// verify if tarball exist in the storage
hasTarball(fileName: string): Promise<boolean>;
// verify if package exist in the storage
hasPackage(): Promise<boolean>;
}
export type IPackageStorage = ILocalPackageManager | void;
export type IPluginStorage<T> = ILocalData<T>;
export type IPackageStorageManager = ILocalPackageManager;
/**
* @deprecated use @verdaccio/core pluginUtils instead
*/
interface ILocalData<T> extends IPlugin<T>, ITokenActions {
logger: Logger;
config: T & Config;
add(name: string): Promise<void>;
remove(name: string): Promise<void>;
get(): Promise<any>;
init(): Promise<void>;
getSecret(): Promise<string>;
setSecret(secret: string): Promise<any>;
getPackageStorage(packageInfo: string): IPackageStorage;
}

View file

View file

@ -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;
};

View file

@ -0,0 +1,5 @@
export * from './plugins';
export * from './manifest';
export * from './search';
export * from './commons';
export * from './configuration';

View file

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

View file

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

View file

@ -0,0 +1,5 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["src/types.ts"],
"sort": ["source-order"]
}

View file

@ -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,
},
},
});

View file

@ -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",

View file

@ -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<PrettyOptionsExtended, 'prettyStamp'>,
hasColors: boolean
): string {
const { prettyStamp } = options;
const { level, msg, sub } = templateObjects;

View file

@ -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';

View file

@ -53,6 +53,7 @@ export const subSystemLevels = {
white: {
in: ARROWS.LEFT,
out: ARROWS.RIGHT,
auth: ARROWS.NEUTRAL,
fs: ARROWS.EQUAL,
default: ARROWS.NEUTRAL,
},

View file

@ -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;
});
}

View file

@ -2,4 +2,5 @@ import { PrettyOptions } from 'pino';
export interface PrettyOptionsExtended extends PrettyOptions {
prettyStamp: boolean;
colors?: boolean;
}

View file

@ -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}`;

View file

@ -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 "`;

View file

@ -1,6 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`prettyFactory should return a function with options 1`] = `
" trace--- [trace] - foo
"
`;

View file

@ -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', () => {

View file

@ -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');
});
});

View file

@ -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');
});
});

View file

@ -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"

View file

@ -1 +1 @@
export { setup, createLogger, logger, LoggerConfigItem } from './logger';
export { setup, createLogger, logger, prepareSetup } from './logger';

View file

@ -1,18 +1,24 @@
// <reference types="node" />
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;
}

View file

@ -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');
});
});
});

View file

@ -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');
});
});
});

View file

@ -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;

View file

@ -6,6 +6,7 @@ module.exports = {
},
globals: {
__APP_VERSION__: true,
NodeJS: true,
},
parserOptions: {
allowImportExportEverywhere: true,

View file

@ -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",

View file

@ -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: