From 43f70437af7a196f23d2a951f857ecc809131e02 Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Sun, 28 Mar 2021 10:40:52 +0200 Subject: [PATCH] feat!: drop node 8 and node 10 (#2142) There is no breaking changes on features aside of Node version --- .github/workflows/ci.yml | 2 +- .github/workflows/e2e-jest-workflow.yml | 20 ++--- babel.config.js | 2 +- package.json | 10 +-- src/lib/cli.ts | 32 ++------ src/lib/utils.ts | 72 +++++------------- .../partials/config/yaml/api.web.spec.yaml | 2 +- yarn.lock | Bin 513141 -> 511448 bytes 8 files changed, 42 insertions(+), 98 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecb54cb4e..81c7cbaf3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - node_version: [10, 12, 14, 15] + node_version: [12, 14, 15] runs-on: ubuntu-latest diff --git a/.github/workflows/e2e-jest-workflow.yml b/.github/workflows/e2e-jest-workflow.yml index 158ff416f..4eeb80fc1 100644 --- a/.github/workflows/e2e-jest-workflow.yml +++ b/.github/workflows/e2e-jest-workflow.yml @@ -14,10 +14,10 @@ jobs: steps: - uses: actions/checkout@v2.3.3 - - name: 'Use Node.js 10.x' + - name: 'Use Node.js 12.x' uses: actions/setup-node@v2.1.5 with: - node-version: 10.x + node-version: 12.x - name: Install Dependencies run: yarn install - name: 'Run verdaccio in the background' @@ -45,10 +45,10 @@ jobs: steps: - uses: actions/checkout@v2.3.3 - - name: 'Use Node.js 10.x' + - name: 'Use Node.js 12.x' uses: actions/setup-node@v2.1.5 with: - node-version: 10.x + node-version: 12.x - name: Install Dependencies run: yarn install - name: 'Run verdaccio in the background' @@ -79,10 +79,10 @@ jobs: steps: - uses: actions/checkout@v2.3.3 - - name: 'Use Node.js 10.x' + - name: 'Use Node.js 12.x' uses: actions/setup-node@v2.1.5 with: - node-version: 10.x + node-version: 12.x - name: 'install latest npm' run: npm i -g npm@latest-6 - name: Install Dependencies @@ -112,10 +112,10 @@ jobs: steps: - uses: actions/checkout@v2.3.3 - - name: 'Use Node.js 10.x' + - name: 'Use Node.js 12.x' uses: actions/setup-node@v2.1.5 with: - node-version: 10.x + node-version: 12.x - name: 'install latest npm' run: npm i -g npm - name: Install Dependencies @@ -146,10 +146,10 @@ jobs: steps: - uses: actions/checkout@v2.3.3 - - name: 'Use Node.js 10.x' + - name: 'Use Node.js 12.x' uses: actions/setup-node@v2.1.5 with: - node-version: 10.x + node-version: 12.x - name: 'install latest pnpm' run: npm i -g pnpm - name: Install Dependencies diff --git a/babel.config.js b/babel.config.js index e785b9b9c..3722f2772 100644 --- a/babel.config.js +++ b/babel.config.js @@ -4,7 +4,7 @@ module.exports = { '@babel/env', { targets: { - node: '8' + node: '12', }, }, ], diff --git a/package.json b/package.json index 32614f9ff..2df9a4c39 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "express": "4.17.1", "handlebars": "4.7.7", "http-errors": "1.8.0", - "js-yaml": "3.14.1", + "js-yaml": "4.0.0", "jsonwebtoken": "8.5.1", "kleur": "4.1.4", "lodash": "4.17.21", @@ -44,7 +44,7 @@ "marked": "2.0.1", "mime": "2.5.2", "minimatch": "3.0.4", - "mkdirp": "0.5.5", + "mkdirp": "1.0.4", "mv": "2.1.1", "pkginfo": "0.4.1", "request": "2.88.0", @@ -74,8 +74,8 @@ "@babel/preset-typescript": "7.13.0", "@babel/register": "7.13.8", "@babel/runtime": "7.13.9", - "@commitlint/cli": "8.3.5", - "@commitlint/config-conventional": "8.3.4", + "@commitlint/cli": "12.0.1", + "@commitlint/config-conventional": "12.0.1", "@octokit/rest": "16.43.2", "@types/async": "3.2.4", "@types/bunyan": "1.8.6", @@ -113,7 +113,7 @@ "eslint-plugin-simple-import-sort": "7.0.0", "eslint-plugin-verdaccio": "9.6.1", "fs-extra": "9.1.0", - "get-stdin": "7.0.0", + "get-stdin": "8.0.0", "husky": "2.7.0", "in-publish": "2.0.1", "jest": "25.5.4", diff --git a/src/lib/cli.ts b/src/lib/cli.ts index 6ccac2927..0b0204d3e 100644 --- a/src/lib/cli.ts +++ b/src/lib/cli.ts @@ -13,21 +13,13 @@ import { parseConfigFile } from './utils'; require('pkginfo')(module); if (process.getuid && process.getuid() === 0) { - global.console.warn( - bgYellow().red( - "*** WARNING: Verdaccio doesn't need superuser privileges. Don't run it under root! ***" - ) - ); + global.console.warn(bgYellow().red("*** WARNING: Verdaccio doesn't need superuser privileges. Don't run it under root! ***")); } -const MIN_NODE_VERSION = '6.9.0'; +const MIN_NODE_VERSION = '12.0.0'; if (semver.satisfies(process.version, `>=${MIN_NODE_VERSION}`) === false) { - global.console.error( - bgRed( - `Verdaccio requires at least Node.js ${MIN_NODE_VERSION} or higher, please upgrade your Node.js distribution` - ) - ); + global.console.error(bgRed(`Verdaccio requires at least Node.js ${MIN_NODE_VERSION} or higher, please upgrade your Node.js distribution`)); process.exit(1); } @@ -68,19 +60,9 @@ function init() { logger.logger.warn({ file: configPathLocation }, 'config file - @{file}'); - startVerdaccio( - verdaccioConfiguration, - cliListener, - configPathLocation, - pkgVersion, - pkgName, - listenDefaultCallback - ); + startVerdaccio(verdaccioConfiguration, cliListener, configPathLocation, pkgVersion, pkgName, listenDefaultCallback); } catch (err) { - logger.logger.fatal( - { file: configPathLocation, err: err }, - 'cannot open config file @{file}: @{!err.message}' - ); + logger.logger.fatal({ file: configPathLocation, err: err }, 'cannot open config file @{file}: @{!err.message}'); process.exit(1); } } @@ -94,7 +76,7 @@ if (commander.info) { Binaries: ['Node', 'Yarn', 'npm'], Virtualization: ['Docker'], Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'], - npmGlobalPackages: ['verdaccio'] + npmGlobalPackages: ['verdaccio'], }); // eslint-disable-next-line no-console console.log(data); @@ -113,7 +95,7 @@ if (commander.info) { process.on('uncaughtException', function (err) { logger.logger.fatal( { - err: err + err: err, }, 'uncaught exception, please report this\n@{err.stack}' ); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 3de685f03..9c2f3d129 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -12,29 +12,10 @@ import sanitizyReadme from '@verdaccio/readme'; import { Package, Version, Author } from '@verdaccio/types'; import { Request } from 'express'; -import { - getConflict, - getBadData, - getBadRequest, - getInternalError, - getUnauthorized, - getForbidden, - getServiceUnavailable, - getNotFound, - getCode -} from '@verdaccio/commons-api'; +import { getConflict, getBadData, getBadRequest, getInternalError, getUnauthorized, getForbidden, getServiceUnavailable, getNotFound, getCode } from '@verdaccio/commons-api'; import { generateGravatarUrl, GENERIC_AVATAR } from '../utils/user'; import { StringValue, AuthorAvatar } from '../../types'; -import { - APP_ERROR, - DEFAULT_PORT, - DEFAULT_DOMAIN, - DEFAULT_PROTOCOL, - CHARACTER_ENCODING, - HEADERS, - DIST_TAGS, - DEFAULT_USER -} from './constants'; +import { APP_ERROR, DEFAULT_PORT, DEFAULT_DOMAIN, DEFAULT_PROTOCOL, CHARACTER_ENCODING, HEADERS, DIST_TAGS, DEFAULT_USER } from './constants'; import { normalizeContributors } from './storage-utils'; @@ -139,11 +120,7 @@ export function validateMetadata(object: Package, name: string): Package { * Create base url for registry. * @return {String} base registry url */ -export function combineBaseUrl( - protocol: string, - host: string | void, - prefix?: string | void -): string { +export function combineBaseUrl(protocol: string, host: string | void, prefix?: string | void): string { const result = `${protocol}://${host}`; const prefixOnlySlash = prefix === '/'; @@ -174,11 +151,7 @@ export function extractTarballFromUrl(url: string): string { * @param {*} config * @return {String} a filtered package */ -export function convertDistRemoteToLocalTarballUrls( - pkg: Package, - req: Request, - urlPrefix: string | void -): Package { +export function convertDistRemoteToLocalTarballUrls(pkg: Package, req: Request, urlPrefix: string | void): Package { for (const ver in pkg.versions) { if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) { const distName = pkg.versions[ver].dist; @@ -196,12 +169,7 @@ export function convertDistRemoteToLocalTarballUrls( * @param {*} uri * @return {String} a parsed url */ -export function getLocalRegistryTarballUri( - uri: string, - pkgName: string, - req: Request, - urlPrefix: string | void -): string { +export function getLocalRegistryTarballUri(uri: string, pkgName: string, req: Request, urlPrefix: string | void): string { const currentHost = req.headers.host; if (!currentHost) { @@ -279,7 +247,7 @@ export function parseAddress(urlAddress: any): any { return { proto: urlPattern[2] || DEFAULT_PROTOCOL, host: urlPattern[6] || urlPattern[7] || DEFAULT_DOMAIN, - port: urlPattern[8] || DEFAULT_PORT + port: urlPattern[8] || DEFAULT_PORT, }; } @@ -288,7 +256,7 @@ export function parseAddress(urlAddress: any): any { if (urlPattern) { return { proto: urlPattern[2] || DEFAULT_PROTOCOL, - path: urlPattern[4] + path: urlPattern[4], }; } @@ -362,7 +330,7 @@ const parseIntervalTable = { d: 86400000, w: 7 * 86400000, M: 30 * 86400000, - y: 365 * 86400000 + y: 365 * 86400000, }; /** @@ -381,11 +349,7 @@ export function parseInterval(interval: any): number { return; } const m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/); - if ( - !m || - parseIntervalTable[m[4]] >= last_suffix || - (m[4] === '' && last_suffix !== Infinity) - ) { + if (!m || parseIntervalTable[m[4]] >= last_suffix || (m[4] === '' && last_suffix !== Infinity)) { throw Error('invalid interval: ' + interval); } last_suffix = parseIntervalTable[m[4]]; @@ -419,13 +383,13 @@ export const ErrorCode = { getForbidden, getServiceUnavailable, getNotFound, - getCode + getCode, }; export function parseConfigFile(configPath: string): any { try { if (/\.ya?ml$/i.test(configPath)) { - return YAML.safeLoad(fs.readFileSync(configPath, CHARACTER_ENCODING.UTF8)); + return YAML.load(fs.readFileSync(configPath, CHARACTER_ENCODING.UTF8)); } return require(configPath); } catch (e) { @@ -488,9 +452,7 @@ export function deleteProperties(propertiesToDelete: string[], objectItem: any): export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvatar { const pkgInfoCopy = { ...pkgInfo } as any; const author: any = _.get(pkgInfo, 'latest.author', null) as any; - const contributors: AuthorAvatar[] = normalizeContributors( - _.get(pkgInfo, 'latest.contributors', []) - ); + const contributors: AuthorAvatar[] = normalizeContributors(_.get(pkgInfo, 'latest.contributors', [])); const maintainers = _.get(pkgInfo, 'latest.maintainers', []); // for author. @@ -503,7 +465,7 @@ export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvata pkgInfoCopy.latest.author = { avatar: GENERIC_AVATAR, email: '', - author + author, }; } @@ -517,7 +479,7 @@ export function addGravatarSupport(pkgInfo: Package, online = true): AuthorAvata contributor = { avatar: GENERIC_AVATAR, email: contributor, - name: contributor + name: contributor, }; } @@ -580,7 +542,7 @@ export function formatAuthor(author: AuthorFormat): any { let authorDetails = { name: DEFAULT_USER, email: '', - url: '' + url: '', }; if (_.isNil(author)) { @@ -590,14 +552,14 @@ export function formatAuthor(author: AuthorFormat): any { if (_.isString(author)) { authorDetails = { ...authorDetails, - name: author as string + name: author as string, }; } if (_.isObject(author)) { authorDetails = { ...authorDetails, - ...(author as Author) + ...(author as Author), }; } diff --git a/test/unit/partials/config/yaml/api.web.spec.yaml b/test/unit/partials/config/yaml/api.web.spec.yaml index 8ee87bde1..4510a627e 100644 --- a/test/unit/partials/config/yaml/api.web.spec.yaml +++ b/test/unit/partials/config/yaml/api.web.spec.yaml @@ -17,4 +17,4 @@ packages: unpublish: xxx proxy: npmjs logs: - - { type: stdout, format: pretty, level: warn } + - { type: stdout, format: pretty, level: trace } diff --git a/yarn.lock b/yarn.lock index 5ceed0d391311f5579bd2217b32ac397f0aa59d2..fc553f22d8d845ffe258144fd41753c648829065 100644 GIT binary patch delta 4832 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 delta 6380 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