@verdaccio/config 7.0.0-next.5 → 7.0.0-next.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @verdaccio/config
2
2
 
3
+ ## 7.0.0-next.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 4d96324: chore(config): increase test coverage
8
+ - @verdaccio/core@7.0.0-next.6
9
+ - @verdaccio/utils@7.0.0-next.6
10
+
3
11
  ## 7.0.0-next.5
4
12
 
5
13
  ### Minor Changes
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Verdaccio contributors
3
+ Copyright (c) 2024 Verdaccio contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -28,7 +28,7 @@ const PACKAGE_ACCESS = exports.PACKAGE_ACCESS = {
28
28
  };
29
29
  function normalizeUserList(groupsList) {
30
30
  const result = [];
31
- if (_lodash.default.isNil(groupsList)) {
31
+ if (_lodash.default.isNil(groupsList) || _lodash.default.isEmpty(groupsList)) {
32
32
  return result;
33
33
  }
34
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"package-access.js","names":["_assert","_interopRequireDefault","require","_lodash","_core","obj","__esModule","default","ROLES","exports","$ALL","ALL","$AUTH","$ANONYMOUS","DEPRECATED_ALL","DEPRECATED_AUTH","DEPRECATED_ANONYMOUS","PACKAGE_ACCESS","SCOPE","normalizeUserList","groupsList","result","_","isNil","isString","groupsArray","split","push","Array","isArray","errorUtils","getInternalError","JSON","stringify","flatten","normalisePackageAccess","packages","normalizedPkgs","access","publish","unpublish","proxy","pkg","Object","prototype","hasOwnProperty","call","packageAccess","isInvalid","isObject","assert","isUndefined"],"sources":["../src/package-access.ts"],"sourcesContent":["import assert from 'assert';\nimport _ from 'lodash';\n\nimport { errorUtils } from '@verdaccio/core';\nimport { PackageAccess } from '@verdaccio/types';\n\nexport interface LegacyPackageList {\n [key: string]: PackageAccess;\n}\n\n// @deprecated use @verdaccio/core:authUtils\nexport const ROLES = {\n $ALL: '$all',\n ALL: 'all',\n $AUTH: '$authenticated',\n $ANONYMOUS: '$anonymous',\n DEPRECATED_ALL: '@all',\n DEPRECATED_AUTH: '@authenticated',\n DEPRECATED_ANONYMOUS: '@anonymous',\n};\n\n// @deprecated use @verdaccio/core:authUtils\nexport const PACKAGE_ACCESS = {\n SCOPE: '@*/*',\n ALL: '**',\n};\n\nexport function normalizeUserList(groupsList: any): any {\n const result: any[] = [];\n if (_.isNil(groupsList)) {\n return result;\n }\n\n // if it's a string, split it to array\n if (_.isString(groupsList)) {\n const groupsArray = groupsList.split(/\\s+/);\n\n result.push(groupsArray);\n } else if (Array.isArray(groupsList)) {\n result.push(groupsList);\n } else {\n throw errorUtils.getInternalError(\n 'CONFIG: bad package acl (array or string expected): ' + JSON.stringify(groupsList)\n );\n }\n\n return _.flatten(result);\n}\n\nexport function normalisePackageAccess(packages: LegacyPackageList): LegacyPackageList {\n const normalizedPkgs: LegacyPackageList = { ...packages };\n if (_.isNil(normalizedPkgs['**'])) {\n normalizedPkgs['**'] = {\n access: [],\n publish: [],\n unpublish: [],\n proxy: [],\n };\n }\n\n for (const pkg in packages) {\n if (Object.prototype.hasOwnProperty.call(packages, pkg)) {\n const packageAccess = packages[pkg];\n const isInvalid = _.isObject(packageAccess) && _.isArray(packageAccess) === false;\n assert(isInvalid, `CONFIG: bad \"'${pkg}'\" package description (object expected)`);\n\n normalizedPkgs[pkg].access = normalizeUserList(packageAccess.access);\n normalizedPkgs[pkg].publish = normalizeUserList(packageAccess.publish);\n normalizedPkgs[pkg].proxy = normalizeUserList(packageAccess.proxy);\n // if unpublish is not defined, we set to false to fallback in publish access\n normalizedPkgs[pkg].unpublish = _.isUndefined(packageAccess.unpublish)\n ? false\n : normalizeUserList(packageAccess.unpublish);\n }\n }\n\n return normalizedPkgs;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"package-access.js","names":["_assert","_interopRequireDefault","require","_lodash","_core","obj","__esModule","default","ROLES","exports","$ALL","ALL","$AUTH","$ANONYMOUS","DEPRECATED_ALL","DEPRECATED_AUTH","DEPRECATED_ANONYMOUS","PACKAGE_ACCESS","SCOPE","normalizeUserList","groupsList","result","_","isNil","isEmpty","isString","groupsArray","split","push","Array","isArray","errorUtils","getInternalError","JSON","stringify","flatten","normalisePackageAccess","packages","normalizedPkgs","access","publish","unpublish","proxy","pkg","Object","prototype","hasOwnProperty","call","packageAccess","isInvalid","isObject","assert","isUndefined"],"sources":["../src/package-access.ts"],"sourcesContent":["import assert from 'assert';\nimport _ from 'lodash';\n\nimport { errorUtils } from '@verdaccio/core';\nimport { PackageAccess } from '@verdaccio/types';\n\nexport interface LegacyPackageList {\n [key: string]: PackageAccess;\n}\n\n// @deprecated use @verdaccio/core:authUtils\nexport const ROLES = {\n $ALL: '$all',\n ALL: 'all',\n $AUTH: '$authenticated',\n $ANONYMOUS: '$anonymous',\n DEPRECATED_ALL: '@all',\n DEPRECATED_AUTH: '@authenticated',\n DEPRECATED_ANONYMOUS: '@anonymous',\n};\n\n// @deprecated use @verdaccio/core:authUtils\nexport const PACKAGE_ACCESS = {\n SCOPE: '@*/*',\n ALL: '**',\n};\n\nexport function normalizeUserList(groupsList: any): any {\n const result: any[] = [];\n if (_.isNil(groupsList) || _.isEmpty(groupsList)) {\n return result;\n }\n\n // if it's a string, split it to array\n if (_.isString(groupsList)) {\n const groupsArray = groupsList.split(/\\s+/);\n\n result.push(groupsArray);\n } else if (Array.isArray(groupsList)) {\n result.push(groupsList);\n } else {\n throw errorUtils.getInternalError(\n 'CONFIG: bad package acl (array or string expected): ' + JSON.stringify(groupsList)\n );\n }\n\n return _.flatten(result);\n}\n\nexport function normalisePackageAccess(packages: LegacyPackageList): LegacyPackageList {\n const normalizedPkgs: LegacyPackageList = { ...packages };\n if (_.isNil(normalizedPkgs['**'])) {\n normalizedPkgs['**'] = {\n access: [],\n publish: [],\n unpublish: [],\n proxy: [],\n };\n }\n\n for (const pkg in packages) {\n if (Object.prototype.hasOwnProperty.call(packages, pkg)) {\n const packageAccess = packages[pkg];\n const isInvalid = _.isObject(packageAccess) && _.isArray(packageAccess) === false;\n assert(isInvalid, `CONFIG: bad \"'${pkg}'\" package description (object expected)`);\n\n normalizedPkgs[pkg].access = normalizeUserList(packageAccess.access);\n normalizedPkgs[pkg].publish = normalizeUserList(packageAccess.publish);\n normalizedPkgs[pkg].proxy = normalizeUserList(packageAccess.proxy);\n // if unpublish is not defined, we set to false to fallback in publish access\n normalizedPkgs[pkg].unpublish = _.isUndefined(packageAccess.unpublish)\n ? false\n : normalizeUserList(packageAccess.unpublish);\n }\n }\n\n return normalizedPkgs;\n}\n"],"mappings":"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"}
package/jest.config.js CHANGED
@@ -3,8 +3,7 @@ const config = require('../../jest/config');
3
3
  module.exports = Object.assign({}, config, {
4
4
  coverageThreshold: {
5
5
  global: {
6
- // FIXME: increase to 90
7
- lines: 85,
6
+ lines: 90,
8
7
  },
9
8
  },
10
9
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verdaccio/config",
3
- "version": "7.0.0-next.5",
3
+ "version": "7.0.0-next.6",
4
4
  "description": "logger",
5
5
  "main": "./build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -29,8 +29,8 @@
29
29
  "node": ">=12"
30
30
  },
31
31
  "dependencies": {
32
- "@verdaccio/core": "7.0.0-next.5",
33
- "@verdaccio/utils": "7.0.0-next.5",
32
+ "@verdaccio/core": "7.0.0-next.6",
33
+ "@verdaccio/utils": "7.0.0-next.6",
34
34
  "debug": "4.3.4",
35
35
  "js-yaml": "4.1.0",
36
36
  "lodash": "4.17.21",
@@ -27,7 +27,7 @@ export const PACKAGE_ACCESS = {
27
27
 
28
28
  export function normalizeUserList(groupsList: any): any {
29
29
  const result: any[] = [];
30
- if (_.isNil(groupsList)) {
30
+ if (_.isNil(groupsList) || _.isEmpty(groupsList)) {
31
31
  return result;
32
32
  }
33
33
 
@@ -0,0 +1,42 @@
1
+ import { getUserAgent } from '../src';
2
+
3
+ describe('getUserAgent', () => {
4
+ test('should return custom user agent when customUserAgent is true', () => {
5
+ const customUserAgent = true;
6
+ const version = '1.0.0';
7
+ const name = 'MyAgent';
8
+
9
+ const result = getUserAgent(customUserAgent, version, name);
10
+
11
+ expect(result).toBe('MyAgent/1.0.0');
12
+ });
13
+
14
+ test('should return custom user agent when customUserAgent is a non-empty string', () => {
15
+ const customUserAgent = 'CustomAgent/1.0.0';
16
+ const version = '1.0.0';
17
+ const name = 'MyAgent';
18
+
19
+ const result = getUserAgent(customUserAgent, version, name);
20
+
21
+ expect(result).toBe('CustomAgent/1.0.0');
22
+ });
23
+
24
+ test('should return "hidden" when customUserAgent is false', () => {
25
+ const customUserAgent = false;
26
+ const version = '1.0.0';
27
+ const name = 'MyAgent';
28
+
29
+ const result = getUserAgent(customUserAgent, version, name);
30
+
31
+ expect(result).toBe('hidden');
32
+ });
33
+
34
+ test('should return default user agent when customUserAgent is undefined', () => {
35
+ const version = '1.0.0';
36
+ const name = 'MyAgent';
37
+
38
+ const result = getUserAgent(undefined, version, name);
39
+
40
+ expect(result).toBe('MyAgent/1.0.0');
41
+ });
42
+ });
@@ -13,6 +13,7 @@ describe('Config builder', () => {
13
13
  proxy: 'some',
14
14
  })
15
15
  .addLogger({ level: 'info', type: 'stdout', format: 'json' })
16
+ .addAuth({ htpasswd: { file: '.htpasswd' } })
16
17
  .addStorage('/tmp/verdaccio')
17
18
  .addSecurity({ api: { legacy: true } });
18
19
  expect(config.getConfig()).toEqual({
@@ -21,6 +22,11 @@ describe('Config builder', () => {
21
22
  legacy: true,
22
23
  },
23
24
  },
25
+ auth: {
26
+ htpasswd: {
27
+ file: '.htpasswd',
28
+ },
29
+ },
24
30
  storage: '/tmp/verdaccio',
25
31
  packages: {
26
32
  'upstream/*': {
@@ -106,6 +106,20 @@ describe('checkSecretKey', () => {
106
106
  const config = new Config(parseConfigFile(resolveConf('default')));
107
107
  expect(typeof config.checkSecretKey('') === 'string').toBeTruthy();
108
108
  });
109
+
110
+ test('with enhanced legacy signature', () => {
111
+ const config = new Config(parseConfigFile(resolveConf('default')));
112
+ config.security.enhancedLegacySignature = true;
113
+ expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
114
+ expect(config.secret.length).toBe(32);
115
+ });
116
+
117
+ test('without enhanced legacy signature', () => {
118
+ const config = new Config(parseConfigFile(resolveConf('default')));
119
+ config.security.enhancedLegacySignature = false;
120
+ expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
121
+ expect(config.secret.length).toBe(64);
122
+ });
109
123
  });
110
124
 
111
125
  describe('getMatchedPackagesSpec', () => {
@@ -159,3 +173,18 @@ describe('VERDACCIO_STORAGE_PATH', () => {
159
173
  delete process.env.VERDACCIO_STORAGE_PATH;
160
174
  });
161
175
  });
176
+
177
+ describe('configPath', () => {
178
+ test('should set configPath in config', () => {
179
+ const defaultConfig = parseConfigFile(resolveConf('default'));
180
+ const config = new Config(defaultConfig);
181
+ expect(config.getConfigPath()).toBe(path.join(__dirname, '../src/conf/default.yaml'));
182
+ });
183
+
184
+ test('should throw an error if configPath is not provided', () => {
185
+ const defaultConfig = parseConfigFile(resolveConf('default'));
186
+ defaultConfig.configPath = '';
187
+ defaultConfig.config_path = '';
188
+ expect(() => new Config(defaultConfig)).toThrow('configPath property is required');
189
+ });
190
+ });
@@ -1,7 +1,7 @@
1
1
  import _ from 'lodash';
2
2
 
3
3
  import { parseConfigFile } from '../src';
4
- import { PACKAGE_ACCESS, normalisePackageAccess } from '../src/package-access';
4
+ import { PACKAGE_ACCESS, normalisePackageAccess, normalizeUserList } from '../src/package-access';
5
5
  import { parseConfigurationFile } from './utils';
6
6
 
7
7
  describe('Package access utilities', () => {
@@ -123,4 +123,30 @@ describe('Package access utilities', () => {
123
123
  expect(_.isArray(all.publish)).toBeTruthy();
124
124
  });
125
125
  });
126
+ describe('normaliseUserList', () => {
127
+ test('should normalize user list', () => {
128
+ const groupsList = 'admin superadmin';
129
+ const result = normalizeUserList(groupsList);
130
+ expect(result).toEqual(['admin', 'superadmin']);
131
+ });
132
+
133
+ test('should normalize empty user list', () => {
134
+ const groupsList = '';
135
+ const result = normalizeUserList(groupsList);
136
+ expect(result).toEqual([]);
137
+ });
138
+
139
+ test('should normalize user list array', () => {
140
+ const groupsList = ['admin', 'superadmin'];
141
+ const result = normalizeUserList(groupsList);
142
+ expect(result).toEqual(['admin', 'superadmin']);
143
+ });
144
+
145
+ test('should throw error for invalid user list', () => {
146
+ const groupsList = { group: 'admin' };
147
+ expect(() => {
148
+ normalizeUserList(groupsList);
149
+ }).toThrow('CONFIG: bad package acl (array or string expected): {"group":"admin"}');
150
+ });
151
+ });
126
152
  });